aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTyler Schicke <tyler_schicke@brown.edu>2019-04-09 02:45:06 -0400
committerTyler Schicke <tyler_schicke@brown.edu>2019-04-09 02:45:06 -0400
commitcf86d1b7917f0317af550293344f784a341bd7b9 (patch)
tree6a0ede46a4127812ebfecb2c9a8386a1de9fde61
parent5e086920bf97297a02bcd38faea56454c2220279 (diff)
Added script field and added list of scripts to list field
-rw-r--r--src/client/util/Scripting.ts21
-rw-r--r--src/client/views/collections/CollectionBaseView.tsx21
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx4
-rw-r--r--src/fields/Document.ts7
-rw-r--r--src/fields/ListField.ts96
-rw-r--r--src/fields/ScriptField.ts64
-rw-r--r--src/server/Message.ts3
-rw-r--r--src/server/ServerUtil.ts3
8 files changed, 187 insertions, 32 deletions
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 468484928..9015f21cf 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -30,8 +30,10 @@ export interface ScriptError {
export type ScriptResult = ScriptSucccess | ScriptError;
-export interface CompileSuccess {
- compiled: true;
+export interface CompiledScript {
+ readonly compiled: true;
+ readonly originalScript: string;
+ readonly options: Readonly<ScriptOptions>;
run(args?: { [name: string]: any }): ScriptResult;
}
@@ -40,9 +42,9 @@ export interface CompileError {
errors: any[];
}
-export type CompiledScript = CompileSuccess | CompileError;
+export type CompileResult = CompiledScript | CompileError;
-function Run(script: string | undefined, customParams: string[], diagnostics: any[]): CompiledScript {
+function Run(script: string | undefined, customParams: string[], diagnostics: any[], originalScript: string, options: ScriptOptions): CompileResult {
const errors = diagnostics.some(diag => diag.category === ts.DiagnosticCategory.Error);
if (errors || !script) {
return { compiled: false, errors: diagnostics };
@@ -68,7 +70,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
return { success: false, error };
}
};
- return { compiled: true, run };
+ return { compiled: true, run, originalScript, options };
}
interface File {
@@ -130,7 +132,7 @@ export interface ScriptOptions {
params?: { [name: string]: string };
}
-export function CompileScript(script: string, { requiredType = "", addReturn = false, params = {} }: ScriptOptions = {}): CompiledScript {
+export function CompileScript(script: string, { requiredType = "", addReturn = false, params = {} }: ScriptOptions = {}): CompileResult {
let host = new ScriptingCompilerHost;
let paramArray: string[] = [];
if ("this" in params) {
@@ -140,7 +142,10 @@ export function CompileScript(script: string, { requiredType = "", addReturn = f
if (key === "this") continue;
paramArray.push(key);
}
- let paramString = paramArray.map(key => `${key}: ${params[key]}`).join(", ");
+ let paramString = paramArray.map(key => {
+ const val = params[key];
+ return `${key}: ${val}`;
+ }).join(", ");
let funcScript = `(function(${paramString})${requiredType ? `: ${requiredType}` : ''} {
${addReturn ? `return ${script};` : script}
})`;
@@ -152,7 +157,7 @@ export function CompileScript(script: string, { requiredType = "", addReturn = f
let diagnostics = ts.getPreEmitDiagnostics(program).concat(testResult.diagnostics);
- return Run(outputText, paramArray, diagnostics);
+ return Run(outputText, paramArray, diagnostics, script, { requiredType, addReturn, params });
}
export function OrLiteralType(returnType: string): string {
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 32462a4c1..4afa7cbf6 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -10,6 +10,8 @@ import { ListField } from '../../../fields/ListField';
import { action } from 'mobx';
import { Transform } from '../../util/Transform';
import { observer } from 'mobx-react';
+import { CompileScript } from '../../util/Scripting';
+import { ScriptField } from '../../../fields/ScriptField';
export enum CollectionViewType {
Invalid,
@@ -106,7 +108,24 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
} else {
let proto = props.Document.GetPrototype();
if (!proto || proto === FieldWaiting || !this.createsCycle(proto, doc)) {
- props.Document.SetOnPrototype(props.fieldKey, new ListField([doc]));
+ const field = new ListField([doc]);
+ // const script = CompileScript(`
+ // if(added) {
+ // console.log("added " + field.Title);
+ // } else {
+ // console.log("removed " + field.Title);
+ // }
+ // `, {
+ // addReturn: false,
+ // params: {
+ // field: Document.name,
+ // added: "boolean"
+ // }
+ // });
+ // if (script.compiled) {
+ // field.addScript(new ScriptField(script));
+ // }
+ props.Document.SetOnPrototype(props.fieldKey, field);
}
else {
return false;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 52933818f..587f60b3d 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -121,14 +121,14 @@ export class CollectionSchemaView extends CollectionSubView {
return field || "";
}}
SetValue={(value: string) => {
- let script = CompileScript(value, { addReturn: true, params: { this: "Document" } });
+ let script = CompileScript(value, { addReturn: true, params: { this: Document.name } });
if (!script.compiled) {
return false;
}
return applyToDoc(props.Document, script.run);
}}
OnFillDown={(value: string) => {
- let script = CompileScript(value, { addReturn: true, params: { this: "Document" } });
+ let script = CompileScript(value, { addReturn: true, params: { this: Document.name } });
if (!script.compiled) {
return;
}
diff --git a/src/fields/Document.ts b/src/fields/Document.ts
index 4584660fb..60eaf5b51 100644
--- a/src/fields/Document.ts
+++ b/src/fields/Document.ts
@@ -16,10 +16,7 @@ import { HistogramField } from "../client/northstar/dash-fields/HistogramField";
export class Document extends Field {
//TODO tfs: We should probably store FieldWaiting in fields when we request it from the server so that we don't set up multiple server gets for the same document and field
- public fields: ObservableMap<
- string,
- { key: Key; field: Field }
- > = new ObservableMap();
+ public fields: ObservableMap<string, { key: Key; field: Field }> = new ObservableMap();
public _proxies: ObservableMap<string, FieldId> = new ObservableMap();
constructor(id?: string, save: boolean = true) {
@@ -406,7 +403,7 @@ export class Document extends Field {
}
else if (field) {
copy.Set(key!, field.Copy());
- }
+ }
});
}
});
diff --git a/src/fields/ListField.ts b/src/fields/ListField.ts
index b6eab5f86..8311e737b 100644
--- a/src/fields/ListField.ts
+++ b/src/fields/ListField.ts
@@ -5,12 +5,18 @@ import { Types } from "../server/Message";
import { BasicField } from "./BasicField";
import { Field, FieldId } from "./Field";
import { FieldMap } from "../client/SocketStub";
+import { ScriptField } from "./ScriptField";
export class ListField<T extends Field> extends BasicField<T[]> {
private _proxies: string[] = [];
- constructor(data: T[] = [], id?: FieldId, save: boolean = true) {
+ private _scriptIds: string[] = [];
+ private scripts: ScriptField[] = [];
+
+ constructor(data: T[] = [], scripts: ScriptField[] = [], id?: FieldId, save: boolean = true) {
super(data, save, id);
+ this.scripts = scripts;
this.updateProxies();
+ this._scriptIds = this.scripts.map(script => script.Id);
if (save) {
Server.UpdateField(this);
}
@@ -25,17 +31,22 @@ export class ListField<T extends Field> extends BasicField<T[]> {
this.observeDisposer();
}
this.observeDisposer = observe(this.Data as IObservableArray<T>, (change: IArrayChange<T> | IArraySplice<T>) => {
+ const target = change.object;
this.updateProxies();
if (change.type === "splice") {
+ this.runScripts(change.removed, false);
UndoManager.AddEvent({
- undo: () => this.Data.splice(change.index, change.addedCount, ...change.removed),
- redo: () => this.Data.splice(change.index, change.removedCount, ...change.added)
+ undo: () => target.splice(change.index, change.addedCount, ...change.removed),
+ redo: () => target.splice(change.index, change.removedCount, ...change.added)
});
+ this.runScripts(change.added, true);
} else {
+ this.runScripts([change.oldValue], false);
UndoManager.AddEvent({
- undo: () => this.Data[change.index] = change.oldValue,
- redo: () => this.Data[change.index] = change.newValue
+ undo: () => target[change.index] = change.oldValue,
+ redo: () => target[change.index] = change.newValue
});
+ this.runScripts([change.newValue], true);
}
if (!this._processingServerUpdate) {
Server.UpdateField(this);
@@ -43,19 +54,60 @@ export class ListField<T extends Field> extends BasicField<T[]> {
});
}
+ private runScripts(fields: T[], added: boolean) {
+ for (const script of this.scripts) {
+ this.runScript(fields, script, added);
+ }
+ }
+
+ private runScript(fields: T[], script: ScriptField, added: boolean) {
+ if (!this._processingServerUpdate) {
+ for (const field of fields) {
+ script.script.run({ field, added });
+ }
+ }
+ }
+
+ addScript(script: ScriptField) {
+ this.scripts.push(script);
+ this._scriptIds.push(script.Id);
+
+ this.runScript(this.Data, script, true);
+ UndoManager.AddEvent({
+ undo: () => this.removeScript(script),
+ redo: () => this.addScript(script),
+ });
+ Server.UpdateField(this);
+ }
+
+ removeScript(script: ScriptField) {
+ const index = this.scripts.indexOf(script);
+ if (index === -1) {
+ return;
+ }
+ this.scripts.splice(index, 1);
+ this._scriptIds.splice(index, 1);
+ UndoManager.AddEvent({
+ undo: () => this.addScript(script),
+ redo: () => this.removeScript(script),
+ });
+ this.runScript(this.Data, script, false);
+ Server.UpdateField(this);
+ }
+
protected setData(value: T[]) {
+ this.runScripts(this.data, false);
+
this.data = observable(value);
this.updateProxies();
this.observeList();
+ this.runScripts(this.data, true);
}
private updateProxies() {
this._proxies = this.Data.map(field => field.Id);
}
- UpdateFromServer(fields: string[]) {
- this._proxies = fields;
- }
private arraysEqual(a: any[], b: any[]) {
if (a === b) return true;
if (a === null || b === null) return false;
@@ -73,7 +125,7 @@ export class ListField<T extends Field> extends BasicField<T[]> {
}
init(callback: (field: Field) => any) {
- Server.GetFields(this._proxies, action((fields: FieldMap) => {
+ const fieldsPromise = Server.GetFields(this._proxies).then(action((fields: FieldMap) => {
if (!this.arraysEqual(this._proxies, this.data.map(field => field.Id))) {
var dataids = this.data.map(d => d.Id);
var proxies = this._proxies.map(p => p);
@@ -102,8 +154,13 @@ export class ListField<T extends Field> extends BasicField<T[]> {
}
this._processingServerUpdate = false;
}
- callback(this);
}));
+
+ const scriptsPromise = Server.GetFields(this._scriptIds).then((fields: FieldMap) => {
+ this.scripts = this._scriptIds.map(id => fields[id] as ScriptField);
+ });
+
+ Promise.all([fieldsPromise, scriptsPromise]).then(() => callback(this));
}
ToScriptString(): string {
@@ -114,17 +171,26 @@ export class ListField<T extends Field> extends BasicField<T[]> {
return new ListField<T>(this.Data);
}
- ToJson(): { type: Types, data: string[], _id: string } {
+
+ UpdateFromServer(data: { fields: string[], scripts: string[] }) {
+ this._proxies = data.fields;
+ this._scriptIds = data.scripts;
+ }
+ ToJson(): { type: Types, data: { fields: string[], scripts: string[] }, _id: string } {
return {
type: Types.List,
- data: this._proxies || [],
+ data: {
+ fields: this._proxies,
+ scripts: this._scriptIds,
+ },
_id: this.Id
};
}
- static FromJson(id: string, ids: string[]): ListField<Field> {
- let list = new ListField([], id, false);
- list._proxies = ids;
+ static FromJson(id: string, data: { fields: string[], scripts: string[] }): ListField<Field> {
+ let list = new ListField([], [], id, false);
+ list._proxies = data.fields;
+ list._scriptIds = data.scripts;
return list;
}
} \ No newline at end of file
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
new file mode 100644
index 000000000..24c1d9b3a
--- /dev/null
+++ b/src/fields/ScriptField.ts
@@ -0,0 +1,64 @@
+import { Field, FieldId } from "./Field";
+import { Types } from "../server/Message";
+import { CompileScript, ScriptOptions, CompiledScript } from "../client/util/Scripting";
+import { Server } from "../client/Server";
+
+export interface ScriptData {
+ script: string;
+ options: ScriptOptions;
+}
+
+export class ScriptField extends Field {
+ readonly script: CompiledScript;
+
+ constructor(script: CompiledScript, id?: FieldId, save: boolean = true) {
+ super(id);
+
+ this.script = script;
+
+ if (save) {
+ Server.UpdateField(this);
+ }
+ }
+
+ static FromJson(id: string, data: ScriptData): ScriptField {
+ const script = CompileScript(data.script, data.options);
+ if (!script.compiled) {
+ throw new Error("Can't compile script");
+ }
+ return new ScriptField(script, id, false);
+ }
+
+ ToScriptString() {
+ return "new ScriptField(...)";
+ }
+
+ GetValue() {
+ return this.script;
+ }
+
+ TrySetValue(): boolean {
+ throw new Error("Script fields currently can't be modified");
+ }
+
+ UpdateFromServer() {
+ throw new Error("Script fields currently can't be updated");
+ }
+
+ ToJson(): { _id: string, type: Types, data: ScriptData } {
+ const { options, originalScript } = this.script;
+ return {
+ _id: this.Id,
+ type: Types.Script,
+ data: {
+ script: originalScript,
+ options
+ },
+ };
+ }
+
+ Copy(): Field {
+ //Script fields are currently immutable, so we can fake copy them
+ return this;
+ }
+} \ No newline at end of file
diff --git a/src/server/Message.ts b/src/server/Message.ts
index 0274609bb..d22e5c17c 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -61,7 +61,8 @@ export enum Types {
PDF,
Tuple,
HistogramOp,
- Boolean
+ Boolean,
+ Script,
}
export class DocumentTransfer implements Transferable {
diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts
index 2c2bfd0c9..0973f82b1 100644
--- a/src/server/ServerUtil.ts
+++ b/src/server/ServerUtil.ts
@@ -18,6 +18,7 @@ import { PDFField } from "../fields/PDFField";
import { TupleField } from "../fields/TupleField";
import { BooleanField } from "../fields/BooleanField";
import { HistogramField } from "../client/northstar/dash-fields/HistogramField";
+import { ScriptField } from "../fields/ScriptField";
export class ServerUtils {
public static prepend(extension: string): string {
@@ -60,6 +61,8 @@ export class ServerUtils {
return new PDFField(new URL(data), id, false);
case Types.List:
return ListField.FromJson(id, data);
+ case Types.Script:
+ return ScriptField.FromJson(id, data);
case Types.Audio:
return new AudioField(new URL(data), id, false);
case Types.Video: