From dccf5909f4a4bec35559b23a2f355ab4c7a94086 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 24 Jun 2022 19:03:00 -0400
Subject: fixed myImports pane to show imports.  fixed saving/loading zip files
 of a collection.  fixed errors related to NumT casting to number

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 32 ++++++----------------
 1 file changed, 9 insertions(+), 23 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 542b1fce1..3c2047db7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1660,8 +1660,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const mores = ContextMenu.Instance.findByDescription("More...");
         const moreItems = mores && "subitems" in mores ? mores.subitems : [];
         if (!Doc.noviceMode) {
+            e.persist();
             moreItems.push({ description: "Export collection", icon: "download", event: async () => Doc.Zip(this.props.Document) });
-            moreItems.push({ description: "Import exported collection", icon: "upload", event: ({ x, y }) => this.importDocument(x, y) });
+            moreItems.push({ description: "Import exported collection", icon: "upload", event: ({ x, y }) => this.importDocument(e.clientX, e.clientY) });
         }
         !mores && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "eye" });
     }
@@ -1670,28 +1671,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const input = document.createElement("input");
         input.type = "file";
         input.accept = ".zip";
-        input.onchange = async _e => {
-            const upload = Utils.prepend("/uploadDoc");
-            const formData = new FormData();
-            const file = input.files && input.files[0];
-            if (file) {
-                formData.append('file', file);
-                formData.append('remap', "true");
-                const response = await fetch(upload, { method: "POST", body: formData });
-                const json = await response.json();
-                if (json !== "error") {
-                    const doc = await DocServer.GetRefField(json);
-                    if (doc instanceof Doc) {
-                        const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y);
-                        doc.x = xx, doc.y = yy;
-                        this.props.addDocument?.(doc);
-                        setTimeout(() =>
-                            SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => {
-                                docs.docs.forEach(d => LinkManager.Instance.addLink(d));
-                            }), 2000); // need to give solr some time to update so that this query will find any link docs we've added.
-                    }
-                }
-            }
+        input.onchange = _e => {
+            input.files && Doc.importDocument(input.files[0]).then(doc => {
+                if (doc instanceof Doc) {
+                    const [xx, yy] = this.getTransform().transformPoint(x, y);
+                    doc.x = xx, doc.y = yy;
+                    this.props.addDocument?.(doc);}
+            });
         };
         input.click();
     }
-- 
cgit v1.2.3-70-g09d2


From b1eb13fc6264b1272daba824ff0e4cd2bca8a6fa Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 28 Jun 2022 12:23:44 -0400
Subject: cleaned up some more currentUserUtils - fixed some issues with 
 prev/next key frame appearing.

---
 src/client/documents/Documents.ts                  |  29 +++++-
 src/client/util/CurrentUserUtils.ts                | 111 ++++-----------------
 .../views/collections/CollectionDockingView.tsx    |  37 +++++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  19 +++-
 .../collections/collectionFreeForm/MarqueeView.tsx |   2 +-
 src/client/views/topbar/TopBar.tsx                 |   3 +-
 src/fields/Doc.ts                                  |  25 +----
 7 files changed, 97 insertions(+), 129 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 3780df5b9..4690d856d 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -13,7 +13,7 @@ import { SchemaHeaderField } from "../../fields/SchemaHeaderField";
 import { ComputedField, ScriptField } from "../../fields/ScriptField";
 import { Cast, NumCast, StrCast } from "../../fields/Types";
 import { AudioField, ImageField, MapField, PdfField, RecordingField, VideoField, WebField, YoutubeField } from "../../fields/URLField";
-import { SharingPermissions } from "../../fields/util";
+import { inheritParentAcls, SharingPermissions } from "../../fields/util";
 import { Upload } from "../../server/SharedMediaTypes";
 import { aggregateBounds, OmitKeys, Utils } from "../../Utils";
 import { YoutubeBox } from "../apis/youtube/YoutubeBox";
