aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLeonard Kugis <leonard@kug.is>2026-09-04 16:56:09 +0200
committerLeonard Kugis <leonard@kug.is>2026-09-04 16:56:09 +0200
commitee6bd2b184cb79aca8445bccb458ce42edad9efa (patch)
tree2e2735aa5ea085bad9052235a02707ccce9041cb
parent558c90807b09b16757c88f2e50bdbd123d425f37 (diff)
downloadmedman-ee6bd2b184cb79aca8445bccb458ce42edad9efa.tar.gz
HTML: Made sidebar and header adjustable
-rw-r--r--templates/index.html112
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}">&times;</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>