From e3885b47fbb579e4e56dea7359c59196d49817f6 Mon Sep 17 00:00:00 2001 From: KarnerF Date: Sun, 30 Aug 2026 10:45:07 +0200 Subject: [PATCH] Merge Games+Images into one panel; add game icon endpoint; card layout with icons --- main.c | 155 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 82 insertions(+), 73 deletions(-) diff --git a/main.c b/main.c index 5f6ccbf..7017759 100644 --- a/main.c +++ b/main.c @@ -1108,7 +1108,6 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { H("",L(LS_IMG_OVERRIDES)); H("",L(LS_MANUAL)); H("",L(LS_GAMES)); - H("",L(LS_IMAGES)); H("",L(LS_NOTIFS)); H(""); /* raw panel accessible via button in backup section, no sidebar tab */ @@ -1593,25 +1592,16 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { H("",L(LS_ADD_ENTRY)); H(""); - /* Panel: Spiele */ + /* Panel: Spiele & Images (kombiniert) */ H("
"); H("
%s
",L(LS_GAMES)); if(!has_api) H("

ab 1.7alpha3

"); H("
" - "
"); H("

%s

",L(LS_LOADING)); - H("
"); - - /* Panel: Images */ - H("
"); - H("
%s
",L(LS_IMAGES)); - if(!has_api) H("

ab 1.7alpha3

"); - H("
" - "
"); + H("
%s
",L(LS_IMAGES)); H("

%s

",L(LS_LOADING)); H("
"); @@ -1721,8 +1711,7 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { "sessionStorage.setItem('panel',el.getAttribute('data-p'));" "if(el.getAttribute('data-p')==='log')refreshLog();" "if(el.getAttribute('data-p')==='raw'){rawLoad();}" - "if(el.getAttribute('data-p')==='spl')loadGames();" - "if(el.getAttribute('data-p')==='img')loadImages();" + "if(el.getAttribute('data-p')==='spl')loadGamePanel();" "var ct=document.querySelector('.content');if(ct)ct.scrollTop=0;}" /* restore panel after USB-triggered reload */ "var _rp=sessionStorage.getItem('panel');" "if(_rp){var _rb=document.querySelector('.nav-item[data-p=\"'+_rp+'\"]');if(_rb)showP(_rb);}" @@ -1777,32 +1766,65 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { "if(l){l.textContent=nm;l.title=path;l.style.display='inline';}if(b)b.style.display='inline';}" "setTimeout(refreshStatus,2000);})" ".catch(function(){if(btn)btn.disabled=false;});}""function showElfPicker(btn,paths,cb){var old=document.getElementById('sm-elf-picker');if(old)old.remove();var picker=document.createElement('div');picker.id='sm-elf-picker';picker.style.cssText='position:fixed;top:0;left:0;width:100%%;height:100%%;background:rgba(0,0,0,.75);display:flex;align-items:center;justify-content:center;z-index:9999;';var box=document.createElement('div');box.style.cssText='background:#0d111a;border:1px solid #1e2a42;border-radius:14px;padding:36px;min-width:500px;max-width:800px;';var title=document.createElement('p');title.style.cssText='margin:0 0 20px;font-size:.7rem;text-transform:uppercase;letter-spacing:.12em;color:#64748b;font-family:monospace;';title.textContent=_t.sel_sm;box.appendChild(title);paths.forEach(function(p){var b=document.createElement('button');b.style.cssText='display:block;width:100%%;margin-bottom:10px;padding:16px 18px;border-radius:8px;border:1px solid #1e2a42;background:#131927;color:#e2e8f0;cursor:pointer;text-align:left;font-family:monospace;font-size:.75rem;word-break:break-all;';b.textContent=p;b.onclick=function(){picker.remove();if(cb)cb(p);else launchElf(btn,p);};box.appendChild(b);});var cancel=document.createElement('button');cancel.style.cssText='display:block;width:100%%;padding:14px;border-radius:8px;border:1px solid #1e2a42;background:transparent;color:#64748b;cursor:pointer;font-family:monospace;font-size:.75rem;margin-top:6px;';cancel.textContent=_t.cancel;cancel.onclick=function(){picker.remove();};box.appendChild(cancel);picker.appendChild(box);document.body.appendChild(picker);}""document.querySelector('form').addEventListener('keydown',function(e){""if(e.key==='Enter'&&e.target.tagName!=='BUTTON')e.preventDefault();""});"); - H("function loadGames(){" - "var el=document.getElementById('games-list');if(!el)return;" - "var old=el.innerHTML;" - "el.innerHTML='

Lade...

';" - "fetch('/api/sm/games',{method:'POST'}).then(function(r){return r.json();})" - ".then(function(d){" - "if(d.error||d.status){el.innerHTML='

SM API: '+(d.error||'Fehler')+'

';return;}" - "if(!d.games||!d.games.length){el.innerHTML='

Keine Spiele gefunden

';return;}" - "var h='';" - "h+='';" - "h+='';" - "h+='';" - "h+='';" - "h+='';" - "d.games.forEach(function(g){" - "var mn=g.mounted?'● aktiv':'○';" + H("function loadGamePanel(){" + "var gl=document.getElementById('games-list');" + "var il=document.getElementById('images-list');" + "if(gl)gl.innerHTML='

Lade...

';" + "if(il)il.innerHTML='';" + "var gp=fetch('/api/sm/games',{method:'POST'}).then(function(r){return r.json();}).catch(function(){return null;});" + "var ip=fetch('/api/sm/images',{method:'POST'}).then(function(r){return r.json();}).catch(function(){return null;});" + "Promise.all([gp,ip]).then(function(res){" + "var gd=res[0],id=res[1];" + "if(!gl)return;" + "if(!gd||gd.error||gd.status){" + "gl.innerHTML='

SM API: '+(gd&&gd.error?gd.error:'nicht erreichbar')+'

';}" + "else if(!gd.games||!gd.games.length){" + "gl.innerHTML='

Keine Spiele gefunden

';}" + "else{" + "var h='';" + "gd.games.forEach(function(g){" + "var tid=g.title_id||'';" + "var mn=g.mounted" + "?'● aktiv'" + ":'○ inaktiv';" "var ba=g.mounted" - "?''" - ":'';" - "h+='';" - "h+='';" - "h+='';" - "h+='';" - "h+='';" - "});h+='
TitelIDMount
'+(g.title_name||'?')+''+g.title_id+''+mn+''+ba+'
';el.innerHTML=h;})" - ".catch(function(){el.innerHTML=old;});}" + "?''" + ":'';" + "var ip2=g.image_path?'
'+g.image_path+'
':'';" + "h+='
';" + "h+='';" + "h+='
';" + "h+='
'+(g.title_name||tid)+'
';" + "h+='
'+tid+'
';" + "h+=ip2+'
';" + "h+='
'+mn+ba+'
';" + "});gl.innerHTML=h;}" + "if(!il)return;" + "if(!id||id.error||id.status){" + "il.innerHTML='

'+(id&&id.error?id.error:'nicht erreichbar')+'

';return;}" + "if(!id.images||!id.images.length){il.innerHTML='

Keine Image-Dateien gefunden

';return;}" + "var ih='';" + "ih+='';" + "ih+='';" + "ih+='';" + "ih+='';" + "id.images.forEach(function(img){" + "var parts=(img.path||'').split('/');" + "var fn=parts.pop()||img.path;" + "var dir=parts.join('/')||'/';" + "var ext=fn.indexOf('.')>-1?fn.split('.').pop().toUpperCase():'';" + "var sz=img.size?(img.size>1073741824?(img.size/1073741824).toFixed(2)+' GB':(img.size/1048576).toFixed(0)+' MB'):'?';" + "var st=img.mounted?'● aktiv':'○';" + "ih+='';" + "ih+='';" + "ih+='';" + "ih+='';" + "ih+='';" + "});ih+='
Datei / PfadTypGr\\u00f6\\u00dfeMount
'+fn+'';" + "ih+='
'+dir+'
'+ext+''+sz+''+st+'
';il.innerHTML=ih;});}" + "function loadGames(){loadGamePanel();}" "function mountGame(tid){" "var el=document.getElementById('games-list');" "if(el)el.innerHTML='

Mounting '+tid+'...

';" @@ -1810,8 +1832,8 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { ".then(function(r){return r.json();})" ".then(function(d){" "if(d.error&&el)el.innerHTML='

'+d.error+'

';" - "setTimeout(loadGames,3000);})" - ".catch(function(){setTimeout(loadGames,3000);});}" + "setTimeout(loadGamePanel,3000);})" + ".catch(function(){setTimeout(loadGamePanel,3000);});}" "function unmountGame(tid){" "var el=document.getElementById('games-list');" "if(el)el.innerHTML='

Unmounting '+tid+'...

';" @@ -1819,37 +1841,8 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { ".then(function(r){return r.json();})" ".then(function(d){" "if(d.error&&el)el.innerHTML='

'+d.error+'

';" - "setTimeout(loadGames,3000);})" - ".catch(function(){setTimeout(loadGames,3000);});}" - "function loadImages(){" - "var el=document.getElementById('images-list');if(!el)return;" - "el.innerHTML='

Lade...

';" - "fetch('/api/sm/images',{method:'POST'}).then(function(r){return r.json();})" - ".then(function(d){" - "if(d.error||d.status){el.innerHTML='

SM API: '+(d.error||'Fehler')+'

';return;}" - "if(!d.images||!d.images.length){el.innerHTML='

Keine Images gefunden

';return;}" - "var h='';" - "h+='';" - "h+='';" - "h+='';" - "h+='';" - "h+='';" - "d.images.forEach(function(img){" - "var parts=(img.path||'').split('/');" - "var fn=parts.pop()||img.path;" - "var dir=parts.join('/')||'/';" - "var ext=fn.indexOf('.')>-1?fn.split('.').pop().toUpperCase():'';" - "var sz=img.size?(img.size>1073741824?(img.size/1073741824).toFixed(2)+' GB':(img.size/1048576).toFixed(0)+' MB'):'?';" - "var st=img.mounted?'● aktiv':'○';" - "h+='';" - "h+='';" - "h+='';" - "h+='';" - "h+='';" - "});h+='
Datei / PfadTypGr\\u00f6\\u00dfeMount
';" - "h+=''+fn+'';" - "h+='
'+dir+'
'+ext+''+sz+''+st+'
';el.innerHTML=h;})" - ".catch(function(){el.innerHTML='

SM API nicht erreichbar

';});}"); + "setTimeout(loadGamePanel,3000);})" + ".catch(function(){setTimeout(loadGamePanel,3000);});}"); H("function addPath(){" "var c=document.getElementById('paths-list');" "var d=document.createElement('div');d.className='path-row';" @@ -2863,6 +2856,22 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) { write_prefs(&p); mg_http_reply(c,200,"Content-Type: application/json\r\n","{\"ok\":true}"); } + else if(mg_match(hm->uri,mg_str("/api/game/icon"),NULL)){ + char tid[64]={0}; mg_http_get_var(&hm->query,"title_id",tid,sizeof(tid)); + /* sanitise: only allow alphanumeric title IDs */ + for(int i=0;tid[i];i++) if(!isalnum((unsigned char)tid[i])){tid[0]=0;break;} + if(!tid[0]){mg_http_reply(c,400,"","");return;} + char p[128]; + snprintf(p,sizeof(p),"/system_data/priv/appmeta/%s/icon0.png",tid); + FILE *f=fopen(p,"rb"); + if(!f){mg_http_reply(c,404,"","");return;} + fseek(f,0,SEEK_END); long sz=ftell(f); fseek(f,0,SEEK_SET); + if(sz<=0||sz>524288){fclose(f);mg_http_reply(c,404,"","");return;} + char *buf=malloc(sz); if(!buf){fclose(f);mg_http_reply(c,500,"","");return;} + fread(buf,1,sz,f); fclose(f); + mg_http_reply(c,200,"Content-Type: image/png\r\nCache-Control: max-age=3600\r\n","%.*s",(int)sz,buf); + free(buf); + } else if(mg_match(hm->uri,mg_str("/api/sm/games"),NULL)|| mg_match(hm->uri,mg_str("/api/sm/images"),NULL)){ const char *smpath=mg_match(hm->uri,mg_str("/api/sm/games"),NULL)?"/api/v1/games":"/api/v1/images";