You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
158 lines
4.4 KiB
158 lines
4.4 KiB
import { detectLanguage, translations } from "./i18n.js";
|
|
import { getRouteConfig, getRouteFromHash, loadPartial, navigateTo } from "./router.js";
|
|
import { TerminalConsole } from "./terminal-console.js";
|
|
|
|
const pageTitleEl = document.getElementById("pageTitle");
|
|
const pageOutletEl = document.getElementById("pageOutlet");
|
|
const navLinks = Array.from(document.querySelectorAll(".nav-link"));
|
|
const navGroups = Array.from(document.querySelectorAll(".nav-group.has-flyout"));
|
|
const navGroupToggles = Array.from(document.querySelectorAll("[data-group-toggle]"));
|
|
const sidebarToggleEl = document.getElementById("sidebarToggle");
|
|
const sidebarBackdropEl = document.getElementById("sidebarBackdrop");
|
|
const langKoEl = document.getElementById("langKo");
|
|
const langEnEl = document.getElementById("langEn");
|
|
|
|
let currentLanguage = detectLanguage();
|
|
let activeRoute = getRouteFromHash();
|
|
let activeController = null;
|
|
|
|
function t(key) {
|
|
return translations[currentLanguage]?.[key] ?? translations.en[key] ?? key;
|
|
}
|
|
|
|
function applyTranslations(root = document) {
|
|
document.documentElement.lang = currentLanguage;
|
|
document.title = currentLanguage === "ko" ? "TeraClone 콘솔 서버" : "TeraClone Console Server";
|
|
|
|
root.querySelectorAll("[data-i18n]").forEach((element) => {
|
|
element.textContent = t(element.dataset.i18n);
|
|
});
|
|
|
|
updatePageTitle();
|
|
updateLanguageButtons();
|
|
activeController?.updateTranslations?.(t);
|
|
}
|
|
|
|
function updateLanguageButtons() {
|
|
langKoEl?.classList.toggle("active", currentLanguage === "ko");
|
|
langEnEl?.classList.toggle("active", currentLanguage === "en");
|
|
}
|
|
|
|
function updatePageTitle() {
|
|
pageTitleEl.textContent = t(getRouteConfig(activeRoute).titleKey);
|
|
}
|
|
|
|
function setSidebarOpen(open) {
|
|
document.body.classList.toggle("sidebar-open", open);
|
|
if (sidebarBackdropEl) {
|
|
sidebarBackdropEl.hidden = !open;
|
|
}
|
|
}
|
|
|
|
function setActiveNav(routeName) {
|
|
navLinks.forEach((item) => {
|
|
item.classList.toggle("active", item.dataset.route === routeName);
|
|
});
|
|
}
|
|
|
|
function bindNav() {
|
|
navLinks.forEach((item) => {
|
|
item.addEventListener("click", () => {
|
|
navigateTo(item.dataset.route);
|
|
navGroups.forEach((group) => group.classList.remove("open"));
|
|
const parentGroup = item.closest(".nav-group");
|
|
if (parentGroup && window.innerWidth > 920) {
|
|
parentGroup.classList.add("suppress-hover");
|
|
}
|
|
item.blur();
|
|
});
|
|
});
|
|
|
|
navGroups.forEach((group) => {
|
|
group.addEventListener("mouseleave", () => {
|
|
group.classList.remove("suppress-hover");
|
|
});
|
|
});
|
|
|
|
navGroupToggles.forEach((toggle) => {
|
|
toggle.addEventListener("click", () => {
|
|
if (window.innerWidth > 920) {
|
|
return;
|
|
}
|
|
|
|
const group = toggle.closest(".nav-group");
|
|
if (!group) {
|
|
return;
|
|
}
|
|
|
|
const willOpen = !group.classList.contains("open");
|
|
navGroups.forEach((item) => item.classList.remove("open"));
|
|
group.classList.toggle("open", willOpen);
|
|
});
|
|
});
|
|
}
|
|
|
|
function bindChrome() {
|
|
sidebarToggleEl?.addEventListener("click", () => {
|
|
setSidebarOpen(!document.body.classList.contains("sidebar-open"));
|
|
});
|
|
|
|
sidebarBackdropEl?.addEventListener("click", () => {
|
|
setSidebarOpen(false);
|
|
});
|
|
|
|
langKoEl?.addEventListener("click", () => {
|
|
currentLanguage = "ko";
|
|
localStorage.setItem("teraclone-language", currentLanguage);
|
|
applyTranslations(document);
|
|
});
|
|
|
|
langEnEl?.addEventListener("click", () => {
|
|
currentLanguage = "en";
|
|
localStorage.setItem("teraclone-language", currentLanguage);
|
|
applyTranslations(document);
|
|
});
|
|
|
|
window.addEventListener("resize", () => {
|
|
if (window.innerWidth > 920) {
|
|
setSidebarOpen(false);
|
|
navGroups.forEach((group) => group.classList.remove("open"));
|
|
}
|
|
});
|
|
}
|
|
|
|
async function renderRoute(routeName) {
|
|
activeController?.unmount?.();
|
|
activeController = null;
|
|
|
|
const partial = await loadPartial(routeName);
|
|
pageOutletEl.innerHTML = partial;
|
|
activeRoute = routeName;
|
|
setActiveNav(routeName);
|
|
applyTranslations(pageOutletEl);
|
|
|
|
if (routeName === "terminal") {
|
|
activeController = new TerminalConsole(t);
|
|
activeController.mount();
|
|
}
|
|
|
|
if (window.innerWidth <= 920) {
|
|
setSidebarOpen(false);
|
|
}
|
|
}
|
|
|
|
async function syncRoute() {
|
|
const routeName = getRouteFromHash();
|
|
await renderRoute(routeName);
|
|
}
|
|
|
|
bindNav();
|
|
bindChrome();
|
|
applyTranslations(document);
|
|
window.addEventListener("hashchange", syncRoute);
|
|
|
|
if (!window.location.hash) {
|
|
navigateTo("overview");
|
|
} else {
|
|
syncRoute();
|
|
}
|
|
|