From a571960b5c99b997d780e2069243950d9676912c Mon Sep 17 00:00:00 2001 From: Hamcha Date: Mon, 22 Jun 2020 16:17:37 +0200 Subject: [PATCH] Try multiple times if a page request fail, add initial loading screen --- src/TabManager.ts | 76 +++++++++++++++++++++++++++++++++-------------- src/index.ts | 67 +++++++++++++++++++++++++++++++---------- src/userscript.ts | 5 +--- src/utils.ts | 8 ++++- style/bgus.scss | 6 ++-- style/main.scss | 54 ++++++++++++++++++++++++++------- 6 files changed, 159 insertions(+), 57 deletions(-) diff --git a/src/TabManager.ts b/src/TabManager.ts index 0cacc81..0548fe6 100644 --- a/src/TabManager.ts +++ b/src/TabManager.ts @@ -3,7 +3,8 @@ import speen from "~/assets/images/speen.svg"; import { getPageHTML } from "./wiki"; import { processHTML, bindFunctions, CURRENT_VERSION } from "./userscript"; import cache from "./cache"; -import { nextAnimationFrame } from "./utils"; +import { nextAnimationFrame, delay } from "./utils"; +import { TabInfo } from "./sections"; // @ts-expect-error: Parcel image import import unknown from "~/assets/images/tab-icons/unknown.svg"; @@ -15,15 +16,10 @@ function initWaiting(elem: HTMLElement) { const spinnerImg = document.createElement("img"); spinnerImg.src = speen; spinnerContainer.appendChild(spinnerImg); - const spinnerText = document.createElement("p"); - spinnerText.appendChild( - document.createTextNode("You start skimming through the manual...") - ); - spinnerContainer.appendChild(spinnerText); elem.appendChild(spinnerContainer); } -async function loadPage(page: string, elem: HTMLElement) { +async function loadPage(page: string, elem: HTMLElement): Promise { let html: string | null = null; const key = `page:${page}`; @@ -45,7 +41,18 @@ async function loadPage(page: string, elem: HTMLElement) { // Fetch page content if (!html) { console.log(`${page}: fetching`); - html = await getPageHTML(page); + let retries = 0; + while (retries < 5) { + try { + // eslint-disable-next-line no-await-in-loop + html = await getPageHTML(page); + break; + } catch (e) { + retries += 1; + // eslint-disable-next-line no-await-in-loop + await delay(1000); + } + } // Convert relative links to absolute (and proxied) html = html.replace(/"\/wiki/gi, '"//tgproxy.ovo.ovh/wiki'); @@ -53,15 +60,14 @@ async function loadPage(page: string, elem: HTMLElement) { await nextAnimationFrame(); // Set as HTML content and run HTML manipulations on it - const div = document.createElement("div"); - div.className = elem.className; + const div = elem.cloneNode(false) as HTMLDivElement; div.innerHTML = html; console.log(`${page}: processing`); processHTML(div, page); // Save result to cache - cache.set(key, div.outerHTML, CURRENT_VERSION).then(() => { + cache.set(key, div.innerHTML, CURRENT_VERSION).then(() => { console.log(`${page}: saved to cache`); }); @@ -69,12 +75,13 @@ async function loadPage(page: string, elem: HTMLElement) { elem = div; } else { // Set cached content as HTML - elem.outerHTML = html; + elem.innerHTML = html; } bindFunctions(elem, page); - console.log(`${page}: userscript applied`); elem.classList.remove("waiting"); + + return elem; } type TabElements = { @@ -97,6 +104,8 @@ export default class TabManager { sections: Record = {}; + loading: boolean; + constructor( sectionlist: HTMLElement, tablist: HTMLElement, @@ -107,6 +116,25 @@ export default class TabManager { this.tabContentContainer = tabcontent; } + /** + * Set app-wide loading state + * @param value is app still loading? + */ + setLoading(value: boolean): void { + if (value) { + document.getElementById("app").classList.add("waiting"); + initWaiting(this.tabContentContainer); + const spinnerContainer = this.tabContentContainer.querySelector(".speen"); + spinnerContainer.appendChild( + document.createTextNode("Loading wiki pages") + ); + } else { + document.getElementById("app").classList.remove("waiting"); + const elem = this.tabContentContainer.querySelector(".speen"); + this.tabContentContainer.removeChild(elem); + } + } + /** * Create section and add it to the section list * @param name Section name @@ -161,7 +189,7 @@ export default class TabManager { * @param icon Icon to show * @param setActive Also set the tab as active */ - openTab( + async openTab( section: string, page: string, options: { @@ -169,7 +197,8 @@ export default class TabManager { active?: boolean; text?: string; } - ): void { + ): Promise { + const { icon, active, text } = options; // Create tab list item const tabListItem = document.createElement("div"); tabListItem.className = "tab"; @@ -182,11 +211,10 @@ export default class TabManager { this.setActive(section, page); }); const iconElement = document.createElement("img"); - iconElement.src = options.icon || unknown; + iconElement.src = icon || unknown; tabListItem.title = page.replace(/_/gi, " "); tabListItem.appendChild(iconElement); - const shortTitle = - options.text || page.substr(page.lastIndexOf("_") + 1, 4); + const shortTitle = text || page.substr(page.lastIndexOf("_") + 1, 4); tabListItem.appendChild(document.createTextNode(shortTitle)); this.tabListContainer.appendChild(tabListItem); @@ -196,9 +224,6 @@ export default class TabManager { tabContentItem.dataset.tab = page; initWaiting(tabContentItem); - // Start loading page for new tab - loadPage(page, tabContentItem); - this.tabContentContainer.appendChild(tabContentItem); // Create tab entry @@ -209,8 +234,15 @@ export default class TabManager { tabListItem.classList.add("hidden"); } + // Start loading page for new tab + const elem = await loadPage(page, tabContentItem); + // Since element can be replaced (when loading for the first time), make sure the reference is updated + if (elem !== tabContentItem) { + this.sections[section].tabs[page].tabContentItem = elem; + } + // If asked for, set it to active - if (options.active) { + if (active) { this.setActive(section, page); } } diff --git a/src/index.ts b/src/index.ts index 890322e..36cefbb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,25 +1,58 @@ import TabManager from "./TabManager"; import sections from "./sections"; +import { nextAnimationFrame } from "./utils"; -const sectionListContainer = document.getElementById("section-list"); -const tabListContainer = document.getElementById("tab-list"); -const tabContentContainer = document.getElementById("tabs"); -const manager = new TabManager( - sectionListContainer, - tabListContainer, - tabContentContainer -); +// @ts-expect-error: Parcel image import +import unknown from "~/assets/images/tab-icons/unknown.svg"; -sections.forEach((section) => { - manager.createSection(section.name); - section.tabs.forEach((tab) => { - manager.openTab(section.name, tab.page, { icon: tab.icon, text: tab.text }); +async function load() { + const sectionListContainer = document.getElementById("section-list"); + const tabListContainer = document.getElementById("tab-list"); + const tabContentContainer = document.getElementById("tabs"); + const manager = new TabManager( + sectionListContainer, + tabListContainer, + tabContentContainer + ); + manager.setLoading(true); + + await nextAnimationFrame(); + + // Add loading "bar" + const spinnerContainer = document.querySelector("#tabs > .speen"); + const icons = document.createElement("div"); + icons.className = "loading-icons"; + sections.forEach((section) => + section.tabs.forEach((tab) => { + const iconElement = document.createElement("img"); + iconElement.dataset.tab = tab.page; + iconElement.src = tab.icon || unknown; + iconElement.title = tab.page.replace(/_/gi, " "); + icons.appendChild(iconElement); + }) + ); + spinnerContainer.appendChild(icons); + + const promises = sections.flatMap((section) => { + manager.createSection(section.name); + return section.tabs.map(async (tab) => { + // Load page + await manager.openTab(section.name, tab.page, { + icon: tab.icon, + text: tab.text, + }); + // Remove icon from loading + icons.removeChild(icons.querySelector(`img[data-tab=${tab.page}]`)); + }); }); -}); - -// Set first page as active -manager.setActive("Medical", "Guide_to_chemistry"); + Promise.all(promises).then(() => { + // Remove app-wide loading + manager.setLoading(false); + // Set first page as active + manager.setActive("Medical", "Guide_to_chemistry"); + }); +} if ("serviceWorker" in navigator) { const x = process.env.SUBPATH ? `${process.env.SUBPATH}/sw.js` : "sw.js"; navigator.serviceWorker @@ -31,3 +64,5 @@ if ("serviceWorker" in navigator) { console.log("Service worker registration failed, error:", error); }); } + +load(); diff --git a/src/userscript.ts b/src/userscript.ts index 8b61c8d..158d907 100644 --- a/src/userscript.ts +++ b/src/userscript.ts @@ -4,12 +4,9 @@ import { findParent } from "./utils"; // This is used for cache busting when userscript changes significantly. // Only change it when you made changes to the processHTML part! -export const CURRENT_VERSION = "e700bb9c309627b944618152a7d8e936ae7a05db"; +export const CURRENT_VERSION = "bb7abd544a19369d4b6b7e3dde3eb3cc34c023d4"; function chemistryFixups(root: HTMLElement) { - // Enable page-specific CSS rules - root.classList.add("bchem"); - // Fix inconsistencies with

