diff options
| author | Leonard Kugis <leonard@kug.is> | 2026-09-04 16:56:09 +0200 |
|---|---|---|
| committer | Leonard Kugis <leonard@kug.is> | 2026-09-04 16:56:09 +0200 |
| commit | ee6bd2b184cb79aca8445bccb458ce42edad9efa (patch) | |
| tree | 2e2735aa5ea085bad9052235a02707ccce9041cb | |
| parent | 558c90807b09b16757c88f2e50bdbd123d425f37 (diff) | |
| download | medman-ee6bd2b184cb79aca8445bccb458ce42edad9efa.tar.gz | |
HTML: Made sidebar and header adjustable
| -rw-r--r-- | templates/index.html | 112 |
1 files changed, 86 insertions, 26 deletions
diff --git a/templates/index.html b/templates/index.html index 9f48eb7..da9c1ff 100644 --- a/templates/index.html +++ b/templates/index.html @@ -13,6 +13,8 @@ } * { margin:0; padding:0; box-sizing:border-box; } body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; background:var(--bg); color:var(--text); min-height:100vh; display:flex; flex-direction:column; } + +/* ── header ─────────────────────────────────────────────── */ header { background:var(--surface); border-bottom:1px solid var(--border); padding:8px 16px; display:flex; align-items:center; gap:8px; flex-wrap:wrap; position:sticky; top:0; z-index:100; } header h1 { font-size:1.1rem; font-weight:700; white-space:nowrap; color:var(--accent); margin-right:auto; } header button, header .btn { background:var(--surface2); color:var(--text); border:1px solid var(--border); border-radius:5px; padding:4px 10px; cursor:pointer; font-size:0.75rem; white-space:nowrap; } @@ -21,6 +23,17 @@ header button:hover { background:var(--border); } .btn-a:hover { filter:brightness(1.1); } .btn-g { background:var(--green)!important; color:#000!important; border-color:var(--green)!important; font-weight:600; } .btn-g:hover { filter:brightness(1.1); } + +/* ── toggle buttons for header & sidebar ────────────────── */ +#b-toggle-header, #b-toggle-side { width:28px; height:28px; padding:0; display:flex; align-items:center; justify-content:center; font-size:0.85rem; border-radius:4px; } +#b-toggle-header { background:var(--surface2); border:1px solid var(--border); color:var(--text2); } +#b-toggle-side { background:var(--surface2); border:1px solid var(--border); color:var(--text2); } + +/* ── header collapsed ───────────────────────────────────── */ +header.collapsed { padding:4px 8px; } +header.collapsed #header-actions { display:none; } +header.collapsed h1 { font-size:0.9rem; } + #sel-bar { display:none; align-items:center; gap:6px; padding:4px 16px; background:var(--accent); color:#000; font-size:0.75rem; font-weight:600; } #sel-bar.on { display:flex; } #sel-bar button { background:rgba(0,0,0,0.2); color:#000; border:none; border-radius:4px; padding:3px 8px; cursor:pointer; font-size:0.7rem; font-weight:600; } @@ -31,8 +44,12 @@ header button:hover { background:var(--border); } #up-area input[type=file] { color:var(--text2); font-size:0.75rem; } #up-area .prog { color:var(--accent); font-size:0.75rem; } +/* ── main layout ────────────────────────────────────────── */ main { flex:1; display:flex; overflow:hidden; } -#side { width:240px; background:var(--surface); border-right:1px solid var(--border); padding:10px; overflow-y:auto; flex-shrink:0; display:flex; flex-direction:column; gap:6px; } + +/* ── sidebar ────────────────────────────────────────────── */ +#side { width:240px; min-width:240px; background:var(--surface); border-right:1px solid var(--border); padding:10px; overflow-y:auto; flex-shrink:0; display:flex; flex-direction:column; gap:6px; transition:width 0.2s, min-width 0.2s, padding 0.2s; } +#side.collapsed { width:0; min-width:0; padding:10px 0; overflow:hidden; border-right:none; } .section { border:1px solid var(--border); border-radius:7px; overflow:hidden; } .sec-h { display:flex; align-items:center; justify-content:space-between; padding:7px 10px; background:var(--surface2); cursor:pointer; user-select:none; } @@ -71,6 +88,7 @@ main { flex:1; display:flex; overflow:hidden; } #dd-info { font-size:0.62rem; color:var(--text2); } #dd-info .dd { margin:1px 0; } +/* ── content area ───────────────────────────────────────── */ #content { flex:1; overflow-y:auto; padding:12px; } #grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(170px,1fr)); gap:8px; } .card { background:var(--surface); border:1px solid var(--border); border-radius:7px; overflow:hidden; transition:all 0.15s; cursor:pointer; position:relative; } @@ -117,12 +135,16 @@ main { flex:1; display:flex; overflow:hidden; } </style> </head> <body> -<header> +<header id="header"> <h1>🧩 MedMan</h1> - <button id="b-up" class="btn-a">📤 Upload</button> - <button id="b-scan" class="btn-g">🔄 Quellen scannen</button> - <button id="b-exp">📦 Export</button> - <label class="btn" style="cursor:pointer;">📥 Import <input type="file" id="f-imp" accept=".json" style="display:none;"></label> + <span id="header-actions"> + <button id="b-up" class="btn-a">📤 Upload</button> + <button id="b-scan" class="btn-g">🔄 Quellen scannen</button> + <button id="b-exp">📦 Export</button> + <label class="btn" style="cursor:pointer;">📥 Import <input type="file" id="f-imp" accept=".json" style="display:none;"></label> + </span> + <button id="b-toggle-header" title="Toggle header">▾</button> + <button id="b-toggle-side" title="Toggle sidebar">◀</button> </header> <div id="sel-bar"> @@ -217,7 +239,34 @@ const S = {files:[],disp:[],page:0,ps:50,sel:new Set(),tags:[],stags:[],sm:false function st(msg){let e=document.getElementById('status');e.textContent=msg;e.classList.add('on');clearTimeout(e._t);e._t=setTimeout(()=>e.classList.remove('on'),2200);} async function api(u,o={}){let r=await fetch(u,o);if(!r.ok){let e=await r.json().catch(()=>({error:r.statusText}));throw new Error(e.error||r.statusText);}return r.json();} -// --- Section toggling --- +// ── Header & sidebar toggle ────────────────────────────────────────────── + +function _persist(k,v){try{localStorage.setItem('medman_'+k,JSON.stringify(v));}catch(e){}} +function _restore(k,def){try{let v=localStorage.getItem('medman_'+k);return v!==null?JSON.parse(v):def;}catch(e){return def;}} + +(function(){ + let headerVis=_restore('header_visible',true); + let sideVis=_restore('sidebar_visible',true); + if(!headerVis){document.getElementById('header').classList.add('collapsed');document.getElementById('b-toggle-header').textContent='▴';} + if(!sideVis){document.getElementById('side').classList.add('collapsed');document.getElementById('b-toggle-side').textContent='▶';} +})(); + +document.getElementById('b-toggle-header').addEventListener('click',()=>{ + let h=document.getElementById('header'),b=document.getElementById('b-toggle-header'); + let collapsed=h.classList.toggle('collapsed'); + b.textContent=collapsed?'▴':'▾'; + _persist('header_visible',!collapsed); +}); + +document.getElementById('b-toggle-side').addEventListener('click',()=>{ + let s=document.getElementById('side'),b=document.getElementById('b-toggle-side'); + let collapsed=s.classList.toggle('collapsed'); + b.textContent=collapsed?'▶':'◀'; + _persist('sidebar_visible',!collapsed); +}); + +// ── Section toggling ───────────────────────────────────────────────────── + document.querySelectorAll('.sec-h').forEach(h=>{ h.addEventListener('click',()=>{ let sec=h.dataset.sec, b=document.getElementById('secb-'+sec); @@ -225,7 +274,8 @@ document.querySelectorAll('.sec-h').forEach(h=>{ }); }); -// --- Tags --- +// ── Tags ───────────────────────────────────────────────────────────────── + async function lt(){S.tags=await api('/api/tags');rTagList();rSearchSuggestions('');} function rTagList(){ let l=document.getElementById('tag-list'); @@ -248,7 +298,8 @@ function toggleSearchTag(name){ rTagList(); rSearchTags(); } -// --- Search --- +// ── Search ─────────────────────────────────────────────────────────────── + function rSearchTags(){ document.getElementById('search-tags').innerHTML=S.stags.map(n=> `<span class="tc sel">${n}<span class="rm" data-rm="${n}">×</span></span>` @@ -279,7 +330,8 @@ document.getElementById('b-search').addEventListener('click',()=>{ }); document.getElementById('b-clear').addEventListener('click',()=>{S.sm=false;S.stags=[];S.page=0;rTagList();rSearchTags();lf();}); -// --- Tag create --- +// ── Tag create ─────────────────────────────────────────────────────────── + document.getElementById('ti-btn').addEventListener('click',async()=>{ let i=document.getElementById('ti-in'),n=i.value.trim(); if(!n)return; await api('/api/tags',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name:n})}); @@ -287,7 +339,8 @@ document.getElementById('ti-btn').addEventListener('click',async()=>{ }); document.getElementById('ti-in').addEventListener('keydown',e=>{if(e.key==='Enter')document.getElementById('ti-btn').click();}); -// --- Files --- +// ── Files ──────────────────────────────────────────────────────────────── + async function lf(tf=null){let u='/api/files'+(tf?`?tag=${encodeURIComponent(tf)}`:'');S.files=await api(u);S.page=0;rGrid();} function rGrid(){ @@ -310,7 +363,6 @@ function rGrid(){ c.addEventListener('click',e=>{ let h=c.dataset.hash; if(e.ctrlKey||e.metaKey){toggleSel(h,c);return;} - // If the click was on the checkbox itself: toggle selection, don't open if(e.target.classList.contains('chk')||e.target.closest('.chk')){ toggleSel(h,c); e.stopPropagation(); return; } @@ -324,7 +376,8 @@ function rGrid(){ function fs(b){if(!b)return'';if(b<1024)return b+' B';if(b<1048576)return (b/1024).toFixed(1)+' KB';if(b<1073741824)return (b/1048576).toFixed(1)+' MB';return (b/1073741824).toFixed(1)+' GB';} -// --- Selection --- +// ── Selection ──────────────────────────────────────────────────────────── + function toggleSel(h,cardEl){ if(S.sel.has(h)){S.sel.delete(h);if(cardEl)cardEl.classList.remove('sel');} else{S.sel.add(h);if(cardEl)cardEl.classList.add('sel');} @@ -337,9 +390,10 @@ function updateSelUI(){ document.getElementById('sel-info').textContent=n?n+' Dateien ausgewählt':'Keine Auswahl'; } function clearSel(){S.sel.clear();updateSelUI();rGrid();} -function selAll(){let pageStart=S.page*S.ps;for(let f of S.disp){S.sel.add(f.hash);}updateSelUI();rGrid();} +function selAll(){for(let f of S.disp){S.sel.add(f.hash);}updateSelUI();rGrid();} + +// ── Navigation helpers ─────────────────────────────────────────────────── -// --- Navigation helpers --- function _clearNavKd(){ if(S._navKd){document.removeEventListener('keydown',S._navKd);S._navKd=null;} } @@ -351,7 +405,8 @@ function opNav(delta){ op(S.files[nidx].hash); } -// --- Selection actions --- +// ── Selection actions ──────────────────────────────────────────────────── + async function selTag(){ if(!S.sel.size){st('Keine Dateien ausgewählt');return;} let opts=S.tags.map(t=>`<option value="${t.id}">${t.name}</option>`).join(''); @@ -415,7 +470,8 @@ async function selDel(){ function sc(msg,cb){document.getElementById('conf-msg').textContent=msg;document.getElementById('conf-modal').classList.add('on');document.getElementById('conf-yes').onclick=()=>{document.getElementById('conf-modal').classList.remove('on');cb();};} -// --- Single file preview --- +// ── Single file preview ────────────────────────────────────────────────── + async function op(h){ _clearNavKd(); S.ph=h; let f=S.files.find(x=>x.hash===h); if(!f)return; @@ -438,7 +494,7 @@ async function op(h){ ht+=`<div class="ar"><button class="act dng" id="delb">🗑 Löschen</button></div>`; ht+=`<p style="margin-top:6px;font-size:0.6rem;"><a href="/raw/${h}" target="_blank" style="color:var(--accent)">Direktlink</a></p>`; - // ── Prev / Next navigation row ──────────────────────────────────── + // Prev / Next navigation row let idx=S.files.findIndex(x=>x.hash===h); let hasPrev=idx>0, hasNext=idx>=0 && idx<S.files.length-1; let prevLabel=hasPrev?`← ${S.files[idx-1].hash.slice(0,8)}…`:'← ──'; @@ -451,12 +507,10 @@ async function op(h){ document.getElementById('m-body').innerHTML=ht; document.getElementById('prev-modal').classList.add('on'); - // Prev / Next button handlers let np=document.getElementById('nav-prev'), nn=document.getElementById('nav-next'); if(np)np.onclick=e=>{e.stopPropagation();opNav(-1);}; if(nn)nn.onclick=e=>{e.stopPropagation();opNav(1);}; - // Keyboard left/right navigation while modal is open S._navKd=(e)=>{ if(e.key==='ArrowLeft'){e.preventDefault();opNav(-1);} else if(e.key==='ArrowRight'){e.preventDefault();opNav(1);} @@ -477,10 +531,12 @@ async function op(h){ await api(`/api/files/${h}/tags/${b.dataset.tid}`,{method:'DELETE'}); st('Tag entfernt'); await lf(S.sm?S.stags[0]:null); op(h);};}); } -// --- Data dirs --- +// ── Data dirs ──────────────────────────────────────────────────────────── + async function ldd(){let d=await api('/api/dat');document.getElementById('dd-info').innerHTML=d.dat.map(x=>`<div class="dd" style="margin-bottom:4px;"><strong>DD${x.i}</strong> (${(x.w*100).toFixed(0)}%)<br>${x.n} Dateien, ${(x.bytes/(1024*1024*1024)).toFixed(1)} GB<br><span style="opacity:0.5;word-break:break-all;font-size:0.55rem;">${x.path}</span></div>`).join('')+(d.src.length?`<div style="margin-top:6px;"><strong>Quellen:</strong><br>${d.src.map(s=>'<span style="opacity:0.5;font-size:0.55rem;">'+s+'</span>').join('<br>')}</div>`:'');} -// --- Header buttons --- +// ── Header buttons ─────────────────────────────────────────────────────── + document.getElementById('b-up').addEventListener('click',()=>document.getElementById('up-area').classList.toggle('on')); document.getElementById('up-cancel').addEventListener('click',()=>document.getElementById('up-area').classList.remove('on')); document.getElementById('up-go').addEventListener('click',async()=>{ @@ -501,13 +557,15 @@ document.getElementById('f-imp').addEventListener('change',async e=>{ await lt();if(S.files.length)await lf();st(`${r.imported||0} importiert`); }); -// --- Selection bar --- +// ── Selection bar ──────────────────────────────────────────────────────── + document.getElementById('sel-tag-btn').addEventListener('click',selTag); document.getElementById('sel-mv-btn').addEventListener('click',selMove); document.getElementById('sel-del-btn').addEventListener('click',selDel); document.getElementById('sel-clr-btn').addEventListener('click',clearSel); -// --- Selection section --- +// ── Selection section ──────────────────────────────────────────────────── + document.getElementById('b-sel-all').addEventListener('click',selAll); document.getElementById('b-sel-tag').addEventListener('click',selTag); document.getElementById('b-sel-untag').addEventListener('click',selUntag); @@ -515,7 +573,8 @@ document.getElementById('b-sel-mv').addEventListener('click',selMove); document.getElementById('b-sel-del').addEventListener('click',selDel); document.getElementById('b-sel-clr').addEventListener('click',clearSel); -// --- Modals --- +// ── Modals ─────────────────────────────────────────────────────────────── + function closeModal(){ _clearNavKd(); document.getElementById('prev-modal').classList.remove('on'); @@ -527,7 +586,8 @@ document.getElementById('prev-modal').addEventListener('click',e=>{if(e.target== document.getElementById('conf-no').addEventListener('click',()=>document.getElementById('conf-modal').classList.remove('on')); document.addEventListener('keydown',e=>{if(e.key==='Escape'){if(document.getElementById('prev-modal').classList.contains('on'))closeModal();}}); -// --- Init --- +// ── Init ───────────────────────────────────────────────────────────────── + lt(); lf(); ldd(); </script> </body> |
