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" : "");