plc_control/web/js/app.js

192 lines
7.1 KiB
JavaScript

import { withStatus } from "./api.js";
import { openChart, renderChart } from "./chart.js";
import { dom } from "./dom.js";
import { closeApiDocDrawer, openApiDocDrawer } from "./docs.js";
import { loadEvents } from "./events.js";
import {
applyBatchEquipmentUnit,
clearEquipmentFilter,
clearPointBinding,
clearSelectedEquipments,
closeEquipmentModal,
loadEquipments,
openCreateEquipmentModal,
resetEquipmentForm,
saveEquipment,
} from "./equipment.js";
import { startPointSocket, startLogs, stopLogs } from "./logs.js";
import { startOps, renderOpsUnits, loadAllEquipmentCards } from "./ops.js";
import {
clearBatchBinding,
browseAndLoadTree,
clearSelectedPoints,
createPoints,
loadPoints,
loadTree,
openBatchBinding,
openPointCreateModal,
renderSelectedNodes,
saveBatchBinding,
savePointBinding,
updatePointFilterSummary,
updateSelectedPointSummary,
} from "./points.js";
import { state } from "./state.js";
import { loadSources, saveSource } from "./sources.js";
import { closeUnitModal, loadUnits, openCreateUnitModal, resetUnitForm, renderUnits, saveUnit } from "./units.js";
function switchView(view) {
state.activeView = view;
const main = document.querySelector("main");
main.className = view === "ops" ? "grid-ops" : "grid-config";
dom.tabOps.classList.toggle("active", view === "ops");
dom.tabConfig.classList.toggle("active", view === "config");
// config-only panels
["top-left", "top-right", "bottom-left", "bottom-right"].forEach((cls) => {
const el = main.querySelector(`.panel.${cls}`);
if (el) el.classList.toggle("hidden", view === "ops");
});
// bottom-mid is log-stream in config, hidden in ops
const logStreamPanel = main.querySelector(".panel.bottom-mid");
if (logStreamPanel) logStreamPanel.classList.toggle("hidden", view === "ops");
// ops-only panels
const opsMain = main.querySelector(".panel.ops-main");
const opsBottom = main.querySelector(".panel.ops-bottom");
if (opsMain) opsMain.classList.toggle("hidden", view === "config");
if (opsBottom) opsBottom.classList.toggle("hidden", view === "config");
if (view === "config") {
startLogs();
} else {
stopLogs();
}
}
function bindEvents() {
dom.unitForm.addEventListener("submit", (event) => withStatus(saveUnit(event)));
dom.sourceForm.addEventListener("submit", (event) => withStatus(saveSource(event)));
dom.equipmentForm.addEventListener("submit", (event) => withStatus(saveEquipment(event)));
dom.pointBindingForm.addEventListener("submit", (event) => withStatus(savePointBinding(event)));
dom.batchBindingForm.addEventListener("submit", (event) => withStatus(saveBatchBinding(event)));
dom.unitResetBtn.addEventListener("click", resetUnitForm);
dom.refreshUnitBtn.addEventListener("click", () => withStatus(loadUnits().then(loadEvents)));
dom.newUnitBtn.addEventListener("click", openCreateUnitModal);
dom.closeUnitModalBtn.addEventListener("click", closeUnitModal);
dom.sourceResetBtn.addEventListener("click", () => dom.sourceForm.reset());
dom.equipmentResetBtn.addEventListener("click", resetEquipmentForm);
dom.refreshEquipmentBtn.addEventListener("click", () => withStatus(loadEquipments()));
dom.newEquipmentBtn.addEventListener("click", openCreateEquipmentModal);
dom.closeEquipmentModalBtn.addEventListener("click", closeEquipmentModal);
dom.clearEquipmentFilterBtn.addEventListener("click", () => withStatus(clearEquipmentFilter()));
dom.applyEquipmentUnitBtn.addEventListener("click", () => withStatus(applyBatchEquipmentUnit()));
dom.clearEquipmentSelectionBtn.addEventListener("click", clearSelectedEquipments);
dom.openPointModalBtn.addEventListener("click", openPointCreateModal);
dom.pointSourceSelect.addEventListener("change", () => {
dom.nodeTree.innerHTML = '<div class="muted">Click "Load Nodes" to fetch node tree</div>';
dom.pointSourceNodeCount.textContent = "Nodes: 0";
});
dom.browseNodesBtn.addEventListener("click", () => withStatus(browseAndLoadTree()));
dom.refreshTreeBtn.addEventListener("click", () => withStatus(loadTree()));
dom.createPointsBtn.addEventListener("click", () => withStatus(createPoints()));
dom.closeModalBtn.addEventListener("click", () => dom.pointModal.classList.add("hidden"));
dom.openSourceFormBtn.addEventListener("click", () => {
dom.sourceForm.reset();
dom.sourceId.value = "";
dom.sourceModal.classList.remove("hidden");
});
dom.closeSourceModalBtn.addEventListener("click", () => dom.sourceModal.classList.add("hidden"));
dom.clearPointBindingBtn.addEventListener("click", () => withStatus(clearPointBinding()));
dom.closePointBindingModalBtn.addEventListener("click", () => {
dom.pointBindingModal.classList.add("hidden");
});
dom.openBatchBindingBtn.addEventListener("click", openBatchBinding);
dom.clearSelectedPointsBtn.addEventListener("click", clearSelectedPoints);
dom.closeBatchBindingModalBtn.addEventListener("click", () => {
dom.batchBindingModal.classList.add("hidden");
});
dom.clearBatchBindingBtn.addEventListener("click", () => withStatus(clearBatchBinding()));
dom.toggleAllPoints.addEventListener("change", () => {
const checked = dom.toggleAllPoints.checked;
dom.pointList.querySelectorAll('input[data-point-select="true"]').forEach((input) => {
input.checked = checked;
input.dispatchEvent(new Event("change"));
});
});
dom.openApiDocBtn.addEventListener("click", () => withStatus(openApiDocDrawer()));
dom.closeApiDocBtn.addEventListener("click", closeApiDocDrawer);
dom.refreshEventBtn.addEventListener("click", () => withStatus(loadEvents()));
dom.refreshChartBtn.addEventListener("click", () => {
if (!state.chartPointId) {
return;
}
withStatus(openChart(state.chartPointId, state.chartPointName));
});
dom.prevPointsBtn.addEventListener("click", () => {
if (state.pointsPage > 1) {
state.pointsPage -= 1;
withStatus(loadPoints());
}
});
dom.nextPointsBtn.addEventListener("click", () => {
const totalPages = Math.max(1, Math.ceil(state.pointsTotal / state.pointsPageSize));
if (state.pointsPage < totalPages) {
state.pointsPage += 1;
withStatus(loadPoints());
}
});
dom.equipmentKeyword.addEventListener("keydown", (event) => {
if (event.key === "Enter") {
event.preventDefault();
withStatus(loadEquipments());
}
});
dom.tabOps.addEventListener("click", () => switchView("ops"));
dom.tabConfig.addEventListener("click", () => switchView("config"));
document.addEventListener("equipments-updated", () => {
renderUnits();
renderOpsUnits();
if (!state.selectedOpsUnitId) loadAllEquipmentCards();
});
document.addEventListener("units-loaded", () => {
renderOpsUnits();
if (state.equipments.length > 0 && !state.selectedOpsUnitId) loadAllEquipmentCards();
});
}
async function bootstrap() {
bindEvents();
switchView("ops");
renderSelectedNodes();
updateSelectedPointSummary();
updatePointFilterSummary();
renderChart();
startPointSocket();
await withStatus(loadUnits());
startOps();
await withStatus(loadSources());
await withStatus(loadEquipments());
await withStatus(loadEvents());
await withStatus(loadPoints());
}
bootstrap();