CUE-SZENENLISTE PRO LAYER + macOS-SUPPORT (35/35 E2E, 123 Checks gesamt)
1) Cue-/Szenenliste JE Layer (35/35 E2E): - Cue sichern = Snapshot (Quelle, Alpha, Position, Groesse, Z-Order, FX1/FX2 + Intensitaeten); Name + fade_ms + follow_ms pro Cue - GO vor/zurueck (steps), Goto (Index), Wrap-around am Listenende - FADE: Quellwechsel = dip-to-black (Phase1 runter, Wechsel bei 0, Phase2 hoch; E2E: alpha 0.49 -> 0.32 -> Ziel 0.8), sonst sanfter Tween von Alpha + FX-Intensitaeten; laufende Fades abbrechbar - AUTO-FOLLOW: follow_ms schaltet nach Ablauf automatisch weiter (E2E: Cue mit 600ms -> Wrap zum naechsten) - DMX CUE-GO: cue_go_channel (Settings + UI), steigende Flanke >=128 = GO auf allen Layern; ohne Flanke kein GO (E2E: 0->1, 1==1 bei High-Halten, neue Flanke 1->0); Sequenzfilter aktiv - Cue-Entfernen mit Indexkorrektur; Cues in Projekten speicher-/ladbar (3 Cues nach Projekt-Load erhalten) - UI: Cue-Bereich je Layer (Cue sichern / GO / Zurueck / Goto / Loeschen, aktiver Cue markiert, Fade+Follow-Anzeige) - Fehlerfaelle: unbekannter Layer 400, fehlendes Medium 400, unbekannter Generator 400 2) macOS (Apple Silicon + Intel) mit Skript: - setup_macos.sh: Homebrew installiert python@3.12, python-tk, GStreamer + alle Plugin-Sets + PyGObject; Apple-Silicon-PATH- Hinweis (/opt/homebrew); Erklaerung warum Homebrew statt gstreamer-.pkg (keine PyGObject-Bindings in .pkg) - Bootstrap (--bootstrap): darwin-Zweig mit brew-Installationspfad - run.py --generate-setup erzeugt jetzt 3 Skripte (Windows .ps1 / Linux .sh / macOS .sh); Bash-Syntax geprueft 3) Gefundene + behobene FEHLER: - WEB-UI-ABSTURZ (echter JS-Ternary-Klammerfehler in loadServers, seit Verteilungs-Commit unentdeckt, weil API-Tests den Browser- Code nie parsen): Klammerstruktur korrigiert + JS-Syntax nun per node --check verifizierbar (Syntax OK) + Remote-Buttons robust per data-Attribute (kein Quote-Escaping mehr) - update_layer blockierte Quellwechsel Video->Generator (Cues brauchen beides): Name entscheidet jetzt den Typ, Wechsel in beide Richtungen - Cue-E2E-Timing: dip-to-black hat 2 Phasen (Pruefung 0.2s/0.6s), Auto-Follow-Index-Fix, Art-Net-Sequenznummern im Test inkrementierend (Sequenzfilter verwirft Duplikate korrekt) E2E-Bilanz: Cue 35/35 + Regressionen Projekte 25/25, Verteilung 33/33, App 14/14, FX 16/16 = 123 Checks gruen
This commit is contained in:
+37
-4
@@ -147,6 +147,7 @@ footer{background:var(--pan);padding:5px 16px;font-size:11px;color:var(--dim);bo
|
||||
<div class="frow"><label>Kanal Playback</label><input type="number" id="s-artnet-playback" min="1" max="512"></div>
|
||||
<div class="frow"><label>Kanal Retrigger</label><input type="number" id="s-artnet-retrigger" min="1" max="512"></div>
|
||||
<div class="frow"><label>Kanal Blackout</label><input type="number" id="s-artnet-blackout" min="1" max="512"></div>
|
||||
<div class="frow"><label>Kanal Cue-GO</label><input type="number" id="s-artnet-cuego" min="1" max="512"><span class="hint">Flanke ≥128 = nächster Cue je Layer</span></div>
|
||||
<div class="frow"><label>Signalverlust</label><select id="s-artnet-policy"><option value="hold">Letzten Zustand halten</option><option value="fade_black">Auf schwarz fallen</option></select>
|
||||
<div class="frow"><label>Timeout Signalverlust</label><input type="number" id="s-artnet-timeout"> s</div>
|
||||
</div>
|
||||
@@ -217,7 +218,16 @@ function renderLayerEdit(ls){const e=$('ledit');
|
||||
<div class="frow"><label>FX1 Intensität</label><input type="range" id="fx1i-${L.id}" min="0" max="100" value="${Math.round((L.fx1_intensity??0.5)*100)}" oninput="api('layers/update',{id:${L.id},fx1_intensity:this.value/100})"></div>
|
||||
<div class="frow"><label>Effekt 2</label><select id="fx2-${L.id}" onchange="api('layers/update',{id:${L.id},fx2:this.value||null})"></select></div>
|
||||
<div class="frow"><label>FX2 Intensität</label><input type="range" id="fx2i-${L.id}" min="0" max="100" value="${Math.round((L.fx2_intensity??0.5)*100)}" oninput="api('layers/update',{id:${L.id},fx2_intensity:this.value/100})"></div>
|
||||
</div>`).join('')||'<p style="color:var(--dim)">Keine aktiven Layer. Medien-Tab öffnen und Datei als Layer hinzufügen.</p>'}}
|
||||
<h2 style="margin-top:10px">Cue-Liste (Szenen)</h2>
|
||||
<div id="cues-${L.id}"></div>
|
||||
<div class="row">
|
||||
<button class="acc" onclick="addCue(${L.id})">Cue sichern</button>
|
||||
<button onclick="cueGo(${L.id},1)">GO ▶</button>
|
||||
<button onclick="cueGo(${L.id},-1)">◀ Zurück</button>
|
||||
</div>
|
||||
</div>`).join('')||'<p style="color:var(--dim)">Keine aktiven Layer. Medien-Tab öffnen und Datei als Layer hinzufügen.</p>'}
|
||||
ls.forEach(renderCues);
|
||||
}
|
||||
|
||||
// Medien
|
||||
async function loadMedia(){try{const d=await(await fetch('/api/media')).json();
|
||||
@@ -260,7 +270,7 @@ async function loadSettings(){try{const s=await(await fetch('/api/settings')).js
|
||||
$('s-artnet-enabled').checked=s.artnet.enabled;$('s-artnet-port').value=s.artnet.listen_port;
|
||||
$('s-artnet-universes').value=s.artnet.universes.join(',');
|
||||
$('s-artnet-master').value=s.artnet.master_channel;$('s-artnet-layer').value=s.artnet.layer_start_channel;
|
||||
$('s-artnet-playback').value=s.artnet.playback_channel;$('s-artnet-retrigger').value=s.artnet.retrigger_channel;$('s-artnet-blackout').value=s.artnet.blackout_channel;
|
||||
$('s-artnet-playback').value=s.artnet.playback_channel;$('s-artnet-retrigger').value=s.artnet.retrigger_channel;$('s-artnet-blackout').value=s.artnet.blackout_channel;$('s-artnet-cuego').value=s.artnet.cue_go_channel??'';
|
||||
$('s-artnet-policy').value=s.artnet.signal_loss_policy;$('s-artnet-timeout').value=s.artnet.signal_loss_timeout_s;
|
||||
$('s-engine-max').value=s.engine.max_layers;$('s-engine-loop').checked=s.engine.loop_default;$('s-output-fs').checked=s.output.fullscreen;
|
||||
$('s-media-dir').value=s.media.dir;$('s-media-max').value=s.media.max_upload_mb;
|
||||
@@ -271,7 +281,7 @@ async function saveSettings(){const body={
|
||||
artnet:{enabled:$('s-artnet-enabled').checked,listen_port:+$('s-artnet-port').value,
|
||||
universes:$('s-artnet-universes').value.split(',').map(x=>parseInt(x.trim())).filter(x=>!isNaN(x)),
|
||||
master_channel:+$('s-artnet-master').value,layer_start_channel:+$('s-artnet-layer').value,
|
||||
playback_channel:+$('s-artnet-playback').value,retrigger_channel:+$('s-artnet-retrigger').value,blackout_channel:+$('s-artnet-blackout').value,
|
||||
playback_channel:+$('s-artnet-playback').value,retrigger_channel:+$('s-artnet-retrigger').value,blackout_channel:+$('s-artnet-blackout').value,cue_go_channel:+$('s-artnet-cuego').value||null,
|
||||
signal_loss_policy:$('s-artnet-policy').value,signal_loss_timeout_s:+$('s-artnet-timeout').value},
|
||||
engine:{max_layers:+$('s-engine-max').value,loop_default:$('s-engine-loop').checked,autostart_project:$('s-autostart').value||null},
|
||||
output:{fullscreen:$('s-output-fs').checked},
|
||||
@@ -311,10 +321,33 @@ async function loadServers(){try{const d=await(await fetch('/api/servers')).json
|
||||
// Medien-Tab: Remote-Medien
|
||||
const rg=$('rgrid');if(rg)rg.innerHTML=servers.map(S=>
|
||||
`<div class="setgrp"><h3>${S.name} <span class="badge${S.online?'':' err'}">${S.online?'online':'offline'}</span></h3>`+
|
||||
(S.online?(S.items||[]).map(m=>`<div class="frow"><label>${m.name} <small style="color:var(--dim)">${fmtSize(m.size)}</small></label><button class="acc" onclick="importRemote('${S.name}','${m.name.replace(/'/g,"\\'")}')">Ins Archiv ziehen</button></div>`).join('')||'<p style="color:var(--dim);font-size:11px">Keine Medien auf dem Server.</p>'):'')+'</div>'
|
||||
(S.online?(S.items||[]).map(m=>`<div class="frow"><label>${m.name} <small style="color:var(--dim)">${fmtSize(m.size)}</small></label><button class="acc" data-srv="${S.name}" data-m="${m.name}" onclick="importRemote(this.dataset.srv,this.dataset.m)">Ins Archiv ziehen</button></div>`).join('')||'<p style="color:var(--dim);font-size:11px">Keine Medien auf dem Server.</p>':'')+'</div>'
|
||||
).join('')||'<p style="color:var(--dim);font-size:11px">Noch keine Server. In den Einstellungen registrieren.</p>';
|
||||
}catch(e){}}
|
||||
|
||||
// ---------- Cue-Listen (Szenen) pro Layer ----------
|
||||
function cueRow(L,c,i){return `<div class="frow" style="padding:2px 0">
|
||||
<label>${i+1}. ${c.name} <small style="color:var(--dim)">${c.fade_ms}ms Fade${c.follow_ms?' · Auto '+Math.round(c.follow_ms/1000)+'s':''}</small></label>
|
||||
${i===L.cue_index?'<span class="badge">aktiv</span>':''}
|
||||
<button onclick="cueGoto(${L.id},${i})">Goto</button>
|
||||
<button class="danger" onclick="delCue(${L.id},${c.cue_id})">✕</button></div>`}
|
||||
function renderCues(L){const box=$('cues-'+L.id);if(!box)return;
|
||||
const ls=(L.cues||[]);
|
||||
if(ls.length===0){box.innerHTML='<p style="color:var(--dim);font-size:11px">Noch keine Cues. Button unten sichert den aktuellen Zustand als Cue.</p>';return}
|
||||
box.innerHTML=ls.map((c,i)=>cueRow(L,c,i)).join('')}
|
||||
async function addCue(id){const nm=prompt('Cue-Name:','Cue');if(nm===null)return;
|
||||
const fm=prompt('Fade-Zeit in ms (0 = harter Schnitt):','500');if(fm===null)return;
|
||||
const fo=prompt('Auto-Follow in ms (0 = aus, sonst Weiter nach Ablauf):','0');if(fo===null)return;
|
||||
const d=await api('layers/cues/add',{id,name:nm||undefined,fade_ms:+fm||0,follow_ms:+fo||0});
|
||||
if(d.cue)toast('Cue gesichert: '+d.cue.name);else if(d.error)toast(d.error,true)}
|
||||
async function cueGo(id,steps){const d=await api('layers/cues/go',{id,steps});
|
||||
if(d.cue)toast('GO → '+d.cue);else if(d.error)toast(d.error,true)}
|
||||
async function cueGoto(id,index){const d=await api('layers/cues/goto',{id,index});
|
||||
if(d.cue)toast('Goto: '+d.cue)}
|
||||
async function delCue(id,cueId){if(!confirm('Cue entfernen?'))return;
|
||||
await api('layers/cues/remove',{id,cue_id:cueId});
|
||||
poll()}
|
||||
|
||||
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