aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/util/Scripting.ts3
-rw-r--r--src/client/util/SerializationHelper.ts14
-rw-r--r--src/client/util/type_decls.d49
-rw-r--r--src/debug/Repl.tsx5
-rw-r--r--src/fields/ScriptField.ts174
-rw-r--r--src/new_fields/util.ts29
6 files changed, 140 insertions, 134 deletions
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index beaf5cb03..40e2ad6bb 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -12,6 +12,7 @@ import { Doc, Field } from '../../new_fields/Doc';
import { ImageField, PdfField, VideoField, AudioField } from '../../new_fields/URLField';
import { List } from '../../new_fields/List';
import { RichTextField } from '../../new_fields/RichTextField';
+import { ScriptField, ComputedField } from '../../fields/ScriptField';
export interface ScriptSucccess {
success: true;
@@ -45,7 +46,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
return { compiled: false, errors: diagnostics };
}
- let fieldTypes = [Doc, ImageField, PdfField, VideoField, AudioField, List, RichTextField];
+ let fieldTypes = [Doc, ImageField, PdfField, VideoField, AudioField, List, RichTextField, ScriptField, ComputedField, CompileScript];
let paramNames = ["Docs", ...fieldTypes.map(fn => fn.name)];
let params: any[] = [Docs, ...fieldTypes];
let compiledFunction = new Function(...paramNames, `return ${script}`);
diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts
index 7ded85e43..a7246d7c4 100644
--- a/src/client/util/SerializationHelper.ts
+++ b/src/client/util/SerializationHelper.ts
@@ -45,13 +45,17 @@ export namespace SerializationHelper {
throw Error(`type '${obj.__type}' not registered. Make sure you register it using a @Deserializable decorator`);
}
- const value = deserialize(serializationTypes[obj.__type], obj);
+ const type = serializationTypes[obj.__type];
+ const value = deserialize(type.ctor, obj);
+ if (type.afterDeserialize) {
+ type.afterDeserialize(value);
+ }
serializing -= 1;
return value;
}
}
-let serializationTypes: { [name: string]: any } = {};
+let serializationTypes: { [name: string]: { ctor: { new(): any }, afterDeserialize?: (obj: any) => void } } = {};
let reverseMap: { [ctor: string]: string } = {};
export interface DeserializableOpts {
@@ -59,9 +63,9 @@ export interface DeserializableOpts {
withFields(fields: string[]): Function;
}
-export function Deserializable(name: string): DeserializableOpts;
+export function Deserializable(name: string, afterDeserialize?: (obj: any) => void): DeserializableOpts;
export function Deserializable(constructor: { new(...args: any[]): any }): void;
-export function Deserializable(constructor: { new(...args: any[]): any } | string): DeserializableOpts | void {
+export function Deserializable(constructor: { new(...args: any[]): any } | string, afterDeserialize?: (obj: any) => void): DeserializableOpts | void {
function addToMap(name: string, ctor: { new(...args: any[]): any }) {
const schema = getDefaultModelSchema(ctor) as any;
if (schema.targetClass !== ctor) {
@@ -69,7 +73,7 @@ export function Deserializable(constructor: { new(...args: any[]): any } | strin
setDefaultModelSchema(ctor, newSchema);
}
if (!(name in serializationTypes)) {
- serializationTypes[name] = ctor;
+ serializationTypes[name] = { ctor, afterDeserialize };
reverseMap[ctor.name] = name;
} else {
throw new Error(`Name ${name} has already been registered as deserializable`);
diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d
index 557f6f574..b3c049d02 100644
--- a/src/client/util/type_decls.d
+++ b/src/client/util/type_decls.d
@@ -141,17 +141,12 @@ declare abstract class RefField {
readonly [Id]: FieldId;
constructor();
- // protected [HandleUpdate]?(diff: any): void;
-
- // abstract [ToScriptString](): string;
}
-declare abstract class ObjectField {
- protected [OnUpdate](diff?: any): void;
- private [Parent]?: RefField | ObjectField;
- // abstract [Copy](): ObjectField;
+declare type FieldId = string;
- // abstract [ToScriptString](): string;
+declare abstract class ObjectField {
+ abstract [Copy](): ObjectField;
}
declare abstract class URLField extends ObjectField {
@@ -161,32 +156,48 @@ declare abstract class URLField extends ObjectField {
constructor(url: URL);
}
-declare class AudioField extends URLField { }
-declare class VideoField extends URLField { }
-declare class ImageField extends URLField { }
-declare class WebField extends URLField { }
-declare class PdfField extends URLField { }
-
-declare type FieldId = string;
+declare class AudioField extends URLField { [Copy](): ObjectField; }
+declare class VideoField extends URLField { [Copy](): ObjectField; }
+declare class ImageField extends URLField { [Copy](): ObjectField; }
+declare class WebField extends URLField { [Copy](): ObjectField; }
+declare class PdfField extends URLField { [Copy](): ObjectField; }
+// @ts-ignore
+declare type Extract<T, U> = T extends U ? T : never;
declare type Field = number | string | boolean | ObjectField | RefField;
+declare type FieldWaiting<T extends RefField = RefField> = T extends undefined ? never : Promise<T | undefined>;
+declare type FieldResult<T extends Field = Field> = Opt<T> | FieldWaiting<Extract<T, RefField>>;
declare type Opt<T> = T | undefined;
declare class Doc extends RefField {
constructor();
- [key: string]: Field | undefined;
+ [key: string]: FieldResult;
// [ToScriptString](): string;
}
declare class ListImpl<T extends Field> extends ObjectField {
constructor(fields?: T[]);
[index: number]: T | (T extends RefField ? Promise<T> : never);
- // [ToScriptString](): string;
- // [Copy](): ObjectField;
+ [Copy](): ObjectField;
}
// @ts-ignore
declare const console: any;
-declare const Documents: any;
+interface DocumentOptions { }
+
+declare const Docs: {
+ ImageDocument(url: string, options?: DocumentOptions): Doc;
+ VideoDocument(url: string, options?: DocumentOptions): Doc;
+ // HistogramDocument(url:string, options?:DocumentOptions);
+ TextDocument(options?: DocumentOptions): Doc;
+ PdfDocument(url: string, options?: DocumentOptions): Doc;
+ WebDocument(url: string, options?: DocumentOptions): Doc;
+ HtmlDocument(html: string, options?: DocumentOptions): Doc;
+ KVPDocument(document: Doc, options?: DocumentOptions): Doc;
+ FreeformDocument(documents: Doc[], options?: DocumentOptions): Doc;
+ SchemaDocument(columns: string[], documents: Doc[], options?: DocumentOptions): Doc;
+ TreeDocument(documents: Doc[], options?: DocumentOptions): Doc;
+ StackingDocument(documents: Doc[], options?: DocumentOptions): Doc;
+};
diff --git a/src/debug/Repl.tsx b/src/debug/Repl.tsx
index c2db3bdcb..91b711c79 100644
--- a/src/debug/Repl.tsx
+++ b/src/debug/Repl.tsx
@@ -4,6 +4,8 @@ import { observer } from 'mobx-react';
import { observable, computed } from 'mobx';
import { CompileScript } from '../client/util/Scripting';
import { makeInterface } from '../new_fields/Schema';
+import { ObjectField } from '../new_fields/ObjectField';
+import { RefField } from '../new_fields/RefField';
@observer
class Repl extends React.Component {
@@ -42,7 +44,8 @@ class Repl extends React.Component {
return (
<div style={{ marginTop: "5px" }}>
<p>{command.command}</p>
- <pre>{JSON.stringify(command.result, null, 2)}</pre>
+ {/* <pre>{JSON.stringify(command.result, null, 2)}</pre> */}
+ <pre>{command.result instanceof RefField || command.result instanceof ObjectField ? "object" : String(command.result)}</pre>
</div>
);
});
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index ae532c9e2..05ed57ea5 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -1,101 +1,97 @@
-// import { Field, FieldId } from "./Field";
-// import { Types } from "../server/Message";
-// import { CompileScript, ScriptOptions, CompiledScript } from "../client/util/Scripting";
-// import { Server } from "../client/Server";
-// import { Without } from "../Utils";
+import { ObjectField } from "../new_fields/ObjectField";
+import { CompiledScript, CompileScript } from "../client/util/Scripting";
+import { Copy, ToScriptString, Parent, SelfProxy } from "../new_fields/FieldSymbols";
+import { serializable, createSimpleSchema, map, primitive, object, deserialize, PropSchema, custom, SKIP } from "serializr";
+import { Deserializable } from "../client/util/SerializationHelper";
+import { computed } from "mobx";
-// export interface SerializableOptions extends Without<ScriptOptions, "capturedVariables"> {
-// capturedIds: { [id: string]: string };
-// }
+function optional(propSchema: PropSchema) {
+ return custom(value => {
+ if (value !== undefined) {
+ return propSchema.serializer(value);
+ }
+ return SKIP;
+ }, (jsonValue: any, context: any, oldValue: any, callback: (err: any, result: any) => void) => {
+ if (jsonValue !== undefined) {
+ return propSchema.deserializer(jsonValue, callback, context, oldValue);
+ }
+ return SKIP;
+ });
+}
-// export interface ScriptData {
-// script: string;
-// options: SerializableOptions;
-// }
+const optionsSchema = createSimpleSchema({
+ requiredType: true,
+ addReturn: true,
+ typecheck: true,
+ params: optional(map(primitive()))
+});
-// export class ScriptField extends Field {
-// private _script?: CompiledScript;
-// get script(): CompiledScript {
-// return this._script!;
-// }
-// private options?: ScriptData;
+function deserializeScript(script: ScriptField) {
+ const comp = CompileScript(script.scriptString, script.options);
+ if (!comp.compiled) {
+ throw new Error("Couldn't compile loaded script");
+ }
+ (script as any)._script = comp;
+}
-// constructor(script?: CompiledScript, id?: FieldId, save: boolean = true) {
-// super(id);
+@Deserializable("script", deserializeScript)
+export class ScriptField extends ObjectField {
+ protected readonly _script: CompiledScript;
-// this._script = script;
+ constructor(script: CompiledScript) {
+ super();
-// if (save) {
-// Server.UpdateField(this);
-// }
-// }
+ this._script = script;
+ }
-// ToScriptString() {
-// return "new ScriptField(...)";
-// }
+ @serializable(object(optionsSchema))
+ get options() {
+ return this._script.options;
+ }
-// GetValue() {
-// return this.script;
-// }
+ @serializable(true)
+ get scriptString(): string {
+ return this._script.originalScript;
+ }
-// TrySetValue(): boolean {
-// throw new Error("Script fields currently can't be modified");
-// }
+ // init(callback: (res: Field) => any) {
+ // const options = this.options!;
+ // const keys = Object.keys(options.options.capturedIds);
+ // Server.GetFields(keys).then(fields => {
+ // let captured: { [name: string]: Field } = {};
+ // keys.forEach(key => captured[options.options.capturedIds[key]] = fields[key]);
+ // const opts: ScriptOptions = {
+ // addReturn: options.options.addReturn,
+ // params: options.options.params,
+ // requiredType: options.options.requiredType,
+ // capturedVariables: captured
+ // };
+ // const script = CompileScript(options.script, opts);
+ // if (!script.compiled) {
+ // throw new Error("Can't compile script");
+ // }
+ // this._script = script;
+ // callback(this);
+ // });
+ // }
-// UpdateFromServer() {
-// throw new Error("Script fields currently can't be updated");
-// }
+ [Copy](): ObjectField {
+ return new ScriptField(this._script);
+ }
-// static FromJson(id: string, data: ScriptData): ScriptField {
-// let field = new ScriptField(undefined, id, false);
-// field.options = data;
-// return field;
-// }
+ [ToScriptString]() {
+ return "script field";
+ }
+}
-// init(callback: (res: Field) => any) {
-// const options = this.options!;
-// const keys = Object.keys(options.options.capturedIds);
-// Server.GetFields(keys).then(fields => {
-// let captured: { [name: string]: Field } = {};
-// keys.forEach(key => captured[options.options.capturedIds[key]] = fields[key]);
-// const opts: ScriptOptions = {
-// addReturn: options.options.addReturn,
-// params: options.options.params,
-// requiredType: options.options.requiredType,
-// capturedVariables: captured
-// };
-// const script = CompileScript(options.script, opts);
-// if (!script.compiled) {
-// throw new Error("Can't compile script");
-// }
-// this._script = script;
-// callback(this);
-// });
-// }
-
-// ToJson() {
-// const { options, originalScript } = this.script;
-// let capturedIds: { [id: string]: string } = {};
-// for (const capt in options.capturedVariables) {
-// capturedIds[options.capturedVariables[capt].Id] = capt;
-// }
-// const opts: SerializableOptions = {
-// ...options,
-// capturedIds
-// };
-// delete (opts as any).capturedVariables;
-// return {
-// id: this.Id,
-// type: Types.Script,
-// data: {
-// script: originalScript,
-// options: opts,
-// },
-// };
-// }
-
-// Copy(): Field {
-// //Script fields are currently immutable, so we can fake copy them
-// return this;
-// }
-// } \ No newline at end of file
+@Deserializable("computed")
+export class ComputedField extends ScriptField {
+ @computed
+ get value() {
+ const val = this._script.run({ this: (this[Parent] as any)[SelfProxy] });
+ if (val.success) {
+ return val.result;
+ }
+ return undefined;
+ }
+} \ No newline at end of file
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 2b304c373..ad07d05a3 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -7,6 +7,7 @@ import { RefField } from "./RefField";
import { ObjectField } from "./ObjectField";
import { action } from "mobx";
import { Parent, OnUpdate, Update, Id } from "./FieldSymbols";
+import { ComputedField } from "../fields/ScriptField";
export const setter = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
if (SerializationHelper.IsSerializing()) {
@@ -60,33 +61,22 @@ export function getter(target: any, prop: string | symbol | number, receiver: an
}
return getField(target, prop);
}
-function getProtoField(protoField: Doc | undefined, prop: string | number, cb?: (field: Field | undefined) => void) {
- if (!protoField) return undefined;
- let field = protoField[prop];
- if (field instanceof Promise) {
- cb && field.then(cb);
- return field;
- } else {
- cb && cb(field);
- return field;
- }
-}
-//TODO The callback parameter is never being passed in currently, so we should be able to get rid of it.
-export function getField(target: any, prop: string | number, ignoreProto: boolean = false, callback?: (field: Field | undefined) => void): any {
+export function getField(target: any, prop: string | number, ignoreProto: boolean = false): any {
const field = target.__fields[prop];
if (field instanceof ProxyField) {
- return field.value(callback);
+ return field.value();
+ }
+ if (field instanceof ComputedField) {
+ return field.value;
}
if (field === undefined && !ignoreProto && prop !== "proto") {
const proto = getField(target, "proto", true);
if (proto instanceof Doc) {
- return getProtoField(proto, prop, callback);
- } else if (proto instanceof Promise) {
- return proto.then(async proto => getProtoField(proto, prop, callback));
+ return proto[prop];
}
+ return undefined;
}
- callback && callback(field);
return field;
}
@@ -95,7 +85,8 @@ export function deleteProperty(target: any, prop: string | number | symbol) {
delete target[prop];
return true;
}
- throw new Error("Currently properties can't be deleted from documents, assign to undefined instead");
+ target[prop] = undefined;
+ return true;
}
export function updateFunction(target: any, prop: any, value: any, receiver: any) {