From 18508ce86517b798e8eff2696dfe1a832837b442 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Sat, 27 Jul 2019 14:38:04 -0400
Subject: Made scripting repl work better with anonymous functions

---
 src/client/views/ScriptingRepl.tsx | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)

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

diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index 9e538cf1b..0cff145b6 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -95,6 +95,7 @@ export class ScriptingValueDisplay extends React.Component<{ scrollToBottom: ()
 @observer
 export class ScriptingRepl extends React.Component {
     @observable private commands: { command: string, result: any }[] = [];
+    private commandsHistory: string[] = [];
 
     @observable private commandString: string = "";
     private commandBuffer: string = "";
@@ -113,12 +114,21 @@ export class ScriptingRepl extends React.Component {
                 Scripting.getGlobals().forEach(global => knownVars[global] = 1);
                 return root => {
                     function visit(node: ts.Node) {
+                        let skip = false;
+                        if (ts.isIdentifier(node)) {
+                            if (ts.isParameter(node.parent)) {
+                                skip = true;
+                                knownVars[node.text] = 1;
+                            }
+                        }
                         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)) {
+                            if (ts.isParameter(node.parent)) {
+                                // delete knownVars[node.text];
+                            } else 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]);
@@ -147,13 +157,16 @@ export class ScriptingRepl extends React.Component {
                 const globals = Scripting.makeMutableGlobalsCopy(docGlobals);
                 const script = CompileScript(this.commandString, { typecheck: false, addReturn: true, editable: true, params: { args: "any" }, transformer: this.getTransformer(), globals });
                 if (!script.compiled) {
+                    this.commands.push({ command: this.commandString, result: script.errors });
                     return;
                 }
                 const result = script.run({ args: this.args });
                 if (!result.success) {
+                    this.commands.push({ command: this.commandString, result: result.error.toString() });
                     return;
                 }
                 this.commands.push({ command: this.commandString, result: result.result });
+                this.commandsHistory.push(this.commandString);
 
                 this.maybeScrollToBottom();
 
@@ -168,7 +181,7 @@ export class ScriptingRepl extends React.Component {
                     if (this.historyIndex === 0) {
                         this.commandBuffer = this.commandString;
                     }
-                    this.commandString = this.commands[this.commands.length - 1 - this.historyIndex].command;
+                    this.commandString = this.commandsHistory[this.commands.length - 1 - this.historyIndex];
                 }
                 break;
             }
@@ -179,7 +192,7 @@ export class ScriptingRepl extends React.Component {
                         this.commandString = this.commandBuffer;
                         this.commandBuffer = "";
                     } else {
-                        this.commandString = this.commands[this.commands.length - 1 - this.historyIndex].command;
+                        this.commandString = this.commandsHistory[this.commands.length - 1 - this.historyIndex];
                     }
                 }
                 break;
-- 
cgit v1.2.3-70-g09d2


From be1e6e967a60f49eec1cb1d404912b0736812323 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Sun, 28 Jul 2019 17:59:00 -0400
Subject: Added ability to capture documents in button scripts

---
 src/client/views/ScriptBox.tsx                     | 23 +++++++-
 src/client/views/ScriptingRepl.tsx                 | 31 +----------
 .../views/collections/CollectionDockingView.tsx    |  1 -
 src/client/views/nodes/ButtonBox.tsx               |  6 +-
 src/client/views/nodes/DocumentIcon.tsx            | 65 ++++++++++++++++++++++
 5 files changed, 94 insertions(+), 32 deletions(-)
 create mode 100644 src/client/views/nodes/DocumentIcon.tsx

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

diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx
index fa236c2da..d073945e5 100644
--- a/src/client/views/ScriptBox.tsx
+++ b/src/client/views/ScriptBox.tsx
@@ -3,11 +3,15 @@ import { observer } from "mobx-react";
 import { observable, action } from "mobx";
 
 import "./ScriptBox.scss";
+import { OverlayView } from "./OverlayView";
+import { DocumentIconContainer } from "./nodes/DocumentIcon";
+import { Opt } from "../../new_fields/Doc";
 
 export interface ScriptBoxProps {
     onSave: (text: string, onError: (error: string) => void) => void;
     onCancel?: () => void;
     initialText?: string;
+    showDocumentIcons?: boolean;
 }
 
 @observer
@@ -30,14 +34,31 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
         console.log(error);
     }
 
