From d2e520983213a1c223f835a42d75ab826a5d120c Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Tue, 27 Aug 2024 18:55:28 +0100 Subject: [PATCH] ts: move "page" stuff to module not the happiest with it, but it works alright. PageManager is instantiated, you pass is Page{} objects, which have a (code)name, page title, and url, and a show, hide, and shouldSkip function, each returning a bool. The first two are self explanatory, the last tells you if the page is disabled for some reason (like on setup some are disabled if messages are). You can then call load(<(code)name>), or prev/next(). --- ts/modules/pages.ts | 106 ++++++++++++++++++++++++++++++++++++++++++ ts/setup.ts | 110 +++++++++++++------------------------------- ts/user.ts | 72 ++++++++++++++++++----------- 3 files changed, 181 insertions(+), 107 deletions(-) create mode 100644 ts/modules/pages.ts diff --git a/ts/modules/pages.ts b/ts/modules/pages.ts new file mode 100644 index 0000000..948474c --- /dev/null +++ b/ts/modules/pages.ts @@ -0,0 +1,106 @@ +export interface Page { + name: string; + title: string; + url: string; + show: () => boolean; + hide: () => boolean; + shouldSkip: () => boolean; + index?: number; +}; + +export interface PageConfig { + hideOthersOnPageShow: boolean; + defaultName: string; + defaultTitle: string; +} + +export class PageManager { + pages: Map; + pageList: string[]; + hideOthers: boolean; + defaultName: string = ""; + defaultTitle: string = ""; + + private _overridePushState = () => { + const pushState = window.history.pushState; + window.history.pushState = function (data: any, __: string, _: string | URL) { + pushState.apply(window.history, arguments); + let ev = { state: data as string } as PopStateEvent; + window.onpopstate(ev); + }; + } + + private _onpopstate = (event: PopStateEvent) => { + let name = event.state; + if (!(event.state in this.pages)) { + name = this.pageList[0] + } + let success = this.pages[name].show(); + if (!success) { + console.log("failed"); + return; + } + if (!(this.hideOthers)) { + console.log("shoudln't hide others"); + return; + } + for (let k of this.pageList) { + if (name != k) { + this.pages[k].hide(); + } + } + console.log("loop ended", this); + } + + constructor(c: PageConfig) { + this.pages = new Map; + this.pageList = []; + this.hideOthers = c.hideOthersOnPageShow; + this.defaultName = c.defaultName; + this.defaultTitle = c.defaultTitle; + + this._overridePushState(); + window.onpopstate = this._onpopstate; + } + + setPage(p: Page) { + p.index = this.pageList.length; + this.pages[p.name] = p; + this.pageList.push(p.name); + } + + load(name: string = "") { + if (!(name in this.pages)) return window.history.pushState(name || this.defaultName, this.defaultTitle, "") + const p = this.pages[name]; + this.loadPage(p); + } + + loadPage (p: Page) { + window.history.pushState(p.name || this.defaultName, p.title, p.url); + } + + prev(name: string = "") { + if (!(name in this.pages)) return console.error(`previous page ${name} not found`); + let p = this.pages[name]; + let shouldSkip = true; + while (shouldSkip && p.index > 0) { + p = this.pages[this.pageList[p.index-1]]; + shouldSkip = p.shouldSkip(); + } + this.loadPage(p); + } + + next(name: string = "") { + if (!(name in this.pages)) return console.error(`previous page ${name} not found`); + let p = this.pages[name]; + console.log("next", name, p); + console.log("pages", this.pages, this.pageList); + let shouldSkip = true; + while (shouldSkip && p.index < this.pageList.length) { + p = this.pages[this.pageList[p.index+1]]; + shouldSkip = p.shouldSkip(); + } + console.log("next ended with", p); + this.loadPage(p); + } +}; diff --git a/ts/setup.ts b/ts/setup.ts index 0fb2f6f..747ed83 100644 --- a/ts/setup.ts +++ b/ts/setup.ts @@ -1,6 +1,7 @@ import { _get, _post, toggleLoader, notificationBox } from "./modules/common.js"; import { lang, LangFile, loadLangSelector } from "./modules/lang.js"; import { ThemeManager } from "./modules/theme.js"; +import { PageManager } from "./modules/pages.js"; interface sWindow extends Window { messages: {}; @@ -18,6 +19,8 @@ const get = (id: string): HTMLElement => document.getElementById(id); const text = (id: string, val: string) => { document.getElementById(id).textContent = val; }; const html = (id: string, val: string) => { document.getElementById(id).innerHTML = val; }; + +// FIXME: Reuse setting types from ts/modules/settings.ts interface boolEvent extends Event { detail: boolean; } @@ -513,61 +516,15 @@ for (let section in settings) { } } -const pageNames: string[][] = []; - -(() => { - const pushState = window.history.pushState; - window.history.pushState = function (data: any, __: string, _: string | URL) { - pushState.apply(window.history, arguments); - let ev = { state: data as string } as PopStateEvent; - window.onpopstate(ev); - }; -})(); - -window.onpopstate = (event: PopStateEvent) => { - if (event.state === "welcome") { - cards[0].classList.remove("unfocused"); - for (let i = 1; i < cards.length; i++) { cards[i].classList.add("unfocused"); } - return; - } - for (let i = 0; i < cards.length; i++) { - if (event.state === pageNames[i][0]) { - cards[i].classList.remove("unfocused"); - } else { - cards[i].classList.add("unfocused"); - } - } -}; +let pages = new PageManager({ + hideOthersOnPageShow: true, + defaultName: "welcome", + defaultTitle: "Setup - jfa-go", +}); const cards = Array.from(document.getElementsByClassName("page-container")[0].querySelectorAll(".card.sectioned")) as Array; (window as any).cards = cards; -const changePageToIndex = (title: string, pageTitle: string, i: number) => { - cards[i].classList.remove("unfocused"); - const urlParams = new URLSearchParams(window.location.search); - const lang = urlParams.get("lang"); - let page = "/#" + title; - if (lang) { page += "?lang=" + lang; } - console.log("pushing", title, pageTitle, page); - window.history.pushState(title || "welcome", pageTitle, page); -}; - -const changePage = (title: string, pageTitle: string) => { - let found = false; - for (let i = 0; i < cards.length; i++) { - if (!found && pageNames[i][0] == title && !(cards[i].classList.contains("hidden"))) { - found = true; - changePageToIndex(title, pageTitle, i); - } else { - cards[i].classList.add("unfocused"); - } - } - if (!found) { - changePageToIndex(title, pageTitle, 0); - } - window.scrollTo(0, 0); -}; - (() => { for (let i = 0; i < cards.length; i++) { const card = cards[i]; @@ -578,37 +535,30 @@ const changePage = (title: string, pageTitle: string) => { if (titleEl.classList.contains("welcome")) { title = ""; } - let pageTitle = titleEl.textContent + " - jfa-go"; - pageNames.push([title, pageTitle]); - if (back) { back.addEventListener("click", () => { - for (let ind = cards.length - 1; ind >= 0; ind--) { - if (ind < i && !(cards[ind].classList.contains("hidden"))) { - changePage(pageNames[ind][0], pageNames[ind][1]); - break; - } - } - }); } - if (next) { - const func = () => { - if (next.hasAttribute("disabled")) return; - for (let ind = 0; ind < cards.length; ind++) { - if (ind > i && !(cards[ind].classList.contains("hidden"))) { - changePage(pageNames[ind][0], pageNames[ind][1]); - break; - } - } - }; - next.addEventListener("click", func) - } + pages.setPage({ + name: title, + title: titleEl.textContent + " - jfa-go", + url: "/#" + title, + show: () => { + cards[i].classList.remove("unfocused"); + return true; + }, + hide: () => { + cards[i].classList.add("unfocused"); + return true; + }, + shouldSkip: () => { + return cards[i].classList.contains("hidden"); + }, + }); + if (back) back.addEventListener("click", () => pages.prev(title)); + if (next) next.addEventListener("click", () => { + if (next.hasAttribute("disabled")) return; + pages.next(title); + }); } })(); -(() => { - let initialLocation = window.location.hash.replace("#", "") || "welcome"; - changePage(initialLocation, "Setup - jfa-go"); -})(); -// window.history.replaceState("welcome", "Setup - jfa-go",); - (() => { const button = document.getElementById("jellyfin-test-connection") as HTMLSpanElement; const ogText = button.textContent; @@ -665,3 +615,5 @@ const changePage = (title: string, pageTitle: string) => { })(); loadLangSelector("setup"); + +pages.load(window.location.hash.replace("#", "")); diff --git a/ts/user.ts b/ts/user.ts index 937a778..b72b22b 100644 --- a/ts/user.ts +++ b/ts/user.ts @@ -5,6 +5,7 @@ import { _get, _post, _delete, notificationBox, whichAnimationEvent, toDateStrin import { Login } from "./modules/login.js"; import { Discord, Telegram, Matrix, ServiceConfiguration, MatrixConfiguration } from "./modules/account-linking.js"; import { Validator, ValidatorConf, ValidatorRespDTO } from "./modules/validator.js"; +import { PageManager } from "./modules/pages.js"; interface userWindow extends Window { jellyfinID: string; @@ -21,6 +22,8 @@ interface userWindow extends Window { referralsEnabled: boolean; } +const basePath = window.location.pathname.replace("/password/reset", ""); + declare var window: userWindow; const theme = new ThemeManager(document.getElementById("button-theme")); @@ -35,6 +38,27 @@ window.token = ""; window.modals = {} as Modals; +let pages = new PageManager({ + hideOthersOnPageShow: true, + defaultName: "", + defaultTitle: document.title, +}); + +pages.setPage({ + name: "", + title: document.title, + url: basePath, + show: () => { + if (!login.loggedIn) login.login("", ""); + return true; + }, + hide: () => { + window.modals.login.close(); + return true; + }, + shouldSkip: () => false, +}); + (() => { window.modals.login = new Modal(document.getElementById("modal-login"), true); window.modals.email = new Modal(document.getElementById("modal-email"), false); @@ -49,38 +73,34 @@ window.modals = {} as Modals; } if (window.pwrEnabled) { window.modals.pwr = new Modal(document.getElementById("modal-pwr"), false); - window.modals.pwr.onclose = () => { - window.history.pushState("", "", window.location.pathname.replace("/password/reset", "")); - }; - const resetButton = document.getElementById("modal-login-pwr"); - resetButton.onclick = () => { - window.history.pushState("reset", "", window.location.pathname+"/password/reset"); - } - - window.onpopstate = (event: PopStateEvent) => { - if ((event.state == "reset" || window.location.pathname.includes("/password/reset")) && window.pwrEnabled) { + pages.setPage({ + name: "reset", + title: document.title, + url: basePath+"/password/reset", + show: () => { const usernameInput = document.getElementById("login-user") as HTMLInputElement; const input = document.getElementById("pwr-address") as HTMLInputElement; input.value = usernameInput.value; - window.modals.login.close(); window.modals.pwr.show(); - } else { + return true; + }, + hide: () => { + // Don't recursively run this through the onclose event window.modals.pwr.close(null, true); - if (!login.loggedIn) login.login("", ""); - } + return true; + }, + shouldSkip: () => false, + }); + window.modals.pwr.onclose = () => { + pages.load(""); }; + const resetButton = document.getElementById("modal-login-pwr"); + resetButton.onclick = () => { + pages.load("reset"); + } } })(); -(() => { - const pushState = window.history.pushState; - window.history.pushState = function (data: any, __: string, _: string | URL) { - pushState.apply(window.history, arguments); - let ev = { state: data as string } as PopStateEvent; - window.onpopstate(ev); - }; -})(); - window.notifications = new notificationBox(document.getElementById('notification-box') as HTMLDivElement, 5); if (window.pwrEnabled && window.linkResetEnabled) { @@ -798,8 +818,4 @@ const generatePermutations = (xs: number[]): [number[], number[]][] => { login.bindLogout(document.getElementById("logout-button")); -(() => { - let data = ""; - if (window.location.pathname.endsWith("/password/reset")) data = "reset"; - window.history.pushState(data, "", window.location.pathname); -})(); +pages.load(window.location.pathname.endsWith("/password/reset") ? "reset" : "");