@@ -1266,7 +1266,7 @@ export namespace DocUtils {
         const documentList: ContextMenuProps[] = DocListCast(DocListCast(CurrentUserUtils.MyTools?.data)[0]?.data).filter(btnDoc => !btnDoc.hidden).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc && doc !== Doc.UserDoc().emptyPresentation).map((dragDoc, i) => ({
             description: ":" + StrCast(dragDoc.title).replace("Untitled ",""),
             event: undoBatch((args: { x: number, y: number }) => {
-                const newDoc = Doc.copyDragFactory(dragDoc);
+                const newDoc = DocUtils.copyDragFactory(dragDoc);
                 if (newDoc) {
                     newDoc.author = Doc.CurrentUserEmail;
                     newDoc.x = x;
@@ -1479,9 +1479,34 @@ export namespace DocUtils {
         }
         return generatedDocuments;
     }
+
+    // copies the specified drag factory document
+    export function copyDragFactory(dragFactory: Doc) {
+        if (!dragFactory) return undefined;
+        const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true);
+        ndoc && Doc.AddDocToList(CurrentUserUtils.MyFileOrphans, "data", Doc.GetProto(ndoc));
+        if (ndoc && dragFactory["dragFactory-count"] !== undefined) {
+            dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1;
+            Doc.SetInPlace(ndoc, "title", ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(), true);
+        }
+
+        if (ndoc && CurrentUserUtils.ActiveDashboard) inheritParentAcls(CurrentUserUtils.ActiveDashboard, ndoc);
+
+        return ndoc;
+    }
+    export function delegateDragFactory(dragFactory: Doc) {
+        const ndoc = Doc.MakeDelegateWithProto(dragFactory);
+        if (ndoc && dragFactory["dragFactory-count"] !== undefined) {
+            dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1;
+            Doc.GetProto(ndoc).title = ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString();
+        }
+        return ndoc;
+    }
 }
 
 ScriptingGlobals.add("Docs", Docs);
+ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc) { return DocUtils.copyDragFactory(dragFactory); });
+ScriptingGlobals.add(function delegateDragFactory(dragFactory: Doc) { return DocUtils.delegateDragFactory(dragFactory); });
 ScriptingGlobals.add(function makeDelegate(proto: any) { const d = Docs.Create.DelegateDocument(proto, { title: "child of " + proto.title }); return d; });
 ScriptingGlobals.add(function generateLinkTitle(self: Doc) {
     const anchor1title = self.anchor1 && self.anchor1 !== self ? Cast(self.anchor1, Doc, null).title : "<?>";
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 6e115ea3c..2a0702a58 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -755,8 +755,8 @@ export class CurrentUserUtils {
                     CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map,
                     CollectionViewType.Grid]),
               title: "Perspective",                    toolTip: "View",                 width: 100,btnType: ButtonType.DropdownList,ignoreClick: true, scripts: { script: 'setView(value, _readOnly_)'}},
-            { title: "Back",    icon: "chevron-left",  toolTip: "Prev Animation Frame", width: 20, btnType: ButtonType.ClickButton,                    scripts: { onClick: 'prevKeyFrame(_readOnly_)'},             funcs: {hidden: 'IsNoviceMode()'}},
-            { title: "Fwd",     icon: "chevron-right", toolTip: "Next Animation Frame", width: 20, btnType: ButtonType.ClickButton,                    scripts: { onClick: 'nextKeyFrame(_readOnly_)'},             funcs: {hidden: 'IsNoviceMode()'}},
+            { title: "Back",    icon: "chevron-left",  toolTip: "Prev Animation Frame", width: 20, btnType: ButtonType.ClickButton,                    scripts: { onClick: 'prevKeyFrame(_readOnly_)'},             funcs: {hidden: 'IsNoviceMode() || !selectedDocumentType(undefined, "freeform")'}},
+            { title: "Fwd",     icon: "chevron-right", toolTip: "Next Animation Frame", width: 20, btnType: ButtonType.ClickButton,                    scripts: { onClick: 'nextKeyFrame(_readOnly_)'},             funcs: {hidden: 'IsNoviceMode() || !selectedDocumentType(undefined, "freeform")'}},
             { title: "Fill",    icon: "fill-drip",     toolTip: "Background Fill Color",width: 20, btnType: ButtonType.ColorButton, ignoreClick: true, scripts: { script:  'setBackgroundColor(value, _readOnly_)'},funcs: {hidden: '!selectedDocumentType()'}}, // Only when a document is selected
             { title: "Header",  icon: "heading",       toolTip: "Header Color",                    btnType: ButtonType.ColorButton, ignoreClick: true, scripts: { script:  'setHeaderColor(value, _readOnly_)'},    funcs: {hidden: '!selectedDocumentType()'}},
             { title: "Overlay", icon: "layer-group",   toolTip: "Overlay",                         btnType: ButtonType.ToggleButton,                   scripts: { onClick: 'toggleOverlay(_readOnly_)'},            funcs: {hidden: '!selectedDocumentType(undefined, "freeform", true)'}}, // Only when floating document is selected in freeform
@@ -1028,8 +1028,10 @@ export class CurrentUserUtils {
 
     public static _urlState: HistoryUtil.DocUrl;
 
-    public static openDashboard = (doc: Doc, fromHistory = false) => {
-        if (!doc) return;
+    /// opens a dashboard as the ActiveDashboard (and adds the dashboard to the users list of dashboards if it's not already there).
+    /// this also sets the readonly state of the dashboard based on the current mode of dash (from its url)
+    public static openDashboard = (doc: Doc|undefined, fromHistory = false) => {
+        if (!doc) return false;
         CurrentUserUtils.MainDocId = doc[Id];
         Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", doc);
 
@@ -1097,46 +1099,15 @@ export class CurrentUserUtils {
         };
         input.click();
     }
+    
+    public static snapshotDashboard() { return CollectionDockingView.TakeSnapshot(CurrentUserUtils.ActiveDashboard);  }
 
-    public static CaptureDashboardThumbnail() {
-        const activeDashboard = CurrentUserUtils.ActiveDashboard;
-        const docView = CollectionDockingView.Instance.props.DocumentView?.();
-        const content = docView?.ContentDiv;
-        if (docView && content && activeDashboard) {
-            const _width = Number(getComputedStyle(content).width.replace("px",""));
-            const _height = Number(getComputedStyle(content).height.replace("px",""));
-            return CollectionFreeFormView.UpdateIcon(
-                docView.layoutDoc[Id] + "-icon" + (new Date()).getTime(),
-                content,
-                _width, _height,
-                _width, _height, 0, 1, true, docView.layoutDoc[Id] + "-icon", 
-                (iconFile, _nativeWidth, _nativeHeight) => {
-                    const img = Docs.Create.ImageDocument(new ImageField(iconFile), { title: docView.rootDoc.title+"-icon", _width, _height, _nativeWidth, _nativeHeight});
-                    const proto = Cast(img.proto, Doc, null)!;
-                    proto["data-nativeWidth"] = _width;
-                    proto["data-nativeHeight"] = _height;
-                    Doc.GetProto(activeDashboard).thumb = img;
-                });
-        }
-
-    }
-
-    public static async snapshotDashboard() {
-        if (CurrentUserUtils.ActiveDashboard) {
-            const copy = await CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard);
-            Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", copy);
-            CurrentUserUtils.openDashboard(copy);
-        }
-    }
-
-    public static closeActiveDashboard = () => {
-        CurrentUserUtils.ActiveDashboard = undefined;
-    }
+    public static closeActiveDashboard = () => { CurrentUserUtils.ActiveDashboard = undefined; }
 
     public static removeDashboard = async (dashboard:Doc) => {
         const dashboards = await DocListCastAsync(CurrentUserUtils.MyDashboards.data);
         if (dashboards?.length) {
-            if (dashboard === CurrentUserUtils.ActiveDashboard) CurrentUserUtils.openDashboard(dashboards.find(doc => doc !== dashboard)!);
+            if (dashboard === CurrentUserUtils.ActiveDashboard) CurrentUserUtils.openDashboard(dashboards.find(doc => doc !== dashboard));
             Doc.RemoveDocFromList(CurrentUserUtils.MyDashboards, "data", dashboard);
             if (!dashboards.length) CurrentUserUtils.ActivePage = "home";
         }
@@ -1214,14 +1185,6 @@ export class CurrentUserUtils {
     public static get ActiveTool(): InkTool { return StrCast(Doc.UserDoc().activeTool, InkTool.None) as InkTool; }
 }
 
-ScriptingGlobals.add(function openDragFactory(dragFactory: Doc) {
-    const copy = Doc.copyDragFactory(dragFactory);
-    if (copy) {
-        CollectionDockingView.AddSplit(copy, "right");
-        const view = DocumentManager.Instance.getFirstDocumentView(copy);
-        view && SelectionManager.SelectView(view, false);
-    }
-});
 ScriptingGlobals.add(function MySharedDocs() { return CurrentUserUtils.MySharedDocs; }, "document containing all shared Docs");
 ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode");
 ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering");
@@ -1232,52 +1195,14 @@ ScriptingGlobals.add(function createNewFolder() { return MainView.Instance.creat
 ScriptingGlobals.add(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }, "returns all the links to the document or its annotations", "(doc: any)");
 ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar");
 ScriptingGlobals.add(function shareDashboard(dashboard: Doc) { SharingManager.Instance.open(undefined, dashboard); }, "opens sharing dialog for Dashboard");
-ScriptingGlobals.add(async function removeDashboard(dashboard: Doc) { CurrentUserUtils.removeDashboard(dashboard); }, "Remove Dashboard from Dashboards");
-ScriptingGlobals.add(function addToDashboards(dashboard: Doc) {
-    const dashboardAlias = Doc.MakeAlias(dashboard);
-    Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", dashboardAlias);
-    CurrentUserUtils.openDashboard(dashboardAlias);
-},
-    "adds Dashboard to set of Dashboards");
-
-ScriptingGlobals.add(function selectedDocumentType(docType?: DocumentType, colType?: CollectionViewType, checkParent?: boolean) {
-    let selected = SelectionManager.Docs().length ? SelectionManager.Docs()[0] : undefined;
-    if (selected && checkParent) {
-        const parentDoc: Doc = Cast(selected.context, Doc, null);
-        selected = parentDoc;
-    }
-    if (selected && docType && selected.type === docType) return true;
-    else if (selected && colType && selected.viewType === colType) return true;
-    else if (selected && !colType && !docType) return true;
-    else return false;
+ScriptingGlobals.add(function removeDashboard(dashboard: Doc) { CurrentUserUtils.removeDashboard(dashboard); }, "Remove Dashboard from Dashboards");
+ScriptingGlobals.add(function addToDashboards(dashboard: Doc) {  CurrentUserUtils.openDashboard( Doc.MakeAlias(dashboard)); },  "adds Dashboard to set of Dashboards");
+ScriptingGlobals.add(function selectedDocumentType(docType?: DocumentType, colType?: CollectionViewType, checkContext?: boolean) {
+    let selected = (sel => checkContext ? DocCast(sel?.context) : sel)(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement());
+    return docType ? selected?.type === docType : colType ? selected?.viewType === colType : true;
 });
 ScriptingGlobals.add(function makeTopLevelFolder() {
-    const folder = Docs.Create.TreeDocument([], { title: "Untitled folder", _stayInCollection: true, isFolder: true });
-    TreeView._editTitleOnLoad = { id: folder[Id], parent: undefined };
-    return Doc.AddDocToList(CurrentUserUtils.MyFilesystem, "data", folder);
-});
-ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) {
-    if (readOnly) return;
-    const sel = SelectionManager.Views()[0];
-    const col = (sel.ComponentView as CollectionFreeFormView);
-    const currentFrame = Cast(sel.props.Document._currentFrame, "number", null);
-    if (currentFrame === undefined) {
-        sel.props.Document._currentFrame = 0;
-        CollectionFreeFormDocumentView.setupKeyframes(col.childDocs, 0);
-    }
-    CollectionFreeFormDocumentView.updateKeyframe(col.childDocs, currentFrame || 0);
-    sel.rootDoc._currentFrame = Math.max(0, (currentFrame || 0) + 1);
-    sel.rootDoc.lastFrame = Math.max(NumCast(sel.rootDoc._currentFrame), NumCast(sel.rootDoc.lastFrame));
-});
-ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) {
-    if (readOnly) return;
-    const sel = SelectionManager.Views()[0];
-    const col = (sel.ComponentView as CollectionFreeFormView);
-    const currentFrame = Cast(sel.props.Document._currentFrame, "number", null);
-    if (currentFrame === undefined) {
-        sel.props.Document._currentFrame = 0;
-        CollectionFreeFormDocumentView.setupKeyframes(col.childDocs, 0);
-    }
-    CollectionFreeFormDocumentView.gotoKeyframe(col.childDocs.slice());
-    sel.rootDoc._currentFrame = Math.max(0, (currentFrame || 0) - 1);
+    TreeView._editTitleOnLoad = { id: Utils.GenerateGuid(), parent: undefined };
+    const opts = { title: "Untitled folder", _stayInCollection: true, isFolder: true };
+    return Doc.AddDocToList(CurrentUserUtils.MyFilesystem, "data", Docs.Create.TreeDocument([], opts, TreeView._editTitleOnLoad.id));
 });
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 07fcd6a7d..0830b6fdf 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -4,12 +4,12 @@ import { action, IReactionDisposer, observable, reaction, runInAction } from "mo
 import { observer } from "mobx-react";
 import * as ReactDOM from 'react-dom';
 import * as GoldenLayout from "../../../client/goldenLayout";
-import { DataSym, Doc, DocListCast, DocListCastAsync, Opt } from "../../../fields/Doc";
+import { Doc, DocListCast, Opt } from "../../../fields/Doc";
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
-import { listSpec } from '../../../fields/Schema';
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
+import { ImageField } from '../../../fields/URLField';
 import { inheritParentAcls } from '../../../fields/util';
 import { emptyFunction, incrementTitleCopy } from '../../../Utils';
 import { DocServer } from "../../DocServer";
@@ -19,14 +19,15 @@ import { CurrentUserUtils } from '../../util/CurrentUserUtils';
 import { DragManager } from "../../util/DragManager";
 import { InteractionUtils } from '../../util/InteractionUtils';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
+import { SelectionManager } from '../../util/SelectionManager';
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { LightboxView } from '../LightboxView';
 import "./CollectionDockingView.scss";
+import { CollectionFreeFormView } from './collectionFreeForm';
 import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView";
 import { CollectionViewType } from './CollectionView';
 import { TabDocView } from './TabDocView';
 import React = require("react");
-import { SelectionManager } from '../../util/SelectionManager';
 const _global = (window /* browser */ || global /* node */) as any;
 
 @observer
@@ -373,13 +374,34 @@ export class CollectionDockingView extends CollectionSubView() {
         }
     }
 
-    public static async Copy(doc: Doc, clone = false) {
+    public CaptureThumbnail() {
+        const content = this.props.DocumentView?.()?.ContentDiv;
+        if (content) {
+            const _width = Number(getComputedStyle(content).width.replace("px",""));
+            const _height = Number(getComputedStyle(content).height.replace("px",""));
+            return CollectionFreeFormView.UpdateIcon(
+                this.layoutDoc[Id] + "-icon" + (new Date()).getTime(),
+                content,
+                _width, _height,
+                _width, _height, 0, 1, true, this.layoutDoc[Id] + "-icon", 
+                (iconFile, _nativeWidth, _nativeHeight) => {
+                    const img = Docs.Create.ImageDocument(new ImageField(iconFile), { title: this.rootDoc.title+"-icon", _width, _height, _nativeWidth, _nativeHeight});
+                    const proto = Cast(img.proto, Doc, null)!;
+                    proto["data-nativeWidth"] = _width;
+                    proto["data-nativeHeight"] = _height;
+                    this.dataDoc.thumb = img;
+                });
+        }
+
+    }
+    public static async TakeSnapshot(doc: Doc|undefined, clone = false) {
+        if (!doc) return undefined;
         let json = StrCast(doc.dockingConfig);
         if (clone) {
-            const cloned = (await Doc.MakeClone(doc));
+            const cloned = await Doc.MakeClone(doc);
             Array.from(cloned.map.entries()).map(entry => json = json.replace(entry[0], entry[1][Id]));
             Doc.GetProto(cloned.clone).dockingConfig = json;
-            return cloned.clone;
+            return CurrentUserUtils.openDashboard(cloned.clone);
         }
         const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
         const origtabids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", "")) || [];
@@ -395,7 +417,8 @@ export class CollectionDockingView extends CollectionSubView() {
             json = json.replace(origtab[Id], newtab[Id]);
             return newtab;
         });
-        return Docs.Create.DockDocument(newtabs, json, { title: incrementTitleCopy(StrCast(doc.title)) });
+        const copy = Docs.Create.DockDocument(newtabs, json, { title: incrementTitleCopy(StrCast(doc.title)) });
+        return CurrentUserUtils.openDashboard(await copy);
     }
 
     @action
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 3c2047db7..b9da4faa4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -153,6 +153,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return this.getContainerTransform().translate(- this.cachedCenteringShiftX, - this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform);
     }
 
