PROJEKTE: Speichern/Laden/Autostart (25/25 E2E inkl. Server-Neustart)
- hms_app/projects.py: ProjectStore mit atomarem Speichern (.hms.json in projects/), Format-Kennung, Namens-Sanitize (Pfad-Traversal-Schutz, Leerzeichen erlaubt), Liste/Loeschen - engine.py: export_state() (Layer mit Alpha/Pos/Groesse/Z/FX + Intensitaeten + Master) und load_state() (ersetzt alle Layer, fehlende Medien werden uebersprungen und gemeldet, Werte geclamped, ungueltige FX-IDs verworfen) - server.py: /api/projects (GET Liste + Autostart), POST projects/save|load|delete|autostart; Loeschen des Autostart-Projekts raeumt config.json automatisch auf - config.py: engine.autostart_project (validiert, optional) - run.py: laedt Autostart-Projekt beim Programmstart - ui.py: neuer Reiter 'Projekte': Name eingeben + Speichern, Tabelle mit Laden/Autostart/Loeschen, Autostart-Badge E2E-BEWEIS (25/25 PASS, mit ECHTEM Server-Neustart): 1 Server startet · 2 Upload · 3 Layer · 4 FX/Alpha/Pos gesetzt · 5 Projekt 'Show A' gespeichert · 6 Traversal-Name sanitizet zu 'evil' · 7 Liste · 8 Layerzahl · 9 Zustand geleert (master=0.1) · 10 Projekt geladen · 11 Layer wiederhergestellt · 12 Alpha 0.7 wiederhergestellt · 13 FX1 blur 0.8 wiederhergestellt · 14 Position x=50 y=20 wiederhergestellt · 15 Master 0.9 · 16 Engine rendert · 17 Autostart gesetzt · 18 Server NEU GESTARTET · 19 Autostart: Layer+FX automatisch geladen · 20 Autostart: Engine rendert (60 Frames) · 21 config.json Autostart · 22 fehlendes Projekt 404 · 23 geloescht · 24 Autostart automatisch geleert · 25 config.json geleert Regressionen: App-E2E 14/14 (app=0) + FX-E2E 16/16 (fx=0) unveraendert gruen
This commit is contained in:
+21
-1
@@ -70,6 +70,7 @@ footer{background:var(--pan);padding:5px 16px;font-size:11px;color:var(--dim);bo
|
||||
<button data-p="live" class="on">Live</button>
|
||||
<button data-p="media">Medien</button>
|
||||
<button data-p="layers">Layer</button>
|
||||
<button data-p="projects">Projekte</button>
|
||||
<button data-p="settings">Einstellungen</button>
|
||||
</div>
|
||||
</header>
|
||||
@@ -112,6 +113,15 @@ footer{background:var(--pan);padding:5px 16px;font-size:11px;color:var(--dim);bo
|
||||
<div id="ledit"></div>
|
||||
</div>
|
||||
|
||||
<div id="page-projects" class="page">
|
||||
<div class="row" style="max-width:480px">
|
||||
<input type="text" id="projname" placeholder="Projektname…" style="flex:1;width:auto">
|
||||
<button class="acc" onclick="saveProject()">Speichern</button>
|
||||
</div>
|
||||
<p style="color:var(--dim);font-size:11px;margin:6px 0 14px">Speichert alle Layer inkl. Effekte, Position, Alpha und Master. Autostart lädt das Projekt beim Programmstart automatisch.</p>
|
||||
<table id="projtable"><thead><tr><th>Projekt</th><th>Layer</th><th>Gespeichert</th><th>Aktionen</th></tr></thead><tbody></tbody></table>
|
||||
</div>
|
||||
|
||||
<div id="page-settings" class="page">
|
||||
<div class="setgrp"><h3>Web-UI</h3>
|
||||
<div class="frow"><label>Port</label><input type="number" id="s-web-port"></div>
|
||||
@@ -159,7 +169,7 @@ async function api(path,body){try{const r=await fetch('/api/'+path,{method:'POST
|
||||
|
||||
// Tabs
|
||||
document.querySelectorAll('#tabbar button').forEach(b=>b.onclick=()=>showTab(b.dataset.p));
|
||||
function showTab(p){document.querySelectorAll('.page').forEach(x=>x.classList.remove('on'));$('page-'+p).classList.add('on');document.querySelectorAll('#tabbar button').forEach(x=>x.classList.toggle('on',x.dataset.p===p));if(p==='media')loadMedia();if(p==='settings')loadSettings();}
|
||||
function showTab(p){document.querySelectorAll('.page').forEach(x=>x.classList.remove('on'));$('page-'+p).classList.add('on');document.querySelectorAll('#tabbar button').forEach(x=>x.classList.toggle('on',x.dataset.p===p));if(p==='media')loadMedia();if(p==='settings')loadSettings();if(p==='projects')loadProjects();}
|
||||
|
||||
// Live-Steuerung
|
||||
$('master').oninput=e=>{api('master',{intensity:e.target.value/100});$('mv').textContent=e.target.value+'%'};
|
||||
@@ -257,6 +267,16 @@ async function saveSettings(){const body={
|
||||
if(d.saved){$('setmsg').textContent='Gespeichert ✓';setTimeout(()=>$('setmsg').textContent='',2500);
|
||||
if(d.hint)toast(d.hint)}}
|
||||
|
||||
async function loadProjects(){try{const d=await(await fetch('/api/projects')).json();
|
||||
const tb=document.querySelector('#projtable tbody');
|
||||
tb.innerHTML=(d.items||[]).map(p=>`<tr><td>${p.name}${d.autostart===p.name?' <span class="badge">Autostart</span>':''}</td><td>${p.layers}</td><td>${p.saved_at||''}</td>
|
||||
<td><button class="acc" onclick="loadProject('${p.name}')">Laden</button> <button onclick="setAutostart('${p.name}')">Autostart</button> <button class="danger" onclick="delProject('${p.name}')">✕</button></td></tr>`).join('')||'<tr><td colspan="4" style="color:var(--dim)">Noch keine Projekte gespeichert.</td></tr>'}catch(e){}}
|
||||
async function saveProject(){const name=$('projname').value.trim();if(!name){toast('Bitte Projektnamen eingeben',true);return}
|
||||
const d=await api('projects/save',{name});if(d.saved){toast('Projekt gespeichert: '+d.saved);$('projname').value='';loadProjects()}}
|
||||
async function loadProject(name){const d=await api('projects/load',{name});if(d.loaded){toast('Projekt geladen: '+name);showTab('live')}else toast(d.error||'Laden fehlgeschlagen',true)}
|
||||
async function delProject(name){if(!confirm(name+' löschen?'))return;await api('projects/delete',{name});loadProjects()}
|
||||
async function setAutostart(name){const d=await api('projects/autostart',{name});if(d.autostart){toast('Autostart aktiv: '+d.autostart);loadProjects()}}
|
||||
|
||||
let FX=[];
|
||||
async function loadFx(){try{FX=await(await fetch('/api/fx')).json();fillFxSelects()}catch(e){}}
|
||||
function fillFxSelects(){document.querySelectorAll('select[id^="fx1-"],select[id^="fx2-"]').forEach(sel=>{
|
||||
|
||||
Reference in New Issue
Block a user