From 61dcd6a0e23384e68a7d7f2f9e0143741f5cbf56 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 15 May 2020 17:36:39 -0400
Subject: moving toward a proper slide transition system

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 118 ++++++++++-----------
 1 file changed, 57 insertions(+), 61 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bf679309c..751ee03d2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -46,6 +46,7 @@ import React = require("react");
 import { CollectionViewType } from "../CollectionView";
 import { Timeline } from "../../animationtimeline/Timeline";
 import { SnappingManager } from "../../../util/SnappingManager";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -53,6 +54,7 @@ export const panZoomSchema = createSchema({
     _panX: "number",
     _panY: "number",
     scale: "number",
+    timecode: "number",
     arrangeScript: ScriptField,
     arrangeInit: ScriptField,
     useClusters: "boolean",
@@ -124,15 +126,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         this.addDocument(newBox);
     }
     addDocument = (newBox: Doc | Doc[]) => {
-        const timecode = Cast(this.props.Document.timecode, "number", null);
-        if (timecode !== undefined) {
-            ((newBox instanceof Doc) ? [newBox] : newBox).map(doc => {
-                doc["x-indexed"] = new List<number>(numberRange(timecode + 1).map(i => NumCast(doc.x)));
-                doc["y-indexed"] = new List<number>(numberRange(timecode + 1).map(i => NumCast(doc.y)));
-                doc.timecode = ComputedField.MakeFunction("collection.timecode", {}, { collection: this.props.Document });
-                doc.x = ComputedField.MakeInterpolated("x", "timecode");
-                doc.y = ComputedField.MakeInterpolated("y", "timecode");
-            });
+        if (this.Document.timecode !== undefined) {
+            CollectionFreeFormDocumentView.setupKeyframes((newBox instanceof Doc) ? [newBox] : newBox, this.Document.timecode, this.props.Document);
         }
 
         if (newBox instanceof Doc) {
@@ -145,6 +140,25 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             // bcz: deal with clusters
         }
     }
+
+    @undoBatch
+    @action
+    nextKeyframe = (): void => {
+        if (this.props.Document.timecode === undefined) {
+            this.props.Document.timecode = 0;
+            CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0, this.props.Document);
+        }
+        const timecode = NumCast(this.props.Document.timecode);
+        CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, timecode);
+        this.props.Document.timecode = Math.max(0, timecode + 1);
+    }
+    @undoBatch
+    @action
+    prevKeyframe = (): void => {
+        CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice());
+        this.props.Document.timecode = Math.max(0, NumCast(this.props.Document.timecode) - 1);
+    }
+
     private selectDocuments = (docs: Doc[]) => {
         SelectionManager.DeselectAll();
         docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
@@ -185,8 +199,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                         for (let i = 0; i < droppedDocs.length; i++) {
                             const d = droppedDocs[i];
                             const layoutDoc = Doc.Layout(d);
-                            d.x = x + NumCast(d.x) - dropX;
-                            d.y = y + NumCast(d.y) - dropY;
+                            if (this.Document.timecode !== undefined) {
+                                CollectionFreeFormDocumentView.setValues(this.Document.timecode, d, x + NumCast(d.x) - dropX, y + NumCast(d.y) - dropY, Cast(d.opacity, "number", null));
+                            } else {
+                                d.x = x + NumCast(d.x) - dropX;
+                                d.y = y + NumCast(d.y) - dropY;
+                            }
                             if (!NumCast(layoutDoc._width)) {
                                 layoutDoc._width = 300;
                             }
@@ -953,10 +971,15 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             return { x: 0, y: 0, transition: "transform 1s", ...result, pair: params.pair, replica: "" };
         }
         const layoutDoc = Doc.Layout(params.pair.layout);
-        const { x, y, z, color, zIndex } = params.pair.layout;
+        const { x, y, opacity } = this.Document.timecode === undefined ? params.pair.layout :
+            CollectionFreeFormDocumentView.getValues(params.pair.layout, this.Document.timecode);
+        if (this.Document.timecode !== undefined) {
+            const time = this.Document.timecode || 0;
+        }
+        const { z, color, zIndex } = params.pair.layout;
         return {
             x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"),
-            transition: StrCast(layoutDoc.transition),
+            transition: StrCast(layoutDoc.transition), opacity: Cast(opacity, "number", null),
             width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number"), pair: params.pair, replica: ""
         };
     }
@@ -1142,39 +1165,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.NativeWidth(), this.props.NativeHeight());
     }
 
-    @undoBatch
-    @action
-    snaphsotInterpolated = (): void => {
-        if (this.props.Document.timecode === undefined) {
-            this.childDocs.map(doc => {
-                this.props.Document.timecode = 0;
-                doc["x-indexed"] = new List<number>([NumCast(doc.x)]);
-                doc["y-indexed"] = new List<number>([NumCast(doc.y)]);
-                doc.timecode = ComputedField.MakeFunction("collection.timecode", {}, { collection: this.props.Document });
-                doc.x = ComputedField.MakeInterpolated("x", "timecode");
-                doc.y = ComputedField.MakeInterpolated("y", "timecode");
-            });
-        }
-        const timecode = NumCast(this.props.Document.timecode);
-        this.childDocs.map(doc => {
-            const xindexed = Cast(doc['x-indexed'], listSpec("number"), null);
-            const yindexed = Cast(doc['y-indexed'], listSpec("number"), null);
-            xindexed.length <= timecode + 1 && xindexed.push(NumCast(doc.x));
-            yindexed.length <= timecode + 1 && yindexed.push(NumCast(doc.y));
-        });
-        this.childDocs.map(doc => doc.transition = "transform 1s");
-        this.props.Document.timecode = Math.max(0, timecode + 1);
-        setTimeout(() => this.childDocs.map(doc => doc.transition = undefined), 1010);
-    }
-    @undoBatch
-    @action
-    backupInterpolated = (): void => {
-        this.childDocs.map(doc => doc.transition = "transform 1s");
-        this.props.Document.timecode = Math.max(0, NumCast(this.props.Document.timecode) - 1);
-        setTimeout(() => this.childDocs.map(doc => doc.transition = undefined), 1010);
-    }
-
-
     private thumbIdentifier?: number;
 
     onContextMenu = (e: React.MouseEvent) => {
@@ -1185,8 +1175,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 this._timelineVisible = !this._timelineVisible;
             }), icon: this._timelineVisible ? faEyeSlash : faEye
         });
