Try multiple times if a page request fail, add initial loading screen

This commit is contained in:
Hamcha 2020-06-22 16:17:37 +02:00
parent bb7abd544a
commit a571960b5c
Signed by: hamcha
GPG key ID: 41467804B19A3315
6 changed files with 159 additions and 57 deletions

View file

@ -3,7 +3,8 @@ import speen from "~/assets/images/speen.svg";
import { getPageHTML } from "./wiki"; import { getPageHTML } from "./wiki";
import { processHTML, bindFunctions, CURRENT_VERSION } from "./userscript"; import { processHTML, bindFunctions, CURRENT_VERSION } from "./userscript";
import cache from "./cache"; import cache from "./cache";
import { nextAnimationFrame } from "./utils"; import { nextAnimationFrame, delay } from "./utils";
import { TabInfo } from "./sections";
// @ts-expect-error: Parcel image import // @ts-expect-error: Parcel image import
import unknown from "~/assets/images/tab-icons/unknown.svg"; import unknown from "~/assets/images/tab-icons/unknown.svg";
@ -15,15 +16,10 @@ function initWaiting(elem: HTMLElement) {
const spinnerImg = document.createElement("img"); const spinnerImg = document.createElement("img");
spinnerImg.src = speen; spinnerImg.src = speen;
spinnerContainer.appendChild(spinnerImg); spinnerContainer.appendChild(spinnerImg);
const spinnerText = document.createElement("p");
spinnerText.appendChild(
document.createTextNode("You start skimming through the manual...")
);
spinnerContainer.appendChild(spinnerText);
elem.appendChild(spinnerContainer); elem.appendChild(spinnerContainer);
} }
async function loadPage(page: string, elem: HTMLElement) { async function loadPage(page: string, elem: HTMLElement): Promise<HTMLElement> {
let html: string | null = null; let html: string | null = null;
const key = `page:${page}`; const key = `page:${page}`;
@ -45,7 +41,18 @@ async function loadPage(page: string, elem: HTMLElement) {
// Fetch page content // Fetch page content
if (!html) { if (!html) {
console.log(`${page}: fetching`); console.log(`${page}: fetching`);
let retries = 0;
while (retries < 5) {
try {
// eslint-disable-next-line no-await-in-loop
html = await getPageHTML(page); 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) // Convert relative links to absolute (and proxied)
html = html.replace(/"\/wiki/gi, '"//tgproxy.ovo.ovh/wiki'); html = html.replace(/"\/wiki/gi, '"//tgproxy.ovo.ovh/wiki');
@ -53,15 +60,14 @@ async function loadPage(page: string, elem: HTMLElement) {
await nextAnimationFrame(); await nextAnimationFrame();
// Set as HTML content and run HTML manipulations on it // Set as HTML content and run HTML manipulations on it
const div = document.createElement("div"); const div = elem.cloneNode(false) as HTMLDivElement;
div.className = elem.className;
div.innerHTML = html; div.innerHTML = html;
console.log(`${page}: processing`); console.log(`${page}: processing`);
processHTML(div, page); processHTML(div, page);
// Save result to cache // 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`); console.log(`${page}: saved to cache`);
}); });
@ -69,12 +75,13 @@ async function loadPage(page: string, elem: HTMLElement) {
elem = div; elem = div;
} else { } else {
// Set cached content as HTML // Set cached content as HTML
elem.outerHTML = html; elem.innerHTML = html;
} }
bindFunctions(elem, page); bindFunctions(elem, page);
console.log(`${page}: userscript applied`);
elem.classList.remove("waiting"); elem.classList.remove("waiting");
return elem;
} }
type TabElements = { type TabElements = {
@ -97,6 +104,8 @@ export default class TabManager {
sections: Record<string, Section> = {}; sections: Record<string, Section> = {};
loading: boolean;
constructor( constructor(
sectionlist: HTMLElement, sectionlist: HTMLElement,
tablist: HTMLElement, tablist: HTMLElement,
@ -107,6 +116,25 @@ export default class TabManager {
this.tabContentContainer = tabcontent; 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 * Create section and add it to the section list
* @param name Section name * @param name Section name
@ -161,7 +189,7 @@ export default class TabManager {
* @param icon Icon to show * @param icon Icon to show
* @param setActive Also set the tab as active * @param setActive Also set the tab as active
*/ */
openTab( async openTab(
section: string, section: string,
page: string, page: string,
options: { options: {
@ -169,7 +197,8 @@ export default class TabManager {
active?: boolean; active?: boolean;
text?: string; text?: string;
} }
): void { ): Promise<void> {
const { icon, active, text } = options;
// Create tab list item // Create tab list item
const tabListItem = document.createElement("div"); const tabListItem = document.createElement("div");
tabListItem.className = "tab"; tabListItem.className = "tab";
@ -182,11 +211,10 @@ export default class TabManager {
this.setActive(section, page); this.setActive(section, page);
}); });
const iconElement = document.createElement("img"); const iconElement = document.createElement("img");
iconElement.src = options.icon || unknown; iconElement.src = icon || unknown;
tabListItem.title = page.replace(/_/gi, " "); tabListItem.title = page.replace(/_/gi, " ");
tabListItem.appendChild(iconElement); tabListItem.appendChild(iconElement);
const shortTitle = const shortTitle = text || page.substr(page.lastIndexOf("_") + 1, 4);
options.text || page.substr(page.lastIndexOf("_") + 1, 4);
tabListItem.appendChild(document.createTextNode(shortTitle)); tabListItem.appendChild(document.createTextNode(shortTitle));
this.tabListContainer.appendChild(tabListItem); this.tabListContainer.appendChild(tabListItem);
@ -196,9 +224,6 @@ export default class TabManager {
tabContentItem.dataset.tab = page; tabContentItem.dataset.tab = page;
initWaiting(tabContentItem); initWaiting(tabContentItem);
// Start loading page for new tab
loadPage(page, tabContentItem);
this.tabContentContainer.appendChild(tabContentItem); this.tabContentContainer.appendChild(tabContentItem);
// Create tab entry // Create tab entry
@ -209,8 +234,15 @@ export default class TabManager {
tabListItem.classList.add("hidden"); 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 asked for, set it to active
if (options.active) { if (active) {
this.setActive(section, page); this.setActive(section, page);
} }
} }

View file

@ -1,6 +1,11 @@
import TabManager from "./TabManager"; import TabManager from "./TabManager";
import sections from "./sections"; import sections from "./sections";
import { nextAnimationFrame } from "./utils";
// @ts-expect-error: Parcel image import
import unknown from "~/assets/images/tab-icons/unknown.svg";
async function load() {
const sectionListContainer = document.getElementById("section-list"); const sectionListContainer = document.getElementById("section-list");
const tabListContainer = document.getElementById("tab-list"); const tabListContainer = document.getElementById("tab-list");
const tabContentContainer = document.getElementById("tabs"); const tabContentContainer = document.getElementById("tabs");
@ -9,17 +14,45 @@ const manager = new TabManager(
tabListContainer, tabListContainer,
tabContentContainer tabContentContainer
); );
manager.setLoading(true);
sections.forEach((section) => { await nextAnimationFrame();
manager.createSection(section.name);
// 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) => { section.tabs.forEach((tab) => {
manager.openTab(section.name, tab.page, { icon: tab.icon, text: tab.text }); 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}]`));
}); });
}); });
Promise.all(promises).then(() => {
// Remove app-wide loading
manager.setLoading(false);
// Set first page as active // Set first page as active
manager.setActive("Medical", "Guide_to_chemistry"); manager.setActive("Medical", "Guide_to_chemistry");
});
}
if ("serviceWorker" in navigator) { if ("serviceWorker" in navigator) {
const x = process.env.SUBPATH ? `${process.env.SUBPATH}/sw.js` : "sw.js"; const x = process.env.SUBPATH ? `${process.env.SUBPATH}/sw.js` : "sw.js";
navigator.serviceWorker navigator.serviceWorker
@ -31,3 +64,5 @@ if ("serviceWorker" in navigator) {
console.log("Service worker registration failed, error:", error); console.log("Service worker registration failed, error:", error);
}); });
} }
load();

View file

@ -4,12 +4,9 @@ import { findParent } from "./utils";
// This is used for cache busting when userscript changes significantly. // This is used for cache busting when userscript changes significantly.
// Only change it when you made changes to the processHTML part! // 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) { function chemistryFixups(root: HTMLElement) {
// Enable page-specific CSS rules
root.classList.add("bchem");
// Fix inconsistencies with <p> on random parts // Fix inconsistencies with <p> on random parts
// Ideally I'd like a <p> or something on every part, wrapping it completely, but for now let's just kill 'em // Ideally I'd like a <p> or something on every part, wrapping it completely, but for now let's just kill 'em
root root

View file

@ -16,4 +16,10 @@ export function nextAnimationFrame(): Promise<void> {
return new Promise((resolve) => requestAnimationFrame(() => resolve())); return new Promise((resolve) => requestAnimationFrame(() => resolve()));
} }
export default { findParent, nextAnimationFrame }; export function delay(ms: number): Promise<void> {
return new Promise((resolve) => {
setTimeout(() => resolve(), ms);
});
}
export default { findParent, nextAnimationFrame, delay };

View file

@ -88,11 +88,11 @@ span.bgus_nested_element:not(.bgus_collapsed) + div.tooltiptext {
margin-left: -5px; 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%; width: 45%;
padding: 10px; padding: 10px;
} }
.bchem table.wikitable { div[data-tab="Guide_to_chemistry"] table.wikitable {
border: 0 !important; border: 0 !important;
.table-head { .table-head {
text-align: center; text-align: center;
@ -101,7 +101,7 @@ span.bgus_nested_element:not(.bgus_collapsed) + div.tooltiptext {
background-color: darken($nanotrasen, 5%) !important; background-color: darken($nanotrasen, 5%) !important;
} }
} }
div.bchem .bgus_fz_selected { div[data-tab="Guide_to_chemistry"] .bgus_fz_selected {
background: $nanotrasen !important; background: $nanotrasen !important;
th, th,
td { td {

View file

@ -20,6 +20,7 @@ body {
z-index: 0; z-index: 0;
img { img {
opacity: 0.4; opacity: 0.4;
width: 80vmin;
} }
} }
@ -37,6 +38,13 @@ body {
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
&.waiting {
#tab-list,
#section-list {
display: none;
}
}
} }
::-webkit-scrollbar { ::-webkit-scrollbar {
@ -59,6 +67,20 @@ body {
border: 1px solid lighten($nanotrasen, 10%); 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 { #tabs {
flex: 1; flex: 1;
z-index: 1; z-index: 1;
@ -85,17 +107,6 @@ body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; 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; background: transparent;
width: 100%; 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;
}
}