Add function Language
Some checks failed
Test / test (push) Has been cancelled

This commit is contained in:
2026-06-16 16:44:04 +07:00
parent 1156e1ab29
commit a2e87aeb29
11 changed files with 1790 additions and 474 deletions

View File

@@ -1,13 +1,16 @@
const el = (id) => document.getElementById(id);
const t = (key, vars) => window.I18n?.t(key, vars) ?? key;
const statusEl = el("status");
const listEl = el("lidarList");
const lidarFormHintEl = el("lidarFormHint");
const navItemEls = Array.from(document.querySelectorAll(".navItem[data-page]"));
const pageOverviewEl = el("pageOverview");
const pageConfigEl = el("pageConfig");
const pageMissionsEl = el("pageMissions");
const pageIntegrationsEl = el("pageIntegrations");
const pageMonitoringEl = el("pageMonitoring");
const pageHelpEl = el("pageHelp");
const contentEl = document.querySelector(".content");
const contentRightEl = el("contentRight");
const overviewBackendEl = el("overviewBackend");
@@ -120,23 +123,19 @@ const state = {
};
function setActivePage(page) {
const valid = ["dashboard", "config", "missions", "integrations"];
const valid = ["dashboard", "config", "missions", "integrations", "monitoring", "help"];
let p = valid.includes(page) ? page : "config";
if (window.AuthApp && !window.AuthApp.canAccessPage(p)) {
const fallback = valid.find((v) => window.AuthApp.canAccessPage(v));
p = fallback || "dashboard";
}
if (page === "overview") p = "dashboard";
navItemEls.forEach((a) => {
const on = (a.dataset.page || "") === p;
a.classList.toggle("active", on);
if (on) a.setAttribute("aria-current", "page");
else a.removeAttribute("aria-current");
});
if (pageOverviewEl) pageOverviewEl.hidden = p !== "dashboard";
if (pageConfigEl) pageConfigEl.hidden = p !== "config";
if (pageMissionsEl) pageMissionsEl.hidden = p !== "missions";
if (pageIntegrationsEl) pageIntegrationsEl.hidden = p !== "integrations";
if (pageMonitoringEl) pageMonitoringEl.hidden = p !== "monitoring";
if (pageHelpEl) pageHelpEl.hidden = p !== "help";
if (configSplitterEl) configSplitterEl.hidden = p !== "config";
if (contentRightEl) contentRightEl.hidden = p !== "config";
if (contentEl) {
@@ -144,6 +143,8 @@ function setActivePage(page) {
contentEl.classList.toggle("content--config", p === "config");
contentEl.classList.toggle("content--missions", p === "missions");
contentEl.classList.toggle("content--integrations", p === "integrations");
contentEl.classList.toggle("content--monitoring", p === "monitoring");
contentEl.classList.toggle("content--help", p === "help");
}
if (p === "missions" && window.MissionsApp) window.MissionsApp.onPageShow();
else if (window.MissionsApp?.onPageHide) window.MissionsApp.onPageHide();
@@ -151,6 +152,7 @@ function setActivePage(page) {
else if (window.DashboardApp?.onPageHide) window.DashboardApp.onPageHide();
if (p === "integrations" && window.IntegrationsApp) window.IntegrationsApp.onPageShow();
else if (window.IntegrationsApp?.onPageHide) window.IntegrationsApp.onPageHide();
window.NavApp?.syncFromPage?.(p);
try {
localStorage.setItem("activePage", p);
} catch {
@@ -159,25 +161,12 @@ function setActivePage(page) {
}
function initNavigation() {
navItemEls.forEach((a) => {
a.addEventListener("click", (evt) => {
evt.preventDefault();
setActivePage(a.dataset.page || "config");
});
});
// Restore last page, default to config (màn hình chính).
let initial = "config";
try {
const saved = localStorage.getItem("activePage");
if (saved === "dashboard" || saved === "overview" || saved === "config" || saved === "missions" || saved === "integrations") {
initial = saved === "overview" ? "dashboard" : saved;
}
} catch {
/* ignore */
}
setActivePage(initial);
if (window.NavApp?.init) window.NavApp.init();
else setActivePage("config");
}
window.LmApp = { setActivePage };
function setLeftPaneWidth(px) {
const v = Math.round(clamp(Number(px), 320, 720));
document.documentElement.style.setProperty("--leftPaneW", `${v}px`);
@@ -629,7 +618,7 @@ function findDuplicateImuFrame(frameId, excludeId = null) {
function clearCanvasSelection() {
state.selectedId = null;
state.selectedImuId = null;
selectedText.textContent = "none";
selectedText.textContent = t("common.none");
setSelectedRelText();
}
@@ -1697,7 +1686,10 @@ function updateLayoutActiveHint() {
if (!layoutActiveHintEl) return;
const name = state.activeLayoutName || "—";
const dirty = state.layoutDirty ? " • chưa lưu" : "";
layoutActiveHintEl.textContent = `Đang chỉnh: ${name}${dirty}`;
layoutActiveHintEl.textContent = t("config.layout.editingHint", {
name,
dirty: dirty ? t("config.layout.unsavedDirty") : "",
});
}
function renderLayoutSelect() {
@@ -1783,7 +1775,7 @@ async function deleteActiveLayoutFromUI() {
return;
}
const name = state.activeLayoutName || state.activeLayoutId;
if (!window.confirm(`Xóa layout «${name}»? Hành động không hoàn tác.`)) return;
if (!window.confirm(t("config.layout.deleteConfirm", { name }))) return;
await api(`/api/layouts/${state.activeLayoutId}`, { method: "DELETE" });
state.viewInitialized = false;
await loadAll();
@@ -2138,7 +2130,7 @@ function setSelectedRelText() {
function renderList() {
if (!state.lidars.length) {
listEl.innerHTML = `<div class="item"><div class="itemName">Chưa có LiDAR</div><div class="itemMeta">Hãy thêm LiDAR ở form phía trên.</div></div>`;
listEl.innerHTML = `<div class="item"><div class="itemName">${t("config.lidar.empty")}</div><div class="itemMeta">${t("config.lidar.emptyHint")}</div></div>`;
return;
}
@@ -2244,7 +2236,7 @@ function updateImuItemPoseUI(id) {
function renderImuList() {
if (!imuListEl) return;
if (!state.imus.length) {
imuListEl.innerHTML = `<div class="item"><div class="itemName">Chưa có IMU</div><div class="itemMeta">Thêm IMU ở form phía trên.</div></div>`;
imuListEl.innerHTML = `<div class="item"><div class="itemName">${t("config.imu.empty")}</div><div class="itemMeta">${t("config.imu.emptyHint")}</div></div>`;
return;
}
@@ -3117,7 +3109,7 @@ async function loadAll() {
state.selectedImuId = null;
}
if (!state.selectedId && !state.selectedImuId) {
selectedText.textContent = "none";
selectedText.textContent = t("common.none");
}
setSelectedRelText();
renderList();
@@ -3130,7 +3122,10 @@ async function loadAll() {
overviewActiveModelEl.textContent = state.layout?.robot?.model || "diff";
}
if (overviewActiveSensorsEl) {
overviewActiveSensorsEl.textContent = `${state.lidars.length} LiDAR • ${state.imus.length} IMU`;
overviewActiveSensorsEl.textContent = t("dashboard.system.sensorCount", {
lidars: state.lidars.length,
imus: state.imus.length,
});
}
if (!state.viewInitialized) {
fitViewToWorld();
@@ -3404,16 +3399,16 @@ saveLayoutBtn?.addEventListener("click", async () => {
await api("/api/health");
await loadMotorCatalog();
await loadAll();
selectedText.textContent = "none";
selectedText.textContent = t("common.none");
selectedRelText.textContent = "—";
setStatus("Sẵn sàng");
setStatus(t("app.status.ready"));
} catch (e) {
const msg = String(e.message || e);
if (overviewBackendEl) overviewBackendEl.textContent = `Lỗi: ${msg}`;
if (overviewBackendEl) overviewBackendEl.textContent = t("common.error", { msg });
if (msg.includes("stack") || msg.includes("Maximum call")) {
setStatus(`Lỗi JavaScript: ${msg}`);
setStatus(`${t("app.status.jsError")}: ${msg}`);
} else {
setStatus(`Không kết nối được backend: ${msg}`);
setStatus(`${t("app.status.backendError")}: ${msg}`);
}
}
};
@@ -3421,3 +3416,15 @@ saveLayoutBtn?.addEventListener("click", async () => {
else window.AuthApp?.whenReady(() => { boot(); });
})();
window.addEventListener("lm:locale-change", () => {
if (typeof renderList === "function") renderList();
if (typeof renderImuList === "function") renderImuList();
if (typeof renderLayoutSelect === "function") renderLayoutSelect();
if (typeof renderLayoutSelect === "function") renderLayoutSelect();
if (typeof updateLayoutActiveHint === "function") updateLayoutActiveHint();
if (typeof renderMotorWheels === "function") renderMotorWheels();
if (typeof renderBicycleMotorWheels === "function") renderBicycleMotorWheels();
if (typeof updateOverview === "function") updateOverview();
window.I18n?.applyDOM?.();
});