UNPKG

graph-explorer

Version:

Graph Explorer can be used to explore and RDF graphs in SPARQL endpoints or on the web.

232 lines (200 loc) 5.37 kB
import { EventSource } from "./events"; export abstract class BatchingScheduler { private useAnimationFrame: boolean; private scheduled: number | undefined; constructor(readonly waitingTime = 0) { this.useAnimationFrame = waitingTime === 0; this.runSynchronously = this.runSynchronously.bind(this); } protected schedule() { if (typeof this.scheduled === "undefined") { if (this.useAnimationFrame) { this.scheduled = requestAnimationFrame(this.runSynchronously); } else { this.scheduled = Number( setTimeout(this.runSynchronously, this.waitingTime) ); } } } protected abstract run(): void; runSynchronously() { const wasScheduled = this.cancelScheduledTimeout(); if (wasScheduled) { this.run(); } } dispose() { this.cancelScheduledTimeout(); } private cancelScheduledTimeout(): boolean { if (typeof this.scheduled !== "undefined") { if (this.useAnimationFrame) { cancelAnimationFrame(this.scheduled); } else { clearTimeout(this.scheduled); } this.scheduled = undefined; return true; } return false; } } export class BufferingQueue<Key extends string> extends BatchingScheduler { private fetchingQueue: Record<string, true> = Object.create(null); constructor(private onFetch: (keys: Key[]) => void, waitingTime = 200) { super(waitingTime); } push(key: Key) { this.fetchingQueue[key] = true; this.schedule(); } clear() { this.fetchingQueue = Object.create(null); } protected run() { const { fetchingQueue, onFetch } = this; this.fetchingQueue = Object.create(null); onFetch(Object.keys(fetchingQueue) as Key[]); } } export class Debouncer extends BatchingScheduler { private callback: (() => void) | undefined; call(callback: () => void) { this.callback = callback; this.schedule(); } protected run() { const callback = this.callback; callback(); } } export class Cancellation { public static NEVER_SIGNAL: CancellationToken = { aborted: false, addEventListener: () => { /* nothing */ }, removeEventListener: () => { /* nothing */ }, }; private source: EventSource<{ abort: undefined }> | undefined = new EventSource(); private aborted = false; readonly signal: CancellationToken; constructor() { this.signal = new (class { constructor(private parent: Cancellation) {} get aborted() { return this.parent.aborted; } addEventListener(event: "abort", handler: () => void) { if (event !== "abort") { return; } if (this.parent.source) { this.parent.source.on("abort", handler); } else { handler(); } } removeEventListener(event: "abort", handler: () => void) { if (event !== "abort") { return; } if (this.parent.source) { this.parent.source.off("abort", handler); } } })(this); } abort() { if (this.aborted) { return; } this.aborted = true; this.source.trigger("abort", undefined); this.source = undefined; } } export interface CancellationToken { readonly aborted: boolean; addEventListener(event: "abort", handler: () => void): void; removeEventListener(event: "abort", handler: () => void): void; } export const CancellationToken = { throwIfAborted(ct: CancellationToken): void { if (ct.aborted) { throw new CancelledError(); } }, mapCancelledToNull<T>( ct: CancellationToken, promise: Promise<T> ): Promise<T | null> { const onResolve = (value: T): T | null => { if (ct.aborted) { return null; } return value; }; const onReject = (err: any): null | Promise<null> => { if (ct.aborted) { return null; } return Promise.reject(err); }; return promise.then(onResolve, onReject); }, }; export class CancelledError extends Error { constructor(message = "Operation was cancelled") { super(message); this.name = CancelledError.name; Object.setPrototypeOf(this, CancelledError.prototype); } } export function delay(timeout: number) { return new Promise((resolve) => setTimeout(resolve, timeout)); } export function animateInterval( duration: number, onProgress: (progress: number) => void, cancellation?: Cancellation ): Promise<void> { return new Promise((resolve) => { let animationFrameId: number; let start: number; const animate = (time: number) => { if (cancellation && cancellation.signal.aborted) { return; } start = start || time; let timePassed = time - start; if (timePassed > duration) { timePassed = duration; } onProgress(timePassed / duration); if (timePassed < duration) { animationFrameId = requestAnimationFrame(animate); } else { resolve(); } }; cancellation.signal.addEventListener("abort", () => { cancelAnimationFrame(animationFrameId); resolve(); }); animationFrameId = requestAnimationFrame(animate); }); } export function easeInOutBezier(t: number) { if (t < 0) { return 0; } if (t > 1) { return 1; } return t * t * (3.0 - 2.0 * t); }