aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/DocServer.ts11
-rw-r--r--src/client/cognitive_services/CognitiveServices.ts87
-rw-r--r--src/client/util/Scripting.ts50
-rw-r--r--src/client/util/SerializationHelper.ts27
-rw-r--r--src/client/views/InkingCanvas.tsx4
-rw-r--r--src/client/views/InkingControl.tsx4
-rw-r--r--src/client/views/MainView.tsx2
-rw-r--r--src/client/views/OverlayView.scss2
-rw-r--r--src/client/views/ScriptingRepl.tsx59
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx93
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx2
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx2
-rw-r--r--src/client/views/collections/CollectionView.tsx9
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx70
-rw-r--r--src/client/views/nodes/DocumentView.tsx21
-rw-r--r--src/client/views/presentationview/PresentationView.tsx12
-rw-r--r--src/new_fields/Doc.ts23
-rw-r--r--src/new_fields/FieldSymbols.ts2
-rw-r--r--src/new_fields/InkField.ts6
-rw-r--r--src/new_fields/List.ts4
-rw-r--r--src/new_fields/Proxy.ts4
-rw-r--r--src/new_fields/RefField.ts3
-rw-r--r--src/new_fields/ScriptField.ts22
-rw-r--r--src/server/index.ts19
24 files changed, 388 insertions, 150 deletions
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 8c64d2b2f..077c8e5ba 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -126,12 +126,11 @@ export namespace DocServer {
// future .proto calls on the Doc won't have to go farther than the cache to get their actual value.
const deserializeField = getSerializedField.then(async fieldJson => {
// deserialize
- const field = SerializationHelper.Deserialize(fieldJson);
+ const field = await SerializationHelper.Deserialize(fieldJson);
// either way, overwrite or delete any promises cached at this id (that we inserted as flags
// to indicate that the field was in the process of being fetched). Now everything
// should be an actual value within or entirely absent from the cache.
if (field !== undefined) {
- await field.proto;
_cache[id] = field;
} else {
delete _cache[id];
@@ -202,18 +201,18 @@ export namespace DocServer {
// future .proto calls on the Doc won't have to go farther than the cache to get their actual value.
const deserializeFields = getSerializedFields.then(async fields => {
const fieldMap: { [id: string]: RefField } = {};
- const protosToLoad: any = [];
+ // const protosToLoad: any = [];
for (const field of fields) {
if (field !== undefined) {
// deserialize
- let deserialized: any = SerializationHelper.Deserialize(field);
+ let deserialized = await SerializationHelper.Deserialize(field);
fieldMap[field.id] = deserialized;
// adds to a list of promises that will be awaited asynchronously
- protosToLoad.push(deserialized.proto);
+ // protosToLoad.push(deserialized.proto);
}
}
// this actually handles the loading of prototypes
- await Promise.all(protosToLoad);
+ // await Promise.all(protosToLoad);
return fieldMap;
});
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index d4085cf76..a778bd47b 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -8,10 +8,12 @@ import { RouteStore } from "../../server/RouteStore";
import { Utils } from "../../Utils";
import { CompileScript } from "../util/Scripting";
import { ComputedField } from "../../new_fields/ScriptField";
+import { InkData } from "../../new_fields/InkField";
export enum Services {
ComputerVision = "vision",
- Face = "face"
+ Face = "face",
+ Handwriting = "handwriting"
}
export enum Confidence {
@@ -132,4 +134,87 @@ export namespace CognitiveServices {
}
+ export namespace Inking {
+
+ export interface AzureStrokeData {
+ id: number;
+ points: string;
+ language?: string;
+ }
+
+ export interface HandwritingUnit {
+ version: number;
+ language: string;
+ unit: string;
+ strokes: AzureStrokeData[];
+ }
+
+ export const analyze = async (inkData: InkData, target: Doc) => {
+ return fetch(Utils.prepend(`${RouteStore.cognitiveServices}/${Services.Handwriting}`)).then(async response => {
+ let apiKey = await response.text();
+ if (!apiKey) {
+ return undefined;
+ }
+
+ let xhttp = new XMLHttpRequest();
+ let serverAddress = "https://api.cognitive.microsoft.com";
+ let endpoint = serverAddress + "/inkrecognizer/v1.0-preview/recognize";
+
+ let requestExecutor = (resolve: any, reject: any) => {
+ let result: any;
+ let body = format(inkData);
+
+ xhttp.onreadystatechange = function () {
+ if (this.readyState === 4) {
+ try {
+ result = JSON.parse(xhttp.responseText);
+ } catch (e) {
+ return reject(e);
+ }
+ switch (this.status) {
+ case 200:
+ return resolve(result);
+ case 400:
+ default:
+ return reject(result);
+ }
+ }
+ };
+
+ xhttp.open("PUT", endpoint, true);
+ xhttp.setRequestHeader('Ocp-Apim-Subscription-Key', apiKey);
+ xhttp.setRequestHeader('Content-Type', 'application/json');
+ xhttp.send(body);
+ };
+
+ let results = (await new Promise<any>(requestExecutor)).recognitionUnits;
+
+ target.inkAnalysis = Docs.Get.DocumentHierarchyFromJson(results, "Ink Analysis");
+ let recognizedText = results.map((item: any) => item.recognizedText);
+ let individualWords = recognizedText.filter((text: string) => text && text.split(" ").length === 1);
+ target.handwriting = individualWords.join(" ");
+ });
+ };
+
+ const format = (inkData: InkData): string => {
+ let entries = inkData.entries(), next = entries.next();
+ let strokes: AzureStrokeData[] = [], id = 0;
+ while (!next.done) {
+ strokes.push({
+ id: id++,
+ points: next.value[1].pathData.map(point => `${point.x},${point.y}`).join(","),
+ language: "en-US"
+ });
+ next = entries.next();
+ }
+ return JSON.stringify({
+ version: 1,
+ language: "en-US",
+ unit: "mm",
+ strokes: strokes
+ });
+ };
+
+ }
+
} \ No newline at end of file
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 46dc320b0..1d0916ac0 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -52,10 +52,10 @@ export namespace Scripting {
} else {
throw new Error("Must either register an object with a name, or give a name and an object");
}
- if (scriptingGlobals.hasOwnProperty(n)) {
+ if (_scriptingGlobals.hasOwnProperty(n)) {
throw new Error(`Global with name ${n} is already registered, choose another name`);
}
- scriptingGlobals[n] = obj;
+ _scriptingGlobals[n] = obj;
}
export function makeMutableGlobalsCopy(globals?: { [name: string]: any }) {
@@ -188,6 +188,10 @@ class ScriptingCompilerHost {
export type Traverser = (node: ts.Node, indentation: string) => boolean | void;
export type TraverserParam = Traverser | { onEnter: Traverser, onLeave: Traverser };
+export type Transformer = {
+ transformer: ts.TransformerFactory<ts.SourceFile>,
+ getVars?: () => { capturedVariables: { [name: string]: Field } }
+};
export interface ScriptOptions {
requiredType?: string;
addReturn?: boolean;
@@ -196,7 +200,7 @@ export interface ScriptOptions {
typecheck?: boolean;
editable?: boolean;
traverser?: TraverserParam;
- transformer?: ts.TransformerFactory<ts.SourceFile>;
+ transformer?: Transformer;
globals?: { [name: string]: any };
}
@@ -213,6 +217,27 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
Scripting.setScriptingGlobals(options.globals);
}
let host = new ScriptingCompilerHost;
+ if (options.traverser) {
+ const sourceFile = ts.createSourceFile('script.ts', script, ts.ScriptTarget.ES2015, true);
+ const onEnter = typeof options.traverser === "object" ? options.traverser.onEnter : options.traverser;
+ const onLeave = typeof options.traverser === "object" ? options.traverser.onLeave : undefined;
+ forEachNode(sourceFile, onEnter, onLeave);
+ }
+ if (options.transformer) {
+ const sourceFile = ts.createSourceFile('script.ts', script, ts.ScriptTarget.ES2015, true);
+ const result = ts.transform(sourceFile, [options.transformer.transformer]);
+ if (options.transformer.getVars) {
+ const newCaptures = options.transformer.getVars();
+ // tslint:disable-next-line: prefer-object-spread
+ options.capturedVariables = Object.assign(capturedVariables, newCaptures.capturedVariables) as any;
+ }
+ const transformed = result.transformed;
+ const printer = ts.createPrinter({
+ newLine: ts.NewLineKind.LineFeed
+ });
+ script = printer.printFile(transformed[0]);
+ result.dispose();
+ }
let paramNames: string[] = [];
if ("this" in params || "this" in capturedVariables) {
paramNames.push("this");
@@ -227,26 +252,11 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
});
for (const key in capturedVariables) {
if (key === "this") continue;
+ const val = capturedVariables[key];
paramNames.push(key);
- paramList.push(`${key}: ${capturedVariables[key].constructor.name}`);
+ paramList.push(`${key}: ${typeof val === "object" ? Object.getPrototypeOf(val).constructor.name : typeof val}`);
}
let paramString = paramList.join(", ");
- if (options.traverser) {
- const sourceFile = ts.createSourceFile('script.ts', script, ts.ScriptTarget.ES2015, true);
- const onEnter = typeof options.traverser === "object" ? options.traverser.onEnter : options.traverser;
- const onLeave = typeof options.traverser === "object" ? options.traverser.onLeave : undefined;
- forEachNode(sourceFile, onEnter, onLeave);
- }
- if (options.transformer) {
- const sourceFile = ts.createSourceFile('script.ts', script, ts.ScriptTarget.ES2015, true);
- const result = ts.transform(sourceFile, [options.transformer]);
- const transformed = result.transformed;
- const printer = ts.createPrinter({
- newLine: ts.NewLineKind.LineFeed
- });
- script = printer.printFile(transformed[0]);
- result.dispose();
- }
let funcScript = `(function(${paramString})${requiredType ? `: ${requiredType}` : ''} {
${addReturn ? `return ${script};` : script}
})`;
diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts
index dca539f3b..94b640afa 100644
--- a/src/client/util/SerializationHelper.ts
+++ b/src/client/util/SerializationHelper.ts
@@ -1,9 +1,14 @@
import { PropSchema, serialize, deserialize, custom, setDefaultModelSchema, getDefaultModelSchema, primitive, SKIP } from "serializr";
-import { Field } from "../../new_fields/Doc";
+import { Field, Doc } from "../../new_fields/Doc";
import { ClientUtils } from "./ClientUtils";
+let serializing = 0;
+export function afterDocDeserialize(cb: (err: any, val: any) => void, err: any, newValue: any) {
+ serializing++;
+ cb(err, newValue);
+ serializing--;
+}
export namespace SerializationHelper {
- let serializing: number = 0;
export function IsSerializing() {
return serializing > 0;
}
@@ -17,18 +22,18 @@ export namespace SerializationHelper {
return obj;
}
- serializing += 1;
+ serializing++;
if (!(obj.constructor.name in reverseMap)) {
throw Error(`type '${obj.constructor.name}' not registered. Make sure you register it using a @Deserializable decorator`);
}
const json = serialize(obj);
json.__type = reverseMap[obj.constructor.name];
- serializing -= 1;
+ serializing--;
return json;
}
- export function Deserialize(obj: any): any {
+ export async function Deserialize(obj: any): Promise<any> {
if (obj === undefined || obj === null) {
return undefined;
}
@@ -37,7 +42,6 @@ export namespace SerializationHelper {
return obj;
}
- serializing += 1;
if (!obj.__type) {
if (ClientUtils.RELEASE) {
console.warn("No property 'type' found in JSON.");
@@ -52,16 +56,15 @@ export namespace SerializationHelper {
}
const type = serializationTypes[obj.__type];
- const value = deserialize(type.ctor, obj);
+ const value = await new Promise(res => deserialize(type.ctor, obj, (err, result) => res(result)));
if (type.afterDeserialize) {
- type.afterDeserialize(value);
+ await type.afterDeserialize(value);
}
- serializing -= 1;
return value;
}
}
-let serializationTypes: { [name: string]: { ctor: { new(): any }, afterDeserialize?: (obj: any) => void } } = {};
+let serializationTypes: { [name: string]: { ctor: { new(): any }, afterDeserialize?: (obj: any) => void | Promise<any> } } = {};
let reverseMap: { [ctor: string]: string } = {};
export interface DeserializableOpts {
@@ -69,7 +72,7 @@ export interface DeserializableOpts {
withFields(fields: string[]): Function;
}
-export function Deserializable(name: string, afterDeserialize?: (obj: any) => void): DeserializableOpts;
+export function Deserializable(name: string, afterDeserialize?: (obj: any) => void | Promise<any>): DeserializableOpts;
export function Deserializable(constructor: { new(...args: any[]): any }): 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 }) {
@@ -135,6 +138,6 @@ export namespace Deserializable {
export function autoObject(): PropSchema {
return custom(
(s) => SerializationHelper.Serialize(s),
- (s) => SerializationHelper.Deserialize(s)
+ (json: any, context: any, oldValue: any, cb: (err: any, result: any) => void) => SerializationHelper.Deserialize(json).then(res => cb(null, res))
);
} \ No newline at end of file
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 3e0d7b476..e48b45b56 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -10,6 +10,8 @@ import { undoBatch, UndoManager } from "../util/UndoManager";
import { StrokeData, InkField, InkTool } from "../../new_fields/InkField";
import { Doc } from "../../new_fields/Doc";
import { Cast, PromiseValue, NumCast } from "../../new_fields/Types";
+import { CognitiveServices } from "../cognitive_services/CognitiveServices";
+import { ContextMenu } from "./ContextMenu";
interface InkCanvasProps {
getScreenTransform: () => Transform;
@@ -178,7 +180,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
render() {
let svgCanvasStyle = InkingControl.Instance.selectedTool !== InkTool.None ? "canSelect" : "noSelect";
return (
- <div className="inkingCanvas" >
+ <div className="inkingCanvas">
<div className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} />
{this.props.children()}
{this.drawnPaths}
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index c7f7bdb66..1910e409b 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -1,5 +1,5 @@
import { observable, action, computed, runInAction } from "mobx";
-import { ColorResult } from 'react-color';
+import { ColorState } from 'react-color';
import React = require("react");
import { observer } from "mobx-react";
import "./InkingControl.scss";
@@ -41,7 +41,7 @@ export class InkingControl extends React.Component {
}
@undoBatch
- switchColor = action((color: ColorResult): void => {
+ switchColor = action((color: ColorState): void => {
this._selectedColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff");
if (InkingControl.Instance.selectedTool === InkTool.None) {
if (MainOverlayTextBox.Instance.SetColor(color.hex)) return;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 94a4835a1..53be0278e 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -394,6 +394,7 @@ export class MainView extends React.Component {
<div id="add-options-content">
<ul id="add-options-list">
<li key="search"><button className="add-button round-button" title="Search" onClick={this.toggleSearch}><FontAwesomeIcon icon="search" size="sm" /></button></li>
+ <li key="presentation"><button className="add-button round-button" title="Open Presentation View" onClick={() => PresentationView.Instance.toggle(undefined)}><FontAwesomeIcon icon="table" size="sm" /></button></li>
<li key="undo"><button className="add-button round-button" title="Undo" style={{ opacity: UndoManager.CanUndo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button></li>
<li key="redo"><button className="add-button round-button" title="Redo" style={{ opacity: UndoManager.CanRedo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button></li>
{btns.map(btn =>
@@ -444,7 +445,6 @@ export class MainView extends React.Component {
this.isSearchVisible = !this.isSearchVisible;
}
-
render() {
return (
<div id="main-div">
diff --git a/src/client/views/OverlayView.scss b/src/client/views/OverlayView.scss
index 4d1e8cf0b..dc122497f 100644
--- a/src/client/views/OverlayView.scss
+++ b/src/client/views/OverlayView.scss
@@ -32,7 +32,7 @@
}
.overlayWindow-resizeDragger {
- background-color: red;
+ background-color: rgb(0, 0, 0);
position: absolute;
right: 0px;
bottom: 0px;
diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index 6eabc7b70..52273c357 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -2,7 +2,7 @@ import * as React from 'react';
import { observer } from 'mobx-react';
import { observable, action } from 'mobx';
import './ScriptingRepl.scss';
-import { Scripting, CompileScript, ts } from '../util/Scripting';
+import { Scripting, CompileScript, ts, Transformer } from '../util/Scripting';
import { DocumentManager } from '../util/DocumentManager';
import { DocumentView } from './nodes/DocumentView';
import { OverlayView } from './OverlayView';
@@ -17,8 +17,11 @@ library.add(faCaretRight);
export class DocumentIcon extends React.Component<{ view: DocumentView, index: number }> {
render() {
this.props.view.props.ScreenToLocalTransform();
- this.props.view.props.Document.width;
- this.props.view.props.Document.height;
+ const doc = this.props.view.props.Document;
+ doc.width;
+ doc.height;
+ doc.x;
+ doc.y;
const screenCoords = this.props.view.screenRect();
return (
@@ -26,7 +29,7 @@ export class DocumentIcon extends React.Component<{ view: DocumentView, index: n
position: "absolute",
transform: `translate(${screenCoords.left + screenCoords.width / 2}px, ${screenCoords.top}px)`,
}}>
- <p >${this.props.index}</p>
+ <p>${this.props.index}</p>
</div>
);
}
@@ -106,31 +109,35 @@ export class ScriptingRepl extends React.Component {
private args: any = {};
- getTransformer: ts.TransformerFactory<ts.SourceFile> = context => {
- const knownVars: { [name: string]: number } = {};
- const usedDocuments: number[] = [];
- Scripting.getGlobals().forEach(global => knownVars[global] = 1);
- return root => {
- function visit(node: ts.Node) {
- node = ts.visitEachChild(node, visit, context);
+ getTransformer = (): Transformer => {
+ return {
+ transformer: context => {
+ const knownVars: { [name: string]: number } = {};
+ const usedDocuments: number[] = [];
+ Scripting.getGlobals().forEach(global => knownVars[global] = 1);
+ return root => {
+ function visit(node: ts.Node) {
+ node = ts.visitEachChild(node, visit, context);
- if (ts.isIdentifier(node)) {
- const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
- const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
- if (isntPropAccess && isntPropAssign && !(node.text in knownVars) && !(node.text in globalThis)) {
- const match = node.text.match(/\$([0-9]+)/);
- if (match) {
- const m = parseInt(match[1]);
- usedDocuments.push(m);
- } else {
- return ts.createPropertyAccess(ts.createIdentifier("args"), node);
+ if (ts.isIdentifier(node)) {
+ const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
+ const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
+ if (isntPropAccess && isntPropAssign && !(node.text in knownVars) && !(node.text in globalThis)) {
+ const match = node.text.match(/\$([0-9]+)/);
+ if (match) {
+ const m = parseInt(match[1]);
+ usedDocuments.push(m);
+ } else {
+ return ts.createPropertyAccess(ts.createIdentifier("args"), node);
+ }
+ }
}
- }
- }
- return node;
+ return node;
+ }
+ return ts.visitNode(root, visit);
+ };
}
- return ts.visitNode(root, visit);
};
}
@@ -142,7 +149,7 @@ export class ScriptingRepl extends React.Component {
const docGlobals: { [name: string]: any } = {};
DocumentManager.Instance.DocumentViews.forEach((dv, i) => docGlobals[`$${i}`] = dv.props.Document);
const globals = Scripting.makeMutableGlobalsCopy(docGlobals);
- const script = CompileScript(this.commandString, { typecheck: false, addReturn: true, editable: true, params: { args: "any" }, transformer: this.getTransformer, globals });
+ const script = CompileScript(this.commandString, { typecheck: false, addReturn: true, editable: true, params: { args: "any" }, transformer: this.getTransformer(), globals });
if (!script.compiled) {
return;
}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 2cf50e551..119aa7c19 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -15,7 +15,7 @@ import { Cast, FieldValue, NumCast, StrCast, BoolCast } from "../../../new_field
import { Docs } from "../../documents/Documents";
import { Gateway } from "../../northstar/manager/Gateway";
import { SetupDrag, DragManager } from "../../util/DragManager";
-import { CompileScript } from "../../util/Scripting";
+import { CompileScript, ts, Transformer } from "../../util/Scripting";
import { Transform } from "../../util/Transform";
import { COLLECTION_BORDER_WIDTH, MAX_ROW_HEIGHT } from '../../views/globalCssVariables.scss';
import { ContextMenu } from "../ContextMenu";
@@ -30,8 +30,6 @@ import { CollectionSubView } from "./CollectionSubView";
import { CollectionVideoView } from "./CollectionVideoView";
import { CollectionView } from "./CollectionView";
import { undoBatch } from "../../util/UndoManager";
-import { timesSeries } from "async";
-import { ImageBox } from "../nodes/ImageBox";
import { ComputedField } from "../../../new_fields/ScriptField";
@@ -99,6 +97,78 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
return this.props.Document;
}
+ getField(row: number, col?: number) {
+ const docs = DocListCast(this.props.Document[this.props.fieldKey]);
+ row = row % docs.length;
+ while (row < 0) row += docs.length;
+ const columns = this.columns;
+ const doc = docs[row];
+ if (col === undefined) {
+ return doc;
+ }
+ if (col >= 0 && col < columns.length) {
+ const column = this.columns[col];
+ return doc[column];
+ }
+ return undefined;
+ }
+
+ createTransformer = (row: number, col: number): Transformer => {
+ const self = this;
+ const captures: { [name: string]: Field } = {};
+
+ const transformer: ts.TransformerFactory<ts.SourceFile> = context => {
+ return root => {
+ function visit(node: ts.Node) {
+ node = ts.visitEachChild(node, visit, context);
+ if (ts.isIdentifier(node)) {
+ const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
+ const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
+ if (isntPropAccess && isntPropAssign) {
+ if (node.text === "$r") {
+ return ts.createNumericLiteral(row.toString());
+ } else if (node.text === "$c") {
+ return ts.createNumericLiteral(col.toString());
+ } else if (node.text === "$") {
+ if (ts.isCallExpression(node.parent)) {
+ captures.doc = self.props.Document;
+ captures.key = self.props.fieldKey;
+ }
+ }
+ }
+ }
+
+ return node;
+ }
+ return ts.visitNode(root, visit);
+ };
+ };
+
+ const getVars = () => {
+ return { capturedVariables: captures };
+ };
+
+ return { transformer, getVars };
+ }
+
+ setComputed(script: string, doc: Doc, field: string, row: number, col: number): boolean {
+ script =
+ `const $ = (row:number, col?:number) => {
+ if(col === undefined) {
+ return (doc as any)[key][row + ${row}];
+ }
+ return (doc as any)[key][row + ${row}][(doc as any).schemaColumns[col + ${col}]];
+ }
+ return ${script}`;
+ const compiled = CompileScript(script, { params: { this: Doc.name }, typecheck: true, transformer: this.createTransformer(row, col) });
+ if (compiled.compiled) {
+ doc[field] = new ComputedField(compiled);
+ return true;
+ }
+
+ return false;
+ }
+
renderCell = (rowProps: CellInfo) => {
let props: FieldViewProps = {
Document: rowProps.original,
@@ -124,12 +194,13 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
(!this.props.CollectionView.props.isSelected() ? undefined :
SetupDrag(reference, () => props.Document, this.props.moveDocument, this.props.Document.schemaDoc ? "copy" : undefined)(e));
};
- let applyToDoc = (doc: Doc, run: (args?: { [name: string]: any }) => any) => {
- const res = run({ this: doc });
+ let applyToDoc = (doc: Doc, row: number, column: number, run: (args?: { [name: string]: any }) => any) => {
+ const res = run({ this: doc, $r: row, $c: column, $: (r: number = 0, c: number = 0) => this.getField(r + row, c + column) });
if (!res.success) return false;
doc[props.fieldKey] = res.result;
return true;
};
+ const colIndex = this.columns.indexOf(rowProps.column.id!);
return (
<div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} key={props.Document[Id]} ref={reference}>
<EditableView
@@ -144,21 +215,23 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
return "";
}}
SetValue={(value: string) => {
- let script = CompileScript(value, { addReturn: true, params: { this: Doc.name } });
+ if (value.startsWith(":=")) {
+ return this.setComputed(value.substring(2), props.Document, rowProps.column.id!, rowProps.index, colIndex);
+ }
+ let script = CompileScript(value, { addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
if (!script.compiled) {
return false;
}
- return applyToDoc(props.Document, script.run);
+ return applyToDoc(props.Document, rowProps.index, colIndex, script.run);
}}
OnFillDown={async (value: string) => {
- let script = CompileScript(value, { addReturn: true, params: { this: Doc.name } });
+ let script = CompileScript(value, { addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
if (!script.compiled) {
return;
}
const run = script.run;
- //TODO This should be able to be refactored to compile the script once
const val = await DocListCastAsync(this.props.Document[this.props.fieldKey]);
- val && val.forEach(doc => applyToDoc(doc, run));
+ val && val.forEach((doc, i) => applyToDoc(doc, i, colIndex, run));
}}>
</EditableView>
</div >
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 54b0e37b5..213aa981d 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -251,7 +251,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
["width = height", this.filteredChildren.filter(f => Math.abs(f[WidthSym]() - f[HeightSym]()) < 1)],
["height > width", this.filteredChildren.filter(f => f[WidthSym]() + 1 <= f[HeightSym]())]]. */}
{this.props.Document.sectionFilter ? Array.from(this.Sections.entries()).
- map(section => this.section(section[0].toString(), section[1] as Doc[])) :
+ map(section => this.section(section[0].toString(), section[1])) :
this.section("", this.filteredChildren)}
</div>
);
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index fe636942c..006de0c70 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -356,7 +356,7 @@ class TreeView extends React.Component<TreeViewProps> {
})());
}
- noOverlays = (doc: Doc) => { return { title: "", caption: "" } };
+ noOverlays = (doc: Doc) => ({ title: "", caption: "" });
render() {
let contentElement: (JSX.Element | null) = null;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 4a51a1f58..7781b26d9 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,5 +1,5 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faProjectDiagram, faSignature, faColumns, faSquare, faTh, faImage, faThList, faTree, faEllipsisV } from '@fortawesome/free-solid-svg-icons';
+import { faProjectDiagram, faSignature, faColumns, faSquare, faTh, faImage, faThList, faTree, faEllipsisV, faFingerprint, faLaptopCode } from '@fortawesome/free-solid-svg-icons';
import { observer } from "mobx-react";
import * as React from 'react';
import { Doc, DocListCast, WidthSym, HeightSym } from '../../../new_fields/Doc';
@@ -25,6 +25,7 @@ library.add(faSquare);
library.add(faProjectDiagram);
library.add(faSignature);
library.add(faThList);
+library.add(faFingerprint);
library.add(faColumns);
library.add(faEllipsisV);
library.add(faImage);
@@ -61,6 +62,12 @@ export class CollectionView extends React.Component<FieldViewProps> {
subItems.push({ description: "Treeview", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Tree), icon: "tree" });
subItems.push({ description: "Stacking", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Stacking), icon: "ellipsis-v" });
subItems.push({ description: "Masonry", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Masonry), icon: "columns" });
+ switch (this.props.Document.viewType) {
+ case CollectionViewType.Freeform: {
+ subItems.push({ description: "Custom", icon: "fingerprint", event: CollectionFreeFormView.AddCustomLayout(this.props.Document, this.props.fieldKey) });
+ break;
+ }
+ }
ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems });
ContextMenu.Instance.addItem({ description: "Apply Template", event: undoBatch(() => this.props.addDocTab && this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight")), icon: "project-diagram" });
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index e7d2a66e5..afa021655 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -31,7 +31,7 @@ import { ScriptField } from "../../../../new_fields/ScriptField";
import { OverlayView, OverlayElementOptions } from "../../OverlayView";
import { ScriptBox } from "../../ScriptBox";
import { CompileScript } from "../../../util/Scripting";
-import { resolve } from "bluebird";
+import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
export const panZoomSchema = createSchema({
@@ -52,6 +52,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
private _lastY: number = 0;
private get _pwidth() { return this.props.PanelWidth(); }
private get _pheight() { return this.props.PanelHeight(); }
+ private inkKey = "ink";
@computed get contentBounds() {
let bounds = this.props.fitToBox && !NumCast(this.nativeWidth) ? Doc.ComputeContentBounds(DocListCast(this.props.Document.data)) : undefined;
@@ -484,42 +485,53 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
});
ContextMenu.Instance.addItem({
- description: "Add freeform arrangement",
- event: () => {
- let addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
- let overlayDisposer: () => void = emptyFunction;
- const script = this.Document[key];
- let originalText: string | undefined = undefined;
- if (script) originalText = script.script.originalScript;
- // tslint:disable-next-line: no-unnecessary-callback-wrapper
- let scriptingBox = <ScriptBox initialText={originalText} onCancel={() => overlayDisposer()} onSave={(text, onError) => {
- const script = CompileScript(text, {
- params,
- requiredType,
- typecheck: false
- });
- if (!script.compiled) {
- onError(script.errors.map(error => error.messageText).join("\n"));
- return;
- }
- const docs = DocListCast(this.Document[this.props.fieldKey]);
- docs.map(d => d.transition = "transform 1s");
- this.Document[key] = new ScriptField(script);
- overlayDisposer();
- setTimeout(() => docs.map(d => d.transition = undefined), 1200);
- }} />;
- overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, options);
- };
- addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300, title: "Layout Initialization" }, { collection: "Doc", docs: "Doc[]" }, undefined);
- addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300, title: "Layout Script" }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
+ description: "Analyze Strokes", event: async () => {
+ let data = Cast(this.fieldExtensionDoc[this.inkKey], InkField);
+ if (!data) {
+ return;
+ }
+ CognitiveServices.Inking.analyze(data.inkData, Doc.GetProto(this.props.Document));
}
});
}
+
private childViews = () => [
<CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
...this.views
]
+
+ public static AddCustomLayout(doc: Doc, dataKey: string): () => void {
+ return () => {
+ let addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
+ let overlayDisposer: () => void = emptyFunction;
+ const script = Cast(doc[key], ScriptField);
+ let originalText: string | undefined = undefined;
+ if (script) originalText = script.script.originalScript;
+ // tslint:disable-next-line: no-unnecessary-callback-wrapper
+ let scriptingBox = <ScriptBox initialText={originalText} onCancel={() => overlayDisposer()} onSave={(text, onError) => {
+ const script = CompileScript(text, {
+ params,
+ requiredType,
+ typecheck: false
+ });
+ if (!script.compiled) {
+ onError(script.errors.map(error => error.messageText).join("\n"));
+ return;
+ }
+ const docs = DocListCast(doc[dataKey]);
+ docs.map(d => d.transition = "transform 1s");
+ doc[key] = new ScriptField(script);
+ overlayDisposer();
+ setTimeout(() => docs.map(d => d.transition = undefined), 1200);
+ }} />;
+ overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, options);
+ };
+ addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300, title: "Layout Initialization" }, { collection: "Doc", docs: "Doc[]" }, undefined);
+ addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300, title: "Layout Script" }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
+ };
+ }
+
render() {
const easing = () => this.props.Document.panTransformType === "Ease";
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 8d7b3d835..35a4613af 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -37,6 +37,7 @@ import { RouteStore } from '../../../server/RouteStore';
import { FormattedTextBox } from './FormattedTextBox';
import { OverlayView } from '../OverlayView';
import { ScriptingRepl } from '../ScriptingRepl';
+import { ClientUtils } from '../../util/ClientUtils';
import { EditableView } from '../EditableView';
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
@@ -59,6 +60,7 @@ library.add(fa.faCrosshairs);
library.add(fa.faDesktop);
library.add(fa.faUnlock);
library.add(fa.faLock);
+library.add(fa.faLaptopCode);
// const linkSchema = createSchema({
@@ -554,20 +556,23 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this.props.removeDocument && this.props.removeDocument(this.props.Document);
}, icon: "window-restore"
});
- cm.addItem({
- description: "Find aliases", event: async () => {
- const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
- this.props.addDocTab && this.props.addDocTab(Docs.Create.SchemaDocument(["title"], aliases, {}), undefined, "onRight"); // bcz: dataDoc?
- }, icon: "search"
- });
+ // cm.addItem({
+ // description: "Find aliases", event: async () => {
+ // const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
+ // this.props.addDocTab && this.props.addDocTab(Docs.Create.SchemaDocument(["title"], aliases, {}), undefined, "onRight"); // bcz: dataDoc?
+ // }, icon: "search"
+ // });
if (this.props.Document.detailedLayout && !this.props.Document.isTemplate) {
cm.addItem({ description: "Toggle detail", event: () => Doc.ToggleDetailLayout(this.props.Document), icon: "image" });
}
+ cm.addItem({ description: "Add Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
cm.addItem({ description: "Add Repl", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
cm.addItem({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" });
- cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "link" });
- cm.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
+ if (!ClientUtils.RELEASE) {
+ cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "link" });
+ cm.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
+ }
cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" });
type User = { email: string, userDocumentId: string };
let usersMenu: ContextMenuProps[] = [];
diff --git a/src/client/views/presentationview/PresentationView.tsx b/src/client/views/presentationview/PresentationView.tsx
index f80840f96..966ade3de 100644
--- a/src/client/views/presentationview/PresentationView.tsx
+++ b/src/client/views/presentationview/PresentationView.tsx
@@ -31,6 +31,8 @@ export interface PresViewProps {
Documents: List<Doc>;
}
+const expandedWidth = 400;
+
@observer
export class PresentationView extends React.Component<PresViewProps> {
public static Instance: PresentationView;
@@ -67,6 +69,14 @@ export class PresentationView extends React.Component<PresViewProps> {
PresentationView.Instance = this;
}
+ toggle = (forcedValue: boolean | undefined) => {
+ if (forcedValue !== undefined) {
+ this.curPresentation.width = forcedValue ? expandedWidth : 0;
+ } else {
+ this.curPresentation.width = this.curPresentation.width === expandedWidth ? 0 : expandedWidth;
+ }
+ }
+
//The first lifecycle function that gets called to set up the current presentation.
async componentWillMount() {
this.props.Documents.forEach(async (doc, index: number) => {
@@ -543,7 +553,7 @@ export class PresentationView extends React.Component<PresViewProps> {
this.curPresentation.data = new List([doc]);
}
- this.curPresentation.width = 400;
+ this.toggle(true);
}
//Function that sets the store of the children docs.
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index bf6cd2fa6..d3d7d584e 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -1,6 +1,6 @@
import { observable, action } from "mobx";
-import { serializable, primitive, map, alias, list } from "serializr";
-import { autoObject, SerializationHelper, Deserializable } from "../client/util/SerializationHelper";
+import { serializable, primitive, map, alias, list, PropSchema, custom } from "serializr";
+import { autoObject, SerializationHelper, Deserializable, afterDocDeserialize } from "../client/util/SerializationHelper";
import { DocServer } from "../client/DocServer";
import { setter, getter, getField, updateFunction, deleteProperty, makeEditable, makeReadOnly } from "./util";
import { Cast, ToConstructor, PromiseValue, FieldValue, NumCast, BoolCast, StrCast } from "./Types";
@@ -68,8 +68,15 @@ export function DocListCast(field: FieldResult): Doc[] {
export const WidthSym = Symbol("Width");
export const HeightSym = Symbol("Height");
+function fetchProto(doc: Doc) {
+ const proto = doc.proto;
+ if (proto instanceof Promise) {
+ return proto;
+ }
+}
+
@scriptingGlobal
-@Deserializable("doc").withFields(["id"])
+@Deserializable("doc", fetchProto).withFields(["id"])
export class Doc extends RefField {
constructor(id?: FieldId, forceSave?: boolean) {
super(id);
@@ -102,7 +109,7 @@ export class Doc extends RefField {
proto: Opt<Doc>;
[key: string]: FieldResult;
- @serializable(alias("fields", map(autoObject())))
+ @serializable(alias("fields", map(autoObject(), { afterDeserialize: afterDocDeserialize })))
private get __fields() {
return this.___fields;
}
@@ -134,14 +141,14 @@ export class Doc extends RefField {
return "invalid";
}
- public [HandleUpdate](diff: any) {
+ public async [HandleUpdate](diff: any) {
const set = diff.$set;
if (set) {
for (const key in set) {
if (!key.startsWith("fields.")) {
continue;
}
- const value = SerializationHelper.Deserialize(set[key]);
+ const value = await SerializationHelper.Deserialize(set[key]);
const fKey = key.substring(7);
this[fKey] = value;
}
@@ -328,8 +335,8 @@ export namespace Doc {
(proto ? proto : doc)[fieldKey + "_ext"] = docExtensionForField;
}, 0);
} else if (doc instanceof Doc) { // backward compatibility -- add fields for docs that don't have them already
- docExtensionForField.extendsDoc === undefined && setTimeout(() => (docExtensionForField as Doc).extendsDoc = doc, 0);
- docExtensionForField.type === undefined && setTimeout(() => (docExtensionForField as Doc).type = DocumentType.EXTENSION, 0);
+ docExtensionForField.extendsDoc === undefined && setTimeout(() => docExtensionForField.extendsDoc = doc, 0);
+ docExtensionForField.type === undefined && setTimeout(() => docExtensionForField.type = DocumentType.EXTENSION, 0);
}
}
export function MakeAlias(doc: Doc) {
diff --git a/src/new_fields/FieldSymbols.ts b/src/new_fields/FieldSymbols.ts
index a436dcf2b..b5b3aa588 100644
--- a/src/new_fields/FieldSymbols.ts
+++ b/src/new_fields/FieldSymbols.ts
@@ -7,4 +7,4 @@ export const Id = Symbol("Id");
export const OnUpdate = Symbol("OnUpdate");
export const Parent = Symbol("Parent");
export const Copy = Symbol("Copy");
-export const ToScriptString = Symbol("Copy"); \ No newline at end of file
+export const ToScriptString = Symbol("ToScriptString"); \ No newline at end of file
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
index 39c6c8ce3..8f64c1c2e 100644
--- a/src/new_fields/InkField.ts
+++ b/src/new_fields/InkField.ts
@@ -19,6 +19,8 @@ export interface StrokeData {
page: number;
}
+export type InkData = Map<string, StrokeData>;
+
const pointSchema = createSimpleSchema({
x: true, y: true
});
@@ -31,9 +33,9 @@ const strokeDataSchema = createSimpleSchema({
@Deserializable("ink")
export class InkField extends ObjectField {
@serializable(map(object(strokeDataSchema)))
- readonly inkData: Map<string, StrokeData>;
+ readonly inkData: InkData;
- constructor(data?: Map<string, StrokeData>) {
+ constructor(data?: InkData) {
super();
this.inkData = data || new Map;
}
diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts
index a2133a990..0c7b77fa5 100644
--- a/src/new_fields/List.ts
+++ b/src/new_fields/List.ts
@@ -1,4 +1,4 @@
-import { Deserializable, autoObject } from "../client/util/SerializationHelper";
+import { Deserializable, autoObject, afterDocDeserialize } from "../client/util/SerializationHelper";
import { Field } from "./Doc";
import { setter, getter, deleteProperty, updateFunction } from "./util";
import { serializable, alias, list } from "serializr";
@@ -254,7 +254,7 @@ class ListImpl<T extends Field> extends ObjectField {
[key: number]: T | (T extends RefField ? Promise<T> : never);
- @serializable(alias("fields", list(autoObject())))
+ @serializable(alias("fields", list(autoObject(), { afterDeserialize: afterDocDeserialize })))
private get __fields() {
return this.___fields;
}
diff --git a/src/new_fields/Proxy.ts b/src/new_fields/Proxy.ts
index 38d874a68..14f08814e 100644
--- a/src/new_fields/Proxy.ts
+++ b/src/new_fields/Proxy.ts
@@ -48,7 +48,7 @@ export class ProxyField<T extends RefField> extends ObjectField {
private failed = false;
private promise?: Promise<any>;
- value(): T | undefined | FieldWaiting {
+ value(): T | undefined | FieldWaiting<T> {
if (this.cache) {
return this.cache;
}
@@ -63,6 +63,6 @@ export class ProxyField<T extends RefField> extends ObjectField {
return field;
}));
}
- return this.promise;
+ return this.promise as any;
}
}
diff --git a/src/new_fields/RefField.ts b/src/new_fields/RefField.ts
index 75ce4287f..5414df2b9 100644
--- a/src/new_fields/RefField.ts
+++ b/src/new_fields/RefField.ts
@@ -1,10 +1,11 @@
import { serializable, primitive, alias } from "serializr";
import { Utils } from "../Utils";
import { Id, HandleUpdate, ToScriptString } from "./FieldSymbols";
+import { afterDocDeserialize } from "../client/util/SerializationHelper";
export type FieldId = string;
export abstract class RefField {
- @serializable(alias("id", primitive()))
+ @serializable(alias("id", primitive({ afterDeserialize: afterDocDeserialize })))
private __id: FieldId;
readonly [Id]: FieldId;
diff --git a/src/new_fields/ScriptField.ts b/src/new_fields/ScriptField.ts
index e5ec34f57..00b4dec2c 100644
--- a/src/new_fields/ScriptField.ts
+++ b/src/new_fields/ScriptField.ts
@@ -2,10 +2,11 @@ import { ObjectField } from "./ObjectField";
import { CompiledScript, CompileScript, scriptingGlobal } from "../client/util/Scripting";
import { Copy, ToScriptString, Parent, SelfProxy } from "./FieldSymbols";
import { serializable, createSimpleSchema, map, primitive, object, deserialize, PropSchema, custom, SKIP } from "serializr";
-import { Deserializable } from "../client/util/SerializationHelper";
+import { Deserializable, autoObject } from "../client/util/SerializationHelper";
import { Doc } from "../new_fields/Doc";
import { Plugins } from "./util";
import { computedFn } from "mobx-utils";
+import { ProxyField } from "./Proxy";
function optional(propSchema: PropSchema) {
return custom(value => {
@@ -34,7 +35,16 @@ const scriptSchema = createSimpleSchema({
originalScript: true
});
-function deserializeScript(script: ScriptField) {
+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");
@@ -48,9 +58,17 @@ export class ScriptField extends ObjectField {
@serializable(object(scriptSchema))
readonly script: CompiledScript;
+ @serializable(autoObject())
+ private captures?: ProxyField<Doc>;
+
constructor(script: CompiledScript) {
super();
+ if (script && script.options.capturedVariables) {
+ const doc = Doc.assign(new Doc, script.options.capturedVariables);
+ this.captures = new ProxyField(doc);
+ }
+
this.script = script;
}
diff --git a/src/server/index.ts b/src/server/index.ts
index 5b086a2cf..66c982adc 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -284,18 +284,15 @@ addSecureRoute(
RouteStore.getCurrUser
);
+const ServicesApiKeyMap = new Map<string, string | undefined>([
+ ["face", process.env.FACE],
+ ["vision", process.env.VISION],
+ ["handwriting", process.env.HANDWRITING]
+]);
+
addSecureRoute(Method.GET, (user, res, req) => {
- let requested = req.params.requestedservice;
- switch (requested) {
- case "face":
- res.send(process.env.FACE);
- break;
- case "vision":
- res.send(process.env.VISION);
- break;
- default:
- res.send(undefined);
- }
+ let service = req.params.requestedservice;
+ res.send(ServicesApiKeyMap.get(service));
}, undefined, `${RouteStore.cognitiveServices}/:requestedservice`);
class NodeCanvasFactory {