Files
yovision/prototypes/assets/app.js
T

204 lines
8.4 KiB
JavaScript

(function () {
"use strict";
const qs = (selector, root = document) => root.querySelector(selector);
const qsa = (selector, root = document) => [...root.querySelectorAll(selector)];
const toastRegion = qs("[data-toast-region]");
let lastFocused = null;
function toast(message) {
if (!toastRegion) return;
const item = document.createElement("div");
item.className = "toast";
item.setAttribute("role", "status");
item.textContent = message;
toastRegion.appendChild(item);
window.setTimeout(() => item.remove(), 3600);
}
function setView(name) {
qsa("[data-view]").forEach((view) => {
const active = view.dataset.view === name;
view.classList.toggle("active", active);
view.hidden = !active;
});
qsa("[data-view-target]").forEach((button) => {
const active = button.dataset.viewTarget === name;
button.classList.toggle("active", active);
if (active) button.setAttribute("aria-current", "page");
else button.removeAttribute("aria-current");
});
const selected = qs(`[data-view-target="${name}"]`);
const title = selected?.dataset.title || selected?.textContent.trim();
const pageName = qs("[data-page-name]");
if (pageName && title) pageName.textContent = title;
qs("#main-content")?.focus({ preventScroll: true });
qs("[data-sidebar]")?.classList.remove("open");
qs("[data-mobile-menu]")?.setAttribute("aria-expanded", "false");
}
qsa("[data-view-target]").forEach((button) => {
button.addEventListener("click", () => setView(button.dataset.viewTarget));
});
qs("[data-mobile-menu]")?.addEventListener("click", () => {
const sidebar = qs("[data-sidebar]");
const open = sidebar.classList.toggle("open");
qs("[data-mobile-menu]").setAttribute("aria-expanded", String(open));
});
function openModal(id, trigger) {
const backdrop = qs(`#${id}`);
if (!backdrop) return;
lastFocused = trigger || document.activeElement;
backdrop.classList.add("open");
backdrop.setAttribute("aria-hidden", "false");
document.body.style.overflow = "hidden";
qs("input, select, textarea, button", qs(".modal", backdrop))?.focus();
}
function closeModal(backdrop) {
if (!backdrop) return;
backdrop.classList.remove("open");
backdrop.setAttribute("aria-hidden", "true");
document.body.style.overflow = "";
lastFocused?.focus();
}
qsa("[data-open-modal]").forEach((trigger) => {
trigger.addEventListener("click", () => openModal(trigger.dataset.openModal, trigger));
});
qsa("[data-close-modal]").forEach((trigger) => {
trigger.addEventListener("click", () => closeModal(trigger.closest(".modal-backdrop")));
});
qsa(".modal-backdrop").forEach((backdrop) => {
backdrop.addEventListener("mousedown", (event) => {
if (event.target === backdrop) closeModal(backdrop);
});
});
document.addEventListener("keydown", (event) => {
if (event.key === "Escape") closeModal(qs(".modal-backdrop.open"));
});
qsa("[role='tab'][data-tab-target]").forEach((tab) => {
tab.addEventListener("click", () => {
const group = tab.closest("[data-tabs]");
qsa("[role='tab']", group).forEach((item) => {
item.setAttribute("aria-selected", String(item === tab));
item.tabIndex = item === tab ? 0 : -1;
});
qsa(".tab-panel", group.parentElement).forEach((panel) => {
const active = panel.id === tab.dataset.tabTarget;
panel.classList.toggle("active", active);
panel.hidden = !active;
});
});
});
qsa("[data-filter-input]").forEach((input) => {
input.addEventListener("input", () => {
const table = qs(input.dataset.filterInput);
const query = input.value.trim().toLocaleLowerCase("zh-CN");
qsa("tbody tr", table).forEach((row) => {
row.hidden = query && !row.textContent.toLocaleLowerCase("zh-CN").includes(query);
});
});
});
qsa("[data-toast]").forEach((button) => {
button.addEventListener("click", () => toast(button.dataset.toast));
});
const addDevice = qs("[data-action='add-device']");
addDevice?.addEventListener("click", () => {
const name = qs("#device-name")?.value.trim() || "新摄像头";
const address = qs("#device-address")?.value.trim() || "192.168.10.88";
const tbody = qs("#device-table tbody");
if (tbody) {
const row = document.createElement("tr");
row.innerHTML = `<td><span class="table-title"></span><span class="table-subtitle">待探测 · video</span></td><td></td><td><span class="status status-warning">待激活</span></td><td>尚未选择</td><td><button class="btn btn-quiet" data-toast="已打开设备探测流程">查看</button></td>`;
qs(".table-title", row).textContent = name;
row.children[1].textContent = address;
qs("button", row).addEventListener("click", () => toast("已打开设备探测流程"));
tbody.prepend(row);
}
closeModal(addDevice.closest(".modal-backdrop"));
toast(`${name} 已保存为待激活设备`);
});
let frameCount = 12842;
qs("[data-action='run-frame']")?.addEventListener("click", (event) => {
event.currentTarget.disabled = true;
event.currentTarget.textContent = "处理中…";
window.setTimeout(() => {
frameCount += 1;
const counter = qs("[data-frame-count]");
if (counter) counter.textContent = frameCount.toLocaleString("zh-CN");
const events = qs("[data-brain-events]");
if (events) {
const item = document.createElement("div");
item.className = "activity-item";
item.innerHTML = `<div class="activity-icon"><svg aria-hidden="true"><use href="#icon-activity"></use></svg></div><div><div class="activity-title">方向越线候选</div><div class="activity-meta">synthetic-camera-01 · 置信度 0.91 · 项目内事件</div></div><div class="activity-time">刚刚</div>`;
events.prepend(item);
}
event.currentTarget.disabled = false;
event.currentTarget.textContent = "处理下一帧";
toast("已完成一帧推理并生成项目内候选事件");
}, 520);
});
qs("[data-action='inject-event']")?.addEventListener("click", (event) => {
const button = event.currentTarget;
const idleText = button.textContent;
button.disabled = true;
button.textContent = "注入中…";
window.setTimeout(() => {
const count = qs("[data-alert-count]");
if (count) count.textContent = String(Number(count.textContent) + 1);
button.disabled = false;
button.textContent = idleText;
closeModal(button.closest(".modal-backdrop"));
toast("合成事件已校验并生成新的 Alert");
}, 520);
});
const alertState = qs("[data-alert-state]");
const ackButton = qs("[data-action='ack-alert']");
const closeButton = qs("[data-action='close-alert']");
ackButton?.addEventListener("click", () => {
alertState.textContent = "已确认";
alertState.className = "status status-warning el-tag";
ackButton.disabled = true;
closeButton.disabled = false;
const timeline = qs("[data-alert-timeline]");
timeline?.insertAdjacentHTML("beforeend", `<div class="timeline-item"><div class="timeline-title">王值班员确认预警</div><div class="timeline-meta">刚刚 · 首个成功 ack · 已写入审计</div></div>`);
toast("预警已由王值班员确认");
});
closeButton?.addEventListener("click", () => {
alertState.textContent = "已关闭";
alertState.className = "status status-success el-tag";
closeButton.disabled = true;
qs("[data-alert-timeline]")?.insertAdjacentHTML("beforeend", `<div class="timeline-item"><div class="timeline-title">处置完成并关闭</div><div class="timeline-meta">刚刚 · 原始 Event 保持不变</div></div>`);
toast("Alert 已关闭,原始 Event 未修改");
});
qsa("[data-select-alert]").forEach((row) => {
const select = () => {
qsa("[data-select-alert]").forEach((item) => item.classList.toggle("selected", item === row));
toast(`已选择:${row.dataset.selectAlert}`);
};
row.addEventListener("click", select);
if (row.tagName !== "BUTTON") {
row.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
select();
}
});
}
});
const initial = qs("[data-view-target].active")?.dataset.viewTarget || qs("[data-view]")?.dataset.view;
if (initial) setView(initial);
})();