+    changeKeyFrame = (back=false) => {
+        const currentFrame = Cast(this.Document._currentFrame, "number", null);
+        if (currentFrame === undefined) {
+            this.Document._currentFrame = 0;
+            CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
+        }
+        if (back) {
+            CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice());
+            this.Document._currentFrame = Math.max(0, (currentFrame || 0) - 1);
+        } else {
+            CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0);
+            this.Document._currentFrame = Math.max(0, (currentFrame || 0) + 1);
+            this.Document.lastFrame = Math.max(NumCast(this.Document._currentFrame), NumCast(this.Document.lastFrame));
+        }
+    }
     @action setKeyFrameEditing = (set: boolean) => this._keyframeEditing = set;
     getKeyFrameEditing = () => this._keyframeEditing;
     onBrowseClickHandler = () => this.props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick);
@@ -2086,4 +2101,6 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY
     });
     Doc.linkFollowHighlight(dv?.props.Document, false);
 }
-ScriptingGlobals.add(CollectionBrowseClick);
\ No newline at end of file
+ScriptingGlobals.add(CollectionBrowseClick);
+ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(); });
+ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) { !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true); });
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 17f1106d0..ab8a34d5a 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -156,7 +156,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                     }));
             } else if (e.key === "s" && e.ctrlKey) {
                 e.preventDefault();
-                const slide = Doc.copyDragFactory(DocCast(Doc.UserDoc().emptySlide))!;
+                const slide = DocUtils.copyDragFactory(DocCast(Doc.UserDoc().emptySlide))!;
                 slide.x = x;
                 slide.y = y;
                 FormattedTextBox.SelectOnLoad = slide[Id];
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 61e836661..b447bdc19 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -14,6 +14,7 @@ import { SelectionManager } from "../../util/SelectionManager";
 import { SettingsManager } from "../../util/SettingsManager";
 import { SharingManager } from "../../util/SharingManager";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { ContextMenu } from "../ContextMenu";
 import { Borders, Colors } from "../global/globalEnums";
 import { MainView } from "../MainView";
@@ -26,7 +27,7 @@ import "./TopBar.scss";
 @observer
 export class TopBar extends React.Component {
       navigateToHome = () => {
-            CurrentUserUtils.CaptureDashboardThumbnail()?.then(() => {
+            CollectionDockingView.Instance.CaptureThumbnail()?.then(() => {
                 CurrentUserUtils.ActivePage = "home";
                 CurrentUserUtils.closeActiveDashboard(); // bcz: if we do this, we need some other way to keep track, for user convenience, of the last dashboard in use
             });
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index a5d952176..b30ca644d 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -23,7 +23,7 @@ import { listSpec } from "./Schema";
 import { ComputedField, ScriptField } from "./ScriptField";
 import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types";
 import { AudioField, ImageField, MapField, PdfField, VideoField, WebField } from "./URLField";
-import { deleteProperty, GetEffectiveAcl, getField, getter, inheritParentAcls, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from "./util";
+import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from "./util";
 import JSZip = require("jszip");
 
 export namespace Field {
@@ -1242,27 +1242,6 @@ export namespace Doc {
         return !curPres ? false : DocListCast(curPres.data).findIndex((val) => Doc.AreProtosEqual(val, doc)) !== -1;
     }
 
-    export function copyDragFactory(dragFactory: Doc) {
-        const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true);
-        ndoc && Doc.AddDocToList(CurrentUserUtils.MyFileOrphans, "data", Doc.GetProto(ndoc));
-        if (ndoc && dragFactory["dragFactory-count"] !== undefined) {
-            dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1;
-            Doc.SetInPlace(ndoc, "title", ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(), true);
-        }
-
-        if (ndoc && CurrentUserUtils.ActiveDashboard) inheritParentAcls(CurrentUserUtils.ActiveDashboard, ndoc);
-
-        return ndoc;
-    }
-    export function delegateDragFactory(dragFactory: Doc) {
-        const ndoc = Doc.MakeDelegateWithProto(dragFactory);
-        if (ndoc && dragFactory["dragFactory-count"] !== undefined) {
-            dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1;
-            Doc.GetProto(ndoc).title = ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString();
-        }
-        return ndoc;
-    }
-
     export function toIcon(doc?: Doc, isOpen?: boolean) {
         switch (StrCast(doc?.type)) {
             case DocumentType.IMG: return "image";
@@ -1454,8 +1433,6 @@ ScriptingGlobals.add(function getProto(doc: any) { return Doc.GetProto(doc); });
 ScriptingGlobals.add(function getDocTemplate(doc?: any) { return Doc.getDocTemplate(doc); });
 ScriptingGlobals.add(function getAlias(doc: any) { return Doc.MakeAlias(doc); });
 ScriptingGlobals.add(function getCopy(doc: any, copyProto: any) { return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); });
-ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc) { return Doc.copyDragFactory(dragFactory); });
-ScriptingGlobals.add(function delegateDragFactory(dragFactory: Doc) { return Doc.delegateDragFactory(dragFactory); });
 ScriptingGlobals.add(function copyField(field: any) { return Field.Copy(field); });
 ScriptingGlobals.add(function docList(field: any) { return DocListCast(field); });
 ScriptingGlobals.add(function addDocToList(doc: Doc, field: string, added:Doc) { return Doc.AddDocToList(doc,field, added); });
-- 
cgit v1.2.3-70-g09d2

