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 untrusted user: 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 { 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<HTMLElement> {
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<string, Section> = {};
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<void> {
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);
}
}

View file

@ -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();

View file

@ -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 <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
root

View file

@ -16,4 +16,10 @@ export function nextAnimationFrame(): Promise<void> {
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;
}
}
.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 {

View file

@ -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;
}
}