+    overlayDisposer?: () => void;
+    onFocus = () => {
+        if (this.overlayDisposer) {
+            this.overlayDisposer();
+        }
+        this.overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
+    }
+
+    onBlur = () => {
+        this.overlayDisposer && this.overlayDisposer();
+    }
+
     render() {
+        let onFocus: Opt<() => void> = undefined, onBlur: Opt<() => void> = undefined;
+        if (this.props.showDocumentIcons) {
+            onFocus = this.onFocus;
+            onBlur = this.onBlur;
+        }
         return (
             <div className="scriptBox-outerDiv">
                 <div className="scriptBox-toolbar">
                     <button onClick={e => { this.props.onSave(this._scriptText, this.onError); e.stopPropagation(); }}>Save</button>
                     <button onClick={e => { this.props.onCancel && this.props.onCancel(); e.stopPropagation(); }}>Cancel</button>
                 </div>
-                <textarea className="scriptBox-textarea" onChange={this.onChange} value={this._scriptText}></textarea>
+                <textarea className="scriptBox-textarea" onChange={this.onChange} value={this._scriptText} onFocus={onFocus} onBlur={onBlur}></textarea>
             </div>
         );
     }
diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index 0cff145b6..e05195ca0 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -4,40 +4,15 @@ import { observable, action } from 'mobx';
 import './ScriptingRepl.scss';
 import { Scripting, CompileScript, ts, Transformer } from '../util/Scripting';
 import { DocumentManager } from '../util/DocumentManager';
-import { DocumentView } from './nodes/DocumentView';
 import { OverlayView } from './OverlayView';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faCaretDown, faCaretRight } from '@fortawesome/free-solid-svg-icons';
+import { DocumentIconContainer } from './nodes/DocumentIcon';
 
 library.add(faCaretDown);
 library.add(faCaretRight);
 
