feat: add diagnostics page and functionality, enhance transport settings in index.html
- Introduced a new diagnostics page (diagnostics.html) with real-time monitoring for Art-Net, sACN, MQTT, and KNX. - Added JavaScript functionality (diagnostics.js) to handle diagnostics data and render updates. - Enhanced index.html with new transport settings including timeline length, beat snap options, and diagnostics button. - Updated styles.css to accommodate new diagnostics layout and transport settings. - Added splash screen (splash.html) for improved user experience during startup. - Included new assets: Time4Midi.psd and updated executable files for version 0.3.6.
This commit is contained in:
@@ -4,14 +4,162 @@ const path = require("node:path");
|
||||
|
||||
app.commandLine.appendSwitch("enable-blink-features", "WebMidi");
|
||||
|
||||
let mainWindow = null;
|
||||
let diagnosticsWindow = null;
|
||||
let splashWindow = null;
|
||||
|
||||
const diagnosticsState = {
|
||||
artnet: { universes: {} },
|
||||
sacn: { universes: {} },
|
||||
mqtt: { topics: {} },
|
||||
knx: { groupAddresses: {} },
|
||||
recentEvents: []
|
||||
};
|
||||
|
||||
function rememberRecentEvent(entry) {
|
||||
diagnosticsState.recentEvents.unshift(entry);
|
||||
diagnosticsState.recentEvents = diagnosticsState.recentEvents.slice(0, 200);
|
||||
}
|
||||
|
||||
function sameChannelValues(left, right) {
|
||||
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let index = 0; index < left.length; index += 1) {
|
||||
if (Number(left[index] || 0) !== Number(right[index] || 0)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function updateDmxDiagnostics(transport, record) {
|
||||
const safeTransport = transport === "artnet" ? "artnet" : "sacn";
|
||||
const previous = diagnosticsState[safeTransport].universes[String(record.universe)];
|
||||
const nextValues = Array.isArray(record.values) ? record.values.slice(0, 512) : [];
|
||||
const changed = !previous || !sameChannelValues(previous.values, nextValues);
|
||||
|
||||
diagnosticsState[safeTransport].universes[String(record.universe)] = {
|
||||
universe: Number(record.universe) || 0,
|
||||
values: nextValues,
|
||||
updatedAt: record.timestamp
|
||||
};
|
||||
|
||||
if (changed) {
|
||||
rememberRecentEvent({
|
||||
kind: "dmx-frame",
|
||||
transport: safeTransport,
|
||||
universe: Number(record.universe) || 0,
|
||||
activeChannels: nextValues.filter((value) => Number(value) > 0).length,
|
||||
timestamp: record.timestamp
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateMqttDiagnostics(record) {
|
||||
diagnosticsState.mqtt.topics[String(record.topic || "")] = {
|
||||
topic: String(record.topic || ""),
|
||||
value: String(record.value ?? ""),
|
||||
updatedAt: record.timestamp
|
||||
};
|
||||
rememberRecentEvent({
|
||||
kind: "mqtt-publish",
|
||||
topic: String(record.topic || ""),
|
||||
value: String(record.value ?? ""),
|
||||
timestamp: record.timestamp
|
||||
});
|
||||
}
|
||||
|
||||
function updateKnxDiagnostics(record) {
|
||||
const key = `${record.groupAddress || ""}:${record.dpt || ""}`;
|
||||
diagnosticsState.knx.groupAddresses[key] = {
|
||||
groupAddress: String(record.groupAddress || ""),
|
||||
dpt: String(record.dpt || ""),
|
||||
value: record.value,
|
||||
updatedAt: record.timestamp
|
||||
};
|
||||
rememberRecentEvent({
|
||||
kind: "knx-write",
|
||||
groupAddress: String(record.groupAddress || ""),
|
||||
dpt: String(record.dpt || ""),
|
||||
value: record.value,
|
||||
timestamp: record.timestamp
|
||||
});
|
||||
}
|
||||
|
||||
function applyDiagnosticsRecord(record) {
|
||||
if (!record || typeof record !== "object") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (record.kind === "dmx-frame") {
|
||||
updateDmxDiagnostics(record.transport, record);
|
||||
} else if (record.kind === "mqtt-publish") {
|
||||
updateMqttDiagnostics(record);
|
||||
} else if (record.kind === "knx-write") {
|
||||
updateKnxDiagnostics(record);
|
||||
}
|
||||
}
|
||||
|
||||
function sendDiagnosticsUpdate(record) {
|
||||
if (!diagnosticsWindow || diagnosticsWindow.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
diagnosticsWindow.webContents.send("diagnostics:update", record);
|
||||
}
|
||||
|
||||
function closeSplashWindow() {
|
||||
if (!splashWindow || splashWindow.isDestroyed()) {
|
||||
splashWindow = null;
|
||||
return;
|
||||
}
|
||||
|
||||
splashWindow.close();
|
||||
splashWindow = null;
|
||||
}
|
||||
|
||||
function createSplashWindow() {
|
||||
if (splashWindow && !splashWindow.isDestroyed()) {
|
||||
return splashWindow;
|
||||
}
|
||||
|
||||
splashWindow = new BrowserWindow({
|
||||
width: 520,
|
||||
height: 420,
|
||||
frame: false,
|
||||
resizable: false,
|
||||
minimizable: false,
|
||||
maximizable: false,
|
||||
movable: true,
|
||||
show: true,
|
||||
center: true,
|
||||
backgroundColor: "#0f1117",
|
||||
icon: path.join(__dirname, "time4midi.png"),
|
||||
webPreferences: {
|
||||
contextIsolation: true,
|
||||
sandbox: false
|
||||
}
|
||||
});
|
||||
|
||||
splashWindow.on("closed", () => {
|
||||
splashWindow = null;
|
||||
});
|
||||
|
||||
splashWindow.loadFile(path.join(__dirname, "src", "splash.html"));
|
||||
return splashWindow;
|
||||
}
|
||||
|
||||
function createWindow() {
|
||||
const win = new BrowserWindow({
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1600,
|
||||
height: 980,
|
||||
minWidth: 1200,
|
||||
minHeight: 760,
|
||||
backgroundColor: "#0f1117",
|
||||
icon: path.join(__dirname, "time4midi.png"),
|
||||
show: false,
|
||||
webPreferences: {
|
||||
contextIsolation: true,
|
||||
sandbox: false,
|
||||
@@ -19,7 +167,49 @@ function createWindow() {
|
||||
}
|
||||
});
|
||||
|
||||
win.loadFile(path.join(__dirname, "src", "index.html"));
|
||||
mainWindow.on("closed", () => {
|
||||
mainWindow = null;
|
||||
});
|
||||
|
||||
mainWindow.once("ready-to-show", () => {
|
||||
setTimeout(() => {
|
||||
closeSplashWindow();
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.show();
|
||||
}
|
||||
}, 650);
|
||||
});
|
||||
|
||||
mainWindow.loadFile(path.join(__dirname, "src", "index.html"));
|
||||
}
|
||||
|
||||
function createDiagnosticsWindow() {
|
||||
if (diagnosticsWindow && !diagnosticsWindow.isDestroyed()) {
|
||||
diagnosticsWindow.focus();
|
||||
return diagnosticsWindow;
|
||||
}
|
||||
|
||||
diagnosticsWindow = new BrowserWindow({
|
||||
width: 1100,
|
||||
height: 820,
|
||||
minWidth: 900,
|
||||
minHeight: 640,
|
||||
backgroundColor: "#0f1117",
|
||||
icon: path.join(__dirname, "time4midi.png"),
|
||||
title: "Time4Midi Diagnose",
|
||||
webPreferences: {
|
||||
contextIsolation: true,
|
||||
sandbox: false,
|
||||
preload: path.join(__dirname, "preload.js")
|
||||
}
|
||||
});
|
||||
|
||||
diagnosticsWindow.on("closed", () => {
|
||||
diagnosticsWindow = null;
|
||||
});
|
||||
|
||||
diagnosticsWindow.loadFile(path.join(__dirname, "src", "diagnostics.html"));
|
||||
return diagnosticsWindow;
|
||||
}
|
||||
|
||||
ipcMain.handle("project:save", async (_event, payload) => {
|
||||
@@ -119,17 +309,38 @@ ipcMain.handle("mappingLibrary:pickSaveFile", async (_event, payload) => {
|
||||
};
|
||||
});
|
||||
|
||||
ipcMain.handle("diagnostics:openWindow", () => {
|
||||
createDiagnosticsWindow();
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
ipcMain.handle("diagnostics:getSnapshot", () => {
|
||||
return diagnosticsState;
|
||||
});
|
||||
|
||||
ipcMain.on("diagnostics:record", (_event, record) => {
|
||||
const normalizedRecord = {
|
||||
...record,
|
||||
timestamp: Number(record?.timestamp) || Date.now()
|
||||
};
|
||||
applyDiagnosticsRecord(normalizedRecord);
|
||||
sendDiagnosticsUpdate(normalizedRecord);
|
||||
});
|
||||
|
||||
app.whenReady().then(() => {
|
||||
createSplashWindow();
|
||||
createWindow();
|
||||
|
||||
app.on("activate", () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
if (!mainWindow || mainWindow.isDestroyed()) {
|
||||
createSplashWindow();
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.on("window-all-closed", () => {
|
||||
closeSplashWindow();
|
||||
if (process.platform !== "darwin") {
|
||||
app.quit();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user