refactoring
This commit is contained in:
290
app/ui-chrome.js
Normal file
290
app/ui-chrome.js
Normal file
@@ -0,0 +1,290 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const SIDEBAR_COLLAPSE_STORAGE_PREFIX = "tarot-sidebar-collapsed:";
|
||||
const DETAIL_COLLAPSE_STORAGE_PREFIX = "tarot-detail-collapsed:";
|
||||
const DEFAULT_DATASET_ENTRY_COLLAPSED = true;
|
||||
const DEFAULT_DATASET_DETAIL_COLLAPSED = false;
|
||||
|
||||
function loadSidebarCollapsedState(storageKey) {
|
||||
try {
|
||||
const raw = window.localStorage?.getItem(storageKey);
|
||||
if (raw === "1") {
|
||||
return true;
|
||||
}
|
||||
if (raw === "0") {
|
||||
return false;
|
||||
}
|
||||
return null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function saveSidebarCollapsedState(storageKey, collapsed) {
|
||||
try {
|
||||
window.localStorage?.setItem(storageKey, collapsed ? "1" : "0");
|
||||
} catch {
|
||||
// Ignore storage failures silently.
|
||||
}
|
||||
}
|
||||
|
||||
function initializeSidebarPopouts() {
|
||||
const layouts = document.querySelectorAll(".planet-layout, .tarot-layout, .kab-layout");
|
||||
|
||||
layouts.forEach((layout, index) => {
|
||||
if (!(layout instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const panel = Array.from(layout.children).find((child) => (
|
||||
child instanceof HTMLElement
|
||||
&& child.matches("aside.planet-list-panel, aside.tarot-list-panel, aside.kab-tree-panel")
|
||||
));
|
||||
|
||||
if (!(panel instanceof HTMLElement) || panel.dataset.sidebarPopoutReady === "1") {
|
||||
return;
|
||||
}
|
||||
|
||||
const header = panel.querySelector(".planet-list-header, .tarot-list-header");
|
||||
if (!(header instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
panel.dataset.sidebarPopoutReady = "1";
|
||||
|
||||
const sectionId = layout.closest("section")?.id || `layout-${index + 1}`;
|
||||
const panelId = panel.id || `${sectionId}-entry-panel`;
|
||||
panel.id = panelId;
|
||||
|
||||
const storageKey = `${SIDEBAR_COLLAPSE_STORAGE_PREFIX}${sectionId}`;
|
||||
|
||||
const collapseBtn = document.createElement("button");
|
||||
collapseBtn.type = "button";
|
||||
collapseBtn.className = "sidebar-toggle-inline";
|
||||
collapseBtn.textContent = "Hide Panel";
|
||||
collapseBtn.setAttribute("aria-label", "Hide entry panel");
|
||||
collapseBtn.setAttribute("aria-controls", panelId);
|
||||
header.appendChild(collapseBtn);
|
||||
|
||||
const openBtn = document.createElement("button");
|
||||
openBtn.type = "button";
|
||||
openBtn.className = "sidebar-popout-open";
|
||||
openBtn.textContent = "Show Panel";
|
||||
openBtn.setAttribute("aria-label", "Show entry panel");
|
||||
openBtn.setAttribute("aria-controls", panelId);
|
||||
openBtn.hidden = true;
|
||||
layout.appendChild(openBtn);
|
||||
|
||||
const applyCollapsedState = (collapsed, persist = true) => {
|
||||
layout.classList.toggle("layout-sidebar-collapsed", collapsed);
|
||||
collapseBtn.setAttribute("aria-expanded", collapsed ? "false" : "true");
|
||||
openBtn.setAttribute("aria-expanded", collapsed ? "false" : "true");
|
||||
openBtn.hidden = !collapsed;
|
||||
|
||||
if (persist) {
|
||||
saveSidebarCollapsedState(storageKey, collapsed);
|
||||
}
|
||||
};
|
||||
|
||||
collapseBtn.addEventListener("click", () => {
|
||||
applyCollapsedState(true);
|
||||
});
|
||||
|
||||
openBtn.addEventListener("click", () => {
|
||||
applyCollapsedState(false);
|
||||
});
|
||||
|
||||
const storedCollapsed = loadSidebarCollapsedState(storageKey);
|
||||
applyCollapsedState(storedCollapsed == null ? DEFAULT_DATASET_ENTRY_COLLAPSED : storedCollapsed, false);
|
||||
});
|
||||
}
|
||||
|
||||
function initializeDetailPopouts() {
|
||||
const layouts = document.querySelectorAll(".planet-layout, .tarot-layout, .kab-layout");
|
||||
|
||||
layouts.forEach((layout, index) => {
|
||||
if (!(layout instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const detailPanel = Array.from(layout.children).find((child) => (
|
||||
child instanceof HTMLElement
|
||||
&& child.matches("section.planet-detail-panel, section.tarot-detail-panel, section.kab-detail-panel")
|
||||
));
|
||||
|
||||
if (!(detailPanel instanceof HTMLElement) || detailPanel.dataset.detailPopoutReady === "1") {
|
||||
return;
|
||||
}
|
||||
|
||||
const heading = detailPanel.querySelector(".planet-detail-heading, .tarot-detail-heading");
|
||||
if (!(heading instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
detailPanel.dataset.detailPopoutReady = "1";
|
||||
|
||||
const sectionId = layout.closest("section")?.id || `layout-${index + 1}`;
|
||||
const panelId = detailPanel.id || `${sectionId}-detail-panel`;
|
||||
detailPanel.id = panelId;
|
||||
|
||||
const detailStorageKey = `${DETAIL_COLLAPSE_STORAGE_PREFIX}${sectionId}`;
|
||||
const sidebarStorageKey = `${SIDEBAR_COLLAPSE_STORAGE_PREFIX}${sectionId}`;
|
||||
|
||||
const collapseBtn = document.createElement("button");
|
||||
collapseBtn.type = "button";
|
||||
collapseBtn.className = "detail-toggle-inline";
|
||||
collapseBtn.textContent = "Hide Detail";
|
||||
collapseBtn.setAttribute("aria-label", "Hide detail panel");
|
||||
collapseBtn.setAttribute("aria-controls", panelId);
|
||||
heading.appendChild(collapseBtn);
|
||||
|
||||
const openBtn = document.createElement("button");
|
||||
openBtn.type = "button";
|
||||
openBtn.className = "detail-popout-open";
|
||||
openBtn.textContent = "Show Detail";
|
||||
openBtn.setAttribute("aria-label", "Show detail panel");
|
||||
openBtn.setAttribute("aria-controls", panelId);
|
||||
openBtn.hidden = true;
|
||||
layout.appendChild(openBtn);
|
||||
|
||||
const applyCollapsedState = (collapsed, persist = true) => {
|
||||
if (collapsed && layout.classList.contains("layout-sidebar-collapsed")) {
|
||||
layout.classList.remove("layout-sidebar-collapsed");
|
||||
const sidebarOpenBtn = layout.querySelector(".sidebar-popout-open");
|
||||
if (sidebarOpenBtn instanceof HTMLButtonElement) {
|
||||
sidebarOpenBtn.hidden = true;
|
||||
sidebarOpenBtn.setAttribute("aria-expanded", "true");
|
||||
}
|
||||
const sidebarCollapseBtn = layout.querySelector(".sidebar-toggle-inline");
|
||||
if (sidebarCollapseBtn instanceof HTMLButtonElement) {
|
||||
sidebarCollapseBtn.setAttribute("aria-expanded", "true");
|
||||
}
|
||||
saveSidebarCollapsedState(sidebarStorageKey, false);
|
||||
}
|
||||
|
||||
layout.classList.toggle("layout-detail-collapsed", collapsed);
|
||||
collapseBtn.setAttribute("aria-expanded", collapsed ? "false" : "true");
|
||||
openBtn.setAttribute("aria-expanded", collapsed ? "false" : "true");
|
||||
openBtn.hidden = !collapsed;
|
||||
|
||||
if (persist) {
|
||||
saveSidebarCollapsedState(detailStorageKey, collapsed);
|
||||
}
|
||||
};
|
||||
|
||||
collapseBtn.addEventListener("click", () => {
|
||||
applyCollapsedState(true);
|
||||
});
|
||||
|
||||
openBtn.addEventListener("click", () => {
|
||||
applyCollapsedState(false);
|
||||
});
|
||||
|
||||
const storedCollapsed = loadSidebarCollapsedState(detailStorageKey);
|
||||
const shouldForceOpenForTarot = sectionId === "tarot-section";
|
||||
const initialCollapsed = shouldForceOpenForTarot
|
||||
? false
|
||||
: (storedCollapsed == null ? DEFAULT_DATASET_DETAIL_COLLAPSED : storedCollapsed);
|
||||
applyCollapsedState(initialCollapsed, false);
|
||||
});
|
||||
}
|
||||
|
||||
function setTopbarDropdownOpen(dropdownEl, isOpen) {
|
||||
if (!(dropdownEl instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
dropdownEl.classList.toggle("is-open", Boolean(isOpen));
|
||||
const trigger = dropdownEl.querySelector("button[aria-haspopup='menu']");
|
||||
if (trigger) {
|
||||
trigger.setAttribute("aria-expanded", isOpen ? "true" : "false");
|
||||
}
|
||||
}
|
||||
|
||||
function closeTopbarDropdowns(exceptEl = null) {
|
||||
const topbarDropdownEls = Array.from(document.querySelectorAll(".topbar-dropdown"));
|
||||
topbarDropdownEls.forEach((dropdownEl) => {
|
||||
if (exceptEl && dropdownEl === exceptEl) {
|
||||
return;
|
||||
}
|
||||
setTopbarDropdownOpen(dropdownEl, false);
|
||||
});
|
||||
}
|
||||
|
||||
function bindTopbarDropdownInteractions() {
|
||||
const topbarDropdownEls = Array.from(document.querySelectorAll(".topbar-dropdown"));
|
||||
if (!topbarDropdownEls.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
topbarDropdownEls.forEach((dropdownEl) => {
|
||||
const trigger = dropdownEl.querySelector("button[aria-haspopup='menu']");
|
||||
if (!(trigger instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTopbarDropdownOpen(dropdownEl, false);
|
||||
|
||||
dropdownEl.addEventListener("mouseenter", () => {
|
||||
setTopbarDropdownOpen(dropdownEl, true);
|
||||
});
|
||||
|
||||
dropdownEl.addEventListener("mouseleave", () => {
|
||||
setTopbarDropdownOpen(dropdownEl, false);
|
||||
});
|
||||
|
||||
dropdownEl.addEventListener("focusout", (event) => {
|
||||
const nextTarget = event.relatedTarget;
|
||||
if (!(nextTarget instanceof Node) || !dropdownEl.contains(nextTarget)) {
|
||||
setTopbarDropdownOpen(dropdownEl, false);
|
||||
}
|
||||
});
|
||||
|
||||
trigger.addEventListener("click", (event) => {
|
||||
event.stopPropagation();
|
||||
const nextOpen = !dropdownEl.classList.contains("is-open");
|
||||
closeTopbarDropdowns(dropdownEl);
|
||||
setTopbarDropdownOpen(dropdownEl, nextOpen);
|
||||
});
|
||||
|
||||
const menuItems = dropdownEl.querySelectorAll(".topbar-dropdown-menu [role='menuitem']");
|
||||
menuItems.forEach((menuItem) => {
|
||||
menuItem.addEventListener("click", () => {
|
||||
closeTopbarDropdowns();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener("click", (event) => {
|
||||
const clickTarget = event.target;
|
||||
if (clickTarget instanceof Node && topbarDropdownEls.some((dropdownEl) => dropdownEl.contains(clickTarget))) {
|
||||
return;
|
||||
}
|
||||
|
||||
closeTopbarDropdowns();
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Escape") {
|
||||
closeTopbarDropdowns();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
initializeSidebarPopouts();
|
||||
initializeDetailPopouts();
|
||||
bindTopbarDropdownInteractions();
|
||||
}
|
||||
|
||||
window.TarotChromeUi = {
|
||||
...(window.TarotChromeUi || {}),
|
||||
init,
|
||||
initializeSidebarPopouts,
|
||||
initializeDetailPopouts,
|
||||
setTopbarDropdownOpen,
|
||||
closeTopbarDropdowns,
|
||||
bindTopbarDropdownInteractions
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user