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
text/typescript
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);
}