-        ContextMenu.Instance.addItem({ description: "Advance", event: this.snaphsotInterpolated, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
-        ContextMenu.Instance.addItem({ description: "Backup ", event: this.backupInterpolated, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
+        ContextMenu.Instance.addItem({ description: "Advance", event: this.nextKeyframe, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
+        ContextMenu.Instance.addItem({ description: "Backup ", event: this.prevKeyframe, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
 
         const options = ContextMenu.Instance.findByDescription("Options...");
         const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
@@ -1342,18 +1332,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     render() {
         TraceMobx();
         const clientRect = this._mainCont?.getBoundingClientRect();
-        // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap)
-        // this.Document.fitX = this.contentBounds && this.contentBounds.x;
-        // this.Document.fitY = this.contentBounds && this.contentBounds.y;
-        // this.Document.fitW = this.contentBounds && (this.contentBounds.r - this.contentBounds.x);
-        // this.Document.fitH = this.contentBounds && (this.contentBounds.b - this.contentBounds.y);
-        // if isAnnotationOverlay is set, then children will be stored in the extension document for the fieldKey.
-        // otherwise, they are stored in fieldKey.  All annotations to this document are stored in the extension document
-        return <div className={"collectionfreeformview-container"}
-            ref={this.createDashEventsTarget}
+        return <div className={"collectionfreeformview-container"} ref={this.createDashEventsTarget}
             onPointerOver={this.onPointerOver}
-            onWheel={this.onPointerWheel} onClick={this.onClick}  //pointerEvents: DraggingManager.GetIsDragging() ? "all" : undefined,
-            onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu}
+            onWheel={this.onPointerWheel}
+            onClick={this.onClick}
+            onPointerDown={this.onPointerDown}
+            onPointerMove={this.onCursorMove}
+            onDrop={this.onExternalDrop.bind(this)}
+            onContextMenu={this.onContextMenu}
             style={{
                 pointerEvents: this.backgroundEvents ? "all" : undefined,
                 transform: this.contentScaling ? `scale(${this.contentScaling})` : "",
@@ -1364,12 +1350,22 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             {!this.Document._LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ?
                 this.placeholder : this.marqueeView}
             <CollectionFreeFormOverlayView elements={this.elementFunc} />
-
+            {this.isAnnotationOverlay ? (null) :
+                <>
+                    <div key="back" style={{ position: "absolute", width: 20, height: 20, right: 0, bottom: 0, }} onClick={this.nextKeyframe}>
+                        <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
+                    </div>
+                    <div key="fwd" style={{ position: "absolute", width: 20, height: 20, right: 20, bottom: 0, }}>
+                        {NumCast(this.props.Document.timecode)}
+                    </div>
+                    <div key="fwd" style={{ position: "absolute", width: 20, height: 20, right: 40, bottom: 0, }} onClick={this.prevKeyframe}>
+                        <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
+                    </div>
+                </>}
             <div className={"pullpane-indicator"}
                 style={{
                     display: this._pullDirection ? "block" : "none",
                     top: clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : 0 : "auto",
-                    // left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x - MainView.Instance.flyoutWidth : 0 : "auto",
                     left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x : 0 : "auto",
                     width: clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
                     height: clientRect ? this._pullDirection === "top" ? this._pullCoords[1] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[1] : clientRect.height : 0,
-- 
cgit v1.2.3-70-g09d2


From b9a3c4adb81b38f550f0ab18be5c40a38aea4597 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 15 May 2020 20:59:35 -0400
Subject: cleaned up frame animation buttons.  Added server code for https? 
 removed unused devserver from webpack.config

---
 .../collectionFreeForm/CollectionFreeFormView.scss | 18 ++++++
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  6 +-
 src/server/server_Initialization.ts                | 17 +++++
 webpack.config.js                                  | 74 +++++++++-------------
 4 files changed, 69 insertions(+), 46 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 60c39c825..7a84fcde1 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -41,6 +41,24 @@
     // touch action none means that the browser will handle none of the touch actions. this allows us to implement our own actions.
     touch-action: none;
 
+    .fwdKeyframe, .numKeyframe, .backKeyframe{
+        cursor: pointer;
+        position: absolute;
+        width: 20;
+        height:20;
+        bottom:0;
+        background:gray;
+    }
+    .backKeyframe {
+        right:45;
+    }
+    .numKeyframe {
+        right:25;
+    }
+    .fwdKeyframe {
+        right:5;
+    }
+
     .collectionfreeformview-placeholder {
         background: gray;
         width: 100%;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 751ee03d2..4be671a76 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1352,13 +1352,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             <CollectionFreeFormOverlayView elements={this.elementFunc} />
             {this.isAnnotationOverlay ? (null) :
                 <>
-                    <div key="back" style={{ position: "absolute", width: 20, height: 20, right: 0, bottom: 0, }} onClick={this.nextKeyframe}>
+                    <div key="fwd" className="backKeyframe" onClick={this.nextKeyframe}>
                         <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
                     </div>
-                    <div key="fwd" style={{ position: "absolute", width: 20, height: 20, right: 20, bottom: 0, }}>
+                    <div key="fwd" className="numKeyframe" >
                         {NumCast(this.props.Document.timecode)}
                     </div>
-                    <div key="fwd" style={{ position: "absolute", width: 20, height: 20, right: 40, bottom: 0, }} onClick={this.prevKeyframe}>
+                    <div key="back" className="fwdKeyframe" onClick={this.prevKeyframe}>
                         <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
                     </div>
                 </>}
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index 4b3094616..14b0dedc3 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -53,6 +53,23 @@ export default async function InitializeServer(routeSetter: RouteSetter) {
         logPort("server", serverPort);
         console.log();
     });
+
+    // var express = require('express')
+    // var fs = require('fs')
+    // var https = require('https')
+    // var app = express()
+
+    // app.get('/', function (req, res) {
+    //   res.send('hello world')
+    // })
+
+    // https.createServer({
+    //   key: fs.readFileSync('server.key'),
+    //   cert: fs.readFileSync('server.cert')
+    // }, app)
+    // .listen(3000, function () {
+    //   console.log('Example app listening on port 3000! Go to https://localhost:3000/')
+    // })
     disconnect = async () => new Promise<Error>(resolve => server.close(resolve));
 
     return isRelease;
diff --git a/webpack.config.js b/webpack.config.js
index 67d492e1f..480ed7aa3 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -63,57 +63,45 @@ module.exports = {
     },
     module: {
         rules: [{
-                test: [/\.tsx?$/],
-                use: [{
-                    loader: 'ts-loader',
-                    options: {
-                        transpileOnly: true
-                    }
-                }]
+            test: [/\.tsx?$/],
+            use: [{
+                loader: 'ts-loader',
+                options: {
+                    transpileOnly: true
+                }
+            }]
+        },
+        {
+            test: /\.scss|css$/,
+            use: [{
+                loader: "style-loader"
             },
             {
-                test: /\.scss|css$/,
-                use: [{
-                        loader: "style-loader"
-                    },
-                    {
-                        loader: "css-loader"
-                    },
-                    {
-                        loader: "sass-loader"
-                    }
-                ]
+                loader: "css-loader"
             },
             {
-                test: /\.(jpg|png|pdf)$/,
-                use: [{
-                    loader: 'file-loader'
-                }]
-            },
-            {
-                test: /\.(png|jpg|gif)$/i,
-                use: [{
-                    loader: 'url-loader',
-                    options: {
-                        limit: 8192
-                    }
-                }]
+                loader: "sass-loader"
             }
+            ]
+        },
+        {
+            test: /\.(jpg|png|pdf)$/,
+            use: [{
+                loader: 'file-loader'
+            }]
+        },
+        {
+            test: /\.(png|jpg|gif)$/i,
+            use: [{
+                loader: 'url-loader',
+                options: {
+                    limit: 8192
+                }
+            }]
+        }
         ]
     },
     plugins,
-    devServer: {
-        compress: false,
-        host: "localhost",
-        contentBase: path.join(__dirname, 'deploy'),
-        port: 4321,
-        hot: true,
-        https: false,
-        overlay: {
-            warnings: true,
-            errors: true
-        }
-    },
     externals: [
         'child_process'
     ]
-- 
cgit v1.2.3-70-g09d2


From a0dc46dc565418fef9a257508a92c788c4ec50af Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 16 May 2020 00:01:06 -0400
Subject: fixed some issues with frame animation.  added comic rendering mode. 
 added server certficate keys to get SSL to work?

---
 src/client/views/MainView.tsx                      |  6 ++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 16 +++++----
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 14 ++++++--
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  3 +-
 src/server/server_Initialization.ts                | 39 +++++++++++-----------
 7 files changed, 48 insertions(+), 34 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f58313f06..3e4833052 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -256,8 +256,9 @@ export class MainView extends React.Component {
         const mainDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600, path: [Doc.UserDoc().myCatalog as Doc] }], { title: `Workspace ${workspaceCount}` }, id, "row");
 
         const toggleTheme = ScriptField.MakeScript(`self.darkScheme = !self.darkScheme`);
-        mainDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!]);
-        mainDoc.contextMenuLabels = new List<string>(["Toggle Theme Colors"]);
+        const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
+        mainDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!, toggleComic!]);
+        mainDoc.contextMenuLabels = new List<string>(["Toggle Theme Colors", "Toggle Comic Mode"]);
 
         Doc.AddDocToList(workspaces, "data", mainDoc);
         // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
@@ -629,3 +630,4 @@ export class MainView extends React.Component {
     }
 }
 Scripting.addGlobal(function freezeSidebar() { MainView.expandFlyout(); });
+Scripting.addGlobal(function toggleComicMode() { Doc.UserDoc().fontFamily = "Comic Sans MS"; Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic" });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4be671a76..d27f3bb0d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -155,6 +155,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @undoBatch
     @action
     prevKeyframe = (): void => {
+        if (this.props.Document.timecode === undefined) {
+            this.props.Document.timecode = 0;
+            CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0, this.props.Document);
+        }
         CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice());
         this.props.Document.timecode = Math.max(0, NumCast(this.props.Document.timecode) - 1);
     }
@@ -1095,7 +1099,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                         this.backgroundActive ?
                             true :
                             (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? false : undefined}
-                    jitterRotation={NumCast(this.props.Document._jitterRotation)}
+                    jitterRotation={NumCast(this.props.Document._jitterRotation) || ((Doc.UserDoc().renderStyle === "comic" ? 10 : 0))}
                     //fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
                     fitToBox={BoolCast(this.props.freezeChildDimensions)} // bcz: check this
                     FreezeDimensions={BoolCast(this.props.freezeChildDimensions)}
@@ -1352,14 +1356,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             <CollectionFreeFormOverlayView elements={this.elementFunc} />
             {this.isAnnotationOverlay ? (null) :
                 <>
-                    <div key="fwd" className="backKeyframe" onClick={this.nextKeyframe}>
-                        <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
+                    <div key="back" className="backKeyframe" onClick={this.prevKeyframe}>
+                        <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
                     </div>
-                    <div key="fwd" className="numKeyframe" >
+                    <div key="num" className="numKeyframe" >
                         {NumCast(this.props.Document.timecode)}
                     </div>
-                    <div key="back" className="fwdKeyframe" onClick={this.prevKeyframe}>
-                        <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
+                    <div key="fwd" className="fwdKeyframe" onClick={this.nextKeyframe}>
+                        <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
                     </div>
                 </>}
             <div className={"pullpane-indicator"}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 492ba6ed6..911a2bc3b 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -107,7 +107,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout ? e.key : "";
             const tbox = Docs.Create.TextDocument("", {
                 _width: 200, _height: 100, x: x, y: y, _autoHeight: true, _fontSize: NumCast(Doc.UserDoc().fontSize),
-                _backgroundColor: StrCast(Doc.UserDoc().backgroundColor),
+                _fontFamily: StrCast(Doc.UserDoc().fontFamily), _backgroundColor: StrCast(Doc.UserDoc().backgroundColor),
                 title: "-typed text-"
             });
             const template = FormattedTextBox.DefaultLayout;
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 5d109a5f2..c9bb543f1 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -87,9 +87,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             const xindexed = Cast(doc['x-indexed'], listSpec("number"), null);
             const yindexed = Cast(doc['y-indexed'], listSpec("number"), null);
             const opacityindexed = Cast(doc['opacity-indexed'], listSpec("number"), null);
-            xindexed.length <= timecode + 1 && xindexed.push(undefined as any as number);
-            yindexed.length <= timecode + 1 && yindexed.push(undefined as any as number);
-            opacityindexed.length <= timecode + 1 && opacityindexed.push(undefined as any as number);
+            xindexed?.length <= timecode + 1 && xindexed.push(undefined as any as number);
+            yindexed?.length <= timecode + 1 && yindexed.push(undefined as any as number);
+            opacityindexed?.length <= timecode + 1 && opacityindexed.push(undefined as any as number);
             doc.transition = "all 1s";
         });
         setTimeout(() => docs.forEach(doc => doc.transition = undefined), 1010);
@@ -129,6 +129,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     NativeHeight = () => this.nativeHeight;
     render() {
         TraceMobx();
+        const backgroundColor = StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document);
         return <div className="collectionFreeFormDocumentView-container"
             style={{
                 boxShadow:
@@ -148,6 +149,13 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 display: this.ZInd === -99 ? "none" : undefined,
                 pointerEvents: this.props.Document.isBackground || this.Opacity === 0 ? "none" : this.props.pointerEvents ? "all" : undefined
             }} >
+            {Doc.UserDoc().renderStyle !== "comic" ? (null) :
+                <div style={{ width: "100%", height: "100%", position: "absolute" }}>
+                    <svg style={{ transform: `scale(1,${this.props.PanelHeight() / this.props.PanelWidth()})`, transformOrigin: "top left", overflow: "visible" }} viewBox="0 0 12 14">
+                        <path d="M 7 0 C 9 -1 13 1 12 4 C 11 10 13 12 10 12 C 6 12 7 13 2 12 Q -1 11 0 8 C 1 4 0 4 0 2 C 0 0 1 0 1 0 C 3 0 3 1 7 0"
+                            style={{ stroke: "black", fill: backgroundColor, strokeWidth: 0.2 }} />
+                    </svg>
+                </div>}
 
             {!this.props.fitToBox ?
                 <DocumentView {...this.props}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 340fa06a8..2048be6fb 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1118,7 +1118,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     render() {
         if (!(this.props.Document instanceof Doc)) return (null);
-        const backgroundColor = StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document);
+        const backgroundColor = Doc.UserDoc().renderStyle === "comic" ? undefined : StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document);
         const finalColor = this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc._viewType === CollectionViewType.Linear ? undefined : backgroundColor;
         const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document);
         const borderRounding = this.layoutDoc.borderRounding;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 5e33e7e70..a00eb366b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -206,6 +206,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 if ((!curTemp && !curProto) || curText || curLayout?.Data.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
                     if (json !== curLayout?.Data) {
                         !curText && tx.storedMarks?.map(m => m.type.name === "pFontSize" && (Doc.UserDoc().fontSize = this.layoutDoc._fontSize = m.attrs.fontSize));
+                        !curText && tx.storedMarks?.map(m => m.type.name === "pFontFamily" && (Doc.UserDoc().fontFamily = this.layoutDoc._fontFamily = m.attrs.fontFamily));
                         this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText);
                         this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
                     }
@@ -1230,7 +1231,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     style={{
                         width: "100%",
                         height: this.props.height ? this.props.height : this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : `calc(100% - ${this.props.ChromeHeight?.() || 0}px`,
-                        background: this.props.background ? this.props.background : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : ""),
+                        background: Doc.UserDoc().renderStyle === "comic" ? "transparent" : this.props.background ? this.props.background : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : ""),
                         opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
                         color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.hideOnLeave ? "white" : "inherit"),
                         pointerEvents: interactive ? "none" : undefined,
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index 14b0dedc3..6866b01e3 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -22,6 +22,7 @@ import { publicDirectory } from '.';
 import { logPort, } from './ActionUtilities';
 import { blue, yellow } from 'colors';
 import * as cors from "cors";
+var https = require('https');
 
 /* RouteSetter is a wrapper around the server that prevents the server
    from being exposed. */
@@ -49,27 +50,25 @@ export default async function InitializeServer(routeSetter: RouteSetter) {
     registerRelativePath(app);
 
     const serverPort = isRelease ? Number(process.env.serverPort) : 1050;
-    const server = app.listen(serverPort, () => {
-        logPort("server", serverPort);
-        console.log();
-    });
 
-    // var express = require('express')
-    // var fs = require('fs')
-    // var https = require('https')
-    // var app = express()
-
-    // app.get('/', function (req, res) {
-    //   res.send('hello world')
-    // })
-
-    // https.createServer({
-    //   key: fs.readFileSync('server.key'),
-    //   cert: fs.readFileSync('server.cert')
-    // }, app)
-    // .listen(3000, function () {
-    //   console.log('Example app listening on port 3000! Go to https://localhost:3000/')
-    // })
+    let server: any;
+    if (isRelease) {
+        server = https.createServer({
+            key: fs.readFileSync(`./${process.env.serverName}.key`),
+            cert: fs.readFileSync(`./${process.env.serverName}.cert`)
+        }, app);
+        server.listen(serverPort, function () {
+            logPort("server", serverPort);
+            console.log();
+            // console.log('Example app listening on port 3000! Go to https://localhost:3000/')
+        });
+    } else {
+        server = app.listen(serverPort, () => {
+            logPort("server", serverPort);
+            console.log();
+        });
+    }
+
     disconnect = async () => new Promise<Error>(resolve => server.close(resolve));
 
     return isRelease;
-- 
cgit v1.2.3-70-g09d2


From ceb616873f76ee19549306a735a09697a56291a2 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 16 May 2020 20:03:34 -0400
Subject: fixed copying of computed fields (bug when aliasing animated
 collections).  fixed opacity for presentation view. renamed timecode to match
 with video timecodes.

---
 .../views/collections/CollectionStackingView.tsx   |  1 +
 src/client/views/collections/CollectionSubView.tsx |  1 +
 src/client/views/collections/CollectionView.tsx    |  1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 45 +++++++++++-----------
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 15 ++++----
 src/client/views/nodes/DocumentView.tsx            |  5 ++-
 src/client/views/nodes/PresBox.tsx                 |  3 +-
 .../views/presentationview/PresElementBox.tsx      |  1 +
 src/fields/Doc.ts                                  |  2 +-
 src/fields/ScriptField.ts                          |  8 +++-
 10 files changed, 47 insertions(+), 35 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index cc6077d98..421bf90c1 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -213,6 +213,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
             onClick={this.onChildClickHandler}
             onDoubleClick={this.onChildDoubleClickHandler}
             ScreenToLocalTransform={dxf}
+            opacity={this.props.childOpacity}
             focus={this.focusDocument}
             ContainingCollectionDoc={this.props.CollectionView?.props.Document}
             ContainingCollectionView={this.props.CollectionView}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index c9eb08b45..0827f8782 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -44,6 +44,7 @@ export interface SubCollectionViewProps extends CollectionViewProps {
     CollectionView: Opt<CollectionView>;
     children?: never | (() => JSX.Element[]) | React.ReactNode;
     ChildLayoutTemplate?: () => Doc;
+    childOpacity?:() => number;
     ChildLayoutString?: string;
     childClickScript?: ScriptField;
     childDoubleClickScript?: ScriptField;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 3b2e5e4fc..acb66acdb 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -72,6 +72,7 @@ export interface CollectionViewCustomProps {
     filterAddDocument: (doc: Doc | Doc[]) => boolean;  // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
     childLayoutTemplate?: () => Opt<Doc>;  // specify a layout Doc template to use for children of the collection
     childLayoutString?: string;  // specify a layout string to use for children of the collection
+    childOpacity?:() => number;
 }
 
 export interface CollectionRenderProps {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d27f3bb0d..e66e95d81 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -54,7 +54,8 @@ export const panZoomSchema = createSchema({
     _panX: "number",
     _panY: "number",
     scale: "number",
-    timecode: "number",
+    currentTimecode: "number",
+    displayTimecode: "number",
     arrangeScript: ScriptField,
     arrangeInit: ScriptField,
     useClusters: "boolean",
@@ -126,8 +127,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         this.addDocument(newBox);
     }
     addDocument = (newBox: Doc | Doc[]) => {
-        if (this.Document.timecode !== undefined) {
-            CollectionFreeFormDocumentView.setupKeyframes((newBox instanceof Doc) ? [newBox] : newBox, this.Document.timecode, this.props.Document);
+        if (this.Document.currentTimecode !== undefined) {
+            CollectionFreeFormDocumentView.setupKeyframes((newBox instanceof Doc) ? [newBox] : newBox, this.Document.currentTimecode, this.props.Document);
         }
 
         if (newBox instanceof Doc) {
@@ -144,23 +145,24 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @undoBatch
     @action
     nextKeyframe = (): void => {
-        if (this.props.Document.timecode === undefined) {
-            this.props.Document.timecode = 0;
+        const currentTimecode = this.Document.currentTimecode;
+        if (currentTimecode === undefined) {
+            this.Document.currentTimecode = 0;
             CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0, this.props.Document);
         }
-        const timecode = NumCast(this.props.Document.timecode);
-        CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, timecode);
-        this.props.Document.timecode = Math.max(0, timecode + 1);
+        CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentTimecode || 0);
+        this.Document.currentTimecode = Math.max(0, (currentTimecode || 0) + 1);
     }
     @undoBatch
     @action
     prevKeyframe = (): void => {
-        if (this.props.Document.timecode === undefined) {
-            this.props.Document.timecode = 0;
+        const currentTimecode = this.Document.currentTimecode;
+        if (currentTimecode === undefined) {
+            this.Document.currentTimecode = 0;
             CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0, this.props.Document);
         }
         CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice());
-        this.props.Document.timecode = Math.max(0, NumCast(this.props.Document.timecode) - 1);
+        this.Document.currentTimecode = Math.max(0, (currentTimecode || 0) - 1);
     }
 
     private selectDocuments = (docs: Doc[]) => {
@@ -203,8 +205,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                         for (let i = 0; i < droppedDocs.length; i++) {
                             const d = droppedDocs[i];
                             const layoutDoc = Doc.Layout(d);
-                            if (this.Document.timecode !== undefined) {
-                                CollectionFreeFormDocumentView.setValues(this.Document.timecode, d, x + NumCast(d.x) - dropX, y + NumCast(d.y) - dropY, Cast(d.opacity, "number", null));
+                            if (this.Document.currentTimecode !== undefined) {
+                                CollectionFreeFormDocumentView.setValues(this.Document.currentTimecode, d, x + NumCast(d.x) - dropX, y + NumCast(d.y) - dropY, Cast(d.opacity, "number", null));
                             } else {
                                 d.x = x + NumCast(d.x) - dropX;
                                 d.y = y + NumCast(d.y) - dropY;
@@ -975,11 +977,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             return { x: 0, y: 0, transition: "transform 1s", ...result, pair: params.pair, replica: "" };
         }
         const layoutDoc = Doc.Layout(params.pair.layout);
-        const { x, y, opacity } = this.Document.timecode === undefined ? params.pair.layout :
-            CollectionFreeFormDocumentView.getValues(params.pair.layout, this.Document.timecode);
-        if (this.Document.timecode !== undefined) {
-            const time = this.Document.timecode || 0;
-        }
+        const { x, y, opacity } = this.Document.currentTimecode === undefined ? params.pair.layout :
+            CollectionFreeFormDocumentView.getValues(params.pair.layout, this.Document.currentTimecode || 0);
         const { z, color, zIndex } = params.pair.layout;
         return {
             x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"),
@@ -1043,8 +1042,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     doFreeformLayout(poolData: Map<string, PoolData>) {
         const layoutDocs = this.childLayoutPairs.map(pair => pair.layout);
         const initResult = this.Document.arrangeInit && this.Document.arrangeInit.script.run({ docs: layoutDocs, collection: this.Document }, console.log);
-        const state = initResult && initResult.success ? initResult.result.scriptState : undefined;
-        const elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : [];
+        const state = initResult?.success ? initResult.result.scriptState : undefined;
+        const elements = initResult?.success ? this.viewDefsToJSX(initResult.result.views) : [];
 
         this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => {
             const pos = this.getCalculatedPositions({ pair, index: i, collection: this.Document, docs: layoutDocs, state });
@@ -1075,7 +1074,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             for (const entry of array) {
                 const lastPos = this._cachedPool.get(entry[0]); // last computed pos
                 const newPos = entry[1];
-                if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex) {
+                if (!lastPos || newPos.opacity !== lastPos.opacity || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex) {
                     this._layoutPoolData.set(entry[0], newPos);
                 }
                 if (!lastPos || newPos.height !== lastPos.height || newPos.width !== lastPos.width) {
@@ -1354,13 +1353,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             {!this.Document._LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ?
                 this.placeholder : this.marqueeView}
             <CollectionFreeFormOverlayView elements={this.elementFunc} />
-            {this.isAnnotationOverlay ? (null) :
+            {this.isAnnotationOverlay || !this.props.isSelected() ? (null) :
                 <>
                     <div key="back" className="backKeyframe" onClick={this.prevKeyframe}>
                         <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
                     </div>
                     <div key="num" className="numKeyframe" >
-                        {NumCast(this.props.Document.timecode)}
+                        {NumCast(this.props.Document.currentTimecode)}
                     </div>
                     <div key="fwd" className="fwdKeyframe" onClick={this.nextKeyframe}>
                         <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index c9bb543f1..5f0e28db2 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -36,11 +36,11 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         const rnd = seed / 233280;
         return min + rnd * (max - min);
     }
-    get displayName() { return "CollectionFreeFormDocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
+    get displayName() { return "CollectionFreeFormDocumentView(" + this.rootDoc.title + ")"; } // this makes mobx trace() statements more descriptive
     get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${this.random(-1, 1) * this.props.jitterRotation}deg)`; }
     get X() { return this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); }
     get Y() { return this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); }
-    get Opacity() { return this.dataProvider ? this.dataProvider.opacity : (this.Document.opacity || 0); }
+    get Opacity() { return this.dataProvider ? this.dataProvider.opacity : Cast(this.layoutDoc.opacity, "number", null); }
     get ZInd() { return this.dataProvider ? this.dataProvider.zIndex : (this.Document.zIndex || 0); }
     get Highlight() { return this.dataProvider?.highlight; }
     get width() { return this.props.sizeProvider && this.sizeProvider ? this.sizeProvider.width : this.layoutDoc[WidthSym](); }
@@ -108,10 +108,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             (doc["x-indexed"] as any).push(NumCast(doc.x));
             (doc["y-indexed"] as any).push(NumCast(doc.y));
             (doc["opacity-indexed"] as any).push(NumCast(doc.opacity, 1));
-            doc.timecode = ComputedField.MakeFunction("collection.timecode", {}, { collection });
-            doc.x = ComputedField.MakeInterpolated("x", "timecode");
-            doc.y = ComputedField.MakeInterpolated("y", "timecode");
-            doc.opacity = ComputedField.MakeInterpolated("opacity", "timecode");
+            doc.displayTimecode = ComputedField.MakeFunction("collection ? collection.currentTimecode : 0", {}, { collection });
+            doc.x = ComputedField.MakeInterpolated("x", "displayTimecode");
+            doc.y = ComputedField.MakeInterpolated("y", "displayTimecode");
+            doc.opacity = ComputedField.MakeInterpolated("opacity", "displayTimecode");
         });
     }
 
@@ -125,6 +125,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     panelHeight = () => (this.sizeProvider?.height || this.props.PanelHeight?.());
     getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.X, -this.Y).scale(1 / this.contentScaling());
     focusDoc = (doc: Doc) => this.props.focus(doc, false);
+    opacity = () => this.Opacity;
     NativeWidth = () => this.nativeWidth;
     NativeHeight = () => this.nativeHeight;
     render() {
@@ -144,7 +145,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 transition: this.props.transition ? this.props.transition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.transition),
                 width: this.width,
                 height: this.height,
-                opacity: this.Opacity,
                 zIndex: this.ZInd,
                 display: this.ZInd === -99 ? "none" : undefined,
                 pointerEvents: this.props.Document.isBackground || this.Opacity === 0 ? "none" : this.props.pointerEvents ? "all" : undefined
@@ -164,6 +164,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     ContentScaling={this.contentScaling}
                     ScreenToLocalTransform={this.getTransform}
                     backgroundColor={this.props.backgroundColor}
+                    opacity={this.opacity}
                     NativeHeight={this.NativeHeight}
                     NativeWidth={this.NativeWidth}
                     PanelWidth={this.panelWidth}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2048be6fb..7bb945264 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -92,6 +92,7 @@ export interface DocumentViewProps {
     pinToPres: (document: Doc) => void;
     backgroundHalo?: () => boolean;
     backgroundColor?: (doc: Doc) => string | undefined;
+    opacity?: () => number;
     ChromeHeight?: () => number;
     dontRegisterView?: boolean;
     layoutKey?: string;
@@ -1119,6 +1120,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     render() {
         if (!(this.props.Document instanceof Doc)) return (null);
         const backgroundColor = Doc.UserDoc().renderStyle === "comic" ? undefined : StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document);
+        const opacity = Cast(this.layoutDoc._opacity, "number", Cast(this.layoutDoc.opacity, "number", Cast(this.Document.opacity, "number", null)));
+        const finalOpacity = this.props.opacity ? this.props.opacity() : opacity;
         const finalColor = this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc._viewType === CollectionViewType.Linear ? undefined : backgroundColor;
         const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document);
         const borderRounding = this.layoutDoc.borderRounding;
@@ -1155,7 +1158,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined,
                 boxShadow: this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined,
                 background: finalColor,
-                opacity: this.Document.opacity,
+                opacity: finalOpacity,
                 fontFamily: StrCast(this.Document._fontFamily, "inherit"),
                 fontSize: Cast(this.Document._fontSize, "number", null)
             }}>
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 342a8a215..6edcae417 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -5,7 +5,7 @@ import { observer } from "mobx-react";
 import { Doc, DocListCast, DocCastAsync } from "../../../fields/Doc";
 import { InkTool } from "../../../fields/InkField";
 import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
-import { returnFalse } from "../../../Utils";
+import { returnFalse, returnOne } from "../../../Utils";
 import { documentSchema } from "../../../fields/documentSchemas";
 import { DocumentManager } from "../../util/DocumentManager";
 import { undoBatch } from "../../util/UndoManager";
@@ -316,6 +316,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
                         PanelWidth={this.props.PanelWidth}
                         PanelHeight={this.panelHeight}
                         moveDocument={returnFalse}
+                        childOpacity={returnOne}
                         childLayoutTemplate={this.childLayoutTemplate}
                         filterAddDocument={this.addDocumentFilter}
                         removeDocument={returnFalse}
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 280ba9093..26c8aa80a 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -177,6 +177,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
                     focus={emptyFunction}
                     whenActiveChanged={returnFalse}
                     bringToFront={returnFalse}
+                    opacity={returnOne}
                     ContainingCollectionView={undefined}
                     ContainingCollectionDoc={undefined}
                     ContentScaling={returnOne}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index a1e1e11b1..9f38f4369 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -574,7 +574,7 @@ export namespace Doc {
                 if (field instanceof RefField) {
                     copy[key] = field;
                 } else if (cfield instanceof ComputedField) {
-                    copy[key] = ComputedField.MakeFunction(cfield.script.originalScript);
+                    copy[key] = cfield[Copy]();// ComputedField.MakeFunction(cfield.script.originalScript);
                 } else if (field instanceof ObjectField) {
                     copy[key] = doc[key] instanceof Doc ?
                         key.includes("layout[") ? Doc.MakeCopy(doc[key] as Doc, false) : doc[key] : // reference documents except copy documents that are expanded teplate fields 
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 0ac1ac360..3a4cdbdf8 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -140,10 +140,14 @@ export class ComputedField extends ScriptField {
     _valueOutsideReaction = (doc: Doc) => this._lastComputedResult = this.script.run({ this: doc, self: Cast(doc.rootDocument, Doc, null) || doc, _last_: this._lastComputedResult }, console.log).result;
 
 
+    [Copy](): ObjectField {
+        return new ComputedField(this.script);
+    }
+
     constructor(script: CompiledScript, setterscript?: CompiledScript) {
         super(script,
-            !setterscript && script?.originalScript.includes("self.timecode") ?
-                ScriptField.CompileScript("self['x' + self.timecode] = value", { value: "any" }, true) : setterscript);
+            !setterscript && script?.originalScript.includes("self.displayTimecode") ?
+                ScriptField.CompileScript("self['x' + self.displayTimecode] = value", { value: "any" }, true) : setterscript);
     }
 
     public static MakeScript(script: string, params: object = {}) {
-- 
cgit v1.2.3-70-g09d2


From 2882dfce48e434f0c0b6a5837fc6212cad1df131 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 17 May 2020 19:43:53 -0400
Subject: fixed presentations to step through slides with "animations"

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  1 +
 src/client/views/nodes/PresBox.tsx                 | 24 +++++++++++++++++-----
 src/fields/ScriptField.ts                          |  6 ------
 3 files changed, 20 insertions(+), 11 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index e66e95d81..dc7d7918a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -152,6 +152,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         }
         CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentTimecode || 0);
         this.Document.currentTimecode = Math.max(0, (currentTimecode || 0) + 1);
+        this.Document.lastTimecode = Math.max(NumCast(this.Document.currentTimecode), NumCast(this.Document.lastTimecode));
     }
     @undoBatch
     @action
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 6edcae417..f4b83dd53 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -15,7 +15,7 @@ import { InkingControl } from "../InkingControl";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./PresBox.scss";
 import { ViewBoxBaseComponent } from "../DocComponent";
-import { makeInterface } from "../../../fields/Schema";
+import { makeInterface, listSpec } from "../../../fields/Schema";
 import { List } from "../../../fields/List";
 import { Docs } from "../../documents/Documents";
 import { PrefetchProxy } from "../../../fields/Proxy";
@@ -59,7 +59,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
     @action
     next = () => {
         this.updateCurrentPresentation();
-        if (this.childDocs[this.itemIndex + 1] !== undefined) {
+        const presTargetDoc = Cast(this.childDocs[this.itemIndex].presentationTargetDoc, Doc, null);
+        const lastFrame = Cast(presTargetDoc.lastTimecode, "number", null);
+        const curFrame = NumCast(presTargetDoc.currentTimecode);
+        if (lastFrame !== undefined && curFrame < lastFrame) {
+            presTargetDoc.currentTimecode = curFrame + 1;
+        }
+        else if (this.childDocs[this.itemIndex + 1] !== undefined) {
             let nextSelected = this.itemIndex + 1;
             this.gotoDocument(nextSelected, this.itemIndex);
 
@@ -188,11 +194,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
 
     //The function that is called when a document is clicked or reached through next or back.
     //it'll also execute the necessary actions if presentation is playing.
-    public gotoDocument = (index: number, fromDoc: number) => {
+    public gotoDocument = action((index: number, fromDoc: number) => {
         this.updateCurrentPresentation();
         Doc.UnBrushAllDocs();
         if (index >= 0 && index < this.childDocs.length) {
             this.rootDoc._itemIndex = index;
+            const presTargetDoc = Cast(this.childDocs[this.itemIndex].presentationTargetDoc, Doc, null);
+            if (presTargetDoc.lastTimecode !== undefined) {
+                presTargetDoc.currentTimecode = 0;
+            }
 
             if (!this.layoutDoc.presStatus) {
                 this.layoutDoc.presStatus = true;
@@ -203,7 +213,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
             this.hideIfNotPresented(index);
             this.showAfterPresented(index);
         }
-    }
+    })
 
     //The function that starts or resets presentaton functionally, depending on status flag.
     startOrResetPres = () => {
@@ -286,7 +296,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
         (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
 
     render() {
-        this.rootDoc.presOrderedDocs = new List<Doc>(this.childDocs.map((child, i) => child));
+        console.log("render = " + this.layoutDoc.title + " " + this.layoutDoc.presStatus);
+        const presOrderedDocs = DocListCast(this.rootDoc.presOrderedDocs);
+        if (presOrderedDocs.length != this.childDocs.length || presOrderedDocs.some((pd, i) => pd !== this.childDocs[i])) {
+            this.rootDoc.presOrderedDocs = new List<Doc>(this.childDocs.slice());
+        }
         const mode = StrCast(this.rootDoc._viewType) as CollectionViewType;
         return <div className="presBox-cont" style={{ minWidth: this.layoutDoc.inOverlay ? 240 : undefined }} >
             <div className="presBox-buttons" style={{ display: this.rootDoc._chromeStatus === "disabled" ? "none" : undefined }}>
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 3a4cdbdf8..566390c0d 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -144,12 +144,6 @@ export class ComputedField extends ScriptField {
         return new ComputedField(this.script);
     }
 
-    constructor(script: CompiledScript, setterscript?: CompiledScript) {
-        super(script,
-            !setterscript && script?.originalScript.includes("self.displayTimecode") ?
-                ScriptField.CompileScript("self['x' + self.displayTimecode] = 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;
-- 
cgit v1.2.3-70-g09d2


From 4c4e92cf70ef475a379ca8a9368ee35dd4f197ed Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 18 May 2020 13:09:02 -0400
Subject: fixes for youtube video snapshots

---
 src/client/views/collections/CollectionSubView.tsx       |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx        |  4 ++--
 src/client/views/nodes/VideoBox.tsx                      | 16 +++++++++-------
 3 files changed, 12 insertions(+), 10 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 33c75a274..673cce14f 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -356,7 +356,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
 
             if (text) {
                 if (text.includes("www.youtube.com/watch")) {
-                    const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/");
+                    const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/").split("&")[0];
                     addDocument(Docs.Create.VideoDocument(url, {
                         ...options,
                         title: url,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index dc7d7918a..d8fe662ae 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -127,7 +127,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         this.addDocument(newBox);
     }
     addDocument = (newBox: Doc | Doc[]) => {
-        if (this.Document.currentTimecode !== undefined) {
+        if (this.Document.currentTimecode !== undefined && !this.props.isAnnotationOverlay) {
             CollectionFreeFormDocumentView.setupKeyframes((newBox instanceof Doc) ? [newBox] : newBox, this.Document.currentTimecode, this.props.Document);
         }
 
@@ -206,7 +206,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                         for (let i = 0; i < droppedDocs.length; i++) {
                             const d = droppedDocs[i];
                             const layoutDoc = Doc.Layout(d);
-                            if (this.Document.currentTimecode !== undefined) {
+                            if (this.Document.currentTimecode !== undefined && !this.props.isAnnotationOverlay) {
                                 CollectionFreeFormDocumentView.setValues(this.Document.currentTimecode, d, x + NumCast(d.x) - dropX, y + NumCast(d.y) - dropY, Cast(d.opacity, "number", null));
                             } else {
                                 d.x = x + NumCast(d.x) - dropX;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index ccf1f5588..bcbd65258 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -104,18 +104,20 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
         canvas.height = 640 * (this.layoutDoc._nativeHeight || 0) / (this.layoutDoc._nativeWidth || 1);
         const ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
         if (ctx) {
-            ctx.rect(0, 0, canvas.width, canvas.height);
-            ctx.fillStyle = "blue";
-            ctx.fill();
+            // ctx.rect(0, 0, canvas.width, canvas.height);
+            // ctx.fillStyle = "blue";
+            // ctx.fill();
             this._videoRef && ctx.drawImage(this._videoRef, 0, 0, canvas.width, canvas.height);
         }
 
         if (!this._videoRef) { // can't find a way to take snapshots of videos
-            const b = Docs.Create.ButtonDocument({
+            const b = Docs.Create.LabelDocument({
                 x: (this.layoutDoc.x || 0) + width, y: (this.layoutDoc.y || 1),
-                _width: 150, _height: 50, title: (this.layoutDoc.currentTimecode || 0).toString()
+                _width: 150, _height: 50, title: (this.layoutDoc.currentTimecode || 0).toString(),
             });
-            b.onClick = ScriptField.MakeScript(`this.currentTimecode = ${(this.layoutDoc.currentTimecode || 0)}`);
+            b.isLinkButton = true;
+            this.props.addDocument?.(b);
+            DocUtils.MakeLink({ doc: b }, { doc: this.rootDoc }, "video snapshot");
         } else {
             //convert to desired file format
             const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
@@ -132,7 +134,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
                     Doc.GetProto(imageSummary)["data-nativeWidth"] = this.layoutDoc._nativeWidth;
                     Doc.GetProto(imageSummary)["data-nativeHeight"] = this.layoutDoc._nativeHeight;
                     imageSummary.isLinkButton = true;
-                    this.props.addDocument && this.props.addDocument(imageSummary);
+                    this.props.addDocument?.(imageSummary);
                     DocUtils.MakeLink({ doc: imageSummary }, { doc: this.rootDoc }, "video snapshot");
                 }
             });
-- 
cgit v1.2.3-70-g09d2


From beece97c0645b1466b2c4210b594220b84953e04 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 18 May 2020 15:37:57 -0400
Subject: added a button to copy a workspace

---
 src/client/views/MainView.tsx                          | 18 +++++++++++++-----
 .../collectionFreeForm/CollectionFreeFormView.tsx      |  2 --
 src/client/views/nodes/DocumentView.tsx                |  2 +-
 3 files changed, 14 insertions(+), 8 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 4a20c39d4..1d6421656 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -186,16 +186,17 @@ export class MainView extends React.Component {
             _LODdisable: true
         };
         const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
-        const mainDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600, path: [Doc.UserDoc().myCatalog as Doc] }], { title: `Workspace ${workspaceCount}` }, id, "row");
+        const workspaceDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600, path: [Doc.UserDoc().myCatalog as Doc] }], { title: `Workspace ${workspaceCount}` }, id, "row");
 
         const toggleTheme = ScriptField.MakeScript(`self.darkScheme = !self.darkScheme`);
         const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
-        mainDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!, toggleComic!]);
-        mainDoc.contextMenuLabels = new List<string>(["Toggle Theme Colors", "Toggle Comic Mode"]);
+        const cloneWorkspace = ScriptField.MakeScript(`cloneWorkspace()`);
+        workspaceDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!, toggleComic!, cloneWorkspace!]);
+        workspaceDoc.contextMenuLabels = new List<string>(["Toggle Theme Colors", "Toggle Comic Mode", "New Workspace Layout"]);
 
-        Doc.AddDocToList(workspaces, "data", mainDoc);
+        Doc.AddDocToList(workspaces, "data", workspaceDoc);
         // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
-        setTimeout(() => this.openWorkspace(mainDoc), 0);
+        setTimeout(() => this.openWorkspace(workspaceDoc), 0);
     }
 
     @action
@@ -564,3 +565,10 @@ export class MainView extends React.Component {
 }
 Scripting.addGlobal(function freezeSidebar() { MainView.expandFlyout(); });
 Scripting.addGlobal(function toggleComicMode() { Doc.UserDoc().fontFamily = "Comic Sans MS"; Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic" });
+Scripting.addGlobal(function cloneWorkspace() {
+    const copiedWorkspace = Doc.MakeCopy(Cast(Doc.UserDoc().activeWorkspace, Doc, null), true);
+    const workspaces = Cast(Doc.UserDoc().myWorkspaces, Doc, null);
+    Doc.AddDocToList(workspaces, "data", copiedWorkspace);
+    // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
+    setTimeout(() => MainView.Instance.openWorkspace(copiedWorkspace), 0);
+});
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d8fe662ae..aeb0a5813 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1179,8 +1179,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 this._timelineVisible = !this._timelineVisible;
             }), icon: this._timelineVisible ? faEyeSlash : faEye
         });
-        ContextMenu.Instance.addItem({ description: "Advance", event: this.nextKeyframe, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
-        ContextMenu.Instance.addItem({ description: "Backup ", event: this.prevKeyframe, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
 
         const options = ContextMenu.Instance.findByDescription("Options...");
         const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7bb945264..b9d33da90 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1167,7 +1167,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 <div className="documentView-contentBlocker" />
             </> :
                 this.innards}
-            {(this.Document.isBackground !== undefined || this.isSelected(false)) && this.props.renderDepth > 0 && this.props.PanelWidth() > 0 ?
+            {(this.Document.isBackground !== undefined || this.isSelected(false)) && (this.Document.type === DocumentType.COL || this.Document.type === DocumentType.IMG) && this.props.renderDepth > 0 && this.props.PanelWidth() > 0 ?
                 <div className="documentView-lock" onClick={() => this.toggleBackground(true)}>
                     <FontAwesomeIcon icon={this.Document.isBackground ? "unlock" : "lock"} style={{ color: this.Document.isBackground ? "red" : undefined }} size="lg" />
                 </div>
-- 
cgit v1.2.3-70-g09d2


From d430c5575e2ac8d38239687c8309248e6f7f712b Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 19 May 2020 20:33:50 -0400
Subject: added nav buttons to webbox.  fixed drag/drop from bing (using
 UseCors)

---
 src/client/views/MainView.tsx                      |  9 +++-
 src/client/views/collections/CollectionSubView.tsx | 20 ++++----
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  1 +
 .../collections/collectionFreeForm/MarqueeView.tsx |  3 +-
 src/client/views/nodes/WebBox.tsx                  | 59 +++++++++++++++++-----
 5 files changed, 67 insertions(+), 25 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 1d6421656..50d445473 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -139,7 +139,10 @@ export class MainView extends React.Component {
 
     initEventListeners = () => {
         window.addEventListener("drop", (e) => { e.preventDefault(); }, false); // drop event handler
-        window.addEventListener("dragover", (e) => e.preventDefault(), false); // drag event handler
+        window.addEventListener("dragover", (e) => {
+            console.log("MDRAG");
+            e.preventDefault();
+        }, false); // drag event handler
         // click interactions for the context menu
         document.addEventListener("pointerdown", this.globalPointerDown);
         document.addEventListener("pointerup", this.globalPointerUp);
@@ -310,7 +313,9 @@ export class MainView extends React.Component {
         const width = this.flyoutWidth;
         return <Measure offset onResize={this.onResize}>
             {({ measureRef }) =>
-                <div ref={measureRef} className="mainContent-div" onDrop={this.onDrop} style={{ width: `calc(100% - ${width}px)` }}>
+                <div ref={measureRef} className="mainContent-div" onDragEnter={e => {
+                    console.log("ENTERING");
+                }} onDrop={this.onDrop} style={{ width: `calc(100% - ${width}px)` }}>
                     {!mainContainer ? (null) : this.mainDocView}
                 </div>
             }
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 673cce14f..208925b1c 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -330,7 +330,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                             let srcWeb: Doc | undefined;
                             if (SelectionManager.SelectedDocuments().length) {
                                 srcWeb = SelectionManager.SelectedDocuments()[0].props.Document;
-                                srcUrl = (srcWeb.data as WebField).url.href.match(/http[s]?:\/\/[^/]*/)?.[0];
+                                srcUrl = (srcWeb.data as WebField).url.href?.match(/http[s]?:\/\/[^/]*/)?.[0];
                             }
                             let reg = new RegExp(Utils.prepend(""), "g");
                             const modHtml = srcUrl ? html.replace(reg, srcUrl) : html;
@@ -339,14 +339,16 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                             this.props.addDocument(htmlDoc);
                             if (srcWeb) {
                                 const focusNode = (SelectionManager.SelectedDocuments()[0].ContentDiv?.getElementsByTagName("iframe")[0].contentDocument?.getSelection()?.focusNode as any);
-                                const rect = "getBoundingClientRect" in focusNode ? focusNode.getBoundingClientRect() : focusNode?.parentElement.getBoundingClientRect();
-                                const x = (rect?.x || 0);
-                                const y = NumCast(srcWeb.scrollTop) + (rect?.y || 0);
-                                const anchor = Docs.Create.FreeformDocument([], { _LODdisable: true, _backgroundColor: "transparent", _width: 25, _height: 25, x, y, annotationOn: srcWeb });
-                                anchor.context = srcWeb;
-                                const key = Doc.LayoutFieldKey(srcWeb);
-                                Doc.AddDocToList(srcWeb, key + "-annotations", anchor);
-                                DocUtils.MakeLink({ doc: htmlDoc }, { doc: anchor });
+                                if (focusNode) {
+                                    const rect = "getBoundingClientRect" in focusNode ? focusNode.getBoundingClientRect() : focusNode?.parentElement.getBoundingClientRect();
+                                    const x = (rect?.x || 0);
+                                    const y = NumCast(srcWeb.scrollTop) + (rect?.y || 0);
+                                    const anchor = Docs.Create.FreeformDocument([], { _LODdisable: true, _backgroundColor: "transparent", _width: 25, _height: 25, x, y, annotationOn: srcWeb });
+                                    anchor.context = srcWeb;
+                                    const key = Doc.LayoutFieldKey(srcWeb);
+                                    Doc.AddDocToList(srcWeb, key + "-annotations", anchor);
+                                    DocUtils.MakeLink({ doc: htmlDoc }, { doc: anchor });
+                                }
                             }
                         }
                         return;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index aeb0a5813..0751d749d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1341,6 +1341,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             onPointerDown={this.onPointerDown}
             onPointerMove={this.onCursorMove}
             onDrop={this.onExternalDrop.bind(this)}
+            onDragOver={e =>   e.preventDefault() }
             onContextMenu={this.onContextMenu}
             style={{
                 pointerEvents: this.backgroundEvents ? "all" : undefined,
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index e2ce70c7d..0244dfc56 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -69,7 +69,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             ContextMenu.Instance.setDefaultItem("?", (str: string) => {
                 const textDoc = Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, {
                     _width: 200, x, y, _nativeHeight: 962, _nativeWidth: 800, isAnnotating: false,
-                    title: "bing"
+                    title: "bing", UseCors: true
                 });
                 this.props.addDocTab(textDoc, "onRight");
             });
@@ -624,6 +624,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
     render() {
         return <div className="marqueeView"
             style={{ overflow: StrCast(this.props.Document._overflow), cursor: MarqueeView.DragMarquee && this ? "crosshair" : "hand" }}
+            onDragOver={e => e.preventDefault()}
             onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} onClick={this.onClick} onPointerDown={this.onPointerDown}>
             {this._visible ? this.marqueeDiv : null}
             {this.props.children}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 8844702d7..63bdbdc5c 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,12 +1,12 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faStickyNote, faPen, faMousePointer } from '@fortawesome/free-solid-svg-icons';
-import { action, computed, observable, trace, IReactionDisposer, reaction } from "mobx";
+import { action, computed, observable, trace, IReactionDisposer, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, FieldResult } from "../../../fields/Doc";
 import { documentSchema } from "../../../fields/documentSchemas";
 import { HtmlField } from "../../../fields/HtmlField";
 import { InkTool } from "../../../fields/InkField";
-import { makeInterface } from "../../../fields/Schema";
+import { makeInterface, listSpec } from "../../../fields/Schema";
 import { Cast, NumCast, BoolCast, StrCast } from "../../../fields/Types";
 import { WebField } from "../../../fields/URLField";
 import { Utils, returnOne, emptyFunction, returnZero } from "../../../Utils";
@@ -25,6 +25,7 @@ import { CollectionFreeFormView } from "../collections/collectionFreeForm/Collec
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
 import { undoBatch } from "../../util/UndoManager";
+import { List } from "../../../fields/List";
 const htmlToText = require("html-to-text");
 
 library.add(faStickyNote);
@@ -79,8 +80,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         this.layoutDoc.scrollTop = this._outerRef.current!.scrollTop = scroll;
     }
     async componentDidMount() {
-
-        this.setURL();
+        const urlField = Cast(this.dataDoc[this.props.fieldKey], WebField);
+        runInAction(() => this._url = urlField?.url.toString() || "");
 
         document.addEventListener("pointerup", this.onLongPressUp);
         document.addEventListener("pointermove", this.onLongPressMove);
@@ -116,18 +117,44 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
     }
 
     @action
-    submitURL = () => {
-        if (!this._url.startsWith("http")) this._url = "http://" + this._url;
-        this.dataDoc[this.props.fieldKey] = new WebField(new URL(this._url));
+    forward = () => {
+        const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), null);
+        const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), null);
+        if (future.length) {
+            history.push(this._url);
+            this.dataDoc[this.fieldKey] = new WebField(new URL(this._url = future.pop()!));
+        }
     }
 
     @action
-    setURL() {
-        const urlField: FieldResult<WebField> = Cast(this.dataDoc[this.props.fieldKey], WebField);
-        if (urlField) this._url = urlField.url.toString();
-        else this._url = "";
+    back = () => {
+        const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), null);
+        const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), null);
+        if (history.length) {
+            if (future === undefined) this.dataDoc[this.fieldKey + "-future"] = new List<string>([this._url]);
+            else future.push(this._url);
+            this.dataDoc[this.fieldKey] = new WebField(new URL(this._url = history.pop()!));
+        }
     }
 
+    @action
+    submitURL = () => {
+        if (!this._url.startsWith("http")) this._url = "http://" + this._url;
+        const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), null);
+        const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), null);
+        const url = Cast(this.dataDoc[this.fieldKey], WebField, null)?.url.toString();
+        if (url) {
+            if (history === undefined) {
+                this.dataDoc[this.fieldKey + "-history"] = new List<string>([url]);
+            } else {
+                history.push(url);
+            }
+            future && (future.length = 0);
+        }
+        this.dataDoc[this.fieldKey] = new WebField(new URL(this._url));
+    }
+
+
     onValueKeyDown = async (e: React.KeyboardEvent) => {
         if (e.key === "Enter") {
             e.stopPropagation();
@@ -178,10 +205,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                                 display: "flex",
                                 flexDirection: "row",
                                 justifyContent: "space-between",
-                                minWidth: "100px",
+                                maxWidth: "120px",
                             }}>
                                 <button className="submitUrl" onClick={this.submitURL}>
-                                    SUBMIT
+                                    GO
+                                </button>
+                                <button className="submitUrl" onClick={this.back}>
+                                    <FontAwesomeIcon icon="caret-left" size="lg"></FontAwesomeIcon>
+                                </button>
+                                <button className="submitUrl" onClick={this.forward}>
+                                    <FontAwesomeIcon icon="caret-right" size="lg"></FontAwesomeIcon>
                                 </button>
                             </div>
                         </div>
-- 
cgit v1.2.3-70-g09d2


From 0b1039b75af01836082f9bb4613e66c6218a6117 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 20 May 2020 00:35:18 -0400
Subject: added link drag drop onto webBox url

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 +-
 src/client/views/nodes/WebBox.tsx                  | 54 ++++++++++++++++------
 src/server/server_Initialization.ts                | 28 ++++++-----
 3 files changed, 59 insertions(+), 27 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 0751d749d..c9b3519aa 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1341,7 +1341,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             onPointerDown={this.onPointerDown}
             onPointerMove={this.onCursorMove}
             onDrop={this.onExternalDrop.bind(this)}
-            onDragOver={e =>   e.preventDefault() }
+            onDragOver={e => {
+                e.preventDefault();
+            }}
             onContextMenu={this.onContextMenu}
             style={{
                 pointerEvents: this.backgroundEvents ? "all" : undefined,
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 63bdbdc5c..c1a585bca 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -116,6 +116,21 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         this._url = e.target.value;
     }
 
+    onUrlDragover = (e: React.DragEvent) => {
+        e.preventDefault();
+    }
+    @action
+    onUrlDrop = (e: React.DragEvent) => {
+        const { dataTransfer } = e;
+        const html = dataTransfer.getData("text/html");
+        const uri = dataTransfer.getData("text/uri-list");
+        const url = uri || html || this._url;
+        this._url = url.startsWith(window.location.origin) ?
+            url.replace(window.location.origin, this._url.match(/http[s]?:\/\/[^\/]*/)?.[0] || "") : url;
+        this.submitURL();
+        e.stopPropagation();
+    }
+
     @action
     forward = () => {
         const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), null);
@@ -140,21 +155,25 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
     @action
     submitURL = () => {
         if (!this._url.startsWith("http")) this._url = "http://" + this._url;
-        const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), null);
-        const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), null);
-        const url = Cast(this.dataDoc[this.fieldKey], WebField, null)?.url.toString();
-        if (url) {
-            if (history === undefined) {
-                this.dataDoc[this.fieldKey + "-history"] = new List<string>([url]);
-            } else {
-                history.push(url);
+        try {
+            const URLy = new URL(this._url);
+            const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), null);
+            const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), null);
+            const url = Cast(this.dataDoc[this.fieldKey], WebField, null)?.url.toString();
+            if (url) {
+                if (history === undefined) {
+                    this.dataDoc[this.fieldKey + "-history"] = new List<string>([url]);
+                } else {
+                    history.push(url);
+                }
+                future && (future.length = 0);
             }
-            future && (future.length = 0);
+            this.dataDoc[this.fieldKey] = new WebField(URLy);
+        } catch (e) {
+            console.log("Error in URL :" + this._url);
         }
-        this.dataDoc[this.fieldKey] = new WebField(new URL(this._url));
     }
 
-
     onValueKeyDown = async (e: React.KeyboardEvent) => {
         if (e.key === "Enter") {
             e.stopPropagation();
@@ -175,7 +194,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
 
     urlEditor() {
         return (
-            <div className="webBox-urlEditor" style={{ top: this._collapsed ? -70 : 0 }}>
+            <div className="webBox-urlEditor"
+                onDrop={this.onUrlDrop}
+                onDragOver={this.onUrlDragover} style={{ top: this._collapsed ? -70 : 0 }}>
                 <div className="urlEditor">
                     <div className="editorBase">
                         <button className="editor-collapse"
@@ -188,7 +209,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                             title="Collapse Url Editor" onClick={this.toggleCollapse}>
                             <FontAwesomeIcon icon="caret-up" size="2x" />
                         </button>
-                        <div className="webBox-buttons" style={{ display: this._collapsed ? "none" : "flex" }}>
+                        <div className="webBox-buttons"
+                            onDrop={this.onUrlDrop}
+                            onDragOver={this.onUrlDragover} style={{ display: this._collapsed ? "none" : "flex" }}>
                             <div className="webBox-freeze" title={"Annotate"} style={{ background: this.layoutDoc.isAnnotating ? "lightBlue" : "gray" }} onClick={this.toggleAnnotationMode} >
                                 <FontAwesomeIcon icon={faPen} size={"2x"} />
                             </div>
@@ -198,6 +221,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                             <input className="webpage-urlInput"
                                 placeholder="ENTER URL"
                                 value={this._url}
+                                onDrop={this.onUrlDrop}
+                                onDragOver={this.onUrlDragover}
                                 onChange={this.onURLChange}
                                 onKeyDown={this.onValueKeyDown}
                             />
@@ -207,7 +232,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                                 justifyContent: "space-between",
                                 maxWidth: "120px",
                             }}>
-                                <button className="submitUrl" onClick={this.submitURL}>
+                                <button className="submitUrl" onClick={this.submitURL}
+                                    onDragOver={this.onUrlDragover} onDrop={this.onUrlDrop}>
                                     GO
                                 </button>
                                 <button className="submitUrl" onClick={this.back}>
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index 14b8776d8..68954fa44 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -146,19 +146,23 @@ function registerCorsProxy(server: express.Express) {
             res.redirect(referer + (referer.endsWith("/") ? "" : "/") + requrl);
         }
         else {
-            req.pipe(request(requrl)).on("response", res => {
-                const headers = Object.keys(res.headers);
-                headers.forEach(headerName => {
-                    const header = res.headers[headerName];
-                    if (Array.isArray(header)) {
-                        res.headers[headerName] = header.filter(h => !headerCharRegex.test(h));
-                    } else if (header) {
-                        if (headerCharRegex.test(header as any)) {
-                            delete res.headers[headerName];
+            try {
+                req.pipe(request(requrl)).on("response", res => {
+                    const headers = Object.keys(res.headers);
+                    headers.forEach(headerName => {
+                        const header = res.headers[headerName];
+                        if (Array.isArray(header)) {
+                            res.headers[headerName] = header.filter(h => !headerCharRegex.test(h));
+                        } else if (header) {
+                            if (headerCharRegex.test(header as any)) {
+                                delete res.headers[headerName];
+                            }
                         }
-                    }
-                });
-            }).pipe(res);
+                    });
+                }).pipe(res);
+            } catch (e) {
+                console.log("problem with Cors URL: " + requrl);
+            }
         }
     });
 }
-- 
cgit v1.2.3-70-g09d2


From 8de8756e5fa8b7031092c870c84f5214e2a244c1 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 20 May 2020 12:16:56 -0400
Subject: fixed up interaction with web pages to allow horizontal scrolling.

---
 src/client/documents/Documents.ts                  |  4 +--
 src/client/views/collections/CollectionMapView.tsx |  4 +--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 11 +++++--
 src/client/views/nodes/DocumentView.tsx            |  7 -----
 src/client/views/nodes/WebBox.tsx                  | 35 ++++++++++++----------
 src/client/views/pdf/PDFViewer.tsx                 |  1 -
 src/fields/documentSchemas.ts                      |  6 ++--
 7 files changed, 36 insertions(+), 32 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 18a0b43ff..b859086d5 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -112,7 +112,7 @@ export interface DocumentOptions {
     caption?: RichTextField;
     ignoreClick?: boolean;
     lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged
-    lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed
+    _lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed
     isAnnotating?: boolean; // whether we web document is annotation mode where links can't be clicked to allow annotations to be created
     opacity?: number;
     defaultBackgroundColor?: string;
@@ -635,7 +635,7 @@ export namespace Docs {
         }
 
         export function WebDocument(url: string, options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocumentType.WEB), url ? new WebField(new URL(url)) : undefined, { _fitWidth: true, _chromeStatus: url ? "disabled" : "enabled", isAnnotating: true, lockedTransform: true, ...options });
+            return InstanceFromProto(Prototypes.get(DocumentType.WEB), url ? new WebField(new URL(url)) : undefined, { _fitWidth: true, _chromeStatus: url ? "disabled" : "enabled", isAnnotating: true, _lockedTransform: true, ...options });
         }
 
         export function HtmlDocument(html: string, options: DocumentOptions = {}) {
diff --git a/src/client/views/collections/CollectionMapView.tsx b/src/client/views/collections/CollectionMapView.tsx
index d91337ce9..a0b7cd8a8 100644
--- a/src/client/views/collections/CollectionMapView.tsx
+++ b/src/client/views/collections/CollectionMapView.tsx
@@ -226,7 +226,7 @@ class CollectionMapView extends CollectionSubView<MapSchema, Partial<IMapProps>
                     initialCenter={center}
                     center={center}
                     onIdle={(_props?: IMapProps, map?: google.maps.Map) => {
-                        if (this.layoutDoc.lockedTransform) {
+                        if (this.layoutDoc._lockedTransform) {
                             // reset zoom (ideally, we could probably can tell the map to disallow zooming somehow instead)
                             map?.setZoom(center?.zoom || 10);
                             map?.setCenter({ lat: center?.lat!, lng: center?.lng! });
@@ -238,7 +238,7 @@ class CollectionMapView extends CollectionSubView<MapSchema, Partial<IMapProps>
                         }
                     }}
                     onDragend={(_props?: IMapProps, map?: google.maps.Map) => {
-                        if (this.layoutDoc.lockedTransform) {
+                        if (this.layoutDoc._lockedTransform) {
                             // reset the drag (ideally, we could probably can tell the map to disallow dragging somehow instead)
                             map?.setCenter({ lat: center?.lat!, lng: center?.lng! });
                         } else {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c9b3519aa..972c09484 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -769,7 +769,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     @action
     onPointerWheel = (e: React.WheelEvent): void => {
-        if (this.props.Document.lockedTransform || this.props.Document.inOverlay) return;
+        if (this.layoutDoc._lockedTransform || this.props.Document.inOverlay) return;
         if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming
             e.stopPropagation();
         }
@@ -805,7 +805,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 else if (ranges.yrange.max <= (panY - panelDim[1] / 2)) panY = ranges.yrange.min - panelDim[1] / 2;
             }
         }
-        if (!this.Document.lockedTransform || this.Document.inOverlay) {
+        if (!this.layoutDoc._lockedTransform || this.Document.inOverlay) {
             this.Document.panTransformType = panType;
             const scale = this.getLocalTransform().inverse().Scale;
             const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
@@ -1169,6 +1169,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.NativeWidth(), this.props.NativeHeight());
     }
 
+    @undoBatch
+    @action
+    toggleLockTransform = (): void => {
+        this.layoutDoc._lockedTransform = this.layoutDoc._lockedTransform ? undefined : true;
+    }
+
     private thumbIdentifier?: number;
 
     onContextMenu = (e: React.MouseEvent) => {
@@ -1189,6 +1195,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         optionItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
         optionItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
         this.props.ContainingCollectionView && optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" });
+        optionItems.push({ description: this.layoutDoc._lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: this.layoutDoc._lockedTransform ? "unlock" : "lock" });
         optionItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
         // layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
         optionItems.push({
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 34aabfe5e..8dbd0232b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -683,12 +683,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this.Document.lockedPosition = this.Document.lockedPosition ? undefined : true;
     }
 
-    @undoBatch
-    @action
-    toggleLockTransform = (): void => {
-        this.Document.lockedTransform = this.Document.lockedTransform ? undefined : true;
-    }
-
     @action
     onContextMenu = async (e: React.MouseEvent | Touch): Promise<void> => {
         // the touch onContextMenu is button 0, the pointer onContextMenu is button 2
@@ -751,7 +745,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const moreItems: ContextMenuProps[] = more && "subitems" in more ? more.subitems : [];
         moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
         moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
-        moreItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
         moreItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
 
         if (!ClientUtils.RELEASE) {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index c1a585bca..1d242d958 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -59,14 +59,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
             iframe.contentDocument.addEventListener('scroll', this.iframeScrolled, false);
             this.layoutDoc.scrollHeight = iframe.contentDocument.children?.[0].scrollHeight || 1000;
             iframe.contentDocument.children[0].scrollTop = NumCast(this.layoutDoc.scrollTop);
+            iframe.contentDocument.children[0].scrollLeft = NumCast(this.layoutDoc.scrollLeft);
         }
         this._reactionDisposer?.();
-        this._reactionDisposer = reaction(() => this.layoutDoc.scrollY,
-            (scrollY) => {
-                if (scrollY !== undefined) {
-                    this._outerRef.current!.scrollTop = scrollY;
+        this._reactionDisposer = reaction(() => ({ y: this.layoutDoc.scrollY, x: this.layoutDoc.scrollX }),
+            ({ x, y }) => {
+                if (y !== undefined) {
+                    this._outerRef.current!.scrollTop = y;
                     this.layoutDoc.scrollY = undefined;
                 }
+                if (x !== undefined) {
+                    this._outerRef.current!.scrollLeft = x;
+                    this.layoutDoc.scrollX = undefined;
+                }
             },
             { fireImmediately: true }
         );
@@ -76,8 +81,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         this._setPreviewCursor?.(e.screenX, e.screenY, false);
     }
     iframeScrolled = (e: any) => {
-        const scroll = e.target?.children?.[0].scrollTop;
-        this.layoutDoc.scrollTop = this._outerRef.current!.scrollTop = scroll;
+        const scrollTop = e.target?.children?.[0].scrollTop;
+        const scrollLeft = e.target?.children?.[0].scrollLeft;
+        this.layoutDoc.scrollTop = this._outerRef.current!.scrollTop = scrollTop;
+        this.layoutDoc.scrollLeft = this._outerRef.current!.scrollLeft = scrollLeft;
     }
     async componentDidMount() {
         const urlField = Cast(this.dataDoc[this.props.fieldKey], WebField);
@@ -182,14 +189,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
     }
 
     toggleAnnotationMode = () => {
-        if (!this.layoutDoc.isAnnotating) {
-            this.layoutDoc.lockedTransform = false;
-            this.layoutDoc.isAnnotating = true;
-        }
-        else {
-            this.layoutDoc.lockedTransform = true;
-            this.layoutDoc.isAnnotating = false;
-        }
+        this.layoutDoc.isAnnotating = !this.layoutDoc.isAnnotating;
     }
 
     urlEditor() {
@@ -427,7 +427,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
             {this.urlEditor()}
         </>);
     }
-    scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.props.Document.scrollTop));
+    scrollXf = () => this.props.ScreenToLocalTransform().translate(NumCast(this.layoutDoc.scrollLeft), NumCast(this.layoutDoc.scrollTop));
     render() {
         return (<div className={`webBox-container`}
             style={{
@@ -449,10 +449,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                         if (iframe.children[0].scrollTop !== outerFrame.scrollTop) {
                             iframe.children[0].scrollTop = outerFrame.scrollTop;
                         }
+                        if (iframe.children[0].scrollLeft !== outerFrame.scrollLeft) {
+                            iframe.children[0].scrollLeft = outerFrame.scrollLeft;
+                        }
                     }
                     //this._outerRef.current!.scrollTop !== this._scrollTop && (this._outerRef.current!.scrollTop = this._scrollTop)
                 }}>
-                <div className={"webBox-innerContent"} style={{ height: NumCast(this.layoutDoc.scrollHeight) }}>
+                <div className={"webBox-innerContent"} style={{ height: NumCast(this.layoutDoc.scrollHeight), width: 4000 }}>
                     <CollectionFreeFormView {...this.props}
                         PanelHeight={this.props.PanelHeight}
                         PanelWidth={this.props.PanelWidth}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index c50969493..810ce5aea 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -125,7 +125,6 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
 
     _lastSearch: string = "";
     componentDidMount = async () => {
-        !this.props.Document.lockedTransform && (this.props.Document.lockedTransform = true);
         // change the address to be the file address of the PNG version of each page
         // file address of the pdf
         const { url: { href } } = Cast(this.dataDoc[this.props.fieldKey], PdfField)!;
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index cacba43b6..142f7e079 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -20,7 +20,9 @@ export const documentSchema = createSchema({
     z: "number",                // z "coordinate" - non-zero specifies the overlay layer of a freeformview
     zIndex: "number",           // zIndex of a document in a freeform view
     scrollY: "number",          // "command" to scroll a document to a position on load (the value will be reset to 0 after that )
+    scrollX: "number",          // "command" to scroll a document to a position on load (the value will be reset to 0 after that )
     scrollTop: "number",        // scroll position of a scrollable document (pdf, text, web)
+    scrollLeft: "number",        // scroll position of a scrollable document (pdf, text, web)
 
     // appearance properties on the layout
     _autoHeight: "boolean",     // whether the height of the document should be computed automatically based on its contents
@@ -74,8 +76,8 @@ export const documentSchema = createSchema({
     isLinkButton: "boolean",    // whether document functions as a link follow button to follow the first link on the document when clicked   
     isBackground: "boolean",    // whether document is a background element and ignores input events (can only select with marquee)
     lockedPosition: "boolean",  // whether the document can be moved (dragged)
-    lockedTransform: "boolean", // whether the document can be panned/zoomed
-
+    _lockedTransform: "boolean",// whether a freeformview can pan/zoom
+    
     // drag drop properties
     dragFactory: Doc,           // the document that serves as the "template" for the onDragStart script.  ie, to drag out copies of the dragFactory document.
     dropAction: "string",       // override specifying what should happen when this document is dropped (can be "alias", "copy", "move")
-- 
cgit v1.2.3-70-g09d2


From a3506d7a8964a113f10bdce672a86678b992b653 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 21 May 2020 19:25:56 -0400
Subject: more fixes to progressivizing.   tweaks to comparison box's clear
 button.

---
 package-lock.json                                  | 372 ++++++++++++++++++++-
 package.json                                       |   1 +
 src/client/views/MainView.tsx                      |   4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   3 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  15 +-
 src/client/views/nodes/ComparisonBox.tsx           |  44 +--
 .../views/presentationview/PresElementBox.tsx      |   1 -
 7 files changed, 400 insertions(+), 40 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/package-lock.json b/package-lock.json
index 2e83c295c..21e4c4e27 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -356,6 +356,19 @@
             "integrity": "sha512-bWBbC7VG2jdjbgZMX0qpds8U/3h3anfIqE81L8jmVrgFZw/urEDnBA78ymGGKTTK6ciBXmmJ/xlok+Re41S8ww==",
             "dev": true
         },
+        "@types/babel-types": {
+            "version": "7.0.7",
+            "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.7.tgz",
+            "integrity": "sha512-dBtBbrc+qTHy1WdfHYjBwRln4+LWqASWakLHsWHR2NWHIFkv4W3O070IGoGLEBrJBvct3r0L1BUPuvURi7kYUQ=="
+        },
+        "@types/babylon": {
+            "version": "6.16.5",
+            "resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.5.tgz",
+            "integrity": "sha512-xH2e58elpj1X4ynnKp9qSnWlsRTIs6n3tgLGNfwAGHwePw0mulHQllV34n0T25uYSu1k0hRKkWXF890B1yS47w==",
+            "requires": {
+                "@types/babel-types": "*"
+            }
+        },
         "@types/bcrypt-nodejs": {
             "version": "0.0.30",
             "resolved": "https://registry.npmjs.org/@types/bcrypt-nodejs/-/bcrypt-nodejs-0.0.30.tgz",
@@ -1388,6 +1401,26 @@
                 "negotiator": "0.6.2"
             }
         },
+        "acorn": {
+            "version": "3.3.0",
+            "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz",
+            "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo="
+        },
+        "acorn-globals": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz",
+            "integrity": "sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=",
+            "requires": {
+                "acorn": "^4.0.4"
+            },
+            "dependencies": {
+                "acorn": {
+                    "version": "4.0.13",
+                    "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz",
+                    "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c="
+                }
+            }
+        },
         "acorn-jsx": {
             "version": "5.2.0",
             "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz",
@@ -1448,6 +1481,31 @@
             "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz",
             "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ=="
         },
+        "align-text": {
+            "version": "0.1.4",
+            "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
+            "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
+            "requires": {
+                "kind-of": "^3.0.2",
+                "longest": "^1.0.1",
+                "repeat-string": "^1.5.2"
+            },
+            "dependencies": {
+                "is-buffer": {
+                    "version": "1.1.6",
+                    "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+                    "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
+                },
+                "kind-of": {
+                    "version": "3.2.2",
+                    "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+                    "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+                    "requires": {
+                        "is-buffer": "^1.1.5"
+                    }
+                }
+            }
+        },
         "amdefine": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
@@ -1985,6 +2043,29 @@
                 }
             }
         },
+        "babel-types": {
+            "version": "6.26.0",
+            "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz",
+            "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=",
+            "requires": {
+                "babel-runtime": "^6.26.0",
+                "esutils": "^2.0.2",
+                "lodash": "^4.17.4",
+                "to-fast-properties": "^1.0.3"
+            },
+            "dependencies": {
+                "to-fast-properties": {
+                    "version": "1.0.3",
+                    "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
+                    "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc="
+                }
+            }
+        },
+        "babylon": {
+            "version": "6.18.0",
+            "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
+            "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ=="
+        },
         "backo2": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
@@ -2599,6 +2680,15 @@
             "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
             "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
         },
+        "center-align": {
+            "version": "0.1.3",
+            "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz",
+            "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=",
+            "requires": {
+                "align-text": "^0.1.3",
+                "lazy-cache": "^1.0.3"
+            }
+        },
         "chai": {
             "version": "4.2.0",
             "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz",
@@ -2622,6 +2712,14 @@
                 "supports-color": "^5.3.0"
             }
         },
+        "character-parser": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz",
+            "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=",
+            "requires": {
+                "is-regex": "^1.0.3"
+            }
+        },
         "check-error": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
@@ -3210,6 +3308,21 @@
             "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
             "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q=="
         },
+        "clean-css": {
+            "version": "4.2.3",
+            "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz",
+            "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==",
+            "requires": {
+                "source-map": "~0.6.0"
+            },
+            "dependencies": {
+                "source-map": {
+                    "version": "0.6.1",
+                    "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+                    "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+                }
+            }
+        },
         "cli-boxes": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz",
@@ -3581,6 +3694,17 @@
             "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
             "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
         },
+        "constantinople": {
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.1.2.tgz",
+            "integrity": "sha512-yePcBqEFhLOqSBtwYOGGS1exHo/s1xjekXiinh4itpNQGCu4KA1euPh1fg07N2wMITZXQkBz75Ntdt1ctGZouw==",
+            "requires": {
+                "@types/babel-types": "^7.0.0",
+                "@types/babylon": "^6.16.2",
+                "babel-types": "^6.26.0",
+                "babylon": "^6.18.0"
+            }
+        },
         "constants-browserify": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
@@ -4635,6 +4759,11 @@
                 "buffer-indexof": "^1.0.0"
             }
         },
+        "doctypes": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz",
+            "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk="
+        },
         "dom-helpers": {
             "version": "3.4.0",
             "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
@@ -5079,8 +5208,7 @@
         "esutils": {
             "version": "2.0.3",
             "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
-            "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
-            "dev": true
+            "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
         },
         "etag": {
             "version": "1.8.1",
@@ -7173,6 +7301,22 @@
             "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz",
             "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE="
         },
+        "is-expression": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-3.0.0.tgz",
+            "integrity": "sha1-Oayqa+f9HzRx3ELHQW5hwkMXrJ8=",
+            "requires": {
+                "acorn": "~4.0.2",
+                "object-assign": "^4.0.1"
+            },
+            "dependencies": {
+                "acorn": {
+                    "version": "4.0.13",
+                    "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz",
+                    "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c="
+                }
+            }
+        },
         "is-extendable": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
@@ -7288,6 +7432,11 @@
                 "isobject": "^3.0.1"
             }
         },
+        "is-promise": {
+            "version": "2.2.2",
+            "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
+            "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="
+        },
         "is-redirect": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz",
@@ -7393,6 +7542,11 @@
             "resolved": "https://registry.npmjs.org/js-datepicker/-/js-datepicker-4.6.6.tgz",
             "integrity": "sha512-kLsE2oHfQM6pzdWNA7Y8HwCHaZIVURItPds7YVJnbe1z1OmQFzN7lfg6yFbDDWOyNjdHZGzWDy3jSDN2W0pNZQ=="
         },
+        "js-stringify": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz",
+            "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds="
+        },
         "js-tokens": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -7552,6 +7706,15 @@
                 "verror": "1.10.0"
             }
         },
+        "jstransformer": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz",
+            "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=",
+            "requires": {
+                "is-promise": "^2.0.0",
+                "promise": "^7.0.1"
+            }
+        },
         "jwa": {
             "version": "1.4.1",
             "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
@@ -7611,6 +7774,11 @@
                 "package-json": "^4.0.0"
             }
         },
+        "lazy-cache": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz",
+            "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4="
+        },
         "lazystream": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz",
@@ -7829,6 +7997,11 @@
                 "object.assign": "^4.1.0"
             }
         },
+        "longest": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
+            "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc="
+        },
         "loose-envify": {
             "version": "1.4.0",
             "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -13423,6 +13596,120 @@
                 "safe-buffer": "^5.1.2"
             }
         },
+        "pug": {
+            "version": "2.0.4",
+            "resolved": "https://registry.npmjs.org/pug/-/pug-2.0.4.tgz",
+            "integrity": "sha512-XhoaDlvi6NIzL49nu094R2NA6P37ijtgMDuWE+ofekDChvfKnzFal60bhSdiy8y2PBO6fmz3oMEIcfpBVRUdvw==",
+            "requires": {
+                "pug-code-gen": "^2.0.2",
+                "pug-filters": "^3.1.1",
+                "pug-lexer": "^4.1.0",
+                "pug-linker": "^3.0.6",
+                "pug-load": "^2.0.12",
+                "pug-parser": "^5.0.1",
+                "pug-runtime": "^2.0.5",
+                "pug-strip-comments": "^1.0.4"
+            }
+        },
+        "pug-attrs": {
+            "version": "2.0.4",
+            "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-2.0.4.tgz",
+            "integrity": "sha512-TaZ4Z2TWUPDJcV3wjU3RtUXMrd3kM4Wzjbe3EWnSsZPsJ3LDI0F3yCnf2/W7PPFF+edUFQ0HgDL1IoxSz5K8EQ==",
+            "requires": {
+                "constantinople": "^3.0.1",
+                "js-stringify": "^1.0.1",
+                "pug-runtime": "^2.0.5"
+            }
+        },
+        "pug-code-gen": {
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-2.0.2.tgz",
+            "integrity": "sha512-kROFWv/AHx/9CRgoGJeRSm+4mLWchbgpRzTEn8XCiwwOy6Vh0gAClS8Vh5TEJ9DBjaP8wCjS3J6HKsEsYdvaCw==",
+            "requires": {
+                "constantinople": "^3.1.2",
+                "doctypes": "^1.1.0",
+                "js-stringify": "^1.0.1",
+                "pug-attrs": "^2.0.4",
+                "pug-error": "^1.3.3",
+                "pug-runtime": "^2.0.5",
+                "void-elements": "^2.0.1",
+                "with": "^5.0.0"
+            }
+        },
+        "pug-error": {
+            "version": "1.3.3",
+            "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-1.3.3.tgz",
+            "integrity": "sha512-qE3YhESP2mRAWMFJgKdtT5D7ckThRScXRwkfo+Erqga7dyJdY3ZquspprMCj/9sJ2ijm5hXFWQE/A3l4poMWiQ=="
+        },
+        "pug-filters": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-3.1.1.tgz",
+            "integrity": "sha512-lFfjNyGEyVWC4BwX0WyvkoWLapI5xHSM3xZJFUhx4JM4XyyRdO8Aucc6pCygnqV2uSgJFaJWW3Ft1wCWSoQkQg==",
+            "requires": {
+                "clean-css": "^4.1.11",
+                "constantinople": "^3.0.1",
+                "jstransformer": "1.0.0",
+                "pug-error": "^1.3.3",
+                "pug-walk": "^1.1.8",
+                "resolve": "^1.1.6",
+                "uglify-js": "^2.6.1"
+            }
+        },
+        "pug-lexer": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-4.1.0.tgz",
+            "integrity": "sha512-i55yzEBtjm0mlplW4LoANq7k3S8gDdfC6+LThGEvsK4FuobcKfDAwt6V4jKPH9RtiE3a2Akfg5UpafZ1OksaPA==",
+            "requires": {
+                "character-parser": "^2.1.1",
+                "is-expression": "^3.0.0",
+                "pug-error": "^1.3.3"
+            }
+        },
+        "pug-linker": {
+            "version": "3.0.6",
+            "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-3.0.6.tgz",
+            "integrity": "sha512-bagfuHttfQOpANGy1Y6NJ+0mNb7dD2MswFG2ZKj22s8g0wVsojpRlqveEQHmgXXcfROB2RT6oqbPYr9EN2ZWzg==",
+            "requires": {
+                "pug-error": "^1.3.3",
+                "pug-walk": "^1.1.8"
+            }
+        },
+        "pug-load": {
+            "version": "2.0.12",
+            "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-2.0.12.tgz",
+            "integrity": "sha512-UqpgGpyyXRYgJs/X60sE6SIf8UBsmcHYKNaOccyVLEuT6OPBIMo6xMPhoJnqtB3Q3BbO4Z3Bjz5qDsUWh4rXsg==",
+            "requires": {
+                "object-assign": "^4.1.0",
+                "pug-walk": "^1.1.8"
+            }
+        },
+        "pug-parser": {
+            "version": "5.0.1",
+            "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-5.0.1.tgz",
+            "integrity": "sha512-nGHqK+w07p5/PsPIyzkTQfzlYfuqoiGjaoqHv1LjOv2ZLXmGX1O+4Vcvps+P4LhxZ3drYSljjq4b+Naid126wA==",
+            "requires": {
+                "pug-error": "^1.3.3",
+                "token-stream": "0.0.1"
+            }
+        },
+        "pug-runtime": {
+            "version": "2.0.5",
+            "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-2.0.5.tgz",
+            "integrity": "sha512-P+rXKn9un4fQY77wtpcuFyvFaBww7/91f3jHa154qU26qFAnOe6SW1CbIDcxiG5lLK9HazYrMCCuDvNgDQNptw=="
+        },
+        "pug-strip-comments": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-1.0.4.tgz",
+            "integrity": "sha512-i5j/9CS4yFhSxHp5iKPHwigaig/VV9g+FgReLJWWHEHbvKsbqL0oP/K5ubuLco6Wu3Kan5p7u7qk8A4oLLh6vw==",
+            "requires": {
+                "pug-error": "^1.3.3"
+            }
+        },
+        "pug-walk": {
+            "version": "1.1.8",
+            "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-1.1.8.tgz",
+            "integrity": "sha512-GMu3M5nUL3fju4/egXwZO0XLi6fW/K3T3VTgFQ14GxNi8btlxgT5qZL//JwZFm/2Fa64J/PNS8AZeys3wiMkVA=="
+        },
         "pump": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@@ -14170,6 +14457,14 @@
             "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=",
             "dev": true
         },
+        "right-align": {
+            "version": "0.1.3",
+            "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz",
+            "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=",
+            "requires": {
+                "align-text": "^0.1.1"
+            }
+        },
         "rimraf": {
             "version": "3.0.2",
             "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -15835,6 +16130,11 @@
             "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
             "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
         },
+        "token-stream": {
+            "version": "0.0.1",
+            "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz",
+            "integrity": "sha1-zu78cXp2xDFvEm0LnbqlXX598Bo="
+        },
         "touch": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
@@ -16136,6 +16436,50 @@
             "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz",
             "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ=="
         },
+        "uglify-js": {
+            "version": "2.8.29",
+            "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz",
+            "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=",
+            "requires": {
+                "source-map": "~0.5.1",
+                "uglify-to-browserify": "~1.0.0",
+                "yargs": "~3.10.0"
+            },
+            "dependencies": {
+                "camelcase": {
+                    "version": "1.2.1",
+                    "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz",
+                    "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk="
+                },
+                "cliui": {
+                    "version": "2.1.0",
+                    "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
+                    "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=",
+                    "requires": {
+                        "center-align": "^0.1.1",
+                        "right-align": "^0.1.1",
+                        "wordwrap": "0.0.2"
+                    }
+                },
+                "yargs": {
+                    "version": "3.10.0",
+                    "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz",
+                    "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=",
+                    "requires": {
+                        "camelcase": "^1.0.2",
+                        "cliui": "^2.1.0",
+                        "decamelize": "^1.0.0",
+                        "window-size": "0.1.0"
+                    }
+                }
+            }
+        },
+        "uglify-to-browserify": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz",
+            "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=",
+            "optional": true
+        },
         "uid-safe": {
             "version": "2.1.5",
             "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
@@ -16473,6 +16817,11 @@
             "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==",
             "dev": true
         },
+        "void-elements": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz",
+            "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w="
+        },
         "vue-template-compiler": {
             "version": "2.6.11",
             "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz",
@@ -17330,6 +17679,20 @@
                 "infobox-parser": "3.3.1"
             }
         },
+        "window-size": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz",
+            "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0="
+        },
+        "with": {
+            "version": "5.1.1",
+            "resolved": "https://registry.npmjs.org/with/-/with-5.1.1.tgz",
+            "integrity": "sha1-+k2qktrzLE6pTtRTyB8EaGtXXf4=",
+            "requires": {
+                "acorn": "^3.1.0",
+                "acorn-globals": "^3.0.0"
+            }
+        },
         "word-wrap": {
             "version": "1.2.3",
             "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
@@ -17346,6 +17709,11 @@
                 "its-set": "^1.1.5"
             }
         },
+        "wordwrap": {
+            "version": "0.0.2",
+            "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz",
+            "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8="
+        },
         "wordwrapjs": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-3.0.0.tgz",
diff --git a/package.json b/package.json
index 1a92656bf..ca82d523d 100644
--- a/package.json
+++ b/package.json
@@ -192,6 +192,7 @@
         "prosemirror-state": "^1.3.2",
         "prosemirror-transform": "^1.2.3",
         "prosemirror-view": "^1.13.8",
+        "pug": "^2.0.4",
         "puppeteer": "^3.1.0",
         "query-string": "^6.10.1",
         "raw-loader": "^1.0.0",
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 8841f7307..b7a75dfb9 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -5,7 +5,7 @@ import {
     faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
     faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
     faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote,
-    faThumbtack, faTree, faTv, faUndoAlt, faVideo
+    faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye
 } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
@@ -118,7 +118,7 @@ export class MainView extends React.Component {
             faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
             faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
             faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
-            faThumbtack, faTree, faTv, faUndoAlt, faVideo);
+            faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye);
         this.initEventListeners();
         this.initAuthenticationRouters();
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 972c09484..9d880d294 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -207,7 +207,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                             const d = droppedDocs[i];
                             const layoutDoc = Doc.Layout(d);
                             if (this.Document.currentTimecode !== undefined && !this.props.isAnnotationOverlay) {
-                                CollectionFreeFormDocumentView.setValues(this.Document.currentTimecode, d, x + NumCast(d.x) - dropX, y + NumCast(d.y) - dropY, Cast(d.opacity, "number", null));
+                                const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.displayTimecode, 1000));
+                                CollectionFreeFormDocumentView.setValues(this.Document.currentTimecode, d, x + vals.x - dropX, y + vals.y - dropY, vals.opacity);
                             } else {
                                 d.x = x + NumCast(d.x) - dropX;
                                 d.y = y + NumCast(d.y) - dropY;
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 57f484214..a4120f958 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -100,13 +100,14 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
 
     public static setupKeyframes(docs: Doc[], timecode: number, collection: Doc) {
-        docs.forEach(doc => {
-            doc["x-indexed"] = new List<number>(numberRange(timecode).map(i => undefined) as any as number[]);
-            doc["y-indexed"] = new List<number>(numberRange(timecode).map(i => undefined) as any as number[]);
-            doc["opacity-indexed"] = new List<number>(numberRange(timecode).map(i => 0));
-            (doc["x-indexed"] as any).push(NumCast(doc.x));
-            (doc["y-indexed"] as any).push(NumCast(doc.y));
-            (doc["opacity-indexed"] as any).push(NumCast(doc.opacity, 1));
+        docs.forEach((doc, i) => {
+            const xlist = new List<number>(numberRange(timecode + 1).map(i => undefined) as any as number[]);
+            const ylist = new List<number>(numberRange(timecode + 1).map(i => undefined) as any as number[]);
+            xlist[Math.max(i - 1)] = xlist[timecode + 1] = NumCast(doc.x);
+            ylist[Math.max(i - 1)] = ylist[timecode + 1] = NumCast(doc.y);
+            doc["x-indexed"] = xlist;
+            doc["y-indexed"] = ylist;
+            doc["opacity-indexed"] = new List<number>(numberRange(timecode).map(i => 1));
             doc.displayTimecode = ComputedField.MakeFunction("collection ? collection.currentTimecode : 0", {}, { collection });
             doc.x = ComputedField.MakeInterpolated("x", "displayTimecode");
             doc.y = ComputedField.MakeInterpolated("y", "displayTimecode");
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index a14e39bd6..d61934380 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -1,6 +1,3 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEye } from '@fortawesome/free-regular-svg-icons';
-import { faAsterisk, faBrain, faFileAudio, faImage, faPaintBrush, faTimes, faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, observable, runInAction, Lambda, IReactionDisposer } from 'mobx';
 import { observer } from "mobx-react";
@@ -19,9 +16,6 @@ import { setupMoveUpEvents, emptyFunction } from '../../../Utils';
 import { SnappingManager } from '../../util/SnappingManager';
 import { DocumentViewProps } from './DocumentView';
 
-library.add(faImage, faEye as any, faPaintBrush, faBrain);
-library.add(faFileAudio, faAsterisk);
-
 export const comparisonSchema = createSchema({});
 
 type ComparisonDocument = makeInterface<[typeof comparisonSchema, typeof documentSchema]>;
@@ -29,18 +23,16 @@ const ComparisonDocument = makeInterface(comparisonSchema, documentSchema);
 
 @observer
 export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, ComparisonDocument>(ComparisonDocument) {
-    protected multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
-
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ComparisonBox, fieldKey); }
+    protected multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
+    private _disposers: (DragManager.DragDropDisposer | undefined)[] = [undefined, undefined];
 
     @observable _animating = "";
-    private _beforeDropDisposer?: DragManager.DragDropDisposer;
-    private _afterDropDisposer?: DragManager.DragDropDisposer;
 
-    protected createDropTarget = (ele: HTMLDivElement | null, fieldKey: string, disposer: Opt<DragManager.DragDropDisposer>) => {
-        disposer?.();
+    protected createDropTarget = (ele: HTMLDivElement | null, fieldKey: string, disposerId: number) => {
+        this._disposers[disposerId]?.();
         if (ele) {
-            return DragManager.MakeDropTarget(ele, (event, dropEvent) => this.dropHandler(event, dropEvent, fieldKey), this.layoutDoc);
+            this._disposers[disposerId] = DragManager.MakeDropTarget(ele, (e, dropEvent) => this.dropHandler(e, dropEvent, fieldKey), this.layoutDoc);
         }
     }
 
@@ -74,7 +66,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, C
     @undoBatch
     clearDoc = (e: React.MouseEvent, fieldKey: string) => {
         e.stopPropagation;
-        e.preventDefault;
         delete this.dataDoc[fieldKey];
     }
 
@@ -82,8 +73,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, C
         const clipWidth = NumCast(this.dataDoc.clipWidth) + "%";
         const childProps: DocumentViewProps = { ...this.props, pointerEvents: false, parentActive: this.props.active };
         const clearButton = (which: string) => {
-            return <div className={`clear-button ${which}`} onClick={e => this.clearDoc(e, `${which}Doc`)}>
-                <FontAwesomeIcon className={`clear-button ${which}`} icon={faTimes} size="sm" />
+            return <div className={`clear-button ${which}`} onPointerDown={e => e.stopPropagation()} onClick={e => this.clearDoc(e, `${which}Doc`)}>
+                <FontAwesomeIcon className={`clear-button ${which}`} icon={"times"} size="sm" />
             </div>
         }
         const displayDoc = (which: string) => {
@@ -93,23 +84,22 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, C
                 {clearButton(which)}
             </> :  // placeholder image if doc is missing
                 <div className="placeholder">
-                    <FontAwesomeIcon className="upload-icon" icon={faCloudUploadAlt} size="lg" />
+                    <FontAwesomeIcon className="upload-icon" icon={"cloud-upload-alt"} size="lg" />
                 </div>
         }
+        const displayBox = (which: string, index: number, cover: number) => {
+            return <div className={`${which}Box-cont`} key={which} style={{ width: this.props.PanelWidth() }}
+                onPointerDown={e => this.registerSliding(e, cover)}
+                ref={ele => this.createDropTarget(ele, `${which}Doc`, index)} >
+                {displayDoc(which)}
+            </div>;
+        }
 
         return (
             <div className={`comparisonBox${this.active() || SnappingManager.GetIsDragging() ? "-interactive" : ""}`}>
-                <div className="afterBox-cont" key={"after"} style={{ width: this.props.PanelWidth() }}
-                    onPointerDown={e => this.registerSliding(e, this.props.PanelWidth() - 5)}
-                    ref={ele => this._afterDropDisposer = this.createDropTarget(ele, "afterDoc", this._afterDropDisposer)} >
-                    {displayDoc("after")}
-                </div>
+                {displayBox("after", 1, this.props.PanelWidth() - 5)}
                 <div className="clip-div" style={{ width: clipWidth, transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, "gray") }}>
-                    <div className="beforeBox-cont" key={"before"} style={{ width: this.props.PanelWidth() }}
-                        onPointerDown={e => this.registerSliding(e, 5)}
-                        ref={ele => this._beforeDropDisposer = this.createDropTarget(ele, "beforeDoc", this._beforeDropDisposer)} >
-                        {displayDoc("before")}
-                    </div>
+                    {displayBox("before", 0, 5)}
                 </div>
 
                 <div className="slide-bar" style={{ left: `calc(${clipWidth} - 0.5px)` }}>
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index f364b40fd..526a3dbf4 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -104,7 +104,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
         if (this.rootDoc.presProgressivize && !rootTarget?.lastTimecode) {
             rootTarget.currentTimecode = 0;
             CollectionFreeFormDocumentView.setupKeyframes(docs, docs.length, this.presBox);
-            docs.forEach((d, i) => numberRange(docs.length - i).forEach(f => Cast(d["opacity-indexed"], listSpec("number"), [])[f + i] = 1));
             rootTarget.lastTimecode = docs.length - 1;
         }
         docs.forEach((d, i) => i && numberRange(i).forEach(f => Cast(d["opacity-indexed"], listSpec("number"), [])[f] = 0));
-- 
cgit v1.2.3-70-g09d2


From 85c052837d21eb48b3a4f1fa7c95db6703b217d1 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 22 May 2020 00:59:42 -0400
Subject: got rid of renderLock for piles.  fixed drag/drop on piles to place
 things correctly.  made piles store their layoutEngine as a field.  fixed
 layout  of piled docs.

---
 .../views/collections/CollectionPileView.scss      |  3 ++
 .../views/collections/CollectionPileView.tsx       | 59 ++++++++++++----------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 16 ++++--
 src/fields/Doc.ts                                  | 44 ++++++++--------
 5 files changed, 70 insertions(+), 54 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/src/client/views/collections/CollectionPileView.scss b/src/client/views/collections/CollectionPileView.scss
index ac874b663..48d07e42b 100644
--- a/src/client/views/collections/CollectionPileView.scss
+++ b/src/client/views/collections/CollectionPileView.scss
@@ -5,4 +5,7 @@
     height: 100%;
     width: 100%;
     overflow: visible;
+    .collectionPileView-innards {
+        width:100%;
+    }
 }
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index e3bcf2a21..020a87b2e 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -1,18 +1,17 @@
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { HeightSym, Opt, WidthSym, Doc } from "../../../fields/Doc";
 import { ScriptField } from "../../../fields/ScriptField";
 import { BoolCast, NumCast, StrCast } from "../../../fields/Types";
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
 import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
 import { CollectionSubView } from "./CollectionSubView";
 import "./CollectionPileView.scss";
 import React = require("react");
 import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../../Utils";
 import { SelectionManager } from "../../util/SelectionManager";
-import { UndoManager } from "../../util/UndoManager";
+import { UndoManager, undoBatch } from "../../util/UndoManager";
 import { SnappingManager } from "../../util/SnappingManager";
+import { DragManager } from "../../util/DragManager";
 
 @observer
 export class CollectionPileView extends CollectionSubView(doc => doc) {
@@ -20,10 +19,12 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
     _doubleTap: boolean | undefined = false;
     _originalChrome: string = "";
     @observable _contentsActive = true;
-    @observable _layoutEngine = "pass";
     @observable _collapsed: boolean = false;
     @observable _childClickedScript: Opt<ScriptField>;
     componentDidMount() {
+        if (this.layoutEngine() !== "pass" && this.layoutEngine() !== "starburst") {
+            this.Document._layoutEngine = "pass";
+        }
         this._originalChrome = StrCast(this.layoutDoc._chromeStatus);
         this.layoutDoc._chromeStatus = "disabled";
         this.layoutDoc.hideFilterView = true;
@@ -33,49 +34,54 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
         this.layoutDoc._chromeStatus = this._originalChrome;
     }
 
-    layoutEngine = () => this._layoutEngine;
+    layoutEngine = () => StrCast(this.Document._layoutEngine);
 
     @computed get contents() {
-        return <div className="collectionPileView-innards" style={{
-            width: "100%",
-            pointerEvents: this.layoutEngine() !== "pass" && (this.props.active() || this.layoutEngine() === "starburst") ? undefined : "none"
-        }} >
+        return <div className="collectionPileView-innards" style={{ pointerEvents: this.layoutEngine() === "starburst" ? undefined : "none" }} >
             <CollectionFreeFormView {...this.props} layoutEngine={this.layoutEngine} />
         </div>;
     }
-
-    specificMenu = (e: React.MouseEvent) => {
-        const layoutItems: ContextMenuProps[] = [];
-        const doc = this.props.Document;
-
-        ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "eye" });
-    }
-
     toggleStarburst = action(() => {
-        if (this._layoutEngine === 'starburst') {
+        if (this.layoutEngine() === 'starburst') {
             const defaultSize = 110;
             this.layoutDoc._overflow = undefined;
+            this.childDocs.forEach(d => Doc.iconify(d));
             this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - NumCast(this.layoutDoc._starburstPileWidth, defaultSize) / 2;
             this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2;
             this.layoutDoc._width = NumCast(this.layoutDoc._starburstPileWidth, defaultSize);
             this.layoutDoc._height = NumCast(this.layoutDoc._starburstPileHeight, defaultSize);
-            this._layoutEngine = 'pass';
+            Doc.pileup(this.childDocs);
+            this.layoutDoc._panX = 0;
+            this.layoutDoc._panY = -10;
+            this.props.Document._layoutEngine = 'pass';
         } else {
             const defaultSize = 25;
             this.layoutDoc._overflow = 'visible';
             !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 500);
             !this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5);
-            if (this._layoutEngine === 'pass') {
+            if (this.layoutEngine() === 'pass') {
                 this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2;
                 this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - defaultSize / 2;
                 this.layoutDoc._starburstPileWidth = this.layoutDoc[WidthSym]();
                 this.layoutDoc._starburstPileHeight = this.layoutDoc[HeightSym]();
             }
+            this.layoutDoc._panX = this.layoutDoc._panY = 0;
             this.layoutDoc._width = this.layoutDoc._height = defaultSize;
-            this._layoutEngine = 'starburst';
+            this.props.Document._layoutEngine = 'starburst';
         }
     });
 
+    @undoBatch
+    @action
+    onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
+        if (super.onInternalDrop(e, de)) {
+            if (de.complete.docDragData) {
+                Doc.pileup(this.childDocs);
+            }
+        }
+        return true;
+    }
+
     _undoBatch: UndoManager.Batch | undefined;
     pointerDown = (e: React.PointerEvent) => {
         let dist = 0;
@@ -107,20 +113,17 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
     }
 
     onClick = (e: React.MouseEvent) => {
-        if (e.button === 0 && (this._doubleTap || this.layoutEngine() === "starburst")) {
+        if (e.button === 0 && this._doubleTap) {
             SelectionManager.DeselectAll();
             this.toggleStarburst();
             e.stopPropagation();
         }
-        // else if (this.layoutEngine() === "pass") {
-        //     runInAction(() => this._contentsActive = false);
-        //     setTimeout(action(() => this._contentsActive = true), 300);
-        // }
     }
 
     render() {
 
-        return <div className={"collectionPileView"} onContextMenu={this.specificMenu} onClick={this.onClick} onPointerDown={this.pointerDown}
+        return <div className={"collectionPileView"} onClick={this.onClick} onPointerDown={this.pointerDown}
+            ref={this.createDashEventsTarget}
             style={{ width: this.props.PanelWidth(), height: `calc(100%  - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}>
             {this.contents}
         </div>;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9d880d294..1611b6935 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1363,7 +1363,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             {!this.Document._LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ?
                 this.placeholder : this.marqueeView}
             <CollectionFreeFormOverlayView elements={this.elementFunc} />
-            {this.isAnnotationOverlay || !this.props.isSelected() ? (null) :
+            {this.isAnnotationOverlay || !this.props.isSelected() || this.props.Document._viewType === CollectionViewType.Pile ? (null) :
                 <>
                     <div key="back" className="backKeyframe" onClick={this.prevKeyframe}>
                         <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 993cabc36..00d19752f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1111,6 +1111,16 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }), 400);
     });
 
+    renderLock() {
+        return (this.Document.isBackground !== undefined || this.isSelected(false)) &&
+            ((this.Document.type === DocumentType.COL && this.Document._viewType !== CollectionViewType.Pile) || this.Document.type === DocumentType.IMG) &&
+            this.props.renderDepth > 0 && this.props.PanelWidth() > 0 ?
+            <div className="documentView-lock" onClick={() => this.toggleBackground(true)}>
+                <FontAwesomeIcon icon={this.Document.isBackground ? "unlock" : "lock"} style={{ color: this.Document.isBackground ? "red" : undefined }} size="lg" />
+            </div>
+            : (null);
+    }
+
     render() {
         if (!(this.props.Document instanceof Doc)) return (null);
         const backgroundColor = Doc.UserDoc().renderStyle === "comic" ? undefined : StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document);
@@ -1161,11 +1171,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 <div className="documentView-contentBlocker" />
             </> :
                 this.innards}
-            {(this.Document.isBackground !== undefined || this.isSelected(false)) && (this.Document.type === DocumentType.COL || this.Document.type === DocumentType.IMG) && this.props.renderDepth > 0 && this.props.PanelWidth() > 0 ?
-                <div className="documentView-lock" onClick={() => this.toggleBackground(true)}>
-                    <FontAwesomeIcon icon={this.Document.isBackground ? "unlock" : "lock"} style={{ color: this.Document.isBackground ? "red" : undefined }} size="lg" />
-                </div>
-                : (null)}
+            {this.renderLock()}
         </div>;
         { this._showKPQuery ? <KeyphraseQueryView keyphrases={this._queries}></KeyphraseQueryView> : undefined; }
     }
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 9f38f4369..1ea686cbb 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1028,28 +1028,32 @@ export namespace Doc {
         if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") doc.deiconifyLayout = layoutKey.replace("layout_", "");
     }
 
-    export function pileup(selected: Doc[], x: number, y: number) {
-        const newCollection = Docs.Create.PileDocument(selected, { title: "pileup", x: x - 55, y: y - 55, _width: 110, _height: 100, _LODdisable: true });
+    export function pileup(docList: Doc[], x?: number, y?: number) {
         let w = 0, h = 0;
-        selected.forEach((d, i) => {
-            Doc.iconify(d);
-            w = Math.max(d[WidthSym](), w);
-            h = Math.max(d[HeightSym](), h);
-        });
-        h = Math.max(h, w * 4 / 3); // converting to an icon does not update the height right away.  so this is a fallback hack to try to do something reasonable
-        selected.forEach((d, i) => {
-            d.x = Math.cos(Math.PI * 2 * i / selected.length) * 10 - w / 2;
-            d.y = Math.sin(Math.PI * 2 * i / selected.length) * 10 - h / 2;
-            d.displayTimecode = undefined;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+        runInAction(() => {
+            docList.forEach(d => {
+                Doc.iconify(d);
+                w = Math.max(d[WidthSym](), w);
+                h = Math.max(d[HeightSym](), h);
+            });
+            h = Math.max(h, w * 4 / 3); // converting to an icon does not update the height right away.  so this is a fallback hack to try to do something reasonable
+            docList.forEach((d, i) => {
+                d.x = Math.cos(Math.PI * 2 * i / docList.length) * 10 - w / 2;
+                d.y = Math.sin(Math.PI * 2 * i / docList.length) * 10 - h / 2;
+                d.displayTimecode = undefined;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+            });
         });
-        newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55;
-        newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55;
-        newCollection._width = newCollection._height = 110;
-        //newCollection.borderRounding = "40px";
-        newCollection._jitterRotation = 10;
-        newCollection._backgroundColor = "gray";
-        newCollection._overflow = "visible";
-        return newCollection;
+        if (x !== undefined && y !== undefined) {
+            const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: x - 55, y: y - 55, _width: 110, _height: 100, _LODdisable: true });
+            newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55;
+            newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55;
+            newCollection._width = newCollection._height = 110;
+            //newCollection.borderRounding = "40px";
+            newCollection._jitterRotation = 10;
+            newCollection._backgroundColor = "gray";
+            newCollection._overflow = "visible";
+            return newCollection;
+        }
     }
 
 
-- 
cgit v1.2.3-70-g09d2


From 36f85e9a31a0c6bdc784f7448c5a1a9372b0d33d Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 22 May 2020 13:47:04 -0400
Subject: fixed screenshots to add to overlay if they can't add to parent. 
 added meta-drag to drop from overlay layer into freeform.  added auto
 hyperlink note from dragging link button to freeform.

---
 src/client/views/DocumentButtonBar.tsx             |  2 +-
 src/client/views/OverlayView.tsx                   | 48 +++++++++++++---------
 src/client/views/collections/CollectionSubView.tsx |  3 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 10 ++++-
 .../collections/collectionFreeForm/MarqueeView.tsx |  4 +-
 src/client/views/nodes/ComparisonBox.tsx           | 10 ++---
 src/client/views/nodes/ScreenshotBox.tsx           | 13 +++++-
 7 files changed, 58 insertions(+), 32 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 2db5cd3ba..a35a8869c 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -121,7 +121,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                 dragComplete: dropEv => {
                     const linkDoc = dropEv.linkDragData?.linkDocument as Doc; // equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
                     if (this.view0 && linkDoc) {
-                        Doc.GetProto(linkDoc).linkRelationship = "hyperlink";
+                        !linkDoc.linkRelationship && (Doc.GetProto(linkDoc).linkRelationship = "hyperlink");
 
                         // we want to allow specific views to handle the link creation in their own way (e.g., rich text makes text hyperlinks)
                         // the dragged view can regiser a linkDropCallback to be notified that the link was made and to update their data structures
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index bfa44fe47..cfa869fb2 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -3,14 +3,17 @@ import { observer } from "mobx-react";
 import * as React from "react";
 import { Doc, DocListCast, Opt } from "../../fields/Doc";
 import { Id } from "../../fields/FieldSymbols";
-import { NumCast } from "../../fields/Types";
-import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, Utils } from "../../Utils";
+import { NumCast, Cast } from "../../fields/Types";
+import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, Utils, setupMoveUpEvents } from "../../Utils";
 import { Transform } from "../util/Transform";
 import { CollectionFreeFormLinksView } from "./collections/collectionFreeForm/CollectionFreeFormLinksView";
 import { DocumentView } from "./nodes/DocumentView";
 import './OverlayView.scss';
 import { Scripting } from "../util/Scripting";
 import { ScriptingRepl } from './ScriptingRepl';
+import { DragManager } from "../util/DragManager";
+import { listSpec } from "../../fields/Schema";
+import { List } from "../../fields/List";
 
 export type OverlayDisposer = () => void;
 
@@ -139,46 +142,51 @@ export class OverlayView extends React.Component {
         return remove;
     }
 
+
     @computed get overlayDocs() {
         const userDocOverlays = Doc.UserDoc().myOverlayDocuments;
         if (!userDocOverlays) {
-            return (null);
+            return null;
         }
         return userDocOverlays instanceof Doc && DocListCast(userDocOverlays.data).map(d => {
             setTimeout(() => d.inOverlay = true, 0);
             let offsetx = 0, offsety = 0;
-            const onPointerMove = action((e: PointerEvent) => {
+            const dref = React.createRef<HTMLDivElement>();
+            const onPointerMove = action((e: PointerEvent, down: number[]) => {
                 if (e.buttons === 1) {
                     d.x = e.clientX + offsetx;
                     d.y = e.clientY + offsety;
-                    e.stopPropagation();
-                    e.preventDefault();
                 }
-            });
-            const onPointerUp = action((e: PointerEvent) => {
-                document.removeEventListener("pointermove", onPointerMove);
-                document.removeEventListener("pointerup", onPointerUp);
-                e.stopPropagation();
-                e.preventDefault();
+                if (e.metaKey) {
+                    const dragData = new DragManager.DocumentDragData([d]);
+                    d.removeDropProperties = new List<string>(["inOverlay"]);
+                    dragData.offset = [-offsetx, -offsety];
+                    dragData.dropAction = "move";
+                    dragData.removeDocument = (doc: Doc | Doc[]) => {
+                        const docs = (doc instanceof Doc) ? [doc] : doc;
+                        docs.forEach(d => Doc.RemoveDocFromList(Cast(Doc.UserDoc().myOverlayDocuments, Doc, null), "data", d));
+                        return true;
+                    };
+                    dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
+                        return dragData.removeDocument!(doc) ? addDocument(doc) : false;
+                    };
+                    DragManager.StartDocumentDrag([dref.current!], dragData, down[0], down[1]);
+                    return true;
+                }
+                return false;
             });
 
             const onPointerDown = (e: React.PointerEvent) => {
+                setupMoveUpEvents(this, e, onPointerMove, emptyFunction, emptyFunction);
                 offsetx = NumCast(d.x) - e.clientX;
                 offsety = NumCast(d.y) - e.clientY;
-                e.stopPropagation();
-                e.preventDefault();
-                document.addEventListener("pointermove", onPointerMove);
-                document.addEventListener("pointerup", onPointerUp);
             };
-            return <div className="overlayView-doc" key={d[Id]} onPointerDown={onPointerDown} style={{ transform: `translate(${d.x}px, ${d.y}px)` }}>
+            return <div className="overlayView-doc" ref={dref} key={d[Id]} onPointerDown={onPointerDown} style={{ width: NumCast(d._width), height: NumCast(d._height), transform: `translate(${d.x}px, ${d.y}px)` }}>
                 <DocumentView
                     Document={d}
                     LibraryPath={emptyPath}
                     ChromeHeight={returnZero}
                     rootSelected={returnTrue}
-                    // isSelected={returnFalse}
-                    // select={emptyFunction}
-                    // layoutKey={"layout"}
                     bringToFront={emptyFunction}
                     addDocument={undefined}
                     removeDocument={undefined}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index a6b0d03b8..423eb1d90 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -218,7 +218,8 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
             ScriptCast(this.props.Document.dropConverter)?.script.run({ dragData: docDragData });
             if (docDragData) {
                 let added = false;
-                if (docDragData.dropAction || docDragData.userDropAction) {
+                const dropaction = docDragData.dropAction || docDragData.userDropAction;
+                if (dropaction && dropaction !== "move") {
                     added = this.addDocument(docDragData.droppedDocuments);
                 } else if (docDragData.moveDocument) {
                     const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 1611b6935..4b218bc18 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -18,7 +18,7 @@ import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
 import { aggregateBounds, intersectRect, returnOne, Utils, returnZero, returnFalse, numberRange } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { DocServer } from "../../../DocServer";
-import { Docs } from "../../../documents/Documents";
+import { Docs, DocUtils } from "../../../documents/Documents";
 import { DocumentManager } from "../../../util/DocumentManager";
 import { DragManager, dropActionType } from "../../../util/DragManager";
 import { HistoryUtil } from "../../../util/History";
@@ -191,6 +191,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const [xp, yp] = xf.transformPoint(de.x, de.y);
         const [xpo, ypo] = xfo.transformPoint(de.x, de.y);
         const zsorted = this.childLayoutPairs.map(pair => pair.layout).slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
+        if (!this.isAnnotationOverlay && de.complete.linkDragData && de.complete.linkDragData.linkSourceDocument !== this.props.Document) {
+            const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
+            this.props.addDocument(source);
+            (de.complete.linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceDocument },
+                "doc annotation")); // TODODO this is where in text links get passed
+            e.stopPropagation();
+            return true;
+        }
         if (super.onInternalDrop(e, de)) {
             if (de.complete.docDragData) {
                 if (de.complete.docDragData.droppedDocuments.length) {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 0244dfc56..c99e74ccd 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -347,8 +347,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         SelectionManager.DeselectAll();
         selected.forEach(d => this.props.removeDocument(d));
         const newCollection = Doc.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2);
-        this.props.addDocument(newCollection);
-        this.props.selectDocuments([newCollection], []);
+        this.props.addDocument(newCollection!);
+        this.props.selectDocuments([newCollection!], []);
         MarqueeOptionsMenu.Instance.fadeOut(true);
         this.hideMarquee();
     }
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index f79fe6e78..77e07ec0c 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -74,8 +74,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, C
         const clearButton = (which: string) => {
             return <div className={`clear-button ${which}`} onPointerDown={e => e.stopPropagation()} onClick={e => this.clearDoc(e, `${which}Doc`)}>
                 <FontAwesomeIcon className={`clear-button ${which}`} icon={"times"} size="sm" />
-            </div>
-        }
+            </div>;
+        };
         const displayDoc = (which: string) => {
             const whichDoc = Cast(this.dataDoc[`${which}Doc`], Doc, null);
             return whichDoc ? <>
@@ -84,15 +84,15 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, C
             </> :  // placeholder image if doc is missing
                 <div className="placeholder">
                     <FontAwesomeIcon className="upload-icon" icon={"cloud-upload-alt"} size="lg" />
-                </div>
-        }
+                </div>;
+        };
         const displayBox = (which: string, index: number, cover: number) => {
             return <div className={`${which}Box-cont`} key={which} style={{ width: this.props.PanelWidth() }}
                 onPointerDown={e => this.registerSliding(e, cover)}
                 ref={ele => this.createDropTarget(ele, `${which}Doc`, index)} >
                 {displayDoc(which)}
             </div>;
-        }
+        };
 
         return (
             <div className={`comparisonBox${this.active() || SnappingManager.GetIsDragging() ? "-interactive" : ""}`}>
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 5d4af2d77..29e3c008a 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -6,7 +6,7 @@ import { action, computed, IReactionDisposer, observable, runInAction } from "mo
 import { observer } from "mobx-react";
 import * as rp from 'request-promise';
 import { documentSchema } from "../../../fields/documentSchemas";
-import { makeInterface } from "../../../fields/Schema";
+import { makeInterface, listSpec } from "../../../fields/Schema";
 import { Cast, NumCast } from "../../../fields/Types";
 import { VideoField } from "../../../fields/URLField";
 import { emptyFunction, returnFalse, returnOne, Utils, returnZero } from "../../../Utils";
@@ -18,6 +18,8 @@ import { ViewBoxBaseComponent } from "../DocComponent";
 import { InkingControl } from "../InkingControl";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./ScreenshotBox.scss";
+import { Doc, WidthSym, HeightSym } from "../../../fields/Doc";
+import { OverlayView } from "../OverlayView";
 const path = require('path');
 
 type ScreenshotDocument = makeInterface<[typeof documentSchema]>;
@@ -72,7 +74,14 @@ export class ScreenshotBox extends ViewBoxBaseComponent<FieldViewProps, Screensh
                             x: NumCast(this.layoutDoc.x) + width, y: NumCast(this.layoutDoc.y),
                             _width: 150, _height: height / width * 150, title: "--screenshot--"
                         });
-                        this.props.addDocument?.(imageSummary);
+                        if (!this.props.addDocument || this.props.addDocument === returnFalse) {
+                            const spt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+                            imageSummary.x = spt[0];
+                            imageSummary.y = spt[1];
+                            Cast(Cast(Doc.UserDoc().myOverlayDocuments, Doc, null)?.data, listSpec(Doc), []).push(imageSummary);
+                        } else {
+                            this.props.addDocument?.(imageSummary);
+                        }
                     }
                 }, 500);
             });
-- 
cgit v1.2.3-70-g09d2


From 43818b137486f3d4431e1c3b9f4de0b0aefba9ca Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 23 May 2020 14:59:01 -0400
Subject: several fixes to progressivization and added UI for editing by
 suspending transparency

---
 .../collectionFreeForm/CollectionFreeFormView.scss |  9 ++++
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 50 +++++++++++++++-------
 .../collections/collectionFreeForm/MarqueeView.tsx |  4 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 14 +++---
 src/client/views/nodes/KeyValuePair.tsx            |  4 +-
 .../views/presentationview/PresElementBox.tsx      |  5 +--
 src/fields/ScriptField.ts                          | 17 ++++----
 src/fields/util.ts                                 |  2 +-
 8 files changed, 66 insertions(+), 39 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 7a84fcde1..5478a1c4a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -51,12 +51,21 @@
     }
     .backKeyframe {
         right:45;
+        svg {
+            display:block;
+            margin:auto;
+        }
     }
     .numKeyframe {
         right:25;
+        text-align:center;
     }
     .fwdKeyframe {
         right:5;
+        svg {
+            display:block;
+            margin:auto;
+        }
     }
 
     .collectionfreeformview-placeholder {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4b218bc18..d0415f77d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -126,21 +126,39 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
         this.addDocument(newBox);
     }
-    addDocument = (newBox: Doc | Doc[]) => {
-        if (this.Document.currentTimecode !== undefined && !this.props.isAnnotationOverlay) {
-            CollectionFreeFormDocumentView.setupKeyframes((newBox instanceof Doc) ? [newBox] : newBox, this.Document.currentTimecode, this.props.Document);
-        }
-
+    addDocument = action((newBox: Doc | Doc[]) => {
+        let retVal = false;
         if (newBox instanceof Doc) {
-            const added = this.props.addDocument(newBox);
-            added && this.bringToFront(newBox);
-            added && this.updateCluster(newBox);
-            return added;
+            retVal = this.props.addDocument(newBox);
+            retVal && this.bringToFront(newBox);
+            retVal && this.updateCluster(newBox);
         } else {
-            return this.props.addDocument(newBox);
+            retVal = this.props.addDocument(newBox);
             // bcz: deal with clusters
         }
-    }
+        if (retVal) {
+            const newBoxes = (newBox instanceof Doc) ? [newBox] : newBox;
+            for (let i = 0; i < newBoxes.length; i++) {
+                const newBox = newBoxes[i];
+                if (newBox.displayTimecode !== undefined) {
+                    const x = newBox.x;
+                    const y = newBox.y;
+                    delete newBox["x-indexed"];
+                    delete newBox["y-indexed"];
+                    delete newBox["opacity-indexed"];
+                    delete newBox.x;
+                    delete newBox.y;
+                    delete newBox.displayTimecode;
+                    newBox.x = x;
+                    newBox.y = y;
+                }
+            }
+            if (this.Document.currentTimecode !== undefined && !this.props.isAnnotationOverlay) {
+                CollectionFreeFormDocumentView.setupKeyframes(newBoxes, this.Document.currentTimecode);
+            }
+        }
+        return retVal;
+    })
 
     @undoBatch
     @action
@@ -148,7 +166,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const currentTimecode = this.Document.currentTimecode;
         if (currentTimecode === undefined) {
             this.Document.currentTimecode = 0;
-            CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0, this.props.Document);
+            CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
         }
         CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentTimecode || 0);
         this.Document.currentTimecode = Math.max(0, (currentTimecode || 0) + 1);
@@ -160,7 +178,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const currentTimecode = this.Document.currentTimecode;
         if (currentTimecode === undefined) {
             this.Document.currentTimecode = 0;
-            CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0, this.props.Document);
+            CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
         }
         CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice());
         this.Document.currentTimecode = Math.max(0, (currentTimecode || 0) - 1);
@@ -992,7 +1010,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const { z, color, zIndex } = params.pair.layout;
         return {
             x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"),
-            transition: StrCast(layoutDoc.transition), opacity: Cast(opacity, "number", null),
+            transition: StrCast(layoutDoc.transition), opacity: this.Document.editing ? 1 : Cast(opacity, "number", null),
             width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number"), pair: params.pair, replica: ""
         };
     }
@@ -1376,8 +1394,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     <div key="back" className="backKeyframe" onClick={this.prevKeyframe}>
                         <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
                     </div>
-                    <div key="num" className="numKeyframe" >
-                        {NumCast(this.props.Document.currentTimecode)}
+                    <div key="num" className="numKeyframe" style={{ backgroundColor: this.Document.editing ? "#759c75" : "#c56565" }} onClick={action(() => this.Document.editing = !this.Document.editing)} >
+                        {NumCast(this.Document.currentTimecode)}
                     </div>
                     <div key="fwd" className="fwdKeyframe" onClick={this.nextKeyframe}>
                         <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index c99e74ccd..ed70ac9e8 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -334,9 +334,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             _LODdisable: true,
             title: "a nested collection",
         });
