From a8664face6f19cd2373bd928c2a2b3043bb6278d Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Tue, 2 Jul 2019 19:02:51 -0400
Subject: Added arrange script to freeform views

---
 src/client/views/OverlayView.tsx | 46 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 46 insertions(+)
 create mode 100644 src/client/views/OverlayView.tsx

(limited to 'src/client/views/OverlayView.tsx')

diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
new file mode 100644
index 000000000..72f1068ce
--- /dev/null
+++ b/src/client/views/OverlayView.tsx
@@ -0,0 +1,46 @@
+import * as React from "react";
+import { observer } from "mobx-react";
+import { observable, action } from "mobx";
+
+export type OverlayDisposer = () => void;
+
+export type OverlayElementOptions = {
+    x: number;
+    y: number;
+    width?: number;
+    height?: number;
+};
+
+@observer
+export class OverlayView extends React.Component {
+    public static Instance: OverlayView;
+    @observable.shallow
+    private _elements: { ele: JSX.Element, options: OverlayElementOptions }[] = [];
+
+    constructor(props: any) {
+        super(props);
+        if (!OverlayView.Instance) {
+            OverlayView.Instance = this;
+        }
+    }
+
+    @action
+    addElement(ele: JSX.Element, options: OverlayElementOptions): OverlayDisposer {
+        const eleWithPosition = { ele, options };
+        this._elements.push(eleWithPosition);
+        return action(() => {
+            const index = this._elements.indexOf(eleWithPosition);
+            if (index !== -1) this._elements.splice(index, 1);
+        });
+    }
+
+    render() {
+        return (
+            <div>
+                {this._elements.map(({ ele, options: { x, y, width, height } }) => (
+                    <div style={{ position: "absolute", transform: `translate(${x}px, ${y}px)`, width, height }}>{ele}</div>
+                ))}
+            </div>
+        );
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 3cdc6f11e262e289b6070d6fa75a59fff7e59b1e Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Wed, 3 Jul 2019 13:42:20 -0400
Subject: Added state to layout scripts

---
 src/client/views/OverlayView.tsx                   |  9 +--
 src/client/views/ScriptBox.scss                    | 17 ++++++
 src/client/views/ScriptBox.tsx                     | 10 +++-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 69 +++++++++++++---------
 4 files changed, 72 insertions(+), 33 deletions(-)
 create mode 100644 src/client/views/ScriptBox.scss

(limited to 'src/client/views/OverlayView.tsx')

diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 72f1068ce..f8fc94274 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -1,6 +1,7 @@
 import * as React from "react";
 import { observer } from "mobx-react";
 import { observable, action } from "mobx";
+import { Utils } from "../../Utils";
 
 export type OverlayDisposer = () => void;
 
@@ -15,7 +16,7 @@ export type OverlayElementOptions = {
 export class OverlayView extends React.Component {
     public static Instance: OverlayView;
     @observable.shallow
-    private _elements: { ele: JSX.Element, options: OverlayElementOptions }[] = [];
+    private _elements: { ele: JSX.Element, id: string, options: OverlayElementOptions }[] = [];
 
     constructor(props: any) {
         super(props);
@@ -26,7 +27,7 @@ export class OverlayView extends React.Component {
 
     @action
     addElement(ele: JSX.Element, options: OverlayElementOptions): OverlayDisposer {
-        const eleWithPosition = { ele, options };
+        const eleWithPosition = { ele, options, id: Utils.GenerateGuid() };
         this._elements.push(eleWithPosition);
         return action(() => {
             const index = this._elements.indexOf(eleWithPosition);
@@ -37,8 +38,8 @@ export class OverlayView extends React.Component {
     render() {
         return (
             <div>
-                {this._elements.map(({ ele, options: { x, y, width, height } }) => (
-                    <div style={{ position: "absolute", transform: `translate(${x}px, ${y}px)`, width, height }}>{ele}</div>
+                {this._elements.map(({ ele, options: { x, y, width, height }, id }) => (
+                    <div key={id} style={{ position: "absolute", transform: `translate(${x}px, ${y}px)`, width, height }}>{ele}</div>
                 ))}
             </div>
         );
diff --git a/src/client/views/ScriptBox.scss b/src/client/views/ScriptBox.scss
new file mode 100644
index 000000000..28326624a
--- /dev/null
+++ b/src/client/views/ScriptBox.scss
@@ -0,0 +1,17 @@
+.scriptBox-outerDiv {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+}
+
+.scriptBox-toolbar {
+    width: 100%;
+}
+
+.scriptBox-textArea {
+    width: 100%;
+    height: 100%;
+    box-sizing: border-box;
+    resize: none;
+}
\ No newline at end of file
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx
index aea9d52a4..fa236c2da 100644
--- a/src/client/views/ScriptBox.tsx
+++ b/src/client/views/ScriptBox.tsx
@@ -2,15 +2,23 @@ import * as React from "react";
 import { observer } from "mobx-react";
 import { observable, action } from "mobx";
 
+import "./ScriptBox.scss";
+
 export interface ScriptBoxProps {
     onSave: (text: string, onError: (error: string) => void) => void;
     onCancel?: () => void;
+    initialText?: string;
 }
 
 @observer
 export class ScriptBox extends React.Component<ScriptBoxProps> {
     @observable
-    private _scriptText: string = "";
+    private _scriptText: string;
+
+    constructor(props: ScriptBoxProps) {
+        super(props);
+        this._scriptText = props.initialText || "";
+    }
 
     @action
     onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 23c0fdd0d..edd2fb1b0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -28,7 +28,7 @@ import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import v5 = require("uuid/v5");
 import { ScriptField } from "../../../../new_fields/ScriptField";
-import { OverlayView } from "../../OverlayView";
+import { OverlayView, OverlayElementOptions } from "../../OverlayView";
 import { ScriptBox } from "../../ScriptBox";
 import { CompileScript } from "../../../util/Scripting";
 
@@ -36,7 +36,9 @@ import { CompileScript } from "../../../util/Scripting";
 export const panZoomSchema = createSchema({
     panX: "number",
     panY: "number",
-    scale: "number"
+    scale: "number",
+    arrangeScript: ScriptField,
+    arrangeInit: ScriptField,
 });
 
 type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof positionSchema, typeof pageSchema]>;
@@ -392,28 +394,34 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         };
     }
 
-    getCalculatedPositions(doc: Doc, index: number, collection: Doc): { x?: number, y?: number, width?: number, height?: number } | undefined {
-        const script = Cast(this.props.Document.arrangeScript, ScriptField);
-        if (!script) {
-            return undefined;
-        }
-        const result = script.script.run({ doc, index, collection });
+    getCalculatedPositions(doc: Doc, index: number, collection: Doc, script: ScriptField, state: any): { x?: number, y?: number, width?: number, height?: number, state?: any } {
+        const result = script.script.run({ doc, index, collection, state });
         if (!result.success) {
-            return undefined;
+            return {};
         }
-        return result.result;
+        return result.result === undefined ? {} : result.result;
     }
 
     @computed.struct
     get views() {
         let curPage = FieldValue(this.Document.curPage, -1);
+        const initScript = this.Document.arrangeInit;
+        const script = this.Document.arrangeScript;
+        let state: any = undefined;
+        if (initScript) {
+            const initResult = initScript.script.run();
+            if (initResult.success) {
+                state = initResult.result;
+            }
+        }
         let docviews = this.childDocs.reduce((prev, doc) => {
             if (!(doc instanceof Doc)) return prev;
             var page = NumCast(doc.page, -1);
             if (Math.round(page) === Math.round(curPage) || page === -1) {
                 let minim = BoolCast(doc.isMinimized, false);
                 if (minim === undefined || !minim) {
-                    const pos = this.getCalculatedPositions(doc, prev.length, this.Document) || {};
+                    const pos = script ? this.getCalculatedPositions(doc, prev.length, this.Document, script, state) : {};
+                    state = pos.state === undefined ? state : pos.state;
                     prev.push(<CollectionFreeFormDocumentView key={doc[Id]} x={pos.x} y={pos.y} width={pos.width} height={pos.height} {...this.getChildDocumentViewProps(doc)} />);
                 }
             }
@@ -458,23 +466,28 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         ContextMenu.Instance.addItem({
             description: "Add freeform arrangement",
             event: () => {
-                let overlayDisposer: () => void;
-                let scriptingBox = <ScriptBox onCancel={() => overlayDisposer()} onSave={(text, onError) => {
-                    const script = CompileScript(text, {
-                        params: {
-                            doc: "Doc", index: "number", collection: "Doc"
-                        },
-                        requiredType: "{x: number, y: number, width?: number, height?: number}",
-                        typecheck: false
-                    });
-                    if (!script.compiled) {
-                        onError(script.errors.map(error => error.messageText).join("\n"));
-                        return;
-                    }
-                    this.props.Document.arrangeScript = new ScriptField(script);
-                    overlayDisposer();
-                }} />;
-                overlayDisposer = OverlayView.Instance.addElement(scriptingBox, { x: 100, y: 100, width: 200, height: 200 });
+                let addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
+                    let overlayDisposer: () => void;
+                    const script = this.Document[key];
+                    let originalText: string | undefined = undefined;
+                    if (script) originalText = script.script.originalScript;
+                    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;
+                        }
+                        this.Document[key] = new ScriptField(script);
+                        overlayDisposer();
+                    }} />;
+                    overlayDisposer = OverlayView.Instance.addElement(scriptingBox, options);
+                };
+                addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300 }, undefined, undefined);
+                addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300 }, { doc: "Doc", index: "number", collection: "Doc", state:"any" }, "{x: number, y: number, width?: number, height?: number}");
             }
         });
     }
-- 
cgit v1.2.3-70-g09d2

