140 lines
3.8 KiB
TypeScript
140 lines
3.8 KiB
TypeScript
/**
|
|
* Convert big-endian u32 to native JavaScript number.
|
|
*/
|
|
export function uint_bytes_to_num(value: Uint8Array): number {
|
|
if (value.length != 4) { throw new Error("can't convert non 4-byte integer"); }
|
|
const data_view = new DataView(value.buffer);
|
|
return data_view.getUint32(0, false);
|
|
}
|
|
|
|
export function utf8_encode(value: string): Uint8Array {
|
|
const encoder = new TextEncoder();
|
|
let res: Uint8Array = new Uint8Array(0);
|
|
for (const ch of value) {
|
|
const cur = encoder.encode(ch)[0];
|
|
const prev = res;
|
|
res = new Uint8Array(prev.length + 1);
|
|
res.set(prev, 0);
|
|
res.set([cur], prev.length);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
export function utf8_decode(value: Uint8Array): string {
|
|
const decoder = new TextDecoder("utf-8");
|
|
return decoder.decode(value);
|
|
}
|
|
|
|
export async function sleep(millis: number): Promise<void> {
|
|
await new Promise<void>((resolver) => {
|
|
setTimeout(resolver, millis);
|
|
});
|
|
}
|
|
|
|
export function wait_for_dom_refresh(): Promise<void> {
|
|
return new Promise<void>((resolver) => {
|
|
requestAnimationFrame(() => {
|
|
requestAnimationFrame(() => {
|
|
resolver();
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Provides a locking abstraction around any ref-passed object to provide inner mutability that can
|
|
* automatically release on promise resolution. This provides "atomic transactions" on the inner
|
|
* type.
|
|
*
|
|
* This allows you to not await the transaction result, but offload the waiting to the next call
|
|
* to run. So other tasks can normally run when the transaction is executing. And if the inner
|
|
* type is needed again... the run call will wait for the (possibly) running transaction to
|
|
* finish first.
|
|
*/
|
|
export class AsyncRunner<T> {
|
|
private inner: T;
|
|
private lock: Promise<void>;
|
|
|
|
public constructor(inner: T) {
|
|
this.inner = inner;
|
|
this.lock = Promise.resolve();
|
|
}
|
|
|
|
public async run(transaction: (target: T) => Promise<void>): Promise<void> {
|
|
await this.lock;
|
|
this.lock = new Promise((resolver) => {
|
|
transaction(this.inner).then(() => { resolver() });
|
|
});
|
|
}
|
|
}
|
|
|
|
export namespace el {
|
|
function add_classes(target: HTMLElement, classes?: string[]): void {
|
|
if (classes === undefined) { return; }
|
|
for (const cls of classes) {
|
|
target.classList.add(cls);
|
|
}
|
|
}
|
|
|
|
export function div(children: HTMLElement[], classes?: string[]): HTMLDivElement {
|
|
const el = document.createElement("div");
|
|
add_classes(el, classes);
|
|
el.append(...children);
|
|
return el;
|
|
}
|
|
|
|
export function h1(text: string, classes?: string[]): HTMLHeadingElement {
|
|
const el = document.createElement("h1");
|
|
el.innerText = text;
|
|
add_classes(el, classes);
|
|
return el;
|
|
}
|
|
|
|
export function h2(text: string, classes?: string[]): HTMLHeadingElement {
|
|
const el = document.createElement("h2");
|
|
el.innerText = text;
|
|
add_classes(el, classes);
|
|
return el;
|
|
}
|
|
|
|
export function p(text: string, classes?: string[]): HTMLParagraphElement {
|
|
const el = document.createElement("p");
|
|
el.innerText = text;
|
|
add_classes(el, classes);
|
|
return el;
|
|
}
|
|
|
|
export async function img(src: string, classes?: string[]): Promise<HTMLImageElement> {
|
|
const el = new Image();
|
|
el.src = src;
|
|
el.draggable = false;
|
|
add_classes(el, classes);
|
|
await new Promise<void>((resolver) => {
|
|
el.addEventListener("load", () => { resolver(); });
|
|
});
|
|
return el;
|
|
}
|
|
|
|
export async function video(
|
|
src: string,
|
|
subtitles_url: string | null,
|
|
classes?: string[],
|
|
): Promise<HTMLVideoElement> {
|
|
const blob = await (await fetch(src)).blob();
|
|
const el = document.createElement("video");
|
|
el.autoplay = true;
|
|
const source = document.createElement("source");
|
|
source.src = URL.createObjectURL(blob);
|
|
el.appendChild(source);
|
|
if (subtitles_url !== null) {
|
|
const subs = document.createElement("track");
|
|
subs.src = subtitles_url;
|
|
subs.kind = "subtitles";
|
|
subs.default = true;
|
|
el.appendChild(subs);
|
|
}
|
|
add_classes(el, classes);
|
|
return el;
|
|
}
|
|
}
|