-        // const dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data");
-        // dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined;
-        // this.marqueeInkDelete(inkData);
+        selected.forEach(d => d.context = newCollection);
         this.hideMarquee();
         return newCollection;
     }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index a4120f958..88fbdd589 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -77,6 +77,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
 
     public static setValues(timecode: number, d: Doc, x?: number, y?: number, opacity?: number) {
+        Cast(d["x-indexed"], listSpec("number"), [])[Math.max(0, timecode - 1)] = x as any as number;
+        Cast(d["y-indexed"], listSpec("number"), null)[Math.max(0, timecode - 1)] = y as any as number;
         Cast(d["x-indexed"], listSpec("number"), [])[timecode] = x as any as number;
         Cast(d["y-indexed"], listSpec("number"), null)[timecode] = y as any as number;
         Cast(d["opacity-indexed"], listSpec("number"), null)[timecode] = opacity as any as number;
@@ -99,16 +101,18 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         setTimeout(() => docs.forEach(doc => doc.transition = undefined), 1010);
     }
 
-    public static setupKeyframes(docs: Doc[], timecode: number, collection: Doc) {
+    public static setupKeyframes(docs: Doc[], timecode: number, progressivize: boolean = false) {
         docs.forEach((doc, i) => {
+            const curTimecode = progressivize ? i : timecode;
             const xlist = new List<number>(numberRange(timecode + 1).map(i => undefined) as any as number[]);
             const ylist = new List<number>(numberRange(timecode + 1).map(i => undefined) as any as number[]);
-            xlist[Math.max(i - 1)] = xlist[timecode + 1] = NumCast(doc.x);
-            ylist[Math.max(i - 1)] = ylist[timecode + 1] = NumCast(doc.y);
+            const olist = new List<number>(numberRange(timecode + 1).map(t => progressivize && t < i ? 0 : 1));
+            xlist[Math.max(curTimecode - 1, 0)] = xlist[curTimecode] = NumCast(doc.x);
+            ylist[Math.max(curTimecode - 1, 0)] = ylist[curTimecode] = NumCast(doc.y);
             doc["x-indexed"] = xlist;
             doc["y-indexed"] = ylist;
-            doc["opacity-indexed"] = new List<number>(numberRange(timecode).map(i => 1));
-            doc.displayTimecode = ComputedField.MakeFunction("collection ? collection.currentTimecode : 0", {}, { collection });
+            doc["opacity-indexed"] = olist;
+            doc.displayTimecode = ComputedField.MakeFunction("self.context ? (self.context.currentTimecode||0) : 0");
             doc.x = ComputedField.MakeInterpolated("x", "displayTimecode");
             doc.y = ComputedField.MakeInterpolated("y", "displayTimecode");
             doc.opacity = ComputedField.MakeInterpolated("opacity", "displayTimecode");
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 956d6556b..3cbe3e494 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -99,9 +99,9 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
                     <div className="keyValuePair-td-key-container">
                         <button style={hover} className="keyValuePair-td-key-delete" onClick={undoBatch(() => {
                             if (Object.keys(props.Document).indexOf(props.fieldKey) !== -1) {
-                                props.Document[props.fieldKey] = undefined;
+                                delete props.Document[props.fieldKey];
                             }
-                            else props.Document.proto![props.fieldKey] = undefined;
+                            else delete props.Document.proto![props.fieldKey];
                         })}>
                             X
                         </button>
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 526a3dbf4..364c1d060 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -101,12 +101,11 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
         this.rootDoc.presProgressivize = !this.rootDoc.presProgressivize;
         const rootTarget = Cast(this.rootDoc.presentationTargetDoc, Doc, null);
         const docs = DocListCast(rootTarget[Doc.LayoutFieldKey(rootTarget)]);
-        if (this.rootDoc.presProgressivize && !rootTarget?.lastTimecode) {
+        if (this.rootDoc.presProgressivize) {
             rootTarget.currentTimecode = 0;
-            CollectionFreeFormDocumentView.setupKeyframes(docs, docs.length, this.presBox);
+            CollectionFreeFormDocumentView.setupKeyframes(docs, docs.length, true);
             rootTarget.lastTimecode = docs.length - 1;
         }
-        docs.forEach((d, i) => i && numberRange(i).forEach(f => Cast(d["opacity-indexed"], listSpec("number"), [])[f] = 0));
     }
 
     /**
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 503c60790..5192af407 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -65,14 +65,14 @@ export class ScriptField extends ObjectField {
     @serializable(autoObject())
     private captures?: ProxyField<Doc>;
 
-    constructor(script: CompiledScript, setterscript?: CompileResult) {
+    constructor(script: CompiledScript, setterscript?: CompiledScript) {
         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.setterscript = setterscript;
         this.script = script;
     }
 
@@ -98,10 +98,10 @@ export class ScriptField extends ObjectField {
     //     }
 
     [Copy](): ObjectField {
-        return new ScriptField(this.script);
+        return new ScriptField(this.script, this.setterscript);
     }
     toString() {
-        return `${this.script.originalScript}`;
+        return `${this.script.originalScript} + ${this.setterscript?.originalScript}`;
     }
 
     [ToScriptString]() {
@@ -141,22 +141,21 @@ export class ComputedField extends ScriptField {
 
 
     [Copy](): ObjectField {
-        return new ComputedField(this.script);
+        return new ComputedField(this.script, this.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) {
+    public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }) {
         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;
+        return compiled.compiled ? new ComputedField(compiled) : undefined;
     }
     public static MakeInterpolated(fieldKey: string, interpolatorKey: string) {
         const getField = ScriptField.CompileScript(`getIndexVal(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;
+        return getField.compiled && setField.compiled ? new ComputedField(getField, setField) : undefined;
     }
 }
 
diff --git a/src/fields/util.ts b/src/fields/util.ts
index a287b0210..024c0f80e 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -116,7 +116,7 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
             return true;
         }
     }
-    if (target.__fields[prop] instanceof ComputedField && target.__fields[prop].setterscript) {
+    if (target.__fields[prop] instanceof ComputedField && target.__fields[prop].setterscript && value !== undefined && !(value instanceof ComputedField)) {
         return ScriptCast(target.__fields[prop])?.setterscript?.run({ self: target[SelfProxy], this: target[SelfProxy], value }).success ? true : false;
     }
     return _setter(target, prop, value, receiver);
-- 
cgit v1.2.3-70-g09d2


From 2ba063ff94a04e89ee1fa1fde7d71a839f5d6856 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 23 May 2020 23:25:22 -0400
Subject: switched frame animations to use currentFrame and activeFrame to fix
 aliasing issue of progressive slides at different frame codes

---
 src/client/documents/Documents.ts                  |  2 ++
 src/client/views/MainView.tsx                      |  6 +---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 41 +++++++++++-----------
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 25 +++++++------
 src/client/views/nodes/PresBox.tsx                 |  6 ++--
 .../views/presentationview/PresElementBox.tsx      |  2 +-
 src/fields/documentSchemas.ts                      |  6 ++--
 7 files changed, 46 insertions(+), 42 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 6b17b4303..2be961108 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -126,6 +126,8 @@ export interface DocumentOptions {
     curPage?: number;
     currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video)  value is in seconds
     displayTimecode?: number; // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
+    currentFrame?: number; // the current frame of a frame-based collection (e.g., progressive slide)
+    activeFrame?: number; // the active frame of a document in a frame base collection
     borderRounding?: string;
     boxShadow?: string;
     dontRegisterChildViews?: boolean;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index b7a75dfb9..358de2333 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -139,10 +139,7 @@ export class MainView extends React.Component {
 
     initEventListeners = () => {
         window.addEventListener("drop", (e) => { e.preventDefault(); }, false); // drop event handler
-        window.addEventListener("dragover", (e) => {
-            console.log("MDRAG");
-            e.preventDefault();
-        }, false); // drag event handler
+        window.addEventListener("dragover", (e) => { e.preventDefault(); }, false); // drag event handler
         // click interactions for the context menu
         document.addEventListener("pointerdown", this.globalPointerDown);
         document.addEventListener("pointerup", this.globalPointerUp);
@@ -246,7 +243,6 @@ export class MainView extends React.Component {
     onDrop = (e: React.DragEvent<HTMLDivElement>) => {
         e.preventDefault();
         e.stopPropagation();
-        console.log("Drop");
     }
 
     @action
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d0415f77d..91e257f80 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -56,6 +56,7 @@ export const panZoomSchema = createSchema({
     scale: "number",
     currentTimecode: "number",
     displayTimecode: "number",
+    currentFrame: "number",
     arrangeScript: ScriptField,
     arrangeInit: ScriptField,
     useClusters: "boolean",
@@ -140,7 +141,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             const newBoxes = (newBox instanceof Doc) ? [newBox] : newBox;
             for (let i = 0; i < newBoxes.length; i++) {
                 const newBox = newBoxes[i];
-                if (newBox.displayTimecode !== undefined) {
+                if (newBox.activeFrame !== undefined) {
                     const x = newBox.x;
                     const y = newBox.y;
                     delete newBox["x-indexed"];
@@ -148,13 +149,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     delete newBox["opacity-indexed"];
                     delete newBox.x;
                     delete newBox.y;
-                    delete newBox.displayTimecode;
+                    delete newBox.activeFrame;
                     newBox.x = x;
                     newBox.y = y;
                 }
             }
-            if (this.Document.currentTimecode !== undefined && !this.props.isAnnotationOverlay) {
-                CollectionFreeFormDocumentView.setupKeyframes(newBoxes, this.Document.currentTimecode);
+            if (this.Document.currentFrame !== undefined && !this.props.isAnnotationOverlay) {
+                CollectionFreeFormDocumentView.setupKeyframes(newBoxes, this.Document.currentFrame);
             }
         }
         return retVal;
@@ -163,25 +164,25 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @undoBatch
     @action
     nextKeyframe = (): void => {
-        const currentTimecode = this.Document.currentTimecode;
-        if (currentTimecode === undefined) {
-            this.Document.currentTimecode = 0;
+        const currentFrame = this.Document.currentFrame;
+        if (currentFrame === undefined) {
+            this.Document.currentFrame = 0;
             CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
         }
-        CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentTimecode || 0);
-        this.Document.currentTimecode = Math.max(0, (currentTimecode || 0) + 1);
-        this.Document.lastTimecode = Math.max(NumCast(this.Document.currentTimecode), NumCast(this.Document.lastTimecode));
+        CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0);
+        this.Document.currentFrame = Math.max(0, (currentFrame || 0) + 1);
+        this.Document.lastTimecode = Math.max(NumCast(this.Document.currentFrame), NumCast(this.Document.lastTimecode));
     }
     @undoBatch
     @action
     prevKeyframe = (): void => {
-        const currentTimecode = this.Document.currentTimecode;
-        if (currentTimecode === undefined) {
-            this.Document.currentTimecode = 0;
+        const currentFrame = this.Document.currentFrame;
+        if (currentFrame === undefined) {
+            this.Document.currentFrame = 0;
             CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
         }
         CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice());
-        this.Document.currentTimecode = Math.max(0, (currentTimecode || 0) - 1);
+        this.Document.currentFrame = Math.max(0, (currentFrame || 0) - 1);
     }
 
     private selectDocuments = (docs: Doc[]) => {
@@ -232,9 +233,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                         for (let i = 0; i < droppedDocs.length; i++) {
                             const d = droppedDocs[i];
                             const layoutDoc = Doc.Layout(d);
-                            if (this.Document.currentTimecode !== undefined && !this.props.isAnnotationOverlay) {
-                                const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.displayTimecode, 1000));
-                                CollectionFreeFormDocumentView.setValues(this.Document.currentTimecode, d, x + vals.x - dropX, y + vals.y - dropY, vals.opacity);
+                            if (this.Document.currentFrame !== undefined && !this.props.isAnnotationOverlay) {
+                                const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000));
+                                CollectionFreeFormDocumentView.setValues(this.Document.currentFrame, d, x + vals.x - dropX, y + vals.y - dropY, vals.opacity);
                             } else {
                                 d.x = x + NumCast(d.x) - dropX;
                                 d.y = y + NumCast(d.y) - dropY;
@@ -1005,8 +1006,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             return { x: 0, y: 0, transition: "transform 1s", ...result, pair: params.pair, replica: "" };
         }
         const layoutDoc = Doc.Layout(params.pair.layout);
-        const { x, y, opacity } = this.Document.currentTimecode === undefined ? params.pair.layout :
-            CollectionFreeFormDocumentView.getValues(params.pair.layout, this.Document.currentTimecode || 0);
+        const { x, y, opacity } = this.Document.currentFrame === undefined ? params.pair.layout :
+            CollectionFreeFormDocumentView.getValues(params.pair.layout, this.Document.currentFrame || 0);
         const { z, color, zIndex } = params.pair.layout;
         return {
             x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"),
@@ -1395,7 +1396,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                         <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
                     </div>
                     <div key="num" className="numKeyframe" style={{ backgroundColor: this.Document.editing ? "#759c75" : "#c56565" }} onClick={action(() => this.Document.editing = !this.Document.editing)} >
-                        {NumCast(this.Document.currentTimecode)}
+                        {NumCast(this.Document.currentFrame)}
                     </div>
                     <div key="fwd" className="fwdKeyframe" onClick={this.nextKeyframe}>
                         <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 88fbdd589..682aed8f5 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -69,21 +69,24 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
 
     public static getValues(doc: Doc, time: number) {
+        const timecode = Math.round(time);
         return ({
-            x: Cast(doc["x-indexed"], listSpec("number"), []).reduce((p, x, i) => (i <= time && x !== undefined) || p === undefined ? x : p, undefined as any as number),
-            y: Cast(doc["y-indexed"], listSpec("number"), []).reduce((p, y, i) => (i <= time && y !== undefined) || p === undefined ? y : p, undefined as any as number),
-            opacity: Cast(doc["opacity-indexed"], listSpec("number"), []).reduce((p, o, i) => i <= time || p === undefined ? o : p, undefined as any as number),
+            x: Cast(doc["x-indexed"], listSpec("number"), []).reduce((p, x, i) => (i <= timecode && x !== undefined) || p === undefined ? x : p, undefined as any as number),
+            y: Cast(doc["y-indexed"], listSpec("number"), []).reduce((p, y, i) => (i <= timecode && y !== undefined) || p === undefined ? y : p, undefined as any as number),
+            opacity: Cast(doc["opacity-indexed"], listSpec("number"), []).reduce((p, o, i) => i <= timecode || p === undefined ? o : p, undefined as any as number),
         });
     }
 
-    public static setValues(timecode: number, d: Doc, x?: number, y?: number, opacity?: number) {
+    public static setValues(time: number, d: Doc, x?: number, y?: number, opacity?: number) {
+        const timecode = Math.round(time);
         Cast(d["x-indexed"], listSpec("number"), [])[Math.max(0, timecode - 1)] = x as any as number;
-        Cast(d["y-indexed"], listSpec("number"), null)[Math.max(0, timecode - 1)] = y as any as number;
+        Cast(d["y-indexed"], listSpec("number"), [])[Math.max(0, timecode - 1)] = y as any as number;
         Cast(d["x-indexed"], listSpec("number"), [])[timecode] = x as any as number;
-        Cast(d["y-indexed"], listSpec("number"), null)[timecode] = y as any as number;
+        Cast(d["y-indexed"], listSpec("number"), [])[timecode] = y as any as number;
         Cast(d["opacity-indexed"], listSpec("number"), null)[timecode] = opacity as any as number;
     }
-    public static updateKeyframe(docs: Doc[], timecode: number) {
+    public static updateKeyframe(docs: Doc[], time: number) {
+        const timecode = Math.round(time);
         docs.forEach(doc => {
             const xindexed = Cast(doc['x-indexed'], listSpec("number"), null);
             const yindexed = Cast(doc['y-indexed'], listSpec("number"), null);
@@ -112,10 +115,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             doc["x-indexed"] = xlist;
             doc["y-indexed"] = ylist;
             doc["opacity-indexed"] = olist;
-            doc.displayTimecode = ComputedField.MakeFunction("self.context ? (self.context.currentTimecode||0) : 0");
-            doc.x = ComputedField.MakeInterpolated("x", "displayTimecode");
-            doc.y = ComputedField.MakeInterpolated("y", "displayTimecode");
-            doc.opacity = ComputedField.MakeInterpolated("opacity", "displayTimecode");
+            doc.activeFrame = ComputedField.MakeFunction("self.context ? (self.context.currentFrame||0) : 0");
+            doc.x = ComputedField.MakeInterpolated("x", "activeFrame");
+            doc.y = ComputedField.MakeInterpolated("y", "activeFrame");
+            doc.opacity = ComputedField.MakeInterpolated("opacity", "activeFrame");
         });
     }
 
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index aeb77a894..b76c06e2e 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -60,9 +60,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
         this.updateCurrentPresentation();
         const presTargetDoc = Cast(this.childDocs[this.itemIndex].presentationTargetDoc, Doc, null);
         const lastFrame = Cast(presTargetDoc.lastTimecode, "number", null);
-        const curFrame = NumCast(presTargetDoc.currentTimecode);
+        const curFrame = NumCast(presTargetDoc.currentFrame);
         if (lastFrame !== undefined && curFrame < lastFrame) {
-            presTargetDoc.currentTimecode = curFrame + 1;
+            presTargetDoc.currentFrame = curFrame + 1;
         }
         else if (this.childDocs[this.itemIndex + 1] !== undefined) {
             let nextSelected = this.itemIndex + 1;
@@ -200,7 +200,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
             this.rootDoc._itemIndex = index;
             const presTargetDoc = Cast(this.childDocs[this.itemIndex].presentationTargetDoc, Doc, null);
             if (presTargetDoc.lastTimecode !== undefined) {
-                presTargetDoc.currentTimecode = 0;
+                presTargetDoc.currentFrame = 0;
             }
 
             if (!this.layoutDoc.presStatus) {
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 364c1d060..31ffde8af 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -102,7 +102,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
         const rootTarget = Cast(this.rootDoc.presentationTargetDoc, Doc, null);
         const docs = DocListCast(rootTarget[Doc.LayoutFieldKey(rootTarget)]);
         if (this.rootDoc.presProgressivize) {
-            rootTarget.currentTimecode = 0;
+            rootTarget.currentFrame = 0;
             CollectionFreeFormDocumentView.setupKeyframes(docs, docs.length, true);
             rootTarget.lastTimecode = docs.length - 1;
         }
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 142f7e079..7e11049ab 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -12,7 +12,9 @@ export const documentSchema = createSchema({
     links: listSpec(Doc),       // computed (readonly) list of links associated with this document
 
     // "Location" properties in a very general sense
-    currentTimecode: "number",  // current play back time of a temporal document (video / audio)
+    currentFrame: "number",     // current frame of a frame based collection (e.g., a progressive slide)
+    activeFrame: "number",      // the active frame of a frame based animated document 
+    urrentTimecode: "number",   // current play back time of a temporal document (video / audio)
     displayTimecode: "number",  // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
     inOverlay: "boolean",       // whether the document is rendered in an OverlayView which handles selection/dragging differently
     x: "number",                // x coordinate when in a freeform view 
@@ -77,7 +79,7 @@ export const documentSchema = createSchema({
     isBackground: "boolean",    // whether document is a background element and ignores input events (can only select with marquee)
     lockedPosition: "boolean",  // whether the document can be moved (dragged)
     _lockedTransform: "boolean",// whether a freeformview can pan/zoom
-    
+
     // drag drop properties
     dragFactory: Doc,           // the document that serves as the "template" for the onDragStart script.  ie, to drag out copies of the dragFactory document.
     dropAction: "string",       // override specifying what should happen when this document is dropped (can be "alias", "copy", "move")
-- 
cgit v1.2.3-70-g09d2


From 19ababdb6098ac36358c86e43ad63ab3066b4663 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 23 May 2020 23:59:34 -0400
Subject: renamed lastTimecode to lastFrame

---
 src/client/documents/Documents.ts                                     | 1 +
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx   | 2 +-
 src/client/views/nodes/PresBox.tsx                                    | 4 ++--
 src/client/views/presentationview/PresElementBox.tsx                  | 2 +-
 src/fields/documentSchemas.ts                                         | 1 +
 5 files changed, 6 insertions(+), 4 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2be961108..7f5b62f22 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -127,6 +127,7 @@ export interface DocumentOptions {
     currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video)  value is in seconds
     displayTimecode?: number; // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
     currentFrame?: number; // the current frame of a frame-based collection (e.g., progressive slide)
+    lastFrame?: number; // the last frame of a frame-based collection (e.g., progressive slide)
     activeFrame?: number; // the active frame of a document in a frame base collection
     borderRounding?: string;
     boxShadow?: string;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 91e257f80..4840bb7e7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -171,7 +171,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         }
         CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0);
         this.Document.currentFrame = Math.max(0, (currentFrame || 0) + 1);
-        this.Document.lastTimecode = Math.max(NumCast(this.Document.currentFrame), NumCast(this.Document.lastTimecode));
+        this.Document.lastFrame = Math.max(NumCast(this.Document.currentFrame), NumCast(this.Document.lastFrame));
     }
     @undoBatch
     @action
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index b76c06e2e..05306c29f 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -59,7 +59,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
     next = () => {
         this.updateCurrentPresentation();
         const presTargetDoc = Cast(this.childDocs[this.itemIndex].presentationTargetDoc, Doc, null);
-        const lastFrame = Cast(presTargetDoc.lastTimecode, "number", null);
+        const lastFrame = Cast(presTargetDoc.lastFrame, "number", null);
         const curFrame = NumCast(presTargetDoc.currentFrame);
         if (lastFrame !== undefined && curFrame < lastFrame) {
             presTargetDoc.currentFrame = curFrame + 1;
@@ -199,7 +199,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
         if (index >= 0 && index < this.childDocs.length) {
             this.rootDoc._itemIndex = index;
             const presTargetDoc = Cast(this.childDocs[this.itemIndex].presentationTargetDoc, Doc, null);
-            if (presTargetDoc.lastTimecode !== undefined) {
+            if (presTargetDoc.lastFrame !== undefined) {
                 presTargetDoc.currentFrame = 0;
             }
 
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 31ffde8af..475fef5b2 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -104,7 +104,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
         if (this.rootDoc.presProgressivize) {
             rootTarget.currentFrame = 0;
             CollectionFreeFormDocumentView.setupKeyframes(docs, docs.length, true);
-            rootTarget.lastTimecode = docs.length - 1;
+            rootTarget.lastFrame = docs.length - 1;
         }
     }
 
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 7e11049ab..32f1b6e6c 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -13,6 +13,7 @@ export const documentSchema = createSchema({
 
     // "Location" properties in a very general sense
     currentFrame: "number",     // current frame of a frame based collection (e.g., a progressive slide)
+    lastFrame: "number",        // last frame of a frame based collection (e.g., a progressive slide)
     activeFrame: "number",      // the active frame of a frame based animated document 
     urrentTimecode: "number",   // current play back time of a temporal document (video / audio)
     displayTimecode: "number",  // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
-- 
cgit v1.2.3-70-g09d2