-@observer
-export class DocumentIcon extends React.Component<{ view: DocumentView, index: number }> {
-    render() {
-        const view = this.props.view;
-        const transform = view.props.ScreenToLocalTransform().scale(view.props.ContentScaling()).inverse();
-        const { x, y, width, height } = transform.transformBounds(0, 0, view.props.PanelWidth(), view.props.PanelHeight());
-
-        return (
-            <div className="documentIcon-outerDiv" style={{
-                position: "absolute",
-                transform: `translate(${x + width / 2}px, ${y}px)`,
-            }}>
-                <p>${this.props.index}</p>
-            </div>
-        );
-    }
-}
-
-@observer
-export class DocumentIconContainer extends React.Component {
-    render() {
-        return DocumentManager.Instance.DocumentViews.map((dv, i) => <DocumentIcon key={i} index={i} view={dv} />);
-    }
-}
-
 @observer
 export class ScriptingObjectDisplay extends React.Component<{ scrollToBottom: () => void, value: { [key: string]: any }, name?: string }> {
     @observable collapsed = true;
@@ -129,7 +104,7 @@ export class ScriptingRepl extends React.Component {
                             if (ts.isParameter(node.parent)) {
                                 // delete knownVars[node.text];
                             } else if (isntPropAccess && isntPropAssign && !(node.text in knownVars) && !(node.text in globalThis)) {
-                                const match = node.text.match(/\$([0-9]+)/);
+                                const match = node.text.match(/\d([0-9]+)/);
                                 if (match) {
                                     const m = parseInt(match[1]);
                                     usedDocuments.push(m);
@@ -153,7 +128,7 @@ export class ScriptingRepl extends React.Component {
         switch (e.key) {
             case "Enter": {
                 const docGlobals: { [name: string]: any } = {};
-                DocumentManager.Instance.DocumentViews.forEach((dv, i) => docGlobals[`$${i}`] = dv.props.Document);
+                DocumentManager.Instance.DocumentViews.forEach((dv, i) => docGlobals[`d${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 });
                 if (!script.compiled) {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 0865058be..1859ebee7 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -577,7 +577,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             return (null);
         }
         let resolvedDataDoc = this._document.layout instanceof Doc ? this._document : this._dataDoc;
-        console.log("pw = " + this.panelWidth() + "," + this.panelHeight() + " " + this.contentScaling());
         return <DocumentView key={this._document[Id]}
             Document={this._document}
             DataDoc={resolvedDataDoc}
diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx
index 744611661..d2c23fdab 100644
--- a/src/client/views/nodes/ButtonBox.tsx
+++ b/src/client/views/nodes/ButtonBox.tsx
@@ -14,6 +14,7 @@ import { Doc } from '../../../new_fields/Doc';
 
 import './ButtonBox.scss';
 import { observer } from 'mobx-react';
+import { DocumentIconContainer } from './DocumentIcon';
 
 library.add(faEdit);
 
@@ -51,7 +52,8 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt
                     const script = CompileScript(text, {
                         params: { this: Doc.name },
                         typecheck: false,
-                        editable: true
+                        editable: true,
+                        transformer: DocumentIconContainer.getTransformer()
                     });
                     if (!script.compiled) {
                         onError(script.errors.map(error => error.messageText).join("\n"));
@@ -59,7 +61,7 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt
                     }
                     this.Document.onClick = new ScriptField(script);
                     overlayDisposer();
-                }} />;
+                }} showDocumentIcons />;
                 overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, { x: 400, y: 200, width: 500, height: 400, title: `${this.Document.title || ""} OnClick` });
             }
         });
diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx
new file mode 100644
index 000000000..f56f5e829
--- /dev/null
+++ b/src/client/views/nodes/DocumentIcon.tsx
@@ -0,0 +1,65 @@
+import { observer } from "mobx-react";
+import * as React from "react";
+import { DocumentView } from "./DocumentView";
+import { DocumentManager } from "../../util/DocumentManager";
+import { Transformer, Scripting, ts } from "../../util/Scripting";
+import { Field } from "../../../new_fields/Doc";
+
+@observer
+export class DocumentIcon extends React.Component<{ view: DocumentView, index: number }> {
+    render() {
+        const view = this.props.view;
+        const transform = view.props.ScreenToLocalTransform().scale(view.props.ContentScaling()).inverse();
+        const { x, y, width, height } = transform.transformBounds(0, 0, view.props.PanelWidth(), view.props.PanelHeight());
+
+        return (
+            <div className="documentIcon-outerDiv" style={{
+                position: "absolute",
+                transform: `translate(${x + width / 2}px, ${y}px)`,
+            }}>
+                <p>d{this.props.index}</p>
+            </div>
+        );
+    }
+}
+
+@observer
+export class DocumentIconContainer extends React.Component {
+    public static getTransformer(): Transformer {
+        const usedDocuments = new Set<number>();
+        return {
+            transformer: 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;
+                            const isntParameter = !ts.isParameter(node.parent);
+                            if (isntPropAccess && isntPropAssign && isntParameter && !(node.text in globalThis)) {
+                                const match = node.text.match(/d([0-9]+)/);
+                                if (match) {
+                                    const m = parseInt(match[1]);
+                                    usedDocuments.add(m);
+                                }
+                            }
+                        }
+
+                        return node;
+                    }
+                    return ts.visitNode(root, visit);
+                };
+            },
+            getVars() {
+                const docs = DocumentManager.Instance.DocumentViews;
+                const capturedVariables: { [name: string]: Field } = {};
+                usedDocuments.forEach(index => capturedVariables[`d${index}`] = docs[index].props.Document);
+                return { capturedVariables };
+            }
+        };
+    }
+    render() {
+        return DocumentManager.Instance.DocumentViews.map((dv, i) => <DocumentIcon key={i} index={i} view={dv} />);
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2

