İlk commit: Stepstead web (statik panel + db)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
commit
dea01dd6a8
18 changed files with 3764 additions and 0 deletions
469
assets/index.html
Normal file
469
assets/index.html
Normal file
|
|
@ -0,0 +1,469 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="tr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="robots" content="noindex,nofollow">
|
||||
<title>Stepstead — Asset Takip</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#0f1419; --panel:#1a2230; --panel2:#222d3d; --line:#2c3a4f;
|
||||
--txt:#e6edf3; --muted:#8b99a8; --gold:#e8b923;
|
||||
--eksik:#e5534b; --yapiliyor:#d9a521; --tamam:#2ea043;
|
||||
--d2:#4a9eff; --d3:#c77dff;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
|
||||
background:var(--bg);color:var(--txt);line-height:1.5}
|
||||
header{padding:20px 16px;border-bottom:1px solid var(--line);
|
||||
position:sticky;top:0;background:var(--bg);z-index:10}
|
||||
h1{margin:0;font-size:20px;color:var(--gold)}
|
||||
.sub{color:var(--muted);font-size:13px;margin-top:4px}
|
||||
.wrap{max-width:1040px;margin:0 auto;padding:16px}
|
||||
.bar{height:10px;background:var(--panel);border-radius:6px;overflow:hidden;margin:10px 0 4px}
|
||||
.bar > i{display:block;height:100%;background:var(--tamam);width:0;transition:width .3s}
|
||||
.barlbl{font-size:12px;color:var(--muted)}
|
||||
.cat{background:var(--panel);border:1px solid var(--line);border-radius:10px;
|
||||
margin:12px 0;overflow:hidden}
|
||||
.cat > summary{cursor:pointer;list-style:none;padding:14px 16px;
|
||||
display:flex;align-items:center;gap:10px;font-weight:600;user-select:none}
|
||||
.cat > summary::-webkit-details-marker{display:none}
|
||||
.cat > summary .chev{transition:transform .2s;color:var(--muted)}
|
||||
.cat[open] > summary .chev{transform:rotate(90deg)}
|
||||
.cat .count{margin-left:auto;font-size:12px;color:var(--muted);font-weight:400;display:flex;gap:5px;flex-wrap:wrap}
|
||||
table{width:100%;border-collapse:collapse}
|
||||
th,td{text-align:left;padding:10px 16px;border-top:1px solid var(--line);font-size:14px;vertical-align:top}
|
||||
th{color:var(--muted);font-weight:500;font-size:12px;text-transform:uppercase;letter-spacing:.04em}
|
||||
td.name{font-weight:600}
|
||||
td.desc{color:var(--muted)}
|
||||
select.st,select.dm{background:var(--panel2);color:var(--txt);border:1px solid var(--line);
|
||||
border-radius:6px;padding:5px 8px;font-size:13px;cursor:pointer}
|
||||
select.st.eksik{color:var(--eksik)} select.st.yapiliyor{color:var(--yapiliyor)} select.st.tamam{color:var(--tamam)}
|
||||
select.dm.d2{color:var(--d2)} select.dm.d3{color:var(--d3)} select.dm.dx{color:var(--muted)}
|
||||
tr.done td.name{text-decoration:line-through;opacity:.6}
|
||||
.del{background:none;border:none;color:var(--muted);cursor:pointer;font-size:16px;opacity:.5}
|
||||
.del:hover{color:var(--eksik);opacity:1}
|
||||
.addrow td{padding:8px 16px 14px}
|
||||
.addrow input{background:var(--panel2);color:var(--txt);border:1px solid var(--line);
|
||||
border-radius:6px;padding:7px 9px;font-size:13px;margin-right:6px}
|
||||
.addrow input.n{width:180px} .addrow input.d{width:240px}
|
||||
.addrow select{background:var(--panel2);color:var(--txt);border:1px solid var(--line);border-radius:6px;padding:7px;font-size:13px;margin-right:6px}
|
||||
.btn{background:var(--gold);color:#1a1300;border:none;border-radius:6px;
|
||||
padding:7px 14px;font-size:13px;font-weight:600;cursor:pointer}
|
||||
.btn.ghost{background:transparent;color:var(--muted);border:1px solid var(--line)}
|
||||
.btn.ghost.on{border-color:var(--gold);color:var(--gold)}
|
||||
.toolbar{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px;align-items:center}
|
||||
.filt{display:flex;gap:6px;margin-right:auto}
|
||||
.pill{font-size:11px;padding:1px 7px;border-radius:10px}
|
||||
.pill.eksik{background:rgba(229,83,75,.15);color:var(--eksik)}
|
||||
.pill.yapiliyor{background:rgba(217,165,33,.15);color:var(--yapiliyor)}
|
||||
.pill.tamam{background:rgba(46,160,67,.15);color:var(--tamam)}
|
||||
.pill.d2{background:rgba(74,158,255,.15);color:var(--d2)}
|
||||
.pill.d3{background:rgba(199,125,255,.15);color:var(--d3)}
|
||||
.hide{display:none !important}
|
||||
.logincard{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:8px}
|
||||
.logincard input{background:#222d3d;color:var(--txt,#e6edf3);border:1px solid #2c3a4f;border-radius:6px;padding:6px 9px;font-size:13px}
|
||||
.logincard .msg{font-size:12px;color:#e5534b}
|
||||
button.up{background:none;border:1px solid #2c3a4f;border-radius:6px;cursor:pointer;font-size:13px;padding:2px 7px}
|
||||
button.up:hover{border-color:#e8b923}
|
||||
#toast{position:fixed;bottom:16px;left:50%;transform:translateX(-50%);background:#1a2230;border:1px solid #e8b923;color:#e6edf3;padding:9px 16px;border-radius:8px;font-size:13px;opacity:0;pointer-events:none;transition:.3s;max-width:90vw;z-index:9}
|
||||
#toast.on{opacity:1}
|
||||
</style>
|
||||
<script src="/nav.js?v=8" data-theme="dark" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="wrap" style="padding-bottom:0">
|
||||
<h1>🎨 Stepstead — Asset Takip</h1>
|
||||
<div class="sub">Eksik görselleri gör, 2D/3D ayır, tamamlandıkça işaretle. Veriler bu tarayıcıda saklanır.</div>
|
||||
<div class="bar"><i id="pbar"></i></div>
|
||||
<div class="barlbl" id="plbl">—</div>
|
||||
<div class="toolbar">
|
||||
<div class="filt">
|
||||
<button class="btn ghost on" data-filt="all">Tümü</button>
|
||||
<button class="btn ghost" data-filt="2d">🟦 2D</button>
|
||||
<button class="btn ghost" data-filt="3d">🟪 3D</button>
|
||||
</div>
|
||||
<a class="btn ghost" href="/assets/upload.html" style="text-decoration:none">📤 Dosya Yükle</a>
|
||||
<button class="btn ghost" id="expBtn">⬇ Dışa aktar</button>
|
||||
<button class="btn ghost" id="impBtn">⬆ İçe aktar</button>
|
||||
<input type="file" id="impFile" accept="application/json" hidden>
|
||||
<button class="btn ghost" id="authBtn">🔑 Giriş</button>
|
||||
</div>
|
||||
<div class="logincard hide" id="loginCard">
|
||||
<input type="email" id="lgEmail" placeholder="E-posta" autocomplete="username">
|
||||
<input type="password" id="lgPass" placeholder="Şifre" autocomplete="current-password">
|
||||
<button class="btn ghost" id="lgBtn">Giriş yap</button>
|
||||
<span class="msg" id="lgMsg"></span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div id="toast"></div>
|
||||
<div class="wrap" id="root"></div>
|
||||
|
||||
<script>
|
||||
const STATES=["eksik","yapiliyor","tamam"];
|
||||
const STLBL={eksik:"Eksik",yapiliyor:"Yapılıyor",tamam:"Tamam"};
|
||||
const DIMS=["2d","3d","yok"];
|
||||
const DMLBL={"2d":"2D","3d":"3D","yok":"—"};
|
||||
const DMCLS={"2d":"d2","3d":"d3","yok":"dx"};
|
||||
const KEY="stepstead_assets_v3";
|
||||
|
||||
// [ad, açıklama, boyut] — 2026-07-04: lokasyon-graf pivotuna göre yenilendi (tile/3D söküldü)
|
||||
const SEED=[
|
||||
{cat:"🗺 Dünya Haritası (EN KRİTİK)", items:[
|
||||
["Ana harita illüstrasyonu","Statik dünya haritası, portre 720×1280; 15 lokasyon pin koordinatları 0-1000 grid'e oturacak (world.gd TextureRect)","2d"],
|
||||
["Pin: köy","Başlangıç köyü işareti","2d"],
|
||||
["Pin: pazar","Ticaret/market işareti","2d"],
|
||||
["Pin: liman","Liman işareti","2d"],
|
||||
["Pin: orman (5 tier)","Fısıltı→Karanlık Orman; tier tonlaması","2d"],
|
||||
["Pin: maden (3 tier)","Taş Ocağı / Derin Maden / Ejder Dağı","2d"],
|
||||
["Pin: su (göl/kıyı)","Aynalı Göl, Fırtına Kıyısı","2d"],
|
||||
["Pin: çayır & av","Çayırlar, Av Toprakları","2d"],
|
||||
["Yol dokusu","Lokasyonlar arası patika çizgi stili (şimdilik Line2D)","2d"],
|
||||
["Oyuncu konumu işareti","'Buradasın' halkası/bayrak + diğer oyuncu 👤N rozeti","2d"],
|
||||
]},
|
||||
{cat:"📍 Lokasyon Kartları (15)", items:[
|
||||
["Köy","Panel üst görseli / vignette","2d"],
|
||||
["Pazar","Tezgâhlar, vendor","2d"],
|
||||
["Liman","Balıkçı iskeleleri","2d"],
|
||||
["Fısıltı Ormanı","Tier 1 orman","2d"],
|
||||
["Gümüş Çamlık","Tier 2 orman","2d"],
|
||||
["Akça Koru","Tier 3 orman","2d"],
|
||||
["Kadim Orman","Tier 4 orman","2d"],
|
||||
["Karanlık Orman","Tier 5 orman","2d"],
|
||||
["Taş Ocağı","Başlangıç madeni","2d"],
|
||||
["Derin Maden","Orta seviye maden","2d"],
|
||||
["Ejder Dağı","Üst seviye maden","2d"],
|
||||
["Aynalı Göl","Tatlı su balıkçılığı","2d"],
|
||||
["Fırtına Kıyısı","Deniz balıkçılığı","2d"],
|
||||
["Çayırlar","Ot toplama","2d"],
|
||||
["Av Toprakları","Avcılık","2d"],
|
||||
]},
|
||||
{cat:"🎴 Olay Kartı İllüstrasyonları (20)", items:[
|
||||
["Gezgin Tacir","Genel yol olayı","2d"],
|
||||
["Haydut Pususu","Genel yol olayı","2d"],
|
||||
["Yaralı Gezgin","Genel yol olayı","2d"],
|
||||
["Kayıp Sandık","Genel yol olayı","2d"],
|
||||
["Dev Ayı","Genel yol olayı","2d"],
|
||||
["Mantar Halkası","Genel yol olayı","2d"],
|
||||
["Eski Harabe","Genel yol olayı","2d"],
|
||||
["Nehir Geçidi","Genel yol olayı","2d"],
|
||||
["Gizemli Yaşlı","Genel yol olayı","2d"],
|
||||
["Kırık Araba","Genel yol olayı","2d"],
|
||||
["Sağanak","Genel yol olayı","2d"],
|
||||
["Şanslı Buluş","Genel yol olayı","2d"],
|
||||
["Kurt Sürüsü","Av Toprakları / Karanlık Orman","2d"],
|
||||
["Vahşi Domuz","Ormanlar / av","2d"],
|
||||
["Taze Geyik İzi","Çayır / av","2d"],
|
||||
["Mağara Örümceği","Madenler","2d"],
|
||||
["Korsan Artığı","Liman / kıyı (gold ödüllü)","2d"],
|
||||
["Yaşlı Avcı","Av Toprakları / Çayırlar","2d"],
|
||||
["Terk Edilmiş Ağ","Göl / kıyı / liman","2d"],
|
||||
["Ayı İni","Kadim / Karanlık Orman","2d"],
|
||||
["Olay kartı çerçevesi","Altın kenarlı kart şablonu + karartma","2d"],
|
||||
["d20 zar görseli","Skill check sonucu (başarılı/başarısız durumları)","2d"],
|
||||
]},
|
||||
{cat:"🎒 Item İkonları (~318, aile bazlı)", items:[
|
||||
["Odunlar (13)","Meşe→Abanoz tier serisi","2d"],
|
||||
["Cevher + külçe (6+6)","Bakır→Altın","2d"],
|
||||
["Otlar (12)","Nane, adaçayı, lavanta...","2d"],
|
||||
["Balıklar (15)","Sazan→Mersin","2d"],
|
||||
["Et + deri (5+5)","Tavşan/geyik/domuz/kurt/ayı","2d"],
|
||||
["Bootstrap malzemeleri","Küçük taş, sopa, sarmaşık","2d"],
|
||||
["Tool'lar","Taş/bakır/demir balta+kazma, 3 olta","2d"],
|
||||
["Consumable'lar","Çaylar, iksirler, yemekler (adım veren)","2d"],
|
||||
["Marangozluk ürünleri","Mobilya/yapı ~80 ikon","2d"],
|
||||
["Okçuluk ürünleri","Yay/ok ~26 ikon","2d"],
|
||||
["Kuyumculuk ürünleri","Takılar ~6 ikon","2d"],
|
||||
["Dericilik ürünleri","Giyim ~20 ikon","2d"],
|
||||
["Nadirlik çerçeveleri","Common→Legendary renk/çerçeve","2d"],
|
||||
]},
|
||||
{cat:"⛺ Sistem Görselleri", items:[
|
||||
["Kamp görseli","Lokasyonda kamp kurulu göstergesi (⛺ yerine)","2d"],
|
||||
["Adım ikonu","👣 yerine özel ikon","2d"],
|
||||
["Gold ikonu","🪙 yerine özel ikon","2d"],
|
||||
["Günlük ödül / streak","🎁 + 🔥 streak görselleri","2d"],
|
||||
["Liderlik rozetleri","🥇🥈🥉 + sıra çerçeveleri","2d"],
|
||||
["Skill ikonları (10)","Madencilik, balıkçılık, toplama, ağaç kesme, avcılık, savaş, demircilik, simya, aşçılık, inşaat","2d"],
|
||||
]},
|
||||
{cat:"🧑 Karakter / NPC Portreleri", items:[
|
||||
["Oyuncu avatarı","2D portre (profil/HUD)","2d"],
|
||||
["Vendor NPC","Pazar tüccarı portresi","2d"],
|
||||
["Olay NPC'leri","Tacir, yaşlı avcı, gizemli yaşlı, haydutlar (kartlarda kullanılır)","2d"],
|
||||
]},
|
||||
{cat:"🖼 UI / Arayüz", items:[
|
||||
["Buton seti","Normal / basılı / pasif durumlar","2d"],
|
||||
["Panel & çerçeve","Envanter, craft, market, lokasyon, chat panelleri","2d"],
|
||||
["Menü ikonları","💬🏪🎁📊🏆🎒🔨 buton ikonları","2d"],
|
||||
["Popup / modal","Onay, ödül, uyarı kutusu çerçevesi","2d"],
|
||||
["Progress bar","Adım / XP / günlük hedef çubukları","2d"],
|
||||
["Sekme & navigasyon","Panel içi sekmeler (Genel/Burada, İlanlar/Sat/Vendor)","2d"],
|
||||
]},
|
||||
{cat:"🏷 Marka", items:[
|
||||
["Logo","Oyun logosu (yatay + kare)","2d"],
|
||||
["Splash ekranı","Açılış görseli","2d"],
|
||||
["App ikonu","Android/iOS launcher ikonu","2d"],
|
||||
["Store görselleri","Mağaza banner / ekran görüntüleri","2d"],
|
||||
]},
|
||||
{cat:"✨ Efekt / Geri bildirim", items:[
|
||||
["Toplama efekti","Kaynak toplandı parçacık/animasyon","2d"],
|
||||
["Craft tamam efekti","Üretim başarılı ışıma","2d"],
|
||||
["Level-up efekti","Seviye atlama kutlaması","2d"],
|
||||
["Günlük ödül konfetisi","Claim anı","2d"],
|
||||
]},
|
||||
{cat:"🔊 Ses", items:[
|
||||
["Tık sesi","UI buton sesi","yok"],
|
||||
["Toplama sesi","Kaynak toplama","yok"],
|
||||
["Craft sesi","Üretim sesi","yok"],
|
||||
["Zar sesi","Olay kartı d20 atışı","yok"],
|
||||
["Olay kartı sesi","Kart açılma","yok"],
|
||||
["Seyahat sesi","Yola çıkma / varış","yok"],
|
||||
["Level-up sesi","Seviye atlama","yok"],
|
||||
["Ödül sesi","Günlük ödül / satış","yok"],
|
||||
["Ambient","Arka plan müzik / ortam (lokasyon türüne göre?)","yok"],
|
||||
]},
|
||||
];
|
||||
|
||||
function freshSeed(){
|
||||
return SEED.map(c=>({cat:c.cat,items:c.items.map(i=>({name:i[0],desc:i[1],dim:i[2],state:"eksik"}))}));
|
||||
}
|
||||
function load(){
|
||||
let data;
|
||||
try{ data=JSON.parse(localStorage.getItem(KEY)); }catch(e){}
|
||||
if(!data || !Array.isArray(data)){
|
||||
// v3: lokasyon-graf pivotu sonrası taze liste; v2'deki durumlar isim eşleşmesiyle korunur
|
||||
data=freshSeed();
|
||||
let old;
|
||||
try{ old=JSON.parse(localStorage.getItem("stepstead_assets_v2")); }catch(e){}
|
||||
if(Array.isArray(old)){
|
||||
const seedNames=new Set();
|
||||
data.forEach(c=>c.items.forEach(i=>seedNames.add(i.name)));
|
||||
const states={};
|
||||
const extras=[];
|
||||
old.forEach(c=>c.items.forEach(i=>{
|
||||
states[i.name]=i.state;
|
||||
if(!seedNames.has(i.name) && i.state!=="tamam")
|
||||
extras.push({name:i.name,desc:i.desc||"",dim:i.dim||"2d",state:i.state||"eksik"});
|
||||
}));
|
||||
data.forEach(c=>c.items.forEach(i=>{ if(states[i.name]) i.state=states[i.name]; }));
|
||||
if(extras.length) data.push({cat:"📦 Eski listeden taşınanlar",items:extras});
|
||||
}
|
||||
save(data);
|
||||
}
|
||||
data.forEach(c=>c.items.forEach(i=>{ if(!i.dim) i.dim="2d"; }));
|
||||
// Sunucu gerçeği: oyuna bağlanmış asset'ler her tarayıcıda "tamam" görünür (2026-07-06)
|
||||
const SERVER_DONE=new Set([
|
||||
"Ana harita illüstrasyonu","Pin: köy","Pin: pazar","Pin: liman",
|
||||
"Pin: orman (5 tier)","Pin: maden (3 tier)","Pin: su (göl/kıyı)","Pin: çayır & av",
|
||||
"Oyuncu konumu işareti",
|
||||
"Köy","Pazar","Liman","Fısıltı Ormanı","Gümüş Çamlık","Akça Koru",
|
||||
"Kadim Orman","Karanlık Orman","Taş Ocağı","Derin Maden","Ejder Dağı",
|
||||
"Aynalı Göl","Fırtına Kıyısı","Çayırlar","Av Toprakları",
|
||||
"Haydut Pususu","Yaralı Gezgin","Kayıp Sandık",
|
||||
]);
|
||||
// Kısmen bağlananlar: her tarayıcıda "yapılıyor" görünür (tamam'ı ezmez)
|
||||
const SERVER_WIP=new Set([]);
|
||||
data.forEach(c=>c.items.forEach(i=>{
|
||||
if(SERVER_DONE.has(i.name)) i.state="tamam";
|
||||
else if(SERVER_WIP.has(i.name) && i.state!=="tamam") i.state="yapiliyor";
|
||||
}));
|
||||
save(data);
|
||||
return data;
|
||||
}
|
||||
function save(d){ localStorage.setItem(KEY,JSON.stringify(d)); }
|
||||
|
||||
let DATA=load();
|
||||
let FILT="all";
|
||||
|
||||
// ---- Giriş + satırdan upload (up_<asset adı>.<ext> olarak kaydedilir) ----
|
||||
const API="https://api.stepstead.com";
|
||||
let TOKEN=localStorage.getItem("ss_upload_token")||"";
|
||||
let ADMIN=false;
|
||||
let PENDING=null; // {c,i} — hangi asset için dosya seçiliyor
|
||||
|
||||
function toast(msg,ms=3500){
|
||||
const t=document.getElementById("toast");
|
||||
t.textContent=msg; t.classList.add("on");
|
||||
clearTimeout(t._tm); t._tm=setTimeout(()=>t.classList.remove("on"),ms);
|
||||
}
|
||||
function updateAuthUI(){
|
||||
document.getElementById("authBtn").textContent=ADMIN?"🚪 Çıkış":"🔑 Giriş";
|
||||
if(window.ssNavRefresh)window.ssNavRefresh();
|
||||
}
|
||||
async function checkAdmin(){
|
||||
if(!TOKEN){ADMIN=false;updateAuthUI();return;}
|
||||
try{
|
||||
const r=await fetch(API+"/admin/uploads",{headers:{Authorization:"Bearer "+TOKEN}});
|
||||
ADMIN=r.ok;
|
||||
if(!r.ok&&(r.status===401||r.status===403)){TOKEN="";localStorage.removeItem("ss_upload_token");}
|
||||
}catch(e){ADMIN=false;}
|
||||
updateAuthUI(); render();
|
||||
}
|
||||
async function doLogin(){
|
||||
const m=document.getElementById("lgMsg"); m.textContent="…";
|
||||
try{
|
||||
const r=await fetch(API+"/auth/login",{method:"POST",headers:{"Content-Type":"application/json"},
|
||||
body:JSON.stringify({email:document.getElementById("lgEmail").value.trim(),
|
||||
password:document.getElementById("lgPass").value})});
|
||||
const b=await r.json().catch(()=>({}));
|
||||
if(r.ok&&b.access_token){
|
||||
TOKEN=b.access_token; localStorage.setItem("ss_upload_token",TOKEN);
|
||||
m.textContent=""; document.getElementById("loginCard").classList.add("hide");
|
||||
await checkAdmin();
|
||||
if(!ADMIN) toast("⚠ Bu hesabın yükleme yetkisi yok");
|
||||
}else m.textContent=b.detail||"Giriş başarısız";
|
||||
}catch(e){ m.textContent="Bağlantı hatası"; }
|
||||
}
|
||||
async function uploadFor(c,i,file){
|
||||
if(!file) return;
|
||||
const it=DATA[c].items[i];
|
||||
toast("⏳ Yükleniyor: "+it.name);
|
||||
const fd=new FormData();
|
||||
fd.append("file",file);
|
||||
fd.append("target_name",it.name);
|
||||
try{
|
||||
const r=await fetch(API+"/admin/upload",{method:"POST",
|
||||
headers:{Authorization:"Bearer "+TOKEN},body:fd});
|
||||
const b=await r.json().catch(()=>({}));
|
||||
if(r.ok){
|
||||
if(it.state!=="tamam") it.state="yapiliyor"; // kontrol bekliyor
|
||||
save(DATA); render();
|
||||
toast("✅ Yüklendi: "+b.filename+" — kontrol edilecek");
|
||||
}else toast("⚠ "+(b.detail||("Hata "+r.status)));
|
||||
}catch(e){ toast("⚠ Bağlantı hatası"); }
|
||||
}
|
||||
document.getElementById("authBtn").onclick=()=>{
|
||||
if(ADMIN){
|
||||
TOKEN=""; localStorage.removeItem("ss_upload_token");
|
||||
ADMIN=false; updateAuthUI(); render(); toast("Çıkış yapıldı");
|
||||
}else document.getElementById("loginCard").classList.toggle("hide");
|
||||
};
|
||||
document.getElementById("lgBtn").onclick=doLogin;
|
||||
document.getElementById("lgPass").addEventListener("keydown",e=>{if(e.key==="Enter")doLogin();});
|
||||
const upInput=document.createElement("input");
|
||||
upInput.type="file"; upInput.accept=".png,.jpg,.jpeg,.webp,.svg,.json,.zip"; upInput.hidden=true;
|
||||
document.body.appendChild(upInput);
|
||||
upInput.onchange=e=>{
|
||||
if(PENDING) uploadFor(PENDING.c,PENDING.i,e.target.files[0]);
|
||||
PENDING=null; upInput.value="";
|
||||
};
|
||||
|
||||
function visible(it){ return FILT==="all" || it.dim===FILT; }
|
||||
|
||||
function render(){
|
||||
const root=document.getElementById("root");
|
||||
root.innerHTML="";
|
||||
let total=0,done=0;
|
||||
DATA.forEach((cat,ci)=>{
|
||||
const shown=cat.items.filter(visible);
|
||||
const cTotal=shown.length;
|
||||
const cDone=shown.filter(i=>i.state==="tamam").length;
|
||||
const cWip=shown.filter(i=>i.state==="yapiliyor").length;
|
||||
const cEksik=cTotal-cDone-cWip;
|
||||
const c2=cat.items.filter(i=>i.dim==="2d").length;
|
||||
const c3=cat.items.filter(i=>i.dim==="3d").length;
|
||||
total+=cTotal; done+=cDone;
|
||||
const det=document.createElement("details");
|
||||
det.className="cat";
|
||||
if(FILT!=="all" && cTotal===0){ det.classList.add("hide"); }
|
||||
const sum=document.createElement("summary");
|
||||
sum.innerHTML=`<span class="chev">▶</span>${cat.cat}
|
||||
<span class="count">
|
||||
${c2?`<span class="pill d2">${c2} 2D</span>`:""}
|
||||
${c3?`<span class="pill d3">${c3} 3D</span>`:""}
|
||||
<span class="pill tamam">${cDone} ✓</span>
|
||||
<span class="pill eksik">${cEksik} eksik</span>
|
||||
</span>`;
|
||||
det.appendChild(sum);
|
||||
const tbl=document.createElement("table");
|
||||
tbl.innerHTML=`<thead><tr><th>Asset</th><th>Açıklama</th><th>Boyut</th><th>Durum</th><th></th></tr></thead>`;
|
||||
const tb=document.createElement("tbody");
|
||||
cat.items.forEach((it,ii)=>{
|
||||
if(!visible(it)) return;
|
||||
const tr=document.createElement("tr");
|
||||
if(it.state==="tamam") tr.className="done";
|
||||
tr.innerHTML=`<td class="name">${esc(it.name)}</td>
|
||||
<td class="desc">${esc(it.desc||"")}</td>
|
||||
<td><select class="dm ${DMCLS[it.dim]}" data-c="${ci}" data-i="${ii}">
|
||||
${DIMS.map(d=>`<option value="${d}" ${d===it.dim?"selected":""}>${DMLBL[d]}</option>`).join("")}
|
||||
</select></td>
|
||||
<td><select class="st ${it.state}" data-c="${ci}" data-i="${ii}">
|
||||
${STATES.map(s=>`<option value="${s}" ${s===it.state?"selected":""}>${STLBL[s]}</option>`).join("")}
|
||||
</select></td>
|
||||
<td style="white-space:nowrap">${ADMIN?`<button class="up" data-c="${ci}" data-i="${ii}" title="Bu asset için görsel yükle">📤</button> `:""}<button class="del" data-c="${ci}" data-i="${ii}" title="Sil">✕</button></td>`;
|
||||
tb.appendChild(tr);
|
||||
});
|
||||
const addtr=document.createElement("tr");
|
||||
addtr.className="addrow";
|
||||
addtr.innerHTML=`<td colspan="5">
|
||||
<input class="n" placeholder="Yeni asset adı" data-add-n="${ci}">
|
||||
<input class="d" placeholder="Açıklama (ops.)" data-add-d="${ci}">
|
||||
<select data-add-dim="${ci}">${DIMS.map(d=>`<option value="${d}">${DMLBL[d]}</option>`).join("")}</select>
|
||||
<button class="btn" data-add="${ci}">+ Ekle</button></td>`;
|
||||
tb.appendChild(addtr);
|
||||
tbl.appendChild(tb);
|
||||
det.appendChild(tbl);
|
||||
root.appendChild(det);
|
||||
});
|
||||
const pct=total?Math.round(done/total*100):0;
|
||||
document.getElementById("pbar").style.width=pct+"%";
|
||||
const fl=FILT==="all"?"":` (${DMLBL[FILT]})`;
|
||||
document.getElementById("plbl").textContent=`${done}/${total} tamamlandı${fl} — %${pct}`;
|
||||
}
|
||||
function esc(s){return (s+"").replace(/[&<>"]/g,c=>({"&":"&","<":"<",">":">",'"':"""}[c]));}
|
||||
|
||||
document.addEventListener("change",e=>{
|
||||
const c=+e.target.dataset.c, i=+e.target.dataset.i;
|
||||
if(e.target.classList.contains("st")){
|
||||
DATA[c].items[i].state=e.target.value; save(DATA); render();
|
||||
}
|
||||
if(e.target.classList.contains("dm")){
|
||||
DATA[c].items[i].dim=e.target.value; save(DATA); render();
|
||||
}
|
||||
});
|
||||
document.addEventListener("click",e=>{
|
||||
if(e.target.dataset.filt){
|
||||
FILT=e.target.dataset.filt;
|
||||
document.querySelectorAll("[data-filt]").forEach(b=>b.classList.toggle("on",b.dataset.filt===FILT));
|
||||
render(); return;
|
||||
}
|
||||
if(e.target.classList.contains("up")){
|
||||
PENDING={c:+e.target.dataset.c,i:+e.target.dataset.i};
|
||||
upInput.click(); return;
|
||||
}
|
||||
if(e.target.classList.contains("del")){
|
||||
const c=+e.target.dataset.c, i=+e.target.dataset.i;
|
||||
if(confirm("Bu asset satırı silinsin mi?")){ DATA[c].items.splice(i,1); save(DATA); render(); }
|
||||
}
|
||||
if(e.target.dataset.add!==undefined){
|
||||
const c=+e.target.dataset.add;
|
||||
const ni=document.querySelector(`[data-add-n="${c}"]`);
|
||||
const di=document.querySelector(`[data-add-d="${c}"]`);
|
||||
const dm=document.querySelector(`[data-add-dim="${c}"]`);
|
||||
const name=ni.value.trim(); if(!name) return;
|
||||
DATA[c].items.push({name,desc:di.value.trim(),dim:dm.value,state:"eksik"});
|
||||
save(DATA); render();
|
||||
document.querySelectorAll(".cat")[c].open=true;
|
||||
}
|
||||
});
|
||||
document.getElementById("expBtn").onclick=()=>{
|
||||
const blob=new Blob([JSON.stringify(DATA,null,2)],{type:"application/json"});
|
||||
const a=document.createElement("a");
|
||||
a.href=URL.createObjectURL(blob);
|
||||
a.download="stepstead-assets.json"; a.click();
|
||||
};
|
||||
document.getElementById("impBtn").onclick=()=>document.getElementById("impFile").click();
|
||||
document.getElementById("impFile").onchange=ev=>{
|
||||
const f=ev.target.files[0]; if(!f) return;
|
||||
const r=new FileReader();
|
||||
r.onload=()=>{ try{ const d=JSON.parse(r.result);
|
||||
if(Array.isArray(d)){ DATA=d; load.call&&0; DATA.forEach(c=>c.items.forEach(i=>{if(!i.dim)i.dim="2d";})); save(DATA); render(); } else alert("Geçersiz dosya"); }
|
||||
catch(e){ alert("Okunamadı"); } };
|
||||
r.readAsText(f);
|
||||
};
|
||||
render();
|
||||
checkAdmin();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue