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.

159 lines
4.4 KiB

4 weeks ago
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();
}