window-page
Version:
Route, setup, and build web pages
165 lines (152 loc) • 3.31 kB
JavaScript
let domReady = false;
function readyLsn() {
if (!domReady) {
domReady = true;
return true;
}
}
function once(emitter, events, filter) {
if (!Array.isArray(events)) events = [events];
const d = Promise.withResolvers();
const listener = (e) => {
if (!filter || filter(e)) {
for (const event of events) emitter.removeEventListener(event, listener);
d.resolve(emitter);
}
};
for (const event of events) emitter.addEventListener(event, listener);
return d.promise;
}
export function domDeferred() {
if (domReady) return;
const d = Promise.withResolvers();
if (document.readyState == "complete") {
domReady = true;
setTimeout(d.resolve);
return d.promise;
}
return Promise.race([
once(document, 'DOMContentLoaded', readyLsn),
once(window, 'load', readyLsn)
]);
}
export class Queue {
#list = [];
#on = false;
#resolve;
done;
started = false;
stopped = false;
constructor() {
this.count = 0;
const { promise, resolve } = Promise.withResolvers();
this.done = promise;
this.#resolve = resolve;
}
get length() {
return this.#list.length + (this.#on ? 1 : 0);
}
queue(job) {
this.stopped = false;
const d = Promise.withResolvers();
const p = d.promise.then(() => job()).finally(() => {
this.#on = false;
this.dequeue();
});
this.#list.push(d);
this.dequeue();
return p;
}
dequeue() {
this.started = true;
if (this.#on) {
return;
}
const d = this.#list.shift();
if (d) {
this.count++;
this.#on = true;
d.resolve();
} else {
this.stopped = true;
this.#resolve();
}
}
}
function isDocVisible() {
return !document.hidden || document.visibilityState == "visible";
}
export class UiQueue {
// run last job when doc is visible, then run all jobs
#d = Promise.withResolvers();
#job;
constructor() {
this.#d.promise.then(() => {
this.#d = null;
const job = this.#job;
if (job) {
this.#job = null;
return job();
}
});
if (!isDocVisible()) {
once(document, 'visibilitychange', isDocVisible).then(this.#d.resolve);
} else {
setTimeout(this.#d.resolve, 0);
}
}
run(job) {
if (this.#d) {
this.#job = job;
} else {
job();
this.#job = null;
}
}
}
export function waitStyles() {
return Promise.all(
Array.from(document.head.querySelectorAll('link[rel="stylesheet"]'))
.map(node => waitSheet(node))
);
}
function waitSheet(link) {
let ok = false;
try {
ok = link.sheet && link.sheet.cssRules;
} catch(ex) {
// bail out
ok = true;
}
if (ok) return;
return once(link, ['load', 'error']);
}
export function loadNode(node) {
const tag = node.nodeName;
const isScript = tag == "SCRIPT";
const copy = node.ownerDocument.createElement(tag);
for (const attr of node.attributes) {
let { name } = attr;
if (isScript) {
if (name == "type") {
continue;
} else if (name == "priv-type") {
name = "type";
}
} else if (name == "rel") {
continue;
} else if (name == "priv-rel") {
name = "rel";
}
copy.setAttribute(name, attr.value);
}
if (isScript && node.textContent && !node.src) {
copy.textContent = node.textContent;
node.parentNode.replaceChild(copy, node);
return Promise.resolve(copy);
} else {
const p = once(copy, ['load', 'error']);
node.parentNode.replaceChild(copy, node);
return p;
}
}