on random parts // Ideally I'd like a

or something on every part, wrapping it completely, but for now let's just kill 'em root diff --git a/src/utils.ts b/src/utils.ts index ddc0367..8ca2d74 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -16,4 +16,10 @@ export function nextAnimationFrame(): Promise { return new Promise((resolve) => requestAnimationFrame(() => resolve())); } -export default { findParent, nextAnimationFrame }; +export function delay(ms: number): Promise { + return new Promise((resolve) => { + setTimeout(() => resolve(), ms); + }); +} + +export default { findParent, nextAnimationFrame, delay }; diff --git a/style/bgus.scss b/style/bgus.scss index fd58af7..7b01c73 100644 --- a/style/bgus.scss +++ b/style/bgus.scss @@ -88,11 +88,11 @@ span.bgus_nested_element:not(.bgus_collapsed) + div.tooltiptext { margin-left: -5px; } } -.bchem table.wikitable > tbody > tr > td:nth-child(2) { +div[data-tab="Guide_to_chemistry"] table.wikitable > tbody > tr > td:nth-child(2) { width: 45%; padding: 10px; } -.bchem table.wikitable { +div[data-tab="Guide_to_chemistry"] table.wikitable { border: 0 !important; .table-head { text-align: center; @@ -101,7 +101,7 @@ span.bgus_nested_element:not(.bgus_collapsed) + div.tooltiptext { background-color: darken($nanotrasen, 5%) !important; } } -div.bchem .bgus_fz_selected { +div[data-tab="Guide_to_chemistry"] .bgus_fz_selected { background: $nanotrasen !important; th, td { diff --git a/style/main.scss b/style/main.scss index d02595e..d261857 100644 --- a/style/main.scss +++ b/style/main.scss @@ -20,6 +20,7 @@ body { z-index: 0; img { opacity: 0.4; + width: 80vmin; } } @@ -37,6 +38,13 @@ body { height: 100%; display: flex; flex-direction: column; + + &.waiting { + #tab-list, + #section-list { + display: none; + } + } } ::-webkit-scrollbar { @@ -59,6 +67,20 @@ body { border: 1px solid lighten($nanotrasen, 10%); } +.speen { + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + font-size: 12pt; + img { + width: 60vmin; + opacity: 0.9; + padding-bottom: 1em; + } +} + #tabs { flex: 1; z-index: 1; @@ -85,17 +107,6 @@ body { display: flex; flex-direction: column; height: 100%; - .speen { - flex: 1; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - img { - width: 60vmin; - opacity: 0.9; - } - } } } @@ -211,3 +222,24 @@ noscript { background: transparent; width: 100%; } + +.loading-icons { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + user-select: none; + font-size: 9pt; + padding: 3pt 7pt; + text-transform: uppercase; + flex-wrap: wrap; + div { + border: 1px solid red; + display: flex; + flex-direction: column; + align-items: center; + } + img { + max-width: 32px; + } +}