Refine routing UI and rebuild Windows package

This commit is contained in:
jhartworks
2026-06-16 17:13:48 +02:00
parent d282eb8b0b
commit f1886a31c9
79 changed files with 247746 additions and 59243 deletions
BIN
View File
Binary file not shown.
+6 -4
View File
File diff suppressed because one or more lines are too long
+246155 -57316
View File
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+1133 -1623
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -9,11 +9,11 @@
"dist:win": "set CSC_IDENTITY_AUTO_DISCOVERY=false&& electron-builder --win portable" "dist:win": "set CSC_IDENTITY_AUTO_DISCOVERY=false&& electron-builder --win portable"
}, },
"devDependencies": { "devDependencies": {
"electron": "^31.7.7", "electron": "^42.4.0",
"electron-builder": "^24.13.3" "electron-builder": "^26.15.3"
}, },
"dependencies": { "dependencies": {
"knx": "^2.5.4", "knx": "^0.0.1",
"modbus-serial": "^8.0.25", "modbus-serial": "^8.0.25",
"mqtt": "^5.10.4", "mqtt": "^5.10.4",
"serialport": "^13.0.0" "serialport": "^13.0.0"
+79 -27
View File
@@ -3,7 +3,7 @@
const RULER_HEIGHT = 34; const RULER_HEIGHT = 34;
const POINT_RADIUS = 7; const POINT_RADIUS = 7;
const FPS_DEFAULT = 30; const FPS_DEFAULT = 30;
const LEFT_GUTTER = 126; const LEFT_GUTTER = 162;
const RIGHT_PADDING = 24; const RIGHT_PADDING = 24;
const MIN_CANVAS_HEIGHT = 420; const MIN_CANVAS_HEIGHT = 420;
const EXTERNAL_RESYNC_MS = 1000; const EXTERNAL_RESYNC_MS = 1000;
@@ -189,7 +189,7 @@
routing: createDefaultRouting(), routing: createDefaultRouting(),
ui: { ui: {
settingsOpen: false, settingsOpen: false,
settingsTab: "routing" settingsTab: "midi"
} }
}; };
@@ -531,13 +531,13 @@
if (track.type === "sacn-channel") return `Uni ${track.universe} / Addr ${track.address}`; if (track.type === "sacn-channel") return `Uni ${track.universe} / Addr ${track.address}`;
if (track.type === "audio-file") { if (track.type === "audio-file") {
const bpmText = track.analysisBpm ? ` / ${Math.round(track.analysisBpm)} BPM` : ""; const bpmText = track.analysisBpm ? ` / ${Math.round(track.analysisBpm)} BPM` : "";
return `${escapeHtml(track.mediaName || basenameFromPath(track.filePath))}${bpmText}`; return `${track.mediaName || basenameFromPath(track.filePath)}${bpmText}`;
} }
if (track.type === "serial-value") return escapeHtml(track.template); if (track.type === "serial-value") return track.template;
if (track.type === "modbus-tcp" || track.type === "modbus-rtu") return `ID ${track.unitId} / Addr ${track.address}`; if (track.type === "modbus-tcp" || track.type === "modbus-rtu") return `ID ${track.unitId} / Addr ${track.address}`;
if (track.type === "knx-value") return `${escapeHtml(track.groupAddress)} / ${escapeHtml(track.dpt)}`; if (track.type === "knx-value") return `${track.groupAddress} / ${track.dpt}`;
if (track.type === "osc-value") return escapeHtml(track.address); if (track.type === "osc-value") return track.address;
return escapeHtml(track.topic); return track.topic;
} }
function basenameFromPath(filePath) { function basenameFromPath(filePath) {
@@ -829,6 +829,29 @@
return escapeHtml(value).replaceAll("\"", """); return escapeHtml(value).replaceAll("\"", """);
} }
function shortenLabel(value, maxLength = 18) {
const text = String(value || "").trim();
if (text.length <= maxLength) {
return text;
}
return `${text.slice(0, Math.max(1, maxLength - 3)).trimEnd()}...`;
}
function truncateCanvasText(text, maxWidth) {
const value = String(text || "");
if (!value) {
return "";
}
if (ctx.measureText(value).width <= maxWidth) {
return value;
}
let shortened = value;
while (shortened.length > 1 && ctx.measureText(`${shortened}...`).width > maxWidth) {
shortened = shortened.slice(0, -1);
}
return `${shortened.trimEnd()}...`;
}
function chooseTimeStep(duration) { function chooseTimeStep(duration) {
if (duration <= 15) return 0.5; if (duration <= 15) return 0.5;
if (duration <= 60) return 1; if (duration <= 60) return 1;
@@ -921,25 +944,29 @@
function drawTrackLaneLabel(track, trackIndex) { function drawTrackLaneLabel(track, trackIndex) {
const top = RULER_HEIGHT + (trackIndex * TRACK_HEIGHT); const top = RULER_HEIGHT + (trackIndex * TRACK_HEIGHT);
const color = getTrackColor(track); const color = getTrackColor(track);
const labelLeft = 66;
const labelWidth = LEFT_GUTTER - labelLeft - 14;
const trackName = track.name || "Unbenannt";
const detailText = describeTrack(track);
ctx.fillStyle = color; ctx.fillStyle = color;
ctx.beginPath(); ctx.beginPath();
ctx.roundRect(18, top + 14, 44, 24, 8); ctx.roundRect(16, top + 15, 40, 22, 8);
ctx.fill(); ctx.fill();
ctx.fillStyle = "#091018"; ctx.fillStyle = "#091018";
ctx.font = "bold 11px Trebuchet MS"; ctx.font = "bold 10px Trebuchet MS";
ctx.textAlign = "center"; ctx.textAlign = "center";
ctx.fillText(getTrackKindLabel(track), 40, top + 30); ctx.fillText(getTrackKindLabel(track), 36, top + 29);
ctx.save(); ctx.textAlign = "left";
ctx.translate(92, top + (TRACK_HEIGHT / 2));
ctx.rotate(-Math.PI / 2);
ctx.fillStyle = "rgba(255,255,255,0.86)"; ctx.fillStyle = "rgba(255,255,255,0.86)";
ctx.font = "bold 13px Trebuchet MS"; ctx.font = "bold 12px Trebuchet MS";
ctx.textAlign = "center"; ctx.fillText(truncateCanvasText(trackName, labelWidth), labelLeft, top + 33);
ctx.fillText(track.name, 0, 0);
ctx.restore(); ctx.fillStyle = "rgba(164,178,204,0.92)";
ctx.font = "11px Trebuchet MS";
ctx.fillText(truncateCanvasText(detailText, labelWidth), labelLeft, top + 52);
if (track.id === state.selectedTrackId) { if (track.id === state.selectedTrackId) {
ctx.strokeStyle = "rgba(110,194,242,0.76)"; ctx.strokeStyle = "rgba(110,194,242,0.76)";
@@ -1137,12 +1164,13 @@
function renderTimelineList() { function renderTimelineList() {
refs.timelineList.innerHTML = ""; refs.timelineList.innerHTML = "";
for (const timeline of state.project.timelines) { for (const timeline of state.project.timelines) {
const shortName = shortenLabel(timeline.name, 26);
const card = document.createElement("div"); const card = document.createElement("div");
card.className = `timeline-card selectable-card ${timeline.id === state.selectedTimelineId ? "active" : ""}`; card.className = `timeline-card selectable-card ${timeline.id === state.selectedTimelineId ? "active" : ""}`;
card.dataset.timelineId = timeline.id; card.dataset.timelineId = timeline.id;
card.innerHTML = ` card.innerHTML = `
<strong>${escapeHtml(timeline.name)}</strong> <strong class="truncate-text" title="${escapeAttribute(timeline.name)}">${escapeHtml(shortName)}</strong>
<div class="meta-row"> <div class="meta-row compact">
<span>${timeline.tracks.length} Tracks</span> <span>${timeline.tracks.length} Tracks</span>
<span>${timeline.duration}s</span> <span>${timeline.duration}s</span>
</div> </div>
@@ -1173,18 +1201,22 @@
} }
for (const track of timeline.tracks) { for (const track of timeline.tracks) {
const fullName = track.name || "Unbenannt";
const shortName = shortenLabel(fullName, 24);
const detail = describeTrack(track);
const shortDetail = shortenLabel(detail, 34);
const card = document.createElement("div"); const card = document.createElement("div");
card.className = `track-card ${track.id === state.selectedTrackId ? "active" : ""}`; card.className = `track-card ${track.id === state.selectedTrackId ? "active" : ""}`;
card.dataset.trackId = track.id; card.dataset.trackId = track.id;
card.innerHTML = ` card.innerHTML = `
<div class="track-card-top"> <div class="track-card-top">
<div class="track-card-body selectable-card" data-track-id="${track.id}"> <div class="track-card-body selectable-card" data-track-id="${track.id}">
<div class="panel-header"> <div class="track-card-header">
<strong>${escapeHtml(track.name)}</strong> <strong class="truncate-text" title="${escapeAttribute(fullName)}">${escapeHtml(shortName)}</strong>
<span class="track-pill ${pillClassForTrack(track)}">${getTrackKindLabel(track)}</span> <span class="track-pill ${pillClassForTrack(track)}" title="${escapeAttribute(getTrackKindLabel(track))}">${getTrackKindLabel(track)}</span>
</div> </div>
<div class="meta-row"> <div class="meta-row compact">
<span>${describeTrack(track)}</span> <span class="truncate-text" title="${escapeAttribute(detail)}">${escapeHtml(shortDetail)}</span>
<span>${track.points.length} Punkte</span> <span>${track.points.length} Punkte</span>
</div> </div>
</div> </div>
@@ -1201,6 +1233,7 @@
function renderTimelineInspector() { function renderTimelineInspector() {
const timeline = getSelectedTimeline(); const timeline = getSelectedTimeline();
refs.timelineTitle.textContent = timeline ? timeline.name : "Keine Timeline ausgewaehlt"; refs.timelineTitle.textContent = timeline ? timeline.name : "Keine Timeline ausgewaehlt";
refs.timelineTitle.title = timeline ? timeline.name : "";
if (!timeline) { if (!timeline) {
refs.timelineInspector.innerHTML = `<div class="empty-state">Keine Timeline ausgewaehlt.</div>`; refs.timelineInspector.innerHTML = `<div class="empty-state">Keine Timeline ausgewaehlt.</div>`;
return; return;
@@ -1635,12 +1668,22 @@
function openSettings(tab = state.ui.settingsTab) { function openSettings(tab = state.ui.settingsTab) {
state.ui.settingsOpen = true; state.ui.settingsOpen = true;
state.ui.settingsTab = tab; state.ui.settingsTab = tab || "midi";
void refreshNetworkAdapters(); void refreshNetworkAdapters();
void refreshAudioOutputs(); void refreshAudioOutputs();
renderAll(); renderAll();
} }
function updateCanvasHoverTitle(x, y) {
const hit = findCanvasHit(x, y);
const nextTitle = hit?.track
? `${hit.track.name || "Unbenannt"}\n${describeTrack(hit.track)}`
: "";
if (refs.timelineCanvas.title !== nextTitle) {
refs.timelineCanvas.title = nextTitle;
}
}
function closeSettings() { function closeSettings() {
state.ui.settingsOpen = false; state.ui.settingsOpen = false;
renderAll(); renderAll();
@@ -2896,12 +2939,12 @@
refs.playButton.addEventListener("click", playTransport); refs.playButton.addEventListener("click", playTransport);
refs.pauseButton.addEventListener("click", pauseTransport); refs.pauseButton.addEventListener("click", pauseTransport);
refs.stopButton.addEventListener("click", () => stopTransport(true)); refs.stopButton.addEventListener("click", () => stopTransport(true));
refs.openSettingsButton.addEventListener("click", () => openSettings("routing")); refs.openSettingsButton.addEventListener("click", () => openSettings());
refs.closeSettingsButton.addEventListener("click", closeSettings); refs.closeSettingsButton.addEventListener("click", closeSettings);
refs.settingsBackdrop.addEventListener("click", closeSettings); refs.settingsBackdrop.addEventListener("click", closeSettings);
refs.settingsTabButtons.forEach((button) => { refs.settingsTabButtons.forEach((button) => {
button.addEventListener("click", () => { button.addEventListener("click", () => {
state.ui.settingsTab = button.dataset.settingsTab || "routing"; state.ui.settingsTab = button.dataset.settingsTab || "midi";
renderAll(); renderAll();
}); });
}); });
@@ -2998,6 +3041,15 @@
addPointFromCanvas(event.clientX - rect.left, event.clientY - rect.top); addPointFromCanvas(event.clientX - rect.left, event.clientY - rect.top);
}); });
refs.timelineCanvas.addEventListener("mousemove", (event) => {
const rect = refs.timelineCanvas.getBoundingClientRect();
updateCanvasHoverTitle(event.clientX - rect.left, event.clientY - rect.top);
});
refs.timelineCanvas.addEventListener("mouseleave", () => {
refs.timelineCanvas.title = "";
});
refs.timelineCanvas.addEventListener("mousedown", (event) => { refs.timelineCanvas.addEventListener("mousedown", (event) => {
const rect = refs.timelineCanvas.getBoundingClientRect(); const rect = refs.timelineCanvas.getBoundingClientRect();
const x = event.clientX - rect.left; const x = event.clientX - rect.left;
+288 -267
View File
@@ -97,14 +97,14 @@
<section class="editor panel"> <section class="editor panel">
<div class="editor-header"> <div class="editor-header">
<div> <div class="editor-title-block">
<div class="eyebrow">Editor</div> <div class="eyebrow">Editor</div>
<h2 id="timelineTitle">Keine Timeline ausgewählt</h2> <h2 id="timelineTitle">Keine Timeline ausgewaehlt</h2>
</div> </div>
<div class="editor-hints"> <div class="editor-hints">
<span>Doppelklick: Punkt setzen</span> <span>Doppelklick: Punkt setzen</span>
<span>Ziehen: Zeit/Wert ändern</span> <span>Ziehen: Zeit/Wert aendern</span>
<span>Entf: Punkt löschen</span> <span>Entf: Punkt loeschen</span>
</div> </div>
</div> </div>
<div class="canvas-frame"> <div class="canvas-frame">
@@ -112,6 +112,22 @@
</div> </div>
</section> </section>
</main> </main>
<aside class="inspector">
<section class="panel inspector-panel">
<div class="panel-header">
<h2>Timeline</h2>
</div>
<div id="timelineInspector"></div>
</section>
<section class="panel inspector-panel">
<div class="panel-header">
<h2>Auswahl</h2>
</div>
<div id="selectionInspector"></div>
</section>
</aside>
</div> </div>
<div id="settingsModal" class="settings-modal" aria-hidden="true"> <div id="settingsModal" class="settings-modal" aria-hidden="true">
@@ -127,311 +143,316 @@
</div> </div>
</div> </div>
<div class="settings-tabbar" role="tablist" aria-label="Einstellungen"> <div class="settings-tabbar" role="tablist" aria-label="Ausgaben">
<button class="settings-tab active" type="button" data-settings-tab="routing">Routing</button> <button class="settings-tab active" type="button" data-settings-tab="midi" title="MIDI">MIDI</button>
<button class="settings-tab" type="button" data-settings-tab="timeline">Timeline</button> <button class="settings-tab" type="button" data-settings-tab="audio" title="Audio">Audio</button>
<button class="settings-tab" type="button" data-settings-tab="selection">Auswahl</button> <button class="settings-tab" type="button" data-settings-tab="artnet" title="Art-Net">Art-Net</button>
<button class="settings-tab" type="button" data-settings-tab="sacn" title="sACN">sACN</button>
<button class="settings-tab" type="button" data-settings-tab="osc" title="Open Sound Control">OSC</button>
<button class="settings-tab" type="button" data-settings-tab="mqtt" title="MQTT">MQTT</button>
<button class="settings-tab" type="button" data-settings-tab="serial" title="Serial">Serial</button>
<button class="settings-tab" type="button" data-settings-tab="modbusTcp" title="Modbus TCP">MB TCP</button>
<button class="settings-tab" type="button" data-settings-tab="modbusRtu" title="Modbus RTU">MB RTU</button>
<button class="settings-tab" type="button" data-settings-tab="knx" title="KNX">KNX</button>
</div> </div>
<div class="settings-content"> <div class="settings-content">
<div class="settings-tab-panel active" data-settings-panel="routing"> <div class="info-card">
<div class="settings-stack"> Adapter = lokale Netzwerkkarte bzw. lokale IP. Leer bedeutet automatische Auswahl durch das Betriebssystem.
<div class="info-card"> </div>
Adapter = lokale Netzwerkkarte bzw. lokale IP. Leer bedeutet automatische Auswahl durch das Betriebssystem.
<div class="settings-tab-panel active" data-settings-panel="midi">
<section class="settings-section">
<div class="panel-header">
<h2>MIDI</h2>
</div> </div>
<label class="field">
<span>MIDI Input</span>
<select id="midiInputSelect"></select>
</label>
<label class="field">
<span>MIDI Output</span>
<select id="midiOutputSelect"></select>
</label>
</section>
</div>
<section class="settings-section"> <div class="settings-tab-panel" data-settings-panel="audio">
<div class="panel-header"> <section class="settings-section">
<h2>MIDI</h2> <div class="panel-header">
</div> <h2>Audio</h2>
<label class="field"> </div>
<span>MIDI Input</span> <label class="field">
<select id="midiInputSelect"></select> <span>Audio Output</span>
</label> <select id="audioOutputSelect"></select>
<label class="field"> </label>
<span>MIDI Output</span> </section>
<select id="midiOutputSelect"></select> </div>
</label>
</section>
<section class="settings-section"> <div class="settings-tab-panel" data-settings-panel="artnet">
<div class="panel-header"> <section class="settings-section">
<h2>Audio</h2> <div class="panel-header">
</div> <h2>Art-Net</h2>
</div>
<label class="field checkbox-row">
<span>Art-Net aktiv</span>
<input id="artnetEnabledInput" type="checkbox">
</label>
<label class="field">
<span>Netzwerkadapter</span>
<select id="artnetAdapterSelect"></select>
</label>
<label class="field">
<span>Art-Net Ziel</span>
<input id="artnetHostInput" type="text" placeholder="255.255.255.255">
</label>
<div class="field-grid">
<label class="field"> <label class="field">
<span>Audio Output</span> <span>Port</span>
<select id="audioOutputSelect"></select> <input id="artnetPortInput" type="number" min="1" max="65535">
</label> </label>
</section> <label class="field">
<span>Universe</span>
<input id="artnetUniverseInput" type="number" min="0" max="32767">
</label>
</div>
<div class="field-grid">
<label class="field">
<span>Timecode Port</span>
<input id="artnetTimecodePortInput" type="number" min="1" max="65535">
</label>
<label class="field checkbox">
<span>Broadcast</span>
<input id="artnetBroadcastInput" type="checkbox">
</label>
</div>
</section>
</div>
<section class="settings-section"> <div class="settings-tab-panel" data-settings-panel="sacn">
<div class="panel-header"> <section class="settings-section">
<h2>Art-Net</h2> <div class="panel-header">
</div> <h2>sACN</h2>
<label class="field checkbox-row"> </div>
<span>Art-Net aktiv</span> <label class="field checkbox-row">
<input id="artnetEnabledInput" type="checkbox"> <span>sACN aktiv</span>
<input id="sacnEnabledInput" type="checkbox">
</label>
<label class="field">
<span>Netzwerkadapter</span>
<select id="sacnAdapterSelect"></select>
</label>
<div class="field-grid">
<label class="field">
<span>sACN Host</span>
<input id="sacnHostInput" type="text" placeholder="239.255.x.x oder Node-IP">
</label> </label>
<label class="field"> <label class="field">
<span>Netzwerkadapter</span> <span>sACN Port</span>
<select id="artnetAdapterSelect"></select> <input id="sacnPortInput" type="number" min="1" max="65535">
</label> </label>
</div>
<div class="field-grid">
<label class="field"> <label class="field">
<span>Art-Net Ziel</span> <span>sACN Source</span>
<input id="artnetHostInput" type="text" placeholder="255.255.255.255"> <input id="sacnSourceNameInput" type="text" placeholder="Time4Midi">
</label> </label>
<div class="field-grid"> <label class="field checkbox">
<label class="field"> <span>Multicast</span>
<span>Port</span> <input id="sacnMulticastInput" type="checkbox">
<input id="artnetPortInput" type="number" min="1" max="65535"> </label>
</label> </div>
<label class="field"> </section>
<span>Universe</span> </div>
<input id="artnetUniverseInput" type="number" min="0" max="32767">
</label>
</div>
<div class="field-grid">
<label class="field">
<span>Timecode Port</span>
<input id="artnetTimecodePortInput" type="number" min="1" max="65535">
</label>
<label class="field checkbox">
<span>Broadcast</span>
<input id="artnetBroadcastInput" type="checkbox">
</label>
</div>
</section>
<section class="settings-section"> <div class="settings-tab-panel" data-settings-panel="osc">
<div class="panel-header"> <section class="settings-section">
<h2>sACN</h2> <div class="panel-header">
</div> <h2>OSC</h2>
<label class="field checkbox-row"> </div>
<span>sACN aktiv</span> <label class="field checkbox-row">
<input id="sacnEnabledInput" type="checkbox"> <span>OSC aktiv</span>
<input id="oscEnabledInput" type="checkbox">
</label>
<label class="field">
<span>Netzwerkadapter</span>
<select id="oscAdapterSelect"></select>
</label>
<div class="field-grid">
<label class="field">
<span>OSC Host</span>
<input id="oscHostInput" type="text" placeholder="127.0.0.1">
</label> </label>
<label class="field"> <label class="field">
<span>Netzwerkadapter</span> <span>OSC Port</span>
<select id="sacnAdapterSelect"></select> <input id="oscPortInput" type="number" min="1" max="65535">
</label> </label>
<div class="field-grid"> </div>
<label class="field"> </section>
<span>sACN Host</span> </div>
<input id="sacnHostInput" type="text" placeholder="239.255.x.x oder Node-IP">
</label>
<label class="field">
<span>sACN Port</span>
<input id="sacnPortInput" type="number" min="1" max="65535">
</label>
</div>
<div class="field-grid">
<label class="field">
<span>sACN Source</span>
<input id="sacnSourceNameInput" type="text" placeholder="Time4Midi">
</label>
<label class="field checkbox">
<span>Multicast</span>
<input id="sacnMulticastInput" type="checkbox">
</label>
</div>
</section>
<section class="settings-section"> <div class="settings-tab-panel" data-settings-panel="mqtt">
<div class="panel-header"> <section class="settings-section">
<h2>OSC</h2> <div class="panel-header">
</div> <h2>MQTT</h2>
<label class="field checkbox-row"> </div>
<span>OSC aktiv</span> <label class="field checkbox-row">
<input id="oscEnabledInput" type="checkbox"> <span>MQTT aktiv</span>
<input id="mqttEnabledInput" type="checkbox">
</label>
<label class="field">
<span>Netzwerkadapter</span>
<select id="mqttAdapterSelect"></select>
</label>
<label class="field">
<span>MQTT Broker</span>
<input id="mqttBrokerInput" type="text" placeholder="mqtt://127.0.0.1:1883">
</label>
<div class="field-grid">
<label class="field">
<span>MQTT User</span>
<input id="mqttUsernameInput" type="text">
</label> </label>
<label class="field"> <label class="field">
<span>Netzwerkadapter</span> <span>MQTT Password</span>
<select id="oscAdapterSelect"></select> <input id="mqttPasswordInput" type="password">
</label> </label>
<div class="field-grid"> </div>
<label class="field"> <label class="field">
<span>OSC Host</span> <span>MQTT Client ID</span>
<input id="oscHostInput" type="text" placeholder="127.0.0.1"> <input id="mqttClientIdInput" type="text" placeholder="time4midi-desktop">
</label> </label>
<label class="field"> </section>
<span>OSC Port</span> </div>
<input id="oscPortInput" type="number" min="1" max="65535">
</label>
</div>
</section>
<section class="settings-section"> <div class="settings-tab-panel" data-settings-panel="serial">
<div class="panel-header"> <section class="settings-section">
<h2>MQTT</h2> <div class="panel-header">
</div> <h2>Serial</h2>
<label class="field checkbox-row"> </div>
<span>MQTT aktiv</span> <label class="field checkbox-row">
<input id="mqttEnabledInput" type="checkbox"> <span>Serial aktiv</span>
<input id="serialEnabledInput" type="checkbox">
</label>
<div class="field-grid">
<label class="field">
<span>Serial Port</span>
<input id="serialPathInput" type="text" placeholder="COM3 oder /dev/ttyUSB0">
</label> </label>
<label class="field"> <label class="field">
<span>Netzwerkadapter</span> <span>Baud</span>
<select id="mqttAdapterSelect"></select> <input id="serialBaudInput" type="number" min="300" max="921600">
</label> </label>
<label class="field"> </div>
<span>MQTT Broker</span> <div class="field-grid">
<input id="mqttBrokerInput" type="text" placeholder="mqtt://127.0.0.1:1883">
</label>
<div class="field-grid">
<label class="field">
<span>MQTT User</span>
<input id="mqttUsernameInput" type="text">
</label>
<label class="field">
<span>MQTT Password</span>
<input id="mqttPasswordInput" type="password">
</label>
</div>
<label class="field">
<span>MQTT Client ID</span>
<input id="mqttClientIdInput" type="text" placeholder="time4midi-desktop">
</label>
</section>
<section class="settings-section">
<div class="panel-header">
<h2>Serial</h2>
</div>
<label class="field checkbox-row">
<span>Serial aktiv</span>
<input id="serialEnabledInput" type="checkbox">
</label>
<div class="field-grid">
<label class="field">
<span>Serial Port</span>
<input id="serialPathInput" type="text" placeholder="COM3 oder /dev/ttyUSB0">
</label>
<label class="field">
<span>Baud</span>
<input id="serialBaudInput" type="number" min="300" max="921600">
</label>
</div>
<div class="field-grid">
<label class="field">
<span>Parity</span>
<select id="serialParitySelect">
<option value="none">None</option>
<option value="even">Even</option>
<option value="odd">Odd</option>
</select>
</label>
<label class="field">
<span>Mode</span>
<select id="serialModeSelect">
<option value="rs232">RS232</option>
<option value="rs485">RS485</option>
</select>
</label>
</div>
</section>
<section class="settings-section">
<div class="panel-header">
<h2>Modbus TCP</h2>
</div>
<label class="field checkbox-row">
<span>Modbus TCP aktiv</span>
<input id="modbusTcpEnabledInput" type="checkbox">
</label>
<label class="field">
<span>Netzwerkadapter</span>
<select id="modbusTcpAdapterSelect"></select>
</label>
<div class="field-grid">
<label class="field">
<span>Modbus TCP Host</span>
<input id="modbusTcpHostInput" type="text" placeholder="127.0.0.1">
</label>
<label class="field">
<span>Port</span>
<input id="modbusTcpPortInput" type="number" min="1" max="65535">
</label>
</div>
</section>
<section class="settings-section">
<div class="panel-header">
<h2>Modbus RTU</h2>
</div>
<label class="field checkbox-row">
<span>Modbus RTU aktiv</span>
<input id="modbusRtuEnabledInput" type="checkbox">
</label>
<div class="field-grid">
<label class="field">
<span>Modbus RTU Port</span>
<input id="modbusRtuPathInput" type="text" placeholder="COM4 oder /dev/ttyUSB1">
</label>
<label class="field">
<span>Baud</span>
<input id="modbusRtuBaudInput" type="number" min="300" max="921600">
</label>
</div>
<label class="field"> <label class="field">
<span>Parity</span> <span>Parity</span>
<select id="modbusRtuParitySelect"> <select id="serialParitySelect">
<option value="none">None</option> <option value="none">None</option>
<option value="even">Even</option> <option value="even">Even</option>
<option value="odd">Odd</option> <option value="odd">Odd</option>
</select> </select>
</label> </label>
</section>
<section class="settings-section">
<div class="panel-header">
<h2>KNX</h2>
</div>
<label class="field checkbox-row">
<span>KNX aktiv</span>
<input id="knxEnabledInput" type="checkbox">
</label>
<label class="field"> <label class="field">
<span>Netzwerkadapter</span> <span>Mode</span>
<select id="knxAdapterSelect"></select> <select id="serialModeSelect">
<option value="rs232">RS232</option>
<option value="rs485">RS485</option>
</select>
</label> </label>
<div class="field-grid">
<label class="field">
<span>KNX IP</span>
<input id="knxIpInput" type="text" placeholder="224.0.23.12 oder Gateway-IP">
</label>
<label class="field">
<span>Port</span>
<input id="knxPortInput" type="number" min="1" max="65535">
</label>
</div>
<div class="field-grid">
<label class="field">
<span>Phys Addr</span>
<input id="knxPhysAddrInput" type="text" placeholder="15.15.15">
</label>
<label class="field checkbox-row">
<span>Tunneling</span>
<input id="knxTunnelingInput" type="checkbox">
</label>
</div>
</section>
</div>
</div>
<div class="settings-tab-panel" data-settings-panel="timeline">
<section class="settings-section">
<div class="panel-header">
<h2>Timeline</h2>
</div> </div>
<div id="timelineInspector"></div>
</section> </section>
</div> </div>
<div class="settings-tab-panel" data-settings-panel="selection"> <div class="settings-tab-panel" data-settings-panel="modbusTcp">
<section class="settings-section"> <section class="settings-section">
<div class="panel-header"> <div class="panel-header">
<h2>Auswahl</h2> <h2>Modbus TCP</h2>
</div>
<label class="field checkbox-row">
<span>Modbus TCP aktiv</span>
<input id="modbusTcpEnabledInput" type="checkbox">
</label>
<label class="field">
<span>Netzwerkadapter</span>
<select id="modbusTcpAdapterSelect"></select>
</label>
<div class="field-grid">
<label class="field">
<span>Modbus TCP Host</span>
<input id="modbusTcpHostInput" type="text" placeholder="127.0.0.1">
</label>
<label class="field">
<span>Port</span>
<input id="modbusTcpPortInput" type="number" min="1" max="65535">
</label>
</div>
</section>
</div>
<div class="settings-tab-panel" data-settings-panel="modbusRtu">
<section class="settings-section">
<div class="panel-header">
<h2>Modbus RTU</h2>
</div>
<label class="field checkbox-row">
<span>Modbus RTU aktiv</span>
<input id="modbusRtuEnabledInput" type="checkbox">
</label>
<div class="field-grid">
<label class="field">
<span>Modbus RTU Port</span>
<input id="modbusRtuPathInput" type="text" placeholder="COM4 oder /dev/ttyUSB1">
</label>
<label class="field">
<span>Baud</span>
<input id="modbusRtuBaudInput" type="number" min="300" max="921600">
</label>
</div>
<label class="field">
<span>Parity</span>
<select id="modbusRtuParitySelect">
<option value="none">None</option>
<option value="even">Even</option>
<option value="odd">Odd</option>
</select>
</label>
</section>
</div>
<div class="settings-tab-panel" data-settings-panel="knx">
<section class="settings-section">
<div class="panel-header">
<h2>KNX</h2>
</div>
<label class="field checkbox-row">
<span>KNX aktiv</span>
<input id="knxEnabledInput" type="checkbox">
</label>
<label class="field">
<span>Netzwerkadapter</span>
<select id="knxAdapterSelect"></select>
</label>
<div class="field-grid">
<label class="field">
<span>KNX IP</span>
<input id="knxIpInput" type="text" placeholder="224.0.23.12 oder Gateway-IP">
</label>
<label class="field">
<span>Port</span>
<input id="knxPortInput" type="number" min="1" max="65535">
</label>
</div>
<div class="field-grid">
<label class="field">
<span>Phys Addr</span>
<input id="knxPhysAddrInput" type="text" placeholder="15.15.15">
</label>
<label class="field checkbox-row">
<span>Tunneling</span>
<input id="knxTunnelingInput" type="checkbox">
</label>
</div> </div>
<div id="selectionInspector"></div>
</section> </section>
</div> </div>
</div> </div>
+82 -3
View File
@@ -76,14 +76,15 @@ select {
.app-shell { .app-shell {
display: grid; display: grid;
grid-template-columns: 286px minmax(0, 1fr); grid-template-columns: 286px minmax(0, 1fr) 308px;
gap: 12px; gap: 12px;
min-height: 100vh; min-height: 100vh;
padding: 12px; padding: 12px;
} }
.sidebar, .sidebar,
.workspace { .workspace,
.inspector {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 12px;
@@ -239,6 +240,14 @@ p {
white-space: nowrap; white-space: nowrap;
} }
.truncate-text {
display: block;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.timeline-list, .timeline-list,
.track-list, .track-list,
#timelineInspector, #timelineInspector,
@@ -337,6 +346,7 @@ p {
.track-card-body { .track-card-body {
flex: 1; flex: 1;
min-width: 0;
} }
.track-card-actions { .track-card-actions {
@@ -379,6 +389,14 @@ p {
min-width: 0; min-width: 0;
} }
.inspector {
min-width: 0;
}
.inspector-panel {
min-height: 0;
}
.transport-bar { .transport-bar {
padding: 12px 14px; padding: 12px 14px;
} }
@@ -404,6 +422,17 @@ p {
min-height: 0; min-height: 0;
} }
.editor-title-block {
min-width: 0;
}
#timelineTitle {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.canvas-frame { .canvas-frame {
margin-top: 10px; margin-top: 10px;
flex: 1; flex: 1;
@@ -476,8 +505,12 @@ p {
} }
.settings-tab { .settings-tab {
min-width: 92px; min-width: 0;
max-width: 96px;
background: rgba(255, 255, 255, 0.04); background: rgba(255, 255, 255, 0.04);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.settings-tab.active { .settings-tab.active {
@@ -489,6 +522,8 @@ p {
min-height: 0; min-height: 0;
overflow: auto; overflow: auto;
padding-right: 4px; padding-right: 4px;
display: grid;
gap: 10px;
} }
.settings-tab-panel { .settings-tab-panel {
@@ -538,9 +573,49 @@ p {
} }
@media (max-width: 1320px) { @media (max-width: 1320px) {
.app-shell {
grid-template-columns: 262px minmax(0, 1fr) 280px;
}
.settings-dialog {
width: min(1040px, calc(100vw - 32px));
}
}
.track-card-header {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.track-card-header strong {
flex: 1;
}
.meta-row.compact {
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
}
.meta-row.compact span:first-child {
min-width: 0;
}
.timeline-card strong {
display: block;
}
@media (max-width: 1180px) {
.app-shell { .app-shell {
grid-template-columns: 270px minmax(0, 1fr); grid-template-columns: 270px minmax(0, 1fr);
} }
.inspector {
grid-column: 1 / -1;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
} }
@media (max-width: 980px) { @media (max-width: 980px) {
@@ -555,6 +630,10 @@ p {
flex-direction: column; flex-direction: column;
} }
.inspector {
grid-template-columns: 1fr;
}
.field-grid { .field-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }