aboutsummaryrefslogtreecommitdiff
path: root/src/client/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util')
-rw-r--r--src/client/util/DragManager.ts189
-rw-r--r--src/client/util/Scripting.ts132
-rw-r--r--src/client/util/ScrollBox.tsx21
-rw-r--r--src/client/util/SelectionManager.ts39
-rw-r--r--src/client/util/Transform.ts119
-rw-r--r--src/client/util/TypedEvent.ts42
-rw-r--r--src/client/util/UndoManager.ts133
-rw-r--r--src/client/util/type_decls.d215
8 files changed, 890 insertions, 0 deletions
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
new file mode 100644
index 000000000..60910a40b
--- /dev/null
+++ b/src/client/util/DragManager.ts
@@ -0,0 +1,189 @@
+import { DocumentDecorations } from "../views/DocumentDecorations";
+import { CollectionDockingView } from "../views/collections/CollectionDockingView";
+import { Document } from "../../fields/Document"
+import { action } from "mobx";
+import { DocumentView } from "../views/nodes/DocumentView";
+
+export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document) {
+ let onRowMove = action((e: PointerEvent): void => {
+ e.stopPropagation();
+ e.preventDefault();
+
+ document.removeEventListener("pointermove", onRowMove);
+ document.removeEventListener('pointerup', onRowUp);
+ DragManager.StartDrag(_reference.current!, { document: docFunc() });
+ });
+ let onRowUp = action((e: PointerEvent): void => {
+ document.removeEventListener("pointermove", onRowMove);
+ document.removeEventListener('pointerup', onRowUp);
+ });
+ let onItemDown = (e: React.PointerEvent) => {
+ // if (this.props.isSelected() || this.props.isTopMost) {
+ if (e.button == 0) {
+ e.stopPropagation();
+ if (e.shiftKey) {
+ CollectionDockingView.Instance.StartOtherDrag(docFunc(), e);
+ } else {
+ document.addEventListener("pointermove", onRowMove);
+ document.addEventListener('pointerup', onRowUp);
+ }
+ }
+ //}
+ }
+ return onItemDown;
+}
+
+export namespace DragManager {
+ export function Root() {
+ const root = document.getElementById("root");
+ if (!root) {
+ throw new Error("No root element found");
+ }
+ return root;
+ }
+
+ let dragDiv: HTMLDivElement;
+
+ export enum DragButtons {
+ Left = 1, Right = 2, Both = Left | Right
+ }
+
+ interface DragOptions {
+ handlers: DragHandlers;
+
+ hideSource: boolean | (() => boolean);
+ }
+
+ export interface DragDropDisposer {
+ (): void;
+ }
+
+ export class DragCompleteEvent {
+ }
+
+ export interface DragHandlers {
+ dragComplete: (e: DragCompleteEvent) => void;
+ }
+
+ export interface DropOptions {
+ handlers: DropHandlers;
+ }
+
+ export class DropEvent {
+ constructor(readonly x: number, readonly y: number, readonly data: { [id: string]: any }) { }
+ }
+
+ export interface DropHandlers {
+ drop: (e: Event, de: DropEvent) => void;
+ }
+
+
+ export function MakeDropTarget(element: HTMLElement, options: DropOptions): DragDropDisposer {
+ if ("canDrop" in element.dataset) {
+ throw new Error("Element is already droppable, can't make it droppable again");
+ }
+ element.dataset["canDrop"] = "true";
+ const handler = (e: Event) => {
+ const ce = e as CustomEvent<DropEvent>;
+ options.handlers.drop(e, ce.detail);
+ };
+ element.addEventListener("dashOnDrop", handler);
+ return () => {
+ element.removeEventListener("dashOnDrop", handler);
+ delete element.dataset["canDrop"]
+ };
+ }
+
+ export function StartDrag(ele: HTMLElement, dragData: { [id: string]: any }, options?: DragOptions) {
+ DocumentDecorations.Instance.Hidden = true;
+ if (!dragDiv) {
+ dragDiv = document.createElement("div");
+ DragManager.Root().appendChild(dragDiv);
+ }
+ const w = ele.offsetWidth, h = ele.offsetHeight;
+ const rect = ele.getBoundingClientRect();
+ const scaleX = rect.width / w, scaleY = rect.height / h;
+ let x = rect.left, y = rect.top;
+ // const offsetX = e.x - rect.left, offsetY = e.y - rect.top;
+ let dragElement = ele.cloneNode(true) as HTMLElement;
+ dragElement.style.opacity = "0.7";
+ dragElement.style.position = "absolute";
+ dragElement.style.bottom = "";
+ dragElement.style.left = "";
+ dragElement.style.transformOrigin = "0 0";
+ dragElement.style.zIndex = "1000";
+ dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
+ dragElement.style.width = `${rect.width / scaleX}px`;
+ dragElement.style.height = `${rect.height / scaleY}px`;
+ // It seems like the above code should be able to just be this:
+ // dragElement.style.transform = `translate(${x}px, ${y}px)`;
+ // dragElement.style.width = `${rect.width}px`;
+ // dragElement.style.height = `${rect.height}px`;
+ dragDiv.appendChild(dragElement);
+
+ let hideSource = false;
+ if (options) {
+ if (typeof options.hideSource === "boolean") {
+ hideSource = options.hideSource;
+ } else {
+ hideSource = options.hideSource();
+ }
+ }
+ const wasHidden = ele.hidden;
+ if (hideSource) {
+ ele.hidden = true;
+ }
+ const moveHandler = (e: PointerEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ x += e.movementX;
+ y += e.movementY;
+ if (e.shiftKey) {
+ abortDrag();
+ const docView: DocumentView = dragData["documentView"];
+ const doc: Document = docView ? docView.props.Document : dragData["document"];
+ CollectionDockingView.Instance.StartOtherDrag(doc, { pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: 0 });
+ }
+ dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
+ };
+
+ const abortDrag = () => {
+ document.removeEventListener("pointermove", moveHandler, true);
+ document.removeEventListener("pointerup", upHandler);
+ dragDiv.removeChild(dragElement);
+ if (hideSource && !wasHidden) {
+ ele.hidden = false;
+ }
+ }
+ const upHandler = (e: PointerEvent) => {
+ abortDrag();
+ FinishDrag(ele, e, dragData, options);
+ };
+ document.addEventListener("pointermove", moveHandler, true);
+ document.addEventListener("pointerup", upHandler);
+ }
+
+ function FinishDrag(dragEle: HTMLElement, e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions) {
+ let parent = dragEle.parentElement;
+ if (parent)
+ parent.removeChild(dragEle);
+ const target = document.elementFromPoint(e.x, e.y);
+ if (parent)
+ parent.appendChild(dragEle);
+ if (!target) {
+ return;
+ }
+ target.dispatchEvent(new CustomEvent<DropEvent>("dashOnDrop", {
+ bubbles: true,
+ detail: {
+ x: e.x,
+ y: e.y,
+ data: dragData
+ }
+ }));
+ if (options) {
+ options.handlers.dragComplete({});
+ }
+ DocumentDecorations.Instance.Hidden = false;
+ }
+} \ No newline at end of file
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
new file mode 100644
index 000000000..46bd1a206
--- /dev/null
+++ b/src/client/util/Scripting.ts
@@ -0,0 +1,132 @@
+// import * as ts from "typescript"
+let ts = (window as any).ts;
+import { Opt, Field } from "../../fields/Field";
+import { Document } from "../../fields/Document";
+import { NumberField } from "../../fields/NumberField";
+import { ImageField } from "../../fields/ImageField";
+import { TextField } from "../../fields/TextField";
+import { RichTextField } from "../../fields/RichTextField";
+import { KeyStore } from "../../fields/KeyStore";
+import { ListField } from "../../fields/ListField";
+// // @ts-ignore
+// import * as typescriptlib from '!!raw-loader!../../../node_modules/typescript/lib/lib.d.ts'
+// // @ts-ignore
+// import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts'
+
+// @ts-ignore
+import * as typescriptlib from '!!raw-loader!./type_decls.d'
+
+
+export interface ExecutableScript {
+ (): any;
+
+ compiled: boolean;
+}
+
+function Compile(script: string | undefined, diagnostics: Opt<any[]>, scope: { [name: string]: any }): ExecutableScript {
+ const compiled = !(diagnostics && diagnostics.some(diag => diag.category == ts.DiagnosticCategory.Error));
+
+ let func: () => Opt<Field>;
+ if (compiled && script) {
+ let fieldTypes = [Document, NumberField, TextField, ImageField, RichTextField, ListField];
+ let paramNames = ["KeyStore", ...fieldTypes.map(fn => fn.name)];
+ let params: any[] = [KeyStore, ...fieldTypes]
+ for (let prop in scope) {
+ if (prop === "this") {
+ continue;
+ }
+ paramNames.push(prop);
+ params.push(scope[prop]);
+ }
+ let thisParam = scope["this"];
+ let compiledFunction = new Function(...paramNames, script);
+ func = function (): Opt<Field> {
+ return compiledFunction.apply(thisParam, params)
+ };
+ } else {
+ func = () => undefined;
+ }
+
+ return Object.assign(func,
+ {
+ compiled
+ });
+}
+
+interface File {
+ fileName: string;
+ content: string;
+}
+
+// class ScriptingCompilerHost implements ts.CompilerHost {
+class ScriptingCompilerHost {
+ files: File[] = [];
+
+ // getSourceFile(fileName: string, languageVersion: ts.ScriptTarget, onError?: ((message: string) => void) | undefined, shouldCreateNewSourceFile?: boolean | undefined): ts.SourceFile | undefined {
+ getSourceFile(fileName: string, languageVersion: any, onError?: ((message: string) => void) | undefined, shouldCreateNewSourceFile?: boolean | undefined): any | undefined {
+ let contents = this.readFile(fileName);
+ if (contents !== undefined) {
+ return ts.createSourceFile(fileName, contents, languageVersion, true);
+ }
+ return undefined;
+ }
+ // getDefaultLibFileName(options: ts.CompilerOptions): string {
+ getDefaultLibFileName(options: any): string {
+ return 'node_modules/typescript/lib/lib.d.ts' // No idea what this means...
+ }
+ writeFile(fileName: string, content: string) {
+ const file = this.files.find(file => file.fileName === fileName);
+ if (file) {
+ file.content = content;
+ } else {
+ this.files.push({ fileName, content })
+ }
+ }
+ getCurrentDirectory(): string {
+ return '';
+ }
+ getCanonicalFileName(fileName: string): string {
+ return this.useCaseSensitiveFileNames() ? fileName : fileName.toLowerCase();
+ }
+ useCaseSensitiveFileNames(): boolean {
+ return true;
+ }
+ getNewLine(): string {
+ return '\n';
+ }
+ fileExists(fileName: string): boolean {
+ return this.files.some(file => file.fileName === fileName);
+ }
+ readFile(fileName: string): string | undefined {
+ let file = this.files.find(file => file.fileName === fileName);
+ if (file) {
+ return file.content;
+ }
+ return undefined;
+ }
+}
+
+export function CompileScript(script: string, scope?: { [name: string]: any }, addReturn: boolean = false): ExecutableScript {
+ let host = new ScriptingCompilerHost;
+ let funcScript = `(function() {
+ ${addReturn ? `return ${script};` : script}
+ })()`
+ host.writeFile("file.ts", funcScript);
+ host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib);
+ let program = ts.createProgram(["file.ts"], {}, host);
+ let testResult = program.emit();
+ let outputText = "return " + host.readFile("file.js");
+
+ let diagnostics = ts.getPreEmitDiagnostics(program).concat(testResult.diagnostics);
+
+ return Compile(outputText, diagnostics, scope || {});
+}
+
+export function ToField(data: any): Opt<Field> {
+ if (typeof data == "string") {
+ return new TextField(data);
+ } else if (typeof data == "number") {
+ return new NumberField(data);
+ }
+ return undefined;
+} \ No newline at end of file
diff --git a/src/client/util/ScrollBox.tsx b/src/client/util/ScrollBox.tsx
new file mode 100644
index 000000000..b6b088170
--- /dev/null
+++ b/src/client/util/ScrollBox.tsx
@@ -0,0 +1,21 @@
+import React = require("react")
+
+export class ScrollBox extends React.Component {
+ onWheel = (e: React.WheelEvent) => {
+ if (e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { // If the element has a scroll bar, then we don't want the containing collection to zoom
+ e.stopPropagation();
+ }
+ }
+
+ render() {
+ return (
+ <div style={{
+ overflow: "auto",
+ width: "100%",
+ height: "100%",
+ }} onWheel={this.onWheel}>
+ {this.props.children}
+ </div>
+ )
+ }
+} \ No newline at end of file
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
new file mode 100644
index 000000000..1a711ae64
--- /dev/null
+++ b/src/client/util/SelectionManager.ts
@@ -0,0 +1,39 @@
+import { observable, action } from "mobx";
+import { DocumentView } from "../views/nodes/DocumentView";
+
+export namespace SelectionManager {
+ class Manager {
+ @observable
+ SelectedDocuments: Array<DocumentView> = [];
+
+ @action
+ SelectDoc(doc: DocumentView, ctrlPressed: boolean): void {
+ // if doc is not in SelectedDocuments, add it
+ if (!ctrlPressed) {
+ manager.SelectedDocuments = [];
+ }
+
+ if (manager.SelectedDocuments.indexOf(doc) === -1) {
+ manager.SelectedDocuments.push(doc)
+ }
+ }
+ }
+
+ const manager = new Manager;
+
+ export function SelectDoc(doc: DocumentView, ctrlPressed: boolean): void {
+ manager.SelectDoc(doc, ctrlPressed)
+ }
+
+ export function IsSelected(doc: DocumentView): boolean {
+ return manager.SelectedDocuments.indexOf(doc) !== -1;
+ }
+
+ export function DeselectAll(): void {
+ manager.SelectedDocuments = []
+ }
+
+ export function SelectedDocuments(): Array<DocumentView> {
+ return manager.SelectedDocuments;
+ }
+} \ No newline at end of file
diff --git a/src/client/util/Transform.ts b/src/client/util/Transform.ts
new file mode 100644
index 000000000..3e1039166
--- /dev/null
+++ b/src/client/util/Transform.ts
@@ -0,0 +1,119 @@
+export class Transform {
+ private _translateX: number = 0;
+ private _translateY: number = 0;
+ private _scale: number = 1;
+
+ static get Identity(): Transform {
+ return new Transform(0, 0, 1);
+ }
+
+ get TranslateX(): number { return this._translateX; }
+ get TranslateY(): number { return this._translateY; }
+ get Scale(): number { return this._scale; }
+
+ constructor(x: number, y: number, scale: number) {
+ this._translateX = x;
+ this._translateY = y;
+ this._scale = scale;
+ }
+
+ translate = (x: number, y: number): Transform => {
+ this._translateX += x;
+ this._translateY += y;
+ return this;
+ }
+
+ scale = (scale: number): Transform => {
+ this._scale *= scale;
+ this._translateX *= scale;
+ this._translateY *= scale;
+ return this;
+ }
+
+ scaleAbout = (scale: number, x: number, y: number): Transform => {
+ this._translateX += x * this._scale - x * this._scale * scale;
+ this._translateY += y * this._scale - y * this._scale * scale;
+ this._scale *= scale;
+ return this;
+ }
+
+ transform = (transform: Transform): Transform => {
+ this._translateX = transform._translateX + transform._scale * this._translateX;
+ this._translateY = transform._translateY + transform._scale * this._translateY;
+ this._scale *= transform._scale;
+ return this;
+ }
+
+ preTranslate = (x: number, y: number): Transform => {
+ this._translateX += this._scale * x;
+ this._translateY += this._scale * y;
+ return this;
+ }
+
+ preScale = (scale: number): Transform => {
+ this._scale *= scale;
+ return this;
+ }
+
+ preTransform = (transform: Transform): Transform => {
+ this._translateX += transform._translateX * this._scale;
+ this._translateY += transform._translateY * this._scale;
+ this._scale *= transform._scale;
+ return this;
+ }
+
+ translated = (x: number, y: number): Transform => {
+ return this.copy().translate(x, y);
+ }
+
+ preTranslated = (x: number, y: number): Transform => {
+ return this.copy().preTranslate(x, y);
+ }
+
+ scaled = (scale: number): Transform => {
+ return this.copy().scale(scale);
+ }
+
+ scaledAbout = (scale: number, x: number, y: number): Transform => {
+ return this.copy().scaleAbout(scale, x, y);
+ }
+
+ preScaled = (scale: number): Transform => {
+ return this.copy().preScale(scale);
+ }
+
+ transformed = (transform: Transform): Transform => {
+ return this.copy().transform(transform);
+ }
+
+ preTransformed = (transform: Transform): Transform => {
+ return this.copy().preTransform(transform);
+ }
+
+ transformPoint = (x: number, y: number): [number, number] => {
+ x *= this._scale;
+ x += this._translateX;
+ y *= this._scale;
+ y += this._translateY;
+ return [x, y];
+ }
+
+ transformDirection = (x: number, y: number): [number, number] => {
+ return [x * this._scale, y * this._scale];
+ }
+
+ transformBounds(x: number, y: number, width: number, height: number): { x: number, y: number, width: number, height: number } {
+ [x, y] = this.transformPoint(x, y);
+ [width, height] = this.transformDirection(width, height);
+ return { x, y, width, height };
+ }
+
+ inverse = () => {
+ return new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale)
+ }
+
+ copy = () => {
+ return new Transform(this._translateX, this._translateY, this._scale);
+ }
+
+} \ No newline at end of file
diff --git a/src/client/util/TypedEvent.ts b/src/client/util/TypedEvent.ts
new file mode 100644
index 000000000..0714a7f5c
--- /dev/null
+++ b/src/client/util/TypedEvent.ts
@@ -0,0 +1,42 @@
+export interface Listener<T> {
+ (event: T): any;
+}
+
+export interface Disposable {
+ dispose(): void;
+}
+
+/** passes through events as they happen. You will not get events from before you start listening */
+export class TypedEvent<T> {
+ private listeners: Listener<T>[] = [];
+ private listenersOncer: Listener<T>[] = [];
+
+ on = (listener: Listener<T>): Disposable => {
+ this.listeners.push(listener);
+ return {
+ dispose: () => this.off(listener)
+ };
+ }
+
+ once = (listener: Listener<T>): void => {
+ this.listenersOncer.push(listener);
+ }
+
+ off = (listener: Listener<T>) => {
+ var callbackIndex = this.listeners.indexOf(listener);
+ if (callbackIndex > -1) this.listeners.splice(callbackIndex, 1);
+ }
+
+ emit = (event: T) => {
+ /** Update any general listeners */
+ this.listeners.forEach((listener) => listener(event));
+
+ /** Clear the `once` queue */
+ this.listenersOncer.forEach((listener) => listener(event));
+ this.listenersOncer = [];
+ }
+
+ pipe = (te: TypedEvent<T>): Disposable => {
+ return this.on((e) => te.emit(e));
+ }
+} \ No newline at end of file
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
new file mode 100644
index 000000000..46ad558f3
--- /dev/null
+++ b/src/client/util/UndoManager.ts
@@ -0,0 +1,133 @@
+import { observable, action } from "mobx";
+
+function propertyDecorator(target: any, key: string | symbol) {
+ Object.defineProperty(target, key, {
+ configurable: true,
+ enumerable: false,
+ get: function () {
+ return 5;
+ },
+ set: function (value: any) {
+ Object.defineProperty(this, key, {
+ enumerable: false,
+ writable: true,
+ configurable: true,
+ value: function (...args: any[]) {
+ try {
+ UndoManager.StartBatch();
+ return value.apply(this, args);
+ } finally {
+ UndoManager.EndBatch();
+ }
+ }
+ })
+ }
+ })
+}
+export function undoBatch(target: any, key: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any {
+ if (!descriptor) {
+ propertyDecorator(target, key);
+ return;
+ }
+ const oldFunction = descriptor.value;
+
+ descriptor.value = function (...args: any[]) {
+ try {
+ UndoManager.StartBatch()
+ return oldFunction.apply(this, args)
+ } finally {
+ UndoManager.EndBatch()
+ }
+ }
+
+ return descriptor;
+}
+
+export namespace UndoManager {
+ export interface UndoEvent {
+ undo: () => void;
+ redo: () => void;
+ }
+ type UndoBatch = UndoEvent[];
+
+ let undoStack: UndoBatch[] = observable([]);
+ let redoStack: UndoBatch[] = observable([]);
+ let currentBatch: UndoBatch | undefined;
+ let batchCounter = 0;
+ let undoing = false;
+
+ export function AddEvent(event: UndoEvent): void {
+ if (currentBatch && batchCounter && !undoing) {
+ currentBatch.push(event);
+ }
+ }
+
+ export function CanUndo(): boolean {
+ return undoStack.length > 0;
+ }
+
+ export function CanRedo(): boolean {
+ return redoStack.length > 0;
+ }
+
+ export function StartBatch(): void {
+ batchCounter++;
+ if (batchCounter > 0) {
+ currentBatch = [];
+ }
+ }
+
+ export const EndBatch = action(() => {
+ batchCounter--;
+ if (batchCounter === 0 && currentBatch && currentBatch.length) {
+ undoStack.push(currentBatch);
+ redoStack.length = 0;
+ currentBatch = undefined;
+ }
+ })
+
+ export function RunInBatch(fn: () => void) {
+ StartBatch();
+ fn();
+ EndBatch();
+ }
+
+ export const Undo = action(() => {
+ if (undoStack.length === 0) {
+ return;
+ }
+
+ let commands = undoStack.pop();
+ if (!commands) {
+ return;
+ }
+
+ undoing = true;
+ for (let i = commands.length - 1; i >= 0; i--) {
+ commands[i].undo();
+ }
+ undoing = false;
+
+ redoStack.push(commands);
+ })
+
+ export const Redo = action(() => {
+ if (redoStack.length === 0) {
+ return;
+ }
+
+ let commands = redoStack.pop();
+ if (!commands) {
+ return;
+ }
+
+ undoing = true;
+ for (let i = 0; i < commands.length; i++) {
+ commands[i].redo();
+ }
+ undoing = false;
+
+ undoStack.push(commands);
+ })
+
+} \ No newline at end of file
diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d
new file mode 100644
index 000000000..679f73f42
--- /dev/null
+++ b/src/client/util/type_decls.d
@@ -0,0 +1,215 @@
+//@ts-ignore
+declare type PropertyKey = string | number | symbol;
+interface Array<T> {
+ length: number;
+ toString(): string;
+ toLocaleString(): string;
+ pop(): T | undefined;
+ push(...items: T[]): number;
+ concat(...items: ConcatArray<T>[]): T[];
+ concat(...items: (T | ConcatArray<T>)[]): T[];
+ join(separator?: string): string;
+ reverse(): T[];
+ shift(): T | undefined;
+ slice(start?: number, end?: number): T[];
+ sort(compareFn?: (a: T, b: T) => number): this;
+ splice(start: number, deleteCount?: number): T[];
+ splice(start: number, deleteCount: number, ...items: T[]): T[];
+ unshift(...items: T[]): number;
+ indexOf(searchElement: T, fromIndex?: number): number;
+ lastIndexOf(searchElement: T, fromIndex?: number): number;
+ every(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): boolean;
+ some(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): boolean;
+ forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;
+ map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
+ filter<S extends T>(callbackfn: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[];
+ filter(callbackfn: (value: T, index: number, array: T[]) => any, thisArg?: any): T[];
+ reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
+ reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
+ reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;
+ reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
+ reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
+ reduceRight<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;
+
+ [n: number]: T;
+}
+
+interface Function {
+ apply(this: Function, thisArg: any, argArray?: any): any;
+ call(this: Function, thisArg: any, ...argArray: any[]): any;
+ bind(this: Function, thisArg: any, ...argArray: any[]): any;
+ toString(): string;
+
+ prototype: any;
+ readonly length: number;
+
+ // Non-standard extensions
+ arguments: any;
+ caller: Function;
+}
+interface Boolean {
+ valueOf(): boolean;
+}
+interface Number {
+ toString(radix?: number): string;
+ toFixed(fractionDigits?: number): string;
+ toExponential(fractionDigits?: number): string;
+ toPrecision(precision?: number): string;
+ valueOf(): number;
+}
+interface IArguments {
+ [index: number]: any;
+ length: number;
+ callee: Function;
+}
+interface RegExp {
+ readonly flags: string;
+ readonly sticky: boolean;
+ readonly unicode: boolean;
+}
+interface String {
+ codePointAt(pos: number): number | undefined;
+ includes(searchString: string, position?: number): boolean;
+ endsWith(searchString: string, endPosition?: number): boolean;
+ normalize(form: "NFC" | "NFD" | "NFKC" | "NFKD"): string;
+ normalize(form?: string): string;
+ repeat(count: number): string;
+ startsWith(searchString: string, position?: number): boolean;
+ anchor(name: string): string;
+ big(): string;
+ blink(): string;
+ bold(): string;
+ fixed(): string;
+ fontcolor(color: string): string;
+ fontsize(size: number): string;
+ fontsize(size: string): string;
+ italics(): string;
+ link(url: string): string;
+ small(): string;
+ strike(): string;
+ sub(): string;
+ sup(): string;
+}
+interface Object {
+ constructor: Function;
+ toString(): string;
+ toLocaleString(): string;
+ valueOf(): Object;
+ hasOwnProperty(v: PropertyKey): boolean;
+ isPrototypeOf(v: Object): boolean;
+ propertyIsEnumerable(v: PropertyKey): boolean;
+}
+interface ConcatArray<T> {
+ readonly length: number;
+ readonly [n: number]: T;
+ join(separator?: string): string;
+ slice(start?: number, end?: number): T[];
+}
+interface URL {
+ hash: string;
+ host: string;
+ hostname: string;
+ href: string;
+ readonly origin: string;
+ password: string;
+ pathname: string;
+ port: string;
+ protocol: string;
+ search: string;
+ username: string;
+ toJSON(): string;
+}
+
+declare type FieldId = string;
+
+declare abstract class Field {
+ Id: FieldId;
+ abstract ToScriptString(): string;
+ abstract TrySetValue(value: any): boolean;
+ abstract GetValue(): any;
+ abstract Copy(): Field;
+}
+
+declare abstract class BasicField<T> extends Field {
+ constructor(data: T);
+ Data: T;
+ TrySetValue(value: any): boolean;
+ GetValue(): any;
+}
+
+declare class TextField extends BasicField<string>{
+ constructor();
+ constructor(data: string);
+ ToScriptString(): string;
+ Copy(): Field;
+}
+declare class ImageField extends BasicField<URL>{
+ constructor();
+ constructor(data: URL);
+ ToScriptString(): string;
+ Copy(): Field;
+}
+declare class HtmlField extends BasicField<string>{
+ constructor();
+ constructor(data: string);
+ ToScriptString(): string;
+ Copy(): Field;
+}
+declare class NumberField extends BasicField<number>{
+ constructor();
+ constructor(data: number);
+ ToScriptString(): string;
+ Copy(): Field;
+}
+declare class WebField extends BasicField<URL>{
+ constructor();
+ constructor(data: URL);
+ ToScriptString(): string;
+ Copy(): Field;
+}
+declare class ListField<T> extends BasicField<T[]>{
+ constructor();
+ constructor(data: T[]);
+ ToScriptString(): string;
+ Copy(): Field;
+}
+declare class Key extends Field {
+ Name: string;
+ TrySetValue(value: any): boolean;
+ GetValue(): any;
+ Copy(): Field;
+ ToScriptString(): string;
+}
+declare type FIELD_WAITING = "<Waiting>";
+declare type Opt<T> = T | undefined;
+declare type FieldValue<T> = Opt<T> | FIELD_WAITING;
+// @ts-ignore
+declare class Document extends Field {
+ TrySetValue(value: any): boolean;
+ GetValue(): any;
+ Copy(): Field;
+ ToScriptString(): string;
+
+ Width(): number;
+ Height(): number;
+ Scale(): number;
+ Title: string;
+
+ Get(key: Key): FieldValue<Field>;
+ GetAsync(key: Key, callback: (field: Field) => void): boolean;
+ GetOrCreateAsync<T extends Field>(key: Key, ctor: { new(): T }, callback: (field: T) => void): void;
+ GetT<T extends Field>(key: Key, ctor: { new(): T }): FieldValue<T>;
+ GetOrCreate<T extends Field>(key: Key, ctor: { new(): T }): T;
+ GetData<T, U extends Field & { Data: T }>(key: Key, ctor: { new(): U }, defaultVal: T): T;
+ GetHtml(key: Key, defaultVal: string): string;
+ GetNumber(key: Key, defaultVal: number): number;
+ GetText(key: Key, defaultVal: string): string;
+ GetList<T extends Field>(key: Key, defaultVal: T[]): T[];
+ Set(key: Key, field: Field | undefined): void;
+ SetData<T, U extends Field & { Data: T }>(key: Key, value: T, ctor: { new(): U }): void;
+ SetText(key: Key, value: string): void;
+ SetNumber(key: Key, value: number): void;
+ GetPrototype(): FieldValue<Document>;
+ GetAllPrototypes(): Document[];
+ MakeDelegate(): Document;
+}