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
+246150 -57311
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"
},
"devDependencies": {
"electron": "^31.7.7",
"electron-builder": "^24.13.3"
"electron": "^42.4.0",
"electron-builder": "^26.15.3"
},
"dependencies": {
"knx": "^2.5.4",
"knx": "^0.0.1",
"modbus-serial": "^8.0.25",
"mqtt": "^5.10.4",
"serialport": "^13.0.0"
+79 -27
View File
@@ -3,7 +3,7 @@
const RULER_HEIGHT = 34;
const POINT_RADIUS = 7;
const FPS_DEFAULT = 30;
const LEFT_GUTTER = 126;
const LEFT_GUTTER = 162;
const RIGHT_PADDING = 24;
const MIN_CANVAS_HEIGHT = 420;
const EXTERNAL_RESYNC_MS = 1000;
@@ -189,7 +189,7 @@
routing: createDefaultRouting(),
ui: {
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 === "audio-file") {
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 === "knx-value") return `${escapeHtml(track.groupAddress)} / ${escapeHtml(track.dpt)}`;
if (track.type === "osc-value") return escapeHtml(track.address);
return escapeHtml(track.topic);
if (track.type === "knx-value") return `${track.groupAddress} / ${track.dpt}`;
if (track.type === "osc-value") return track.address;
return track.topic;
}
function basenameFromPath(filePath) {
@@ -829,6 +829,29 @@
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) {
if (duration <= 15) return 0.5;
if (duration <= 60) return 1;
@@ -921,25 +944,29 @@
function drawTrackLaneLabel(track, trackIndex) {
const top = RULER_HEIGHT + (trackIndex * TRACK_HEIGHT);
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.beginPath();
ctx.roundRect(18, top + 14, 44, 24, 8);
ctx.roundRect(16, top + 15, 40, 22, 8);
ctx.fill();
ctx.fillStyle = "#091018";
ctx.font = "bold 11px Trebuchet MS";
ctx.font = "bold 10px Trebuchet MS";
ctx.textAlign = "center";
ctx.fillText(getTrackKindLabel(track), 40, top + 30);
ctx.fillText(getTrackKindLabel(track), 36, top + 29);
ctx.save();
ctx.translate(92, top + (TRACK_HEIGHT / 2));
ctx.rotate(-Math.PI / 2);
ctx.textAlign = "left";
ctx.fillStyle = "rgba(255,255,255,0.86)";
ctx.font = "bold 13px Trebuchet MS";
ctx.textAlign = "center";
ctx.fillText(track.name, 0, 0);
ctx.restore();
ctx.font = "bold 12px Trebuchet MS";
ctx.fillText(truncateCanvasText(trackName, labelWidth), labelLeft, top + 33);
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) {
ctx.strokeStyle = "rgba(110,194,242,0.76)";
@@ -1137,12 +1164,13 @@
function renderTimelineList() {
refs.timelineList.innerHTML = "";
for (const timeline of state.project.timelines) {
const shortName = shortenLabel(timeline.name, 26);
const card = document.createElement("div");
card.className = `timeline-card selectable-card ${timeline.id === state.selectedTimelineId ? "active" : ""}`;
card.dataset.timelineId = timeline.id;
card.innerHTML = `
<strong>${escapeHtml(timeline.name)}</strong>
<div class="meta-row">
<strong class="truncate-text" title="${escapeAttribute(timeline.name)}">${escapeHtml(shortName)}</strong>
<div class="meta-row compact">
<span>${timeline.tracks.length} Tracks</span>
<span>${timeline.duration}s</span>
</div>
@@ -1173,18 +1201,22 @@
}
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");
card.className = `track-card ${track.id === state.selectedTrackId ? "active" : ""}`;
card.dataset.trackId = track.id;
card.innerHTML = `
<div class="track-card-top">
<div class="track-card-body selectable-card" data-track-id="${track.id}">
<div class="panel-header">
<strong>${escapeHtml(track.name)}</strong>
<span class="track-pill ${pillClassForTrack(track)}">${getTrackKindLabel(track)}</span>
<div class="track-card-header">
<strong class="truncate-text" title="${escapeAttribute(fullName)}">${escapeHtml(shortName)}</strong>
<span class="track-pill ${pillClassForTrack(track)}" title="${escapeAttribute(getTrackKindLabel(track))}">${getTrackKindLabel(track)}</span>
</div>
<div class="meta-row">
<span>${describeTrack(track)}</span>
<div class="meta-row compact">
<span class="truncate-text" title="${escapeAttribute(detail)}">${escapeHtml(shortDetail)}</span>
<span>${track.points.length} Punkte</span>
</div>
</div>
@@ -1201,6 +1233,7 @@
function renderTimelineInspector() {
const timeline = getSelectedTimeline();
refs.timelineTitle.textContent = timeline ? timeline.name : "Keine Timeline ausgewaehlt";
refs.timelineTitle.title = timeline ? timeline.name : "";
if (!timeline) {
refs.timelineInspector.innerHTML = `<div class="empty-state">Keine Timeline ausgewaehlt.</div>`;
return;
@@ -1635,12 +1668,22 @@
function openSettings(tab = state.ui.settingsTab) {
state.ui.settingsOpen = true;
state.ui.settingsTab = tab;
state.ui.settingsTab = tab || "midi";
void refreshNetworkAdapters();
void refreshAudioOutputs();
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() {
state.ui.settingsOpen = false;
renderAll();
@@ -2896,12 +2939,12 @@
refs.playButton.addEventListener("click", playTransport);
refs.pauseButton.addEventListener("click", pauseTransport);
refs.stopButton.addEventListener("click", () => stopTransport(true));
refs.openSettingsButton.addEventListener("click", () => openSettings("routing"));
refs.openSettingsButton.addEventListener("click", () => openSettings());
refs.closeSettingsButton.addEventListener("click", closeSettings);
refs.settingsBackdrop.addEventListener("click", closeSettings);
refs.settingsTabButtons.forEach((button) => {
button.addEventListener("click", () => {
state.ui.settingsTab = button.dataset.settingsTab || "routing";
state.ui.settingsTab = button.dataset.settingsTab || "midi";
renderAll();
});
});
@@ -2998,6 +3041,15 @@
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) => {
const rect = refs.timelineCanvas.getBoundingClientRect();
const x = event.clientX - rect.left;
+288 -267
View File
@@ -97,14 +97,14 @@
<section class="editor panel">
<div class="editor-header">
<div>
<div class="editor-title-block">
<div class="eyebrow">Editor</div>
<h2 id="timelineTitle">Keine Timeline ausgewählt</h2>
<h2 id="timelineTitle">Keine Timeline ausgewaehlt</h2>
</div>
<div class="editor-hints">
<span>Doppelklick: Punkt setzen</span>
<span>Ziehen: Zeit/Wert ändern</span>
<span>Entf: Punkt löschen</span>
<span>Ziehen: Zeit/Wert aendern</span>
<span>Entf: Punkt loeschen</span>
</div>
</div>
<div class="canvas-frame">
@@ -112,6 +112,22 @@
</div>
</section>
</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 id="settingsModal" class="settings-modal" aria-hidden="true">
@@ -127,311 +143,316 @@
</div>
</div>
<div class="settings-tabbar" role="tablist" aria-label="Einstellungen">
<button class="settings-tab active" type="button" data-settings-tab="routing">Routing</button>
<button class="settings-tab" type="button" data-settings-tab="timeline">Timeline</button>
<button class="settings-tab" type="button" data-settings-tab="selection">Auswahl</button>
<div class="settings-tabbar" role="tablist" aria-label="Ausgaben">
<button class="settings-tab active" type="button" data-settings-tab="midi" title="MIDI">MIDI</button>
<button class="settings-tab" type="button" data-settings-tab="audio" title="Audio">Audio</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 class="settings-content">
<div class="settings-tab-panel active" data-settings-panel="routing">
<div class="settings-stack">
<div class="info-card">
Adapter = lokale Netzwerkkarte bzw. lokale IP. Leer bedeutet automatische Auswahl durch das Betriebssystem.
<div class="info-card">
Adapter = lokale Netzwerkkarte bzw. lokale IP. Leer bedeutet automatische Auswahl durch das Betriebssystem.
</div>
<div class="settings-tab-panel active" data-settings-panel="midi">
<section class="settings-section">
<div class="panel-header">
<h2>MIDI</h2>
</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="panel-header">
<h2>MIDI</h2>
</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 class="settings-tab-panel" data-settings-panel="audio">
<section class="settings-section">
<div class="panel-header">
<h2>Audio</h2>
</div>
<label class="field">
<span>Audio Output</span>
<select id="audioOutputSelect"></select>
</label>
</section>
</div>
<section class="settings-section">
<div class="panel-header">
<h2>Audio</h2>
</div>
<div class="settings-tab-panel" data-settings-panel="artnet">
<section class="settings-section">
<div class="panel-header">
<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">
<span>Audio Output</span>
<select id="audioOutputSelect"></select>
<span>Port</span>
<input id="artnetPortInput" type="number" min="1" max="65535">
</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="panel-header">
<h2>Art-Net</h2>
</div>
<label class="field checkbox-row">
<span>Art-Net aktiv</span>
<input id="artnetEnabledInput" type="checkbox">
<div class="settings-tab-panel" data-settings-panel="sacn">
<section class="settings-section">
<div class="panel-header">
<h2>sACN</h2>
</div>
<label class="field checkbox-row">
<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 class="field">
<span>Netzwerkadapter</span>
<select id="artnetAdapterSelect"></select>
<span>sACN Port</span>
<input id="sacnPortInput" type="number" min="1" max="65535">
</label>
</div>
<div class="field-grid">
<label class="field">
<span>Art-Net Ziel</span>
<input id="artnetHostInput" type="text" placeholder="255.255.255.255">
<span>sACN Source</span>
<input id="sacnSourceNameInput" type="text" placeholder="Time4Midi">
</label>
<div class="field-grid">
<label class="field">
<span>Port</span>
<input id="artnetPortInput" type="number" min="1" max="65535">
</label>
<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>
<label class="field checkbox">
<span>Multicast</span>
<input id="sacnMulticastInput" type="checkbox">
</label>
</div>
</section>
</div>
<section class="settings-section">
<div class="panel-header">
<h2>sACN</h2>
</div>
<label class="field checkbox-row">
<span>sACN aktiv</span>
<input id="sacnEnabledInput" type="checkbox">
<div class="settings-tab-panel" data-settings-panel="osc">
<section class="settings-section">
<div class="panel-header">
<h2>OSC</h2>
</div>
<label class="field checkbox-row">
<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 class="field">
<span>Netzwerkadapter</span>
<select id="sacnAdapterSelect"></select>
<span>OSC Port</span>
<input id="oscPortInput" type="number" min="1" max="65535">
</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 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>
</div>
</section>
</div>
<section class="settings-section">
<div class="panel-header">
<h2>OSC</h2>
</div>
<label class="field checkbox-row">
<span>OSC aktiv</span>
<input id="oscEnabledInput" type="checkbox">
<div class="settings-tab-panel" data-settings-panel="mqtt">
<section class="settings-section">
<div class="panel-header">
<h2>MQTT</h2>
</div>
<label class="field checkbox-row">
<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 class="field">
<span>Netzwerkadapter</span>
<select id="oscAdapterSelect"></select>
<span>MQTT Password</span>
<input id="mqttPasswordInput" type="password">
</label>
<div class="field-grid">
<label class="field">
<span>OSC Host</span>
<input id="oscHostInput" type="text" placeholder="127.0.0.1">
</label>
<label class="field">
<span>OSC Port</span>
<input id="oscPortInput" type="number" min="1" max="65535">
</label>
</div>
</section>
</div>
<label class="field">
<span>MQTT Client ID</span>
<input id="mqttClientIdInput" type="text" placeholder="time4midi-desktop">
</label>
</section>
</div>
<section class="settings-section">
<div class="panel-header">
<h2>MQTT</h2>
</div>
<label class="field checkbox-row">
<span>MQTT aktiv</span>
<input id="mqttEnabledInput" type="checkbox">
<div class="settings-tab-panel" data-settings-panel="serial">
<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>Netzwerkadapter</span>
<select id="mqttAdapterSelect"></select>
<span>Baud</span>
<input id="serialBaudInput" type="number" min="300" max="921600">
</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 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>
</div>
<div class="field-grid">
<label class="field">
<span>Parity</span>
<select id="modbusRtuParitySelect">
<select id="serialParitySelect">
<option value="none">None</option>
<option value="even">Even</option>
<option value="odd">Odd</option>
</select>
</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">
<span>Netzwerkadapter</span>
<select id="knxAdapterSelect"></select>
<span>Mode</span>
<select id="serialModeSelect">
<option value="rs232">RS232</option>
<option value="rs485">RS485</option>
</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>
</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 id="timelineInspector"></div>
</section>
</div>
<div class="settings-tab-panel" data-settings-panel="selection">
<div class="settings-tab-panel" data-settings-panel="modbusTcp">
<section class="settings-section">
<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 id="selectionInspector"></div>
</section>
</div>
</div>
+82 -3
View File
@@ -76,14 +76,15 @@ select {
.app-shell {
display: grid;
grid-template-columns: 286px minmax(0, 1fr);
grid-template-columns: 286px minmax(0, 1fr) 308px;
gap: 12px;
min-height: 100vh;
padding: 12px;
}
.sidebar,
.workspace {
.workspace,
.inspector {
display: flex;
flex-direction: column;
gap: 12px;
@@ -239,6 +240,14 @@ p {
white-space: nowrap;
}
.truncate-text {
display: block;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.timeline-list,
.track-list,
#timelineInspector,
@@ -337,6 +346,7 @@ p {
.track-card-body {
flex: 1;
min-width: 0;
}
.track-card-actions {
@@ -379,6 +389,14 @@ p {
min-width: 0;
}
.inspector {
min-width: 0;
}
.inspector-panel {
min-height: 0;
}
.transport-bar {
padding: 12px 14px;
}
@@ -404,6 +422,17 @@ p {
min-height: 0;
}
.editor-title-block {
min-width: 0;
}
#timelineTitle {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.canvas-frame {
margin-top: 10px;
flex: 1;
@@ -476,8 +505,12 @@ p {
}
.settings-tab {
min-width: 92px;
min-width: 0;
max-width: 96px;
background: rgba(255, 255, 255, 0.04);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.settings-tab.active {
@@ -489,6 +522,8 @@ p {
min-height: 0;
overflow: auto;
padding-right: 4px;
display: grid;
gap: 10px;
}
.settings-tab-panel {
@@ -538,9 +573,49 @@ p {
}
@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 {
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) {
@@ -555,6 +630,10 @@ p {
flex-direction: column;
}
.inspector {
grid-template-columns: 1fr;
}
.field-grid {
grid-template-columns: 1fr;
}