aboutsummaryrefslogtreecommitdiff
path: root/src/fields/ScriptField.ts
diff options
context:
space:
mode:
authoranika-ahluwalia <anika.ahluwalia@gmail.com>2020-05-15 13:26:37 -0500
committeranika-ahluwalia <anika.ahluwalia@gmail.com>2020-05-15 13:26:37 -0500
commit4bca98333ada6536a1bf2ecf1681c5c17a3a1ae1 (patch)
tree7fc20099971de42756af3d238e2ea4f9a608cbd3 /src/fields/ScriptField.ts
parent0f54ef61653213bd1b26300cb7d14e3da71d1eea (diff)
parent98c7540fff67c232c1b04f2130ee624f9a70afbd (diff)
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web into script_documents
Diffstat (limited to 'src/fields/ScriptField.ts')
-rw-r--r--src/fields/ScriptField.ts193
1 files changed, 193 insertions, 0 deletions
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
new file mode 100644
index 000000000..f05f431ac
--- /dev/null
+++ b/src/fields/ScriptField.ts
@@ -0,0 +1,193 @@
+import { ObjectField } from "./ObjectField";
+import { CompiledScript, CompileScript, scriptingGlobal, ScriptOptions, CompileError, CompileResult } from "../client/util/Scripting";
+import { Copy, ToScriptString, ToString, Parent, SelfProxy } from "./FieldSymbols";
+import { serializable, createSimpleSchema, map, primitive, object, deserialize, PropSchema, custom, SKIP } from "serializr";
+import { Deserializable, autoObject } from "../client/util/SerializationHelper";
+import { Doc, Field } from "./Doc";
+import { Plugins, setter } from "./util";
+import { computedFn } from "mobx-utils";
+import { ProxyField } from "./Proxy";
+import { Cast } from "./Types";
+
+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;
+ });
+}
+
+const optionsSchema = createSimpleSchema({
+ requiredType: true,
+ addReturn: true,
+ typecheck: true,
+ editable: true,
+ readonly: true,
+ params: optional(map(primitive()))
+});
+
+const scriptSchema = createSimpleSchema({
+ options: object(optionsSchema),
+ originalScript: true
+});
+
+async function deserializeScript(script: ScriptField) {
+ const captures: ProxyField<Doc> = (script as any).captures;
+ if (captures) {
+ const doc = (await captures.value())!;
+ const captured: any = {};
+ const keys = Object.keys(doc);
+ const vals = await Promise.all(keys.map(key => doc[key]) as any);
+ keys.forEach((key, i) => captured[key] = vals[i]);
+ (script.script.options as any).capturedVariables = captured;
+ }
+ const comp = CompileScript(script.script.originalScript, script.script.options);
+ if (!comp.compiled) {
+ throw new Error("Couldn't compile loaded script");
+ }
+ (script as any).script = comp;
+}
+
+@scriptingGlobal
+@Deserializable("script", deserializeScript)
+export class ScriptField extends ObjectField {
+ @serializable(object(scriptSchema))
+ readonly script: CompiledScript;
+ @serializable(object(scriptSchema))
+ readonly setterscript: CompiledScript | undefined;
+
+ @serializable(autoObject())
+ private captures?: ProxyField<Doc>;
+
+ constructor(script: CompiledScript, setterscript?: CompileResult) {
+ super();
+
+ if (script?.options.capturedVariables) {
+ const doc = Doc.assign(new Doc, script.options.capturedVariables);
+ this.captures = new ProxyField(doc);
+ }
+ this.setterscript = setterscript?.compiled ? setterscript : undefined;
+ this.script = script;
+ }
+
+ // 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);
+ // });
+ // }
+
+ [Copy](): ObjectField {
+ return new ScriptField(this.script);
+ }
+ toString() {
+ return `${this.script.originalScript}`;
+ }
+
+ [ToScriptString]() {
+ return "script field";
+ }
+ [ToString]() {
+ return this.script.originalScript;
+ }
+ public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Field }) {
+ const compiled = CompileScript(script, {
+ params: { this: Doc.name, self: Doc.name, _last_: "any", ...params },
+ typecheck: false,
+ editable: true,
+ addReturn: addReturn,
+ capturedVariables
+ });
+ return compiled;
+ }
+ public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }) {
+ const compiled = ScriptField.CompileScript(script, params, true, capturedVariables);
+ return compiled.compiled ? new ScriptField(compiled) : undefined;
+ }
+
+ public static MakeScript(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }) {
+ const compiled = ScriptField.CompileScript(script, params, false, capturedVariables);
+ return compiled.compiled ? new ScriptField(compiled) : undefined;
+ }
+}
+
+@scriptingGlobal
+@Deserializable("computed", deserializeScript)
+export class ComputedField extends ScriptField {
+ _lastComputedResult: any;
+ //TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc
+ value = computedFn((doc: Doc) => this._valueOutsideReaction(doc));
+ _valueOutsideReaction = (doc: Doc) => this._lastComputedResult = this.script.run({ this: doc, self: Cast(doc.rootDocument, Doc, null) || doc, _last_: this._lastComputedResult }, console.log).result;
+
+
+ constructor(script: CompiledScript, setterscript?: CompiledScript) {
+ super(script,
+ !setterscript && script?.originalScript.includes("self.timecode") ?
+ ScriptField.CompileScript("self['x' + self.timecode] = value", { value: "any" }, true) : setterscript);
+ }
+
+ public static MakeScript(script: string, params: object = {}) {
+ const compiled = ScriptField.CompileScript(script, params, false);
+ return compiled.compiled ? new ComputedField(compiled) : undefined;
+ }
+ public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }, setterScript?: string) {
+ const compiled = ScriptField.CompileScript(script, params, true, capturedVariables);
+ const setCompiled = setterScript ? ScriptField.CompileScript(setterScript, params, true, capturedVariables) : undefined;
+ return compiled.compiled ? new ComputedField(compiled, setCompiled?.compiled ? setCompiled : undefined) : undefined;
+ }
+ public static MakeInterpolated(fieldKey: string, interpolatorKey: string) {
+ const getField = ScriptField.CompileScript(`(self['${fieldKey}-indexed'])[self.${interpolatorKey}]`, {}, true, {});
+ const setField = ScriptField.CompileScript(`(self['${fieldKey}-indexed'])[self.${interpolatorKey}] = value`, { value: "any" }, true, {});
+ return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined;
+ }
+}
+
+export namespace ComputedField {
+ let useComputed = true;
+ export function DisableComputedFields() {
+ useComputed = false;
+ }
+
+ export function EnableComputedFields() {
+ useComputed = true;
+ }
+
+ export const undefined = "__undefined";
+
+ export function WithoutComputed<T>(fn: () => T) {
+ DisableComputedFields();
+ try {
+ return fn();
+ } finally {
+ EnableComputedFields();
+ }
+ }
+
+ export function initPlugin() {
+ Plugins.addGetterPlugin((doc, _, value) => {
+ if (useComputed && value instanceof ComputedField) {
+ return { value: value._valueOutsideReaction(doc), shouldReturn: true };
+ }
+ });
+ }
+} \ No newline at end of file