webscale #5

Closed
D wants to merge 1 commit from D/tghandbook:load-performance into master
4 changed files with 266 additions and 67 deletions
Showing only changes of commit b63129d4b8 - Show all commits

View file

@ -2,6 +2,7 @@
import speen from "~/assets/images/speen.svg";
import { getPageHTML } from "./wiki";
import userscript from "./userscript";
import { nextAnimationFrame } from "./utils";
function initWaiting(elem: HTMLElement) {
// Add spinner
@ -27,13 +28,11 @@ async function loadPage(page: string, elem: HTMLElement) {
html = html.replace(/"\/wiki/gi, '"//tgstation13.org/wiki');
// Set as HTML content and run HTML manipulations on it
requestAnimationFrame(() => {
elem.innerHTML = html;
console.log(`${page}: processing`);
userscript(elem, page);
console.log(`${page}: userscript applied`);
elem.classList.remove("waiting");
});
await nextAnimationFrame();
elem.innerHTML = html;
await nextAnimationFrame();
await userscript(elem, page);
elem.classList.remove("waiting");
}
type TabElements = { tabListItem: HTMLElement; tabContentItem: HTMLElement };
@ -50,7 +49,7 @@ export default class TabManager {
this.tabContentContainer = tabcontent;
}
openTab(page: string, setActive: boolean): void {
async openTab(page: string, setActive: boolean): void {
// Create tab list item
const tabListItem = document.createElement("div");
tabListItem.className = "tab";
@ -72,7 +71,7 @@ export default class TabManager {
this.tabContentContainer.appendChild(tabContentItem);
// Start loading page for new tab
loadPage(page, tabContentItem);
await loadPage(page, tabContentItem);
// Create tab entry
this.tabs[page] = { tabListItem, tabContentItem };

View file

@ -1,3 +1,5 @@
import { nextAnimationFrame } from "./utils";
interface ColorRGB {
r: number;
g: number;
@ -15,6 +17,166 @@ export enum ColorFmt {
HEX, // #RRGGBB
}
/* https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
$$('#colors_table tbody tr')
.map((row) => {
const [name, hex] = Array.from(row.children).slice(-3);
return [name.innerText.trim().replace(/\s.+/g, ''), hex.innerText.trim()];
}).sort((a, b) => [a[0], b[0]].sort()[0] === a[0] ? -1 : 1)
.map(([name, hex]) => `${name} : '${hex}',`)
.join('\n')
*/
const namedColors = {
aliceblue : '#f0f8ff',
antiquewhite : '#faebd7',
aqua : '#00ffff',
aquamarine : '#7fffd4',
azure : '#f0ffff',
beige : '#f5f5dc',
bisque : '#ffe4c4',
black : '#000000',
blanchedalmond : '#ffebcd',
blue : '#0000ff',
blueviolet : '#8a2be2',
brown : '#a52a2a',
burlywood : '#deb887',
cadetblue : '#5f9ea0',
chartreuse : '#7fff00',
chocolate : '#d2691e',
coral : '#ff7f50',
cornflowerblue : '#6495ed',
cornsilk : '#fff8dc',
crimson : '#dc143c',
cyan : '#00ffff',
darkblue : '#00008b',
darkcyan : '#008b8b',
darkgoldenrod : '#b8860b',
darkgray : '#a9a9a9',
darkgreen : '#006400',
darkgrey : '#a9a9a9',
darkkhaki : '#bdb76b',
darkmagenta : '#8b008b',
darkolivegreen : '#556b2f',
darkorange : '#ff8c00',
darkorchid : '#9932cc',
darkred : '#8b0000',
darksalmon : '#e9967a',
darkseagreen : '#8fbc8f',
darkslateblue : '#483d8b',
darkslategray : '#2f4f4f',
darkslategrey : '#2f4f4f',
darkturquoise : '#00ced1',
darkviolet : '#9400d3',
deeppink : '#ff1493',
deepskyblue : '#00bfff',
dimgray : '#696969',
dimgrey : '#696969',
dodgerblue : '#1e90ff',
firebrick : '#b22222',
floralwhite : '#fffaf0',
forestgreen : '#228b22',
fuchsia : '#ff00ff',
gainsboro : '#dcdcdc',
ghostwhite : '#f8f8ff',
gold : '#ffd700',
goldenrod : '#daa520',
gray : '#808080',
green : '#008000',
greenyellow : '#adff2f',
grey : '#808080',
honeydew : '#f0fff0',
hotpink : '#ff69b4',
indianred : '#cd5c5c',
indigo : '#4b0082',
ivory : '#fffff0',
khaki : '#f0e68c',
lavender : '#e6e6fa',
lavenderblush : '#fff0f5',
lawngreen : '#7cfc00',
lemonchiffon : '#fffacd',
lightblue : '#add8e6',
lightcoral : '#f08080',
lightcyan : '#e0ffff',
lightgoldenrodyellow : '#fafad2',
lightgray : '#d3d3d3',
lightgreen : '#90ee90',
lightgrey : '#d3d3d3',
lightpink : '#ffb6c1',
lightsalmon : '#ffa07a',
lightseagreen : '#20b2aa',
lightskyblue : '#87cefa',
lightslategray : '#778899',
lightslategrey : '#778899',
lightsteelblue : '#b0c4de',
lightyellow : '#ffffe0',
lime : '#00ff00',
limegreen : '#32cd32',
linen : '#faf0e6',
magenta : '#ff00ff',
maroon : '#800000',
mediumaquamarine : '#66cdaa',
mediumblue : '#0000cd',
mediumorchid : '#ba55d3',
mediumpurple : '#9370db',
mediumseagreen : '#3cb371',
mediumslateblue : '#7b68ee',
mediumspringgreen : '#00fa9a',
mediumturquoise : '#48d1cc',
mediumvioletred : '#c71585',
midnightblue : '#191970',
mintcream : '#f5fffa',
mistyrose : '#ffe4e1',
moccasin : '#ffe4b5',
navajowhite : '#ffdead',
navy : '#000080',
oldlace : '#fdf5e6',
olive : '#808000',
olivedrab : '#6b8e23',
orange : '#ffa500',
orangered : '#ff4500',
orchid : '#da70d6',
palegoldenrod : '#eee8aa',
palegreen : '#98fb98',
paleturquoise : '#afeeee',
palevioletred : '#db7093',
papayawhip : '#ffefd5',
peachpuff : '#ffdab9',
peru : '#cd853f',
pink : '#ffc0cb',
plum : '#dda0dd',
powderblue : '#b0e0e6',
purple : '#800080',
rebeccapurple : '#663399',
red : '#ff0000',
rosybrown : '#bc8f8f',
royalblue : '#4169e1',
saddlebrown : '#8b4513',
salmon : '#fa8072',
sandybrown : '#f4a460',
seagreen : '#2e8b57',
seashell : '#fff5ee',
sienna : '#a0522d',
silver : '#c0c0c0',
skyblue : '#87ceeb',
slateblue : '#6a5acd',
slategray : '#708090',
slategrey : '#708090',
snow : '#fffafa',
springgreen : '#00ff7f',
steelblue : '#4682b4',
tan : '#d2b48c',
teal : '#008080',
thistle : '#d8bfd8',
tomato : '#ff6347',
turquoise : '#40e0d0',
violet : '#ee82ee',
wheat : '#f5deb3',
white : '#ffffff',
whitesmoke : '#f5f5f5',
yellow : '#ffff00',
yellowgreen : '#9acd32',
}
function hsvToRgb({ h, s, v }: ColorHSV): ColorRGB {
const i = Math.floor(h * 6);
const f = h * 6 - i;
@ -72,18 +234,30 @@ function rgbToHsv({ r, g, b }: ColorRGB): ColorHSV {
}
// Hacky way to get RGB values FOR SURE!
function nameToRGB(name: string): ColorRGB {
const nameToRGB = function self(name: string): ColorRGB {
if ( namedColors[name] ) {
return parseColor(namedColors[name]);
}
// Create fake div
const fakeDiv = document.createElement("div");
fakeDiv.style.color = name;
document.body.appendChild(fakeDiv);
self.fakeDiv = self.fakeDiv || (() => {
const tmp = document.createElement("div");
Object.assign(tmp.style, {
position: 'fixed';
height: 0;
width: 0;
top: -99;
left: -99;
});
document.body.appendChild(tmp);
return tmp;
})();
self.fakeDiv.style.color = name;
// Get color of div
const cs = window.getComputedStyle(fakeDiv);
const pv = cs.getPropertyValue("color");
// Remove div after obtaining desired color value
document.body.removeChild(fakeDiv);
const cs = window.getComputedStyle(self.fakeDiv);
const pv = cs.color; //TODO slow
return parseColor(pv);
}
@ -92,10 +266,10 @@ function nameToRGB(name: string): ColorRGB {
// https://stackoverflow.com/questions/11068240/what-is-the-most-efficient-way-to-parse-a-css-color-in-javascript
function parseColor(input: string): ColorRGB {
// Hex format
if (input.substr(0, 1) === "#") {
if (input[0] === "#") {
const collen = (input.length - 1) / 3;
const fact = [17, 1, 0.062272][collen - 1];
return {
return { //TODO refactor with substring
r: Math.round(parseInt(input.substr(1, collen), 16) * fact) / 256,
g:
Math.round(parseInt(input.substr(1 + collen, collen), 16) * fact) / 256,

View file

@ -1,10 +1,10 @@
import { darken, ColorFmt, lighten } from "./darkmode";
import searchBox from "./search";
import { findParent } from "./utils";
import { nextAnimationFrame, findParent } from "./utils";
export default function userscript(root: HTMLElement, docname: string): void {
export default async function userscript(root: HTMLElement, docname: string): void {
root.querySelectorAll(".mw-editsection").forEach((editLink) => {
editLink.parentElement.removeChild(editLink);
window.requestAnimationFrame(() => editLink.remove())
});
// Darken bgcolor
@ -37,6 +37,7 @@ export default function userscript(root: HTMLElement, docname: string): void {
});
// Remove fixed widths
await nextAnimationFrame();
root.querySelectorAll("table[width]").forEach((td) => {
td.setAttribute("width", "100%");
});
@ -47,16 +48,18 @@ export default function userscript(root: HTMLElement, docname: string): void {
});
// Fixup spacing on top quotes
root
.querySelectorAll<HTMLImageElement>("table .floatright > a > img")
.forEach((img) => {
const row = findParent(img, (el) => el instanceof HTMLTableRowElement);
const td = document.createElement("td");
row.appendChild(td);
});
const tmpFloatRows = Array.from(root.querySelectorAll<HTMLImageElement>("table .floatright > a > img"))
.map((img) => {
return findParent(img, (el) => el instanceof HTMLTableRowElement);
})
await nextAnimationFrame();
tmpFloatRows.forEach((row) => {
const td = document.createElement("td");
row.appendChild(td);
});
// Group headers and content so stickies don't overlap
root.querySelectorAll("h3,h2").forEach((h3) => {
root.querySelectorAll("h3,h2").forEach((h3) => { //NOTE slow
const parent = h3.parentNode;
const div = document.createElement("div");
parent.insertBefore(div, h3);
@ -71,17 +74,28 @@ export default function userscript(root: HTMLElement, docname: string): void {
});
// Move id from header to container, if one is found
root.querySelectorAll<HTMLElement>(".mw-headline").forEach((span) => {
// Find nearest container
const container = findParent(span, (el) =>
el.classList.contains("mw-headline-cont")
);
if (container) {
container.id = span.id;
span.id += "-span";
container.dataset.name = span.innerText;
}
});
const tmpHeaders = Array.from(root.querySelectorAll<HTMLElement>(".mw-headline"))
.map((span) => {
// Find nearest container
const container = findParent(span, (el) =>
el.classList.contains("mw-headline-cont")
);
if (container) {
return [container, span, span.id, span.textContent]; //NOTE slow
} else {
return null;
}
})
.filter((e) => e !== null);
await nextAnimationFrame();
for (const [container, span, spanId, spanInnerText] of tmpHeaders) {
container.id = spanId;
span.id += "-span";
}
await nextAnimationFrame();
for (const [container, span, spanId, spanInnerText] of tmpHeaders) {
container.dataset.name = spanInnerText;
}
// Tell user that better chemistry is loading
const postbody = root;
@ -94,32 +108,35 @@ export default function userscript(root: HTMLElement, docname: string): void {
</table>`;
postbody.insertBefore(statusMessage, postbody.firstChild);
function betterChemistry() {
async function betterChemistry() {
// 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
document
.querySelectorAll(
"table.wikitable > tbody > tr:not(:first-child) > td:nth-child(2), .tooltiptext"
)
.forEach((td) => {
const tmpTooltiptext = Array.from(document.querySelectorAll(
"table.wikitable > tbody > tr:not(:first-child) > td:nth-child(2), .tooltiptext"
))
.map((td) => {
const tmp = td.cloneNode() as HTMLElement;
// The cast to Array is necessary because, while childNodes's NodeList technically has a forEach method, it's a live list and operations mess with its lenght in the middle of the loop.
// Nodes can only have one parent so append removes them from the original NodeList and shifts the following one back into the wrong index.
Array.from(td.childNodes).forEach((el) => {
Array.from(td.childNodes).forEach((el) => { //TODO really slow
if (el instanceof HTMLParagraphElement) {
tmp.append(...el.childNodes);
} else {
tmp.append(el);
}
});
td.parentNode.replaceChild(tmp, td);
return [td, td.parentNode, tmp];
});
await nextAnimationFrame();
for (const [td, parent, newTD] of tmpTooltiptext) {
parent.replaceChild(newTD, td);
}
// Enrich "x part" with checkboxes and parts
Array.from(document.querySelectorAll("td"))
.filter((el) => el.innerText.indexOf(" part") >= 0)
.forEach((el) => {
el.innerHTML = el.innerHTML.replace(
const tmpParts = Array.from(document.querySelectorAll("td"))
.filter((el) => el.textContent.indexOf(" part") >= 0)
.map((el) => {
const newInnerHTML = el.innerHTML.replace( //TODO slow
/((\d+)\s+(?:parts?|units?))(.*?(?:<\/a>|\n|$))/gi,
(match, ...m) =>
`<label class="bgus_part ${
@ -133,7 +150,12 @@ export default function userscript(root: HTMLElement, docname: string): void {
'<span class="bgus_nobreak bgus_nested_element">$1<span class="bgus_twistie"></span></span>'
)}`
);
return [el, newInnerHTML];
});
await nextAnimationFrame();
for (const [el, newInnerHTML] of tmpParts) {
el.innerHTML = newInnerHTML;
}
// Add event to autofill child checkboxes
root
.querySelectorAll(".bgus_part_tooltip > .bgus_checkbox")
@ -156,7 +178,7 @@ export default function userscript(root: HTMLElement, docname: string): void {
});
// Wrap every recipe with extra metadata
root.querySelectorAll<HTMLElement>(".bgus_part").forEach((el) => {
root.querySelectorAll<HTMLElement>(".bgus_part").forEach((el) => { //NOTE slow-ish
if ("parts" in el.parentElement.dataset) {
el.parentElement.dataset.parts = (
parseInt(el.parentElement.dataset.parts, 10) +
@ -220,11 +242,11 @@ export default function userscript(root: HTMLElement, docname: string): void {
const remTable = root.querySelector(
"#Non-craftable_Medicines + h4 + p + table"
);
remTable.parentElement.removeChild(remTable);
remTable.remove();
root
.querySelectorAll<HTMLElement>("div[data-name] .wikitable.sortable tr")
.forEach((row) => {
.forEach((row) => { //TODO slow
const sectionEl = findParent(
row,
(sel) => "name" in sel.dataset && sel.dataset.name !== ""
@ -238,7 +260,7 @@ export default function userscript(root: HTMLElement, docname: string): void {
th.classList.add("table-head");
return;
}
th.parentElement.removeChild(th);
th.remove();
});
return;
}
@ -285,11 +307,11 @@ export default function userscript(root: HTMLElement, docname: string): void {
}
title.classList.add("reagent-ext");
title.innerHTML = content;
if (desc) desc.parentElement.removeChild(desc);
if (treatment) treatment.parentElement.removeChild(treatment);
if (metabolism) metabolism.parentElement.removeChild(metabolism);
if (overdose) overdose.parentElement.removeChild(overdose);
if (addiction) addiction.parentElement.removeChild(addiction);
if (desc) desc.remove();
if (treatment) treatment.remove();
if (metabolism) metabolism.remove();
if (overdose) overdose.remove();
if (addiction) addiction.remove();
});
document.body.addEventListener("keydown", (ev) => {
@ -335,7 +357,7 @@ export default function userscript(root: HTMLElement, docname: string): void {
});
}
function betterGeneric() {
async function betterGeneric() {
const el = Array.from(
root.querySelectorAll<HTMLElement>("div.mw-headline-cont[id][data-name]")
);
@ -352,10 +374,10 @@ export default function userscript(root: HTMLElement, docname: string): void {
switch (docname) {
case "Guide_to_chemistry":
betterChemistry();
await betterChemistry();
break;
default:
betterGeneric();
await betterGeneric();
break;
}
// Everything is loaded, remove loading bar

View file

@ -1,3 +1,7 @@
export function nextAnimationFrame(): Promise {
return new Promise(requestAnimationFrame);
}
export function findParent(
base: HTMLElement,
matchFn: (candidate: HTMLElement) => boolean
@ -12,4 +16,4 @@ export function findParent(
return parent;
}
export default { findParent };
export default { nextAnimationFrame, findParent };