From 2c565fd81daca02cabb9598c699cedb7611c3841 Mon Sep 17 00:00:00 2001
From: ljungster <parkerljung@gmail.com>
Date: Sat, 12 Mar 2022 07:44:01 -0500
Subject: attempting to add note-taking

I think this has something to do with the view not being rendered in novice mode. Assuming this is an issue in CollectionMenu.tsx.

Essentially what I did was add a note-taking view wherever I found a stacking view (via global search)
---
 src/client/views/collections/CollectionMenu.tsx | 125 +++++++++++++++++++++++-
 1 file changed, 124 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 131f5ba46..1ab4e9207 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -278,6 +278,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
 
     @computed get _freeform_commands() { return Doc.UserDoc().noviceMode ? [this._viewCommand, this._saveFilterCommand] : [this._viewCommand, this._saveFilterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand]; }
     @computed get _stacking_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._contentCommand, this._templateCommand]; }
+    @computed get _notetaking_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._contentCommand, this._templateCommand]; }
     @computed get _masonry_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._contentCommand, this._templateCommand]; }
     @computed get _schema_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._templateCommand, this._narrativeCommand]; }
     @computed get _doc_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._openLinkInCommand, this._onClickCommand]; }
@@ -289,6 +290,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
             case CollectionViewType.Tree: return this._tree_commands;
             case CollectionViewType.Schema: return this._schema_commands;
             case CollectionViewType.Stacking: return this._stacking_commands;
+            case CollectionViewType.NoteTaking: return this._notetaking_commands;
             case CollectionViewType.Masonry: return this._stacking_commands;
             case CollectionViewType.Time: return this._freeform_commands;
             case CollectionViewType.Carousel: return this._freeform_commands;
@@ -326,6 +328,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
             case CollectionViewType.Invalid:
             case CollectionViewType.Freeform: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={this.props.type === CollectionViewType.Invalid} />);
             case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" {...this.props} />);
+            case CollectionViewType.NoteTaking: return (<CollectionNoteTakingViewChrome key="collchrome" {...this.props} />);
             case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" {...this.props} />);
             case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" {...this.props} />);
             case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" {...this.props} />);
@@ -496,7 +499,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
             const size: number = PresBox.Instance?._selectedArray.size;
             const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined;
             const activeDoc = presSelected ? PresBox.Instance?.childDocs[PresBox.Instance?.childDocs.indexOf(presSelected) + 1] : PresBox.Instance?.childDocs[PresBox.Instance?.childDocs.length - 1];
-            if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking) {
+            if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking || targetDoc._viewType === CollectionViewType.NoteTaking) {
                 const scroll = targetDoc._scrollTop;
                 activeDoc.presPinView = true;
                 activeDoc.presPinViewScroll = scroll;
@@ -987,6 +990,126 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
     }
 }
 
+@observer
+export class CollectionNoteTakingViewChrome extends React.Component<CollectionViewMenuProps> {
+    @observable private _currentKey: string = "";
+    @observable private suggestions: string[] = [];
+
+    get document() { return this.props.docView.props.Document; }
+
+    @computed private get descending() { return StrCast(this.document._columnsSort) === "descending"; }
+    @computed get pivotField() { return StrCast(this.document._pivotField); }
+
+    getKeySuggestions = async (value: string): Promise<string[]> => {
+        const val = value.toLowerCase();
+        const docs = DocListCast(this.document[this.props.fieldKey]);
+
+        if (Doc.UserDoc().noviceMode) {
+            if (docs instanceof Doc) {
+                const keys = Object.keys(docs).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 ||
+                    key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 ||
+                    (key[0].toUpperCase() === key[0] && key[0] !== "_"));
+                return keys.filter(key => key.toLowerCase().indexOf(val) > -1);
+            } else {
+                const keys = new Set<string>();
+                docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
+                const noviceKeys = Array.from(keys).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 ||
+                    key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 ||
+                    (key[0]?.toUpperCase() === key[0] && key[0] !== "_"));
+                return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1);
+            }
+        }
+
+        if (docs instanceof Doc) {
+            return Object.keys(docs).filter(key => key.toLowerCase().indexOf(val) > -1);
+        } else {
+            const keys = new Set<string>();
+            docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
+            return Array.from(keys).filter(key => key.toLowerCase().indexOf(val) > -1);
+        }
+    }
+
+    @action
+    onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => {
+        this._currentKey = newValue;
+    }
+
+    getSuggestionValue = (suggestion: string) => suggestion;
+
+    renderSuggestion = (suggestion: string) => {
+        return <p>{suggestion}</p>;
+    }
+
+    onSuggestionFetch = async ({ value }: { value: string }) => {
+        const sugg = await this.getKeySuggestions(value);
+        runInAction(() => {
+            this.suggestions = sugg;
+        });
+    }
+
+    @action
+    onSuggestionClear = () => {
+        this.suggestions = [];
+    }
+
+    @action
+    setValue = (value: string) => {
+        this.document._pivotField = value;
+        return true;
+    }
+
+    @action toggleSort = () => {
+        this.document._columnsSort =
+            this.document._columnsSort === "descending" ? "ascending" :
+                this.document._columnsSort === "ascending" ? undefined : "descending";
+    }
+    @action resetValue = () => { this._currentKey = this.pivotField; };
+
+    render() {
+        const doctype = this.props.docView.Document.type;
+        const isPres: boolean = (doctype === DocumentType.PRES);
+        return (
+            isPres ? (null) : <div className="collectionStackingViewChrome-cont">
+                <div className="collectionStackingViewChrome-pivotField-cont">
+                    <div className="collectionStackingViewChrome-pivotField-label">
+                        GROUP BY:
+                    </div>
+                    <div className="collectionStackingViewChrome-sortIcon" onClick={this.toggleSort} style={{ transform: `rotate(${this.descending ? "180" : "0"}deg)` }}>
+                        <FontAwesomeIcon icon="caret-up" size="2x" color="white" />
+                    </div>
+                    <div className="collectionStackingViewChrome-pivotField">
+                        <EditableView
+                            GetValue={() => this.pivotField}
+                            autosuggestProps={
+                                {
+                                    resetValue: this.resetValue,
+                                    value: this._currentKey,
+                                    onChange: this.onKeyChange,
+                                    autosuggestProps: {
+                                        inputProps:
+                                        {
+                                            value: this._currentKey,
+                                            onChange: this.onKeyChange
+                                        },
+                                        getSuggestionValue: this.getSuggestionValue,
+                                        suggestions: this.suggestions,
+                                        alwaysRenderSuggestions: true,
+                                        renderSuggestion: this.renderSuggestion,
+                                        onSuggestionsFetchRequested: this.onSuggestionFetch,
+                                        onSuggestionsClearRequested: this.onSuggestionClear
+                                    }
+                                }}
+                            oneLine
+                            SetValue={this.setValue}
+                            contents={this.pivotField ? this.pivotField : "N/A"}
+                        />
+                    </div>
+                </div>
+            </div>
+        );
+    }
+}
+
 
 @observer
 export class CollectionSchemaViewChrome extends React.Component<CollectionViewMenuProps> {
-- 
cgit v1.2.3-70-g09d2


From b8bef1149b057d59076fe2c5e46d4b42c1f2ecdd Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 3 Aug 2022 09:19:09 -0400
Subject: fixing styles on parker branch

---
 .prettierrc                                     |   4 -
 src/client/documents/Documents.ts               |  80 +++++------
 src/client/views/collections/CollectionMenu.tsx | 173 ++++++++++++++----------
 3 files changed, 143 insertions(+), 114 deletions(-)
 delete mode 100644 .prettierrc

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

diff --git a/.prettierrc b/.prettierrc
deleted file mode 100644
index 222861c34..000000000
--- a/.prettierrc
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-  "tabWidth": 2,
-  "useTabs": false
-}
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 31cb0678a..32d7152fd 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1036,8 +1036,8 @@ export namespace Docs {
         }
 
         export function NoteTakingDocument(documents: Array<Doc>, options: DocumentOptions, id?: string, protoId?: string) {
-          return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.NoteTaking }, id, undefined, protoId);
-      }
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.NoteTaking }, id, undefined, protoId);
+        }
 
         export function MulticolumnDocument(documents: Array<Doc>, options: DocumentOptions) {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Multicolumn });
@@ -1490,48 +1490,52 @@ export namespace DocUtils {
     }
 
     export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number, simpleMenu: boolean = false, pivotField?: string, pivotValue?: string): void {
-        !simpleMenu && ContextMenu.Instance.addItem({
-            description: "Quick Notes",
-            subitems: DocListCast((Doc.UserDoc()["template-notes"] as Doc).data).map((note, i) => ({
-                description: ":" + StrCast(note.title),
-                event: undoBatch((args: { x: number, y: number }) => {
-                    const textDoc = Docs.Create.TextDocument("", {
-                        _width: 200, x, y, _autoHeight: note._autoHeight !== false,
-                        title: StrCast(note.title) + "#" + (note.aliasCount = NumCast(note.aliasCount) + 1),
-                    });
-                    textDoc.layoutKey = "layout_" + note.title;
-                    textDoc[textDoc.layoutKey] = note;
-                    if (pivotField) {
-                      textDoc[pivotField] = pivotValue;
-                    }
-                    docTextAdder(textDoc);
-                }),
-                icon: StrCast(note.icon) as IconProp
-            })) as ContextMenuProps[],
-            icon: "sticky-note"
-        });
+        !simpleMenu &&
+            ContextMenu.Instance.addItem({
+                description: 'Quick Notes',
+                subitems: DocListCast((Doc.UserDoc()['template-notes'] as Doc).data).map((note, i) => ({
+                    description: ':' + StrCast(note.title),
+                    event: undoBatch((args: { x: number; y: number }) => {
+                        const textDoc = Docs.Create.TextDocument('', {
+                            _width: 200,
+                            x,
+                            y,
+                            _autoHeight: note._autoHeight !== false,
+                            title: StrCast(note.title) + '#' + (note.aliasCount = NumCast(note.aliasCount) + 1),
+                        });
+                        textDoc.layoutKey = 'layout_' + note.title;
+                        textDoc[textDoc.layoutKey] = note;
+                        if (pivotField) {
+                            textDoc[pivotField] = pivotValue;
+                        }
+                        docTextAdder(textDoc);
+                    }),
+                    icon: StrCast(note.icon) as IconProp,
+                })) as ContextMenuProps[],
+                icon: 'sticky-note',
+            });
         const documentList: ContextMenuProps[] = DocListCast(DocListCast(Doc.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 = DocUtils.copyDragFactory(dragDoc);
-                if (newDoc) {
-                    newDoc.author = Doc.CurrentUserEmail;
-                    newDoc.x = x;
-                    newDoc.y = y;
-                    EquationBox.SelectOnLoad = newDoc[Id];
-                    if (newDoc.type === DocumentType.RTF) FormattedTextBox.SelectOnLoad = newDoc[Id];
-                    if (pivotField) {
-                      newDoc[pivotField] = pivotValue;
+                description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
+                event: undoBatch((args: { x: number; y: number }) => {
+                    const newDoc = DocUtils.copyDragFactory(dragDoc);
+                    if (newDoc) {
+                        newDoc.author = Doc.CurrentUserEmail;
+                        newDoc.x = x;
+                        newDoc.y = y;
+                        EquationBox.SelectOnLoad = newDoc[Id];
+                        if (newDoc.type === DocumentType.RTF) FormattedTextBox.SelectOnLoad = newDoc[Id];
+                        if (pivotField) {
+                            newDoc[pivotField] = pivotValue;
+                        }
+                        docAdder?.(newDoc);
                     }
-                    docAdder?.(newDoc);
-                }
-            }),
-            icon: Doc.toIcon(dragDoc),
-        })) as ContextMenuProps[];
+                }),
+                icon: Doc.toIcon(dragDoc),
+            })) as ContextMenuProps[];
         ContextMenu.Instance.addItem({
             description: 'Create document',
             subitems: documentList,
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index aa1765c14..cfbcec2d6 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -311,25 +311,49 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
         },
     };
 
-    @computed get _freeform_commands() { return Doc.noviceMode ? [this._viewCommand, this._saveFilterCommand] : [this._viewCommand, this._saveFilterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand]; }
-    @computed get _stacking_commands() { return Doc.noviceMode ? undefined : [this._contentCommand, this._templateCommand]; }
-    @computed get _notetaking_commands() { return Doc.noviceMode ? undefined : [this._contentCommand, this._templateCommand]; }
-    @computed get _masonry_commands() { return Doc.noviceMode ? undefined : [this._contentCommand, this._templateCommand]; }
-    @computed get _schema_commands() { return Doc.noviceMode ? undefined : [this._templateCommand, this._narrativeCommand]; }
-    @computed get _doc_commands() { return Doc.noviceMode ? undefined : [this._openLinkInCommand, this._onClickCommand]; }
-    @computed get _tree_commands() { return undefined; }
+    @computed get _freeform_commands() {
+        return Doc.noviceMode ? [this._viewCommand, this._saveFilterCommand] : [this._viewCommand, this._saveFilterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand];
+    }
+    @computed get _stacking_commands() {
+        return Doc.noviceMode ? undefined : [this._contentCommand, this._templateCommand];
+    }
+    @computed get _notetaking_commands() {
+        return Doc.noviceMode ? undefined : [this._contentCommand, this._templateCommand];
+    }
+    @computed get _masonry_commands() {
+        return Doc.noviceMode ? undefined : [this._contentCommand, this._templateCommand];
+    }
+    @computed get _schema_commands() {
+        return Doc.noviceMode ? undefined : [this._templateCommand, this._narrativeCommand];
+    }
+    @computed get _doc_commands() {
+        return Doc.noviceMode ? undefined : [this._openLinkInCommand, this._onClickCommand];
+    }
+    @computed get _tree_commands() {
+        return undefined;
+    }
     private get _buttonizableCommands() {
         switch (this.props.type) {
-            default: return this._doc_commands;
-            case CollectionViewType.Freeform: return this._freeform_commands;
-            case CollectionViewType.Tree: return this._tree_commands;
-            case CollectionViewType.Schema: return this._schema_commands;
-            case CollectionViewType.Stacking: return this._stacking_commands;
-            case CollectionViewType.NoteTaking: return this._notetaking_commands;
-            case CollectionViewType.Masonry: return this._stacking_commands;
-            case CollectionViewType.Time: return this._freeform_commands;
-            case CollectionViewType.Carousel: return this._freeform_commands;
-            case CollectionViewType.Carousel3D: return this._freeform_commands;
+            default:
+                return this._doc_commands;
+            case CollectionViewType.Freeform:
+                return this._freeform_commands;
+            case CollectionViewType.Tree:
+                return this._tree_commands;
+            case CollectionViewType.Schema:
+                return this._schema_commands;
+            case CollectionViewType.Stacking:
+                return this._stacking_commands;
+            case CollectionViewType.NoteTaking:
+                return this._notetaking_commands;
+            case CollectionViewType.Masonry:
+                return this._stacking_commands;
+            case CollectionViewType.Time:
+                return this._freeform_commands;
+            case CollectionViewType.Carousel:
+                return this._freeform_commands;
+            case CollectionViewType.Carousel3D:
+                return this._freeform_commands;
         }
     }
     private _commandRef = React.createRef<HTMLInputElement>();
@@ -363,12 +387,18 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
             default:
                 return this.otherSubChrome;
             case CollectionViewType.Invalid:
-            case CollectionViewType.Freeform: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={this.props.type === CollectionViewType.Invalid} />);
-            case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" {...this.props} />);
-            case CollectionViewType.NoteTaking: return (<CollectionNoteTakingViewChrome key="collchrome" {...this.props} />);
-            case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" {...this.props} />);
-            case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" {...this.props} />);
-            case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" {...this.props} />);
+            case CollectionViewType.Freeform:
+                return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={this.props.type === CollectionViewType.Invalid} />;
+            case CollectionViewType.Stacking:
+                return <CollectionStackingViewChrome key="collchrome" {...this.props} />;
+            case CollectionViewType.NoteTaking:
+                return <CollectionNoteTakingViewChrome key="collchrome" {...this.props} />;
+            case CollectionViewType.Schema:
+                return <CollectionSchemaViewChrome key="collchrome" {...this.props} />;
+            case CollectionViewType.Tree:
+                return <CollectionTreeViewChrome key="collchrome" {...this.props} />;
+            case CollectionViewType.Masonry:
+                return <CollectionStackingViewChrome key="collchrome" {...this.props} />;
             case CollectionViewType.Carousel:
             case CollectionViewType.Carousel3D:
                 return <Collection3DCarouselViewChrome key="collchrome" {...this.props} />;
@@ -1124,13 +1154,19 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
 
 @observer
 export class CollectionNoteTakingViewChrome extends React.Component<CollectionViewMenuProps> {
-    @observable private _currentKey: string = "";
+    @observable private _currentKey: string = '';
     @observable private suggestions: string[] = [];
 
-    get document() { return this.props.docView.props.Document; }
+    get document() {
+        return this.props.docView.props.Document;
+    }
 
-    @computed private get descending() { return StrCast(this.document._columnsSort) === "descending"; }
-    @computed get pivotField() { return StrCast(this.document._pivotField); }
+    @computed private get descending() {
+        return StrCast(this.document._columnsSort) === 'descending';
+    }
+    @computed get pivotField() {
+        return StrCast(this.document._pivotField);
+    }
 
     getKeySuggestions = async (value: string): Promise<string[]> => {
         const val = value.toLowerCase();
@@ -1138,16 +1174,14 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi
 
         if (Doc.UserDoc().noviceMode) {
             if (docs instanceof Doc) {
-                const keys = Object.keys(docs).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 ||
-                    key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 ||
-                    (key[0].toUpperCase() === key[0] && key[0] !== "_"));
+                const keys = Object.keys(docs).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('creationDate') >= 0 || key.indexOf('lastModified') >= 0 || (key[0].toUpperCase() === key[0] && key[0] !== '_'));
                 return keys.filter(key => key.toLowerCase().indexOf(val) > -1);
             } else {
                 const keys = new Set<string>();
                 docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
-                const noviceKeys = Array.from(keys).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 ||
-                    key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 ||
-                    (key[0]?.toUpperCase() === key[0] && key[0] !== "_"));
+                const noviceKeys = Array.from(keys).filter(
+                    key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('creationDate') >= 0 || key.indexOf('lastModified') >= 0 || (key[0]?.toUpperCase() === key[0] && key[0] !== '_')
+                );
                 return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1);
             }
         }
@@ -1159,81 +1193,77 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi
             docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
             return Array.from(keys).filter(key => key.toLowerCase().indexOf(val) > -1);
         }
-    }
+    };
 
     @action
     onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => {
         this._currentKey = newValue;
-    }
+    };
 
     getSuggestionValue = (suggestion: string) => suggestion;
 
     renderSuggestion = (suggestion: string) => {
         return <p>{suggestion}</p>;
-    }
+    };
 
     onSuggestionFetch = async ({ value }: { value: string }) => {
         const sugg = await this.getKeySuggestions(value);
         runInAction(() => {
             this.suggestions = sugg;
         });
-    }
+    };
 
     @action
     onSuggestionClear = () => {
         this.suggestions = [];
-    }
+    };
 
     @action
     setValue = (value: string) => {
         this.document._pivotField = value;
         return true;
-    }
+    };
 
     @action toggleSort = () => {
-        this.document._columnsSort =
-            this.document._columnsSort === "descending" ? "ascending" :
-                this.document._columnsSort === "ascending" ? undefined : "descending";
-    }
-    @action resetValue = () => { this._currentKey = this.pivotField; };
+        this.document._columnsSort = this.document._columnsSort === 'descending' ? 'ascending' : this.document._columnsSort === 'ascending' ? undefined : 'descending';
+    };
+    @action resetValue = () => {
+        this._currentKey = this.pivotField;
+    };
 
     render() {
         const doctype = this.props.docView.Document.type;
-        const isPres: boolean = (doctype === DocumentType.PRES);
-        return (
-            isPres ? (null) : <div className="collectionStackingViewChrome-cont">
+        const isPres: boolean = doctype === DocumentType.PRES;
+        return isPres ? null : (
+            <div className="collectionStackingViewChrome-cont">
                 <div className="collectionStackingViewChrome-pivotField-cont">
-                    <div className="collectionStackingViewChrome-pivotField-label">
-                        GROUP BY:
-                    </div>
-                    <div className="collectionStackingViewChrome-sortIcon" onClick={this.toggleSort} style={{ transform: `rotate(${this.descending ? "180" : "0"}deg)` }}>
+                    <div className="collectionStackingViewChrome-pivotField-label">GROUP BY:</div>
+                    <div className="collectionStackingViewChrome-sortIcon" onClick={this.toggleSort} style={{ transform: `rotate(${this.descending ? '180' : '0'}deg)` }}>
                         <FontAwesomeIcon icon="caret-up" size="2x" color="white" />
                     </div>
                     <div className="collectionStackingViewChrome-pivotField">
                         <EditableView
                             GetValue={() => this.pivotField}
-                            autosuggestProps={
-                                {
-                                    resetValue: this.resetValue,
-                                    value: this._currentKey,
-                                    onChange: this.onKeyChange,
-                                    autosuggestProps: {
-                                        inputProps:
-                                        {
-                                            value: this._currentKey,
-                                            onChange: this.onKeyChange
-                                        },
-                                        getSuggestionValue: this.getSuggestionValue,
-                                        suggestions: this.suggestions,
-                                        alwaysRenderSuggestions: true,
-                                        renderSuggestion: this.renderSuggestion,
-                                        onSuggestionsFetchRequested: this.onSuggestionFetch,
-                                        onSuggestionsClearRequested: this.onSuggestionClear
-                                    }
-                                }}
+                            autosuggestProps={{
+                                resetValue: this.resetValue,
+                                value: this._currentKey,
+                                onChange: this.onKeyChange,
+                                autosuggestProps: {
+                                    inputProps: {
+                                        value: this._currentKey,
+                                        onChange: this.onKeyChange,
+                                    },
+                                    getSuggestionValue: this.getSuggestionValue,
+                                    suggestions: this.suggestions,
+                                    alwaysRenderSuggestions: true,
+                                    renderSuggestion: this.renderSuggestion,
+                                    onSuggestionsFetchRequested: this.onSuggestionFetch,
+                                    onSuggestionsClearRequested: this.onSuggestionClear,
+                                },
+                            }}
                             oneLine
                             SetValue={this.setValue}
-                            contents={this.pivotField ? this.pivotField : "N/A"}
+                            contents={this.pivotField ? this.pivotField : 'N/A'}
                         />
                     </div>
                 </div>
@@ -1242,7 +1272,6 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi
     }
 }
 
-
 @observer
 export class CollectionSchemaViewChrome extends React.Component<CollectionViewMenuProps> {
     // private _textwrapAllRows: boolean = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
-- 
cgit v1.2.3-70-g09d2


From 513dcaa2d8c86f1cb5236ce89062cace6f418d1b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 10 Aug 2022 14:52:04 -0400
Subject: cleaned up pinning documents with an activeFrame index (and special
 case of isInkMask).  removed text-color from pres boxes which was breaking
 opacity when docs had an appearFrame > 0.

---
 src/client/views/DocumentButtonBar.tsx             |  18 +-
 src/client/views/collections/CollectionMenu.tsx    |  25 ++-
 src/client/views/collections/TabDocView.tsx        |  15 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 208 ++++++++++++---------
 src/client/views/nodes/DocumentView.tsx            |   4 +-
 src/client/views/nodes/trails/PresBox.tsx          |  23 +--
 6 files changed, 163 insertions(+), 130 deletions(-)

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

diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 1f8550ad6..40fc8dae6 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -5,7 +5,7 @@ import { action, computed, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc } from '../../fields/Doc';
 import { RichTextField } from '../../fields/RichTextField';
-import { Cast, NumCast } from '../../fields/Types';
+import { Cast, DocCast, NumCast } from '../../fields/Types';
 import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from '../../Utils';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
@@ -238,15 +238,13 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                 <div
                     className="documentButtonBar-icon"
                     style={{ color: 'white' }}
-                    onClick={e =>
-                        TabDocView.PinDoc(
-                            this.props
-                                .views()
-                                .filter(v => v)
-                                .map(dv => dv!.rootDoc),
-                            { pinDocView: true }
-                        )
-                    }>
+                    onClick={e => {
+                        const docs = this.props
+                            .views()
+                            .filter(v => v)
+                            .map(dv => dv!.rootDoc);
+                        TabDocView.PinDoc(docs, { pinDocView: true, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) });
+                    }}>
                     <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" />
                 </div>
             </Tooltip>
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index cfbcec2d6..8432619de 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -769,6 +769,21 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
         return this.selectedDoc?.type === DocumentType.RTF || (RichTextMenu.Instance?.view as any) ? true : false;
     }
 
+    public static gotoKeyFrame(doc: Doc, newFrame: number) {
+        if (!doc) {
+            return;
+        }
+        const dataField = doc[Doc.LayoutFieldKey(doc)];
+        const childDocs = DocListCast(dataField);
+        const currentFrame = Cast(doc._currentFrame, 'number', null);
+        if (currentFrame === undefined) {
+            doc._currentFrame = 0;
+            CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
+        }
+        CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0);
+        doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame);
+    }
+
     @undoBatch
     @action
     nextKeyframe = (): void => {
@@ -1569,13 +1584,5 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
     }
 }
 ScriptingGlobals.add(function gotoFrame(doc: any, newFrame: any) {
-    const dataField = doc[Doc.LayoutFieldKey(doc)];
-    const childDocs = DocListCast(dataField);
-    const currentFrame = Cast(doc._currentFrame, 'number', null);
-    if (currentFrame === undefined) {
-        doc._currentFrame = 0;
-        CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
-    }
-    CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0);
-    doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame);
+    CollectionFreeFormViewChrome.gotoKeyFrame(doc, newFrame);
 });
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index ec291e72c..f30faab79 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -288,14 +288,15 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     pinDoc.presEndTime = NumCast(doc.clipEnd, duration);
                 }
                 //save position
-                if (pinProps?.setPosition || pinDoc.isInkMask) {
-                    pinDoc.setPosition = true;
-                    pinDoc.y = doc.y;
-                    pinDoc.x = doc.x;
-                    pinDoc.presHideAfter = true;
-                    pinDoc.presHideBefore = true;
+                if (pinProps?.activeFrame !== undefined) {
+                    pinDoc.presActiveFrame = pinProps?.activeFrame;
                     pinDoc.title = doc.title + ' (move)';
-                    pinDoc.presMovement = PresMovement.None;
+                    pinDoc.presMovement = PresMovement.Pan;
+                    if (pinDoc.isInkMask) {
+                        pinDoc.presHideAfter = true;
+                        pinDoc.presHideBefore = true;
+                        pinDoc.presMovement = PresMovement.None;
+                    }
                 }
                 if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true;
                 PresBox.Instance?._selectedArray.clear();
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 284584a3d..e154e8445 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,26 +1,26 @@
-import { action, computed, observable, trace } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, Opt } from "../../../fields/Doc";
-import { List } from "../../../fields/List";
-import { listSpec } from "../../../fields/Schema";
-import { ComputedField } from "../../../fields/ScriptField";
-import { Cast, NumCast, StrCast } from "../../../fields/Types";
-import { TraceMobx } from "../../../fields/util";
-import { DashColor, numberRange, OmitKeys } from "../../../Utils";
-import { DocumentManager } from "../../util/DocumentManager";
-import { SelectionManager } from "../../util/SelectionManager";
-import { Transform } from "../../util/Transform";
-import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
-import { DocComponent } from "../DocComponent";
-import { InkingStroke } from "../InkingStroke";
-import { StyleProp } from "../StyleProvider";
-import "./CollectionFreeFormDocumentView.scss";
-import { DocumentView, DocumentViewProps } from "./DocumentView";
-import React = require("react");
+import { action, computed, observable, trace } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc, Opt } from '../../../fields/Doc';
+import { List } from '../../../fields/List';
+import { listSpec } from '../../../fields/Schema';
+import { ComputedField } from '../../../fields/ScriptField';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { TraceMobx } from '../../../fields/util';
+import { DashColor, numberRange, OmitKeys } from '../../../Utils';
+import { DocumentManager } from '../../util/DocumentManager';
+import { SelectionManager } from '../../util/SelectionManager';
+import { Transform } from '../../util/Transform';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
+import { DocComponent } from '../DocComponent';
+import { InkingStroke } from '../InkingStroke';
+import { StyleProp } from '../StyleProvider';
+import './CollectionFreeFormDocumentView.scss';
+import { DocumentView, DocumentViewProps } from './DocumentView';
+import React = require('react');
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
-    dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined;
-    sizeProvider?: (doc: Doc, replica: string) => { width: number, height: number } | undefined;
+    dataProvider?: (doc: Doc, replica: string) => { x: number; y: number; zIndex?: number; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined;
+    sizeProvider?: (doc: Doc, replica: string) => { width: number; height: number } | undefined;
     renderCutoffProvider: (doc: Doc) => boolean;
     zIndex?: number;
     highlight?: boolean;
@@ -32,29 +32,51 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
 
 @observer
 export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps>() {
-    public static animFields = ["_height", "_width", "x", "y", "_scrollTop", "opacity"];  // fields that are configured to be animatable using animation frames
+    public static animFields = ['_height', '_width', 'x', 'y', '_scrollTop', 'opacity']; // fields that are configured to be animatable using animation frames
     @observable _animPos: number[] | undefined = undefined;
     @observable _contentView: DocumentView | undefined | null;
-    get displayName() { return "CollectionFreeFormDocumentView(" + this.rootDoc.title + ")"; } // this makes mobx trace() statements more descriptive
-    get maskCentering() { return this.props.Document.isInkMask ? InkingStroke.MaskDim / 2 : 0; }
-    get transform() { return `translate(${this.X - this.maskCentering}px, ${this.Y - this.maskCentering}px) rotate(${NumCast(this.Document.jitterRotation, this.props.jitterRotation)}deg)`; }
-    get X() { return this.dataProvider ? this.dataProvider.x : NumCast(this.Document.x); }
-    get Y() { return this.dataProvider ? this.dataProvider.y : NumCast(this.Document.y); }
-    get ZInd() { return this.dataProvider ? this.dataProvider.zIndex : NumCast(this.Document.zIndex); }
-    get Opacity() { return this.dataProvider ? this.dataProvider.opacity : undefined; }
-    get Highlight() { return this.dataProvider?.highlight; }
-    @computed get ShowTitle() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ShowTitle) as (Opt<string>); }
-    @computed get dataProvider() { return this.props.dataProvider?.(this.props.Document, this.props.replica); }
-    @computed get sizeProvider() { return this.props.sizeProvider?.(this.props.Document, this.props.replica); }
+    get displayName() {
+        return 'CollectionFreeFormDocumentView(' + this.rootDoc.title + ')';
+    } // this makes mobx trace() statements more descriptive
+    get maskCentering() {
+        return this.props.Document.isInkMask ? InkingStroke.MaskDim / 2 : 0;
+    }
+    get transform() {
+        return `translate(${this.X - this.maskCentering}px, ${this.Y - this.maskCentering}px) rotate(${NumCast(this.Document.jitterRotation, this.props.jitterRotation)}deg)`;
+    }
+    get X() {
+        return this.dataProvider ? this.dataProvider.x : NumCast(this.Document.x);
+    }
+    get Y() {
+        return this.dataProvider ? this.dataProvider.y : NumCast(this.Document.y);
+    }
+    get ZInd() {
+        return this.dataProvider ? this.dataProvider.zIndex : NumCast(this.Document.zIndex);
+    }
+    get Opacity() {
+        return this.dataProvider ? this.dataProvider.opacity : undefined;
+    }
+    get Highlight() {
+        return this.dataProvider?.highlight;
+    }
+    @computed get ShowTitle() {
+        return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ShowTitle) as Opt<string>;
+    }
+    @computed get dataProvider() {
+        return this.props.dataProvider?.(this.props.Document, this.props.replica);
+    }
+    @computed get sizeProvider() {
+        return this.props.sizeProvider?.(this.props.Document, this.props.replica);
+    }
 
     styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
         if (property === StyleProp.Opacity && doc === this.layoutDoc) return this.Opacity; // only change the opacity for this specific document, not its children
         return this.props.styleProvider?.(doc, props, property);
-    }
+    };
 
     public static getValues(doc: Doc, time: number) {
         return CollectionFreeFormDocumentView.animFields.reduce((p, val) => {
-            p[val] = Cast(`${val}-indexed`, listSpec("number"), [NumCast(doc[val])]).reduce((p, v, i) => (i <= Math.round(time) && v !== undefined) || p === undefined ? v : p, undefined as any as number);
+            p[val] = Cast(`${val}-indexed`, listSpec('number'), [NumCast(doc[val])]).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as number);
             return p;
         }, {} as { [val: string]: Opt<number> });
     }
@@ -62,35 +84,43 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     public static setValues(time: number, d: Doc, vals: { [val: string]: Opt<number> }) {
         const timecode = Math.round(time);
         Object.keys(vals).forEach(val => {
-            const findexed = Cast(d[`${val}-indexed`], listSpec("number"), []).slice();
+            const findexed = Cast(d[`${val}-indexed`], listSpec('number'), []).slice();
             findexed[timecode] = vals[val] as any as number;
             d[`${val}-indexed`] = new List<number>(findexed);
         });
-        d.appearFrame && (d["text-color"] =
-            d.appearFrame === timecode + 1 ? "red" :
-                d.appearFrame < timecode + 1 ? "grey" : "black");
     }
 
     public static updateKeyframe(docs: Doc[], time: number, targetDoc?: Doc) {
         const timecode = Math.round(time);
-        docs.forEach(action(doc => {
-            doc._viewTransition = doc.dataTransition = "all 1s";
-            doc["text-color"] =
-                !doc.appearFrame || !targetDoc ? "black" :
-                    doc.appearFrame === timecode + 1 ? StrCast(targetDoc["pres-text-color"]) :
-                        doc.appearFrame < timecode + 1 ? StrCast(targetDoc["pres-text-viewed-color"]) :
-                            "black";
-            CollectionFreeFormDocumentView.animFields.forEach(val => {
-                const findexed = Cast(doc[`${val}-indexed`], listSpec("number"), null);
-                findexed?.length <= timecode + 1 && findexed.push(undefined as any as number);
-            });
-        }));
-        setTimeout(() => docs.forEach(doc => { doc._viewTransition = undefined; doc.dataTransition = "inherit"; }), 1010);
+        docs.forEach(
+            action(doc => {
+                doc._viewTransition = doc.dataTransition = 'all 1s';
+                CollectionFreeFormDocumentView.animFields.forEach(val => {
+                    const findexed = Cast(doc[`${val}-indexed`], listSpec('number'), null);
+                    findexed?.length <= timecode + 1 && findexed.push(undefined as any as number);
+                });
+            })
+        );
+        setTimeout(
+            () =>
+                docs.forEach(doc => {
+                    doc._viewTransition = undefined;
+                    doc.dataTransition = 'inherit';
+                }),
+            1010
+        );
     }
 
     public static gotoKeyframe(docs: Doc[]) {
-        docs.forEach(doc => doc._viewTransition = doc.dataTransition = "all 1s");
-        setTimeout(() => docs.forEach(doc => { doc._viewTransition = undefined; doc.dataTransition = "inherit"; }), 1010);
+        docs.forEach(doc => (doc._viewTransition = doc.dataTransition = 'all 1s'));
+        setTimeout(
+            () =>
+                docs.forEach(doc => {
+                    doc._viewTransition = undefined;
+                    doc.dataTransition = 'inherit';
+                }),
+            1010
+        );
     }
 
     public static setupZoom(doc: Doc, targDoc: Doc) {
@@ -102,21 +132,22 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         height.push(NumCast(targDoc._height));
         top.push(NumCast(targDoc._height) / -2);
         left.push(NumCast(targDoc._width) / -2);
-        doc["viewfinder-width-indexed"] = width;
-        doc["viewfinder-height-indexed"] = height;
-        doc["viewfinder-top-indexed"] = top;
-        doc["viewfinder-left-indexed"] = left;
+        doc['viewfinder-width-indexed'] = width;
+        doc['viewfinder-height-indexed'] = height;
+        doc['viewfinder-top-indexed'] = top;
+        doc['viewfinder-left-indexed'] = left;
     }
 
     public static setupKeyframes(docs: Doc[], currTimecode: number, makeAppear: boolean = false) {
         docs.forEach(doc => {
             if (doc.appearFrame === undefined) doc.appearFrame = currTimecode;
-            if (!doc["opacity-indexed"]) { // opacity is unlike other fields because it's value should not be undefined before it appears to enable it to fade-in
-                doc["opacity-indexed"] = new List<number>(numberRange(currTimecode + 1).map(t => !doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1));
+            if (!doc['opacity-indexed']) {
+                // opacity is unlike other fields because it's value should not be undefined before it appears to enable it to fade-in
+                doc['opacity-indexed'] = new List<number>(numberRange(currTimecode + 1).map(t => (!doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1)));
             }
-            CollectionFreeFormDocumentView.animFields.forEach(val => doc[val] = ComputedField.MakeInterpolated(val, "activeFrame", doc, currTimecode));
-            doc.activeFrame = ComputedField.MakeFunction("self.context?._currentFrame||0");
-            doc.dataTransition = "inherit";
+            CollectionFreeFormDocumentView.animFields.forEach(val => (doc[val] = ComputedField.MakeInterpolated(val, 'activeFrame', doc, currTimecode)));
+            doc.activeFrame = ComputedField.MakeFunction('self.context?._currentFrame||0');
+            doc.dataTransition = 'inherit';
         });
     }
 
@@ -131,7 +162,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 topDoc.x = spt[0];
                 topDoc.y = spt[1];
                 this.props.removeDocument?.(topDoc);
-                this.props.addDocTab(topDoc, "inParent");
+                this.props.addDocTab(topDoc, 'inParent');
             } else {
                 const spt = this.screenToLocalTransform().inverse().transformPoint(0, 0);
                 const fpt = screenXf.transformPoint(spt[0], spt[1]);
@@ -141,14 +172,14 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             }
             setTimeout(() => SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(topDoc, container)!, false), 0);
         }
-    }
+    };
 
     nudge = (x: number, y: number) => {
         this.props.Document.x = NumCast(this.props.Document.x) + x;
         this.props.Document.y = NumCast(this.props.Document.y) + y;
-    }
-    panelWidth = () => (this.sizeProvider?.width || this.props.PanelWidth?.());
-    panelHeight = () => (this.sizeProvider?.height || this.props.PanelHeight?.());
+    };
+    panelWidth = () => this.sizeProvider?.width || this.props.PanelWidth?.();
+    panelHeight = () => this.sizeProvider?.height || this.props.PanelHeight?.();
     screenToLocalTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.X, -this.Y);
     focusDoc = (doc: Doc) => this.props.focus(doc);
     returnThis = () => this;
@@ -163,24 +194,27 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             PanelHeight: this.panelHeight,
         };
         const background = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
-        const mixBlendMode = StrCast(this.layoutDoc.mixBlendMode) as any || (typeof background === "string" && background && (!background.startsWith("linear") && DashColor(background).alpha() !== 1) ? "multiply" : undefined);
-        return <div className={"collectionFreeFormDocumentView-container"}
-            style={{
-                outline: this.Highlight ? "orange solid 2px" : "",
-                width: this.panelWidth(),
-                height: this.panelHeight(),
-                transform: this.transform,
-                transformOrigin: '50% 50%',
-                transition: this.dataProvider?.transition ?? (this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition)),
-                zIndex: this.ZInd,
-                mixBlendMode: mixBlendMode,
-                display: this.ZInd === -99 ? "none" : undefined
-            }} >
-            {this.props.renderCutoffProvider(this.props.Document) ?
-                <div style={{ position: "absolute", width: this.panelWidth(), height: this.panelHeight(), background: "lightGreen" }} />
-                :
-                <DocumentView {...divProps} ref={action((r: DocumentView | null) => this._contentView = r)} />
-            }
-        </div>;
+        const mixBlendMode = (StrCast(this.layoutDoc.mixBlendMode) as any) || (typeof background === 'string' && background && !background.startsWith('linear') && DashColor(background).alpha() !== 1 ? 'multiply' : undefined);
+        return (
+            <div
+                className={'collectionFreeFormDocumentView-container'}
+                style={{
+                    outline: this.Highlight ? 'orange solid 2px' : '',
+                    width: this.panelWidth(),
+                    height: this.panelHeight(),
+                    transform: this.transform,
+                    transformOrigin: '50% 50%',
+                    transition: this.dataProvider?.transition ?? (this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition)),
+                    zIndex: this.ZInd,
+                    mixBlendMode: mixBlendMode,
+                    display: this.ZInd === -99 ? 'none' : undefined,
+                }}>
+                {this.props.renderCutoffProvider(this.props.Document) ? (
+                    <div style={{ position: 'absolute', width: this.panelWidth(), height: this.panelHeight(), background: 'lightGreen' }} />
+                ) : (
+                    <DocumentView {...divProps} ref={action((r: DocumentView | null) => (this._contentView = r))} />
+                )}
+            </div>
+        );
     }
 }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f87581875..f9ef85595 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1640,8 +1640,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     render() {
         TraceMobx();
-        const xshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
-        const yshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
+        const xshift = () => (this.props.Document.isInkMask && !this.props.LayoutTemplateString && !this.props.LayoutTemplate?.() ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
+        const yshift = () => (this.props.Document.isInkMask && !this.props.LayoutTemplateString && !this.props.LayoutTemplate?.() ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
         const isPresTreeElement: boolean = this.props.treeViewDoc?.type === DocumentType.PRES;
         const isButton: boolean = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear;
         return (
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 6e5eb3300..3bbdce1e4 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -29,10 +29,11 @@ import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentVie
 import { FieldView, FieldViewProps } from '../FieldView';
 import './PresBox.scss';
 import { PresEffect, PresMovement, PresStatus } from './PresEnums';
+import { CollectionFreeFormViewChrome } from '../../collections/CollectionMenu';
 
 export interface PinProps {
     audioRange?: boolean;
-    setPosition?: boolean;
+    activeFrame?: number;
     hidePresBox?: boolean;
     pinWithView?: PinViewProps;
     pinDocView?: boolean; // whether the current view specs of the document should be saved the pinned document
@@ -509,7 +510,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this.childDocs.forEach((doc, index) => {
             const curDoc = Cast(doc, Doc, null);
             const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null);
-            if (tagDoc) tagDoc.opacity = 1;
+            //if (tagDoc) tagDoc.opacity = 1;
             const itemIndexes: number[] = this.getAllIndexes(this.tagDocs, tagDoc);
             const curInd: number = itemIndexes.indexOf(index);
             if (tagDoc === this.layoutDoc.presCollection) {
@@ -519,7 +520,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 } else if (curDoc.presHideBefore) {
                     if (index > this.itemIndex) {
                         tagDoc.opacity = 0;
-                    } else if (!curDoc.presHideAfter) {
+                    } else if (index === this.itemIndex || !curDoc.presHideAfter) {
                         tagDoc.opacity = 1;
                     }
                 }
@@ -527,7 +528,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 } else if (curDoc.presHideAfter) {
                     if (index < this.itemIndex) {
                         tagDoc.opacity = 0;
-                    } else if (!curDoc.presHideBefore) {
+                    } else if (index === this.itemIndex || !curDoc.presHideBefore) {
                         tagDoc.opacity = 1;
                     }
                 }
@@ -821,17 +822,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (doc.presPinView || doc.presentationTargetDoc === this.layoutDoc.presCollection) setTimeout(() => this.updateCurrentPresentation(context), 0);
         else this.updateCurrentPresentation(context);
 
-        if (this.activeItem.setPosition && this.activeItem.y !== undefined && this.activeItem.x !== undefined && this.targetDoc.x !== undefined && this.targetDoc.y !== undefined) {
-            const timer = (ms: number) => new Promise(res => (this._presTimer = setTimeout(res, ms)));
-            const time = 10;
-            const ydiff = NumCast(this.activeItem.y) - NumCast(this.targetDoc.y);
-            const xdiff = NumCast(this.activeItem.x) - NumCast(this.targetDoc.x);
-
-            for (let i = 0; i < time; i++) {
-                this.targetDoc.x = NumCast(this.targetDoc.x) + xdiff / time;
-                this.targetDoc.y = NumCast(this.targetDoc.y) + ydiff / time;
-                await timer(0.1);
-            }
+        if (this.activeItem.presActiveFrame !== undefined) {
+            const context = DocCast(DocCast(this.activeItem.presentationTargetDoc).context);
+            context && CollectionFreeFormViewChrome.gotoKeyFrame(context, NumCast(this.activeItem.presActiveFrame));
         }
     };
 
-- 
cgit v1.2.3-70-g09d2


From 081632757af3c2ec2e4482c1e5fe710c8ee6cad8 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 11 Aug 2022 19:01:37 -0400
Subject: added better ui and backend support for mask ink strokes. added frame
 #'s ui for seeing current animation frame.

---
 src/client/documents/Documents.ts                  | 19 ++++++++--
 src/client/util/CurrentUserUtils.ts                |  6 ++--
 src/client/util/DragManager.ts                     | 14 ++++----
 src/client/views/InkingStroke.tsx                  | 28 +++++++++++----
 src/client/views/MainView.tsx                      |  1 +
 src/client/views/collections/CollectionMenu.tsx    |  2 +-
 src/client/views/collections/TabDocView.tsx        | 10 +++---
 .../CollectionFreeFormLayoutEngines.tsx            |  1 +
 .../collectionFreeForm/CollectionFreeFormView.scss | 23 +++++++++----
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  9 +++--
 src/client/views/global/globalCssVariables.scss    | 13 +++----
 .../views/global/globalCssVariables.scss.d.ts      |  6 ++--
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  7 ++--
 src/client/views/nodes/DocumentView.tsx            |  6 ++--
 src/client/views/nodes/button/FontIconBox.tsx      | 34 +++++++++++++-----
 src/client/views/nodes/trails/PresBox.tsx          | 36 +++++++++++--------
 src/fields/InkField.ts                             | 40 +++++++++++-----------
 17 files changed, 162 insertions(+), 93 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index c7ea04839..cf9ed43e1 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -31,7 +31,7 @@ import { CollectionView } from '../views/collections/CollectionView';
 import { ContextMenu } from '../views/ContextMenu';
 import { ContextMenuProps } from '../views/ContextMenuItem';
 import { DFLT_IMAGE_NATIVE_DIM } from '../views/global/globalCssVariables.scss';
-import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke } from '../views/InkingStroke';
+import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke } from '../views/InkingStroke';
 import { AudioBox } from '../views/nodes/AudioBox';
 import { FontIconBox } from '../views/nodes/button/FontIconBox';
 import { ColorBox } from '../views/nodes/ColorBox';
@@ -918,9 +918,22 @@ export namespace Docs {
             return linkDoc;
         }
 
-        export function InkDocument(color: string, tool: string, strokeWidth: number, strokeBezier: string, fillColor: string, arrowStart: string, arrowEnd: string, dash: string, points: PointData[], options: DocumentOptions = {}) {
+        export function InkDocument(
+            color: string,
+            tool: string,
+            strokeWidth: number,
+            strokeBezier: string,
+            fillColor: string,
+            arrowStart: string,
+            arrowEnd: string,
+            dash: string,
+            points: PointData[],
+            isInkMask: boolean,
+            options: DocumentOptions = {}
+        ) {
             const I = new Doc();
             I[Initializing] = true;
+            I.isInkMask = isInkMask;
             I.type = DocumentType.INK;
             I.layout = InkingStroke.LayoutString('data');
             I.color = color;
@@ -1424,7 +1437,7 @@ export namespace DocUtils {
             created = Docs.Create.RecordingDocument(field.url.href, resolved);
             layout = RecordingBox.LayoutString;
         } else if (field instanceof InkField) {
-            created = Docs.Create.InkDocument(ActiveInkColor(), Doc.ActiveTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), field.inkData, resolved);
+            created = Docs.Create.InkDocument(ActiveInkColor(), Doc.ActiveTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), field.inkData, ActiveIsInkMask(), resolved);
             layout = InkingStroke.LayoutString;
         } else if (field instanceof List && field[0] instanceof Doc) {
             created = Docs.Create.StackingDocument(DocListCast(field), resolved);
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index d19874720..dcf4a71c8 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -645,8 +645,9 @@ export class CurrentUserUtils {
             // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", scripts:{onClick: 'setActiveTool("highlighter")'} },
             { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle",                      scripts: {onClick:'{ return setActiveTool("circle", _readOnly_);}'} },
             // { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveTool("square")' },
-            { title: "Line",   toolTip: "Line (Ctrl+Shift+L)",   btnType: ButtonType.ToggleButton, icon: "minus",                       scripts: {onClick: '{ return setActiveTool("line", _readOnly_);}' }},
-            { title: "Fill",   toolTip: "Fill color",            btnType: ButtonType.ColorButton,  icon: "fill-drip",ignoreClick: true, scripts: {script: "{ return setFillColor(value, _readOnly_);}"} },
+            { title: "Line",   toolTip: "Line (Ctrl+Shift+L)",   btnType: ButtonType.ToggleButton, icon: "minus",                       scripts: {onClick:'{ return setActiveTool("line", _readOnly_);}' }},
+            { title: "Mask",   toolTip: "Mask",                  btnType: ButtonType.ToggleButton, icon: "user-circle",                 scripts: {onClick:'{ return setIsInkMask(_readOnly_);}'} },
+            { title: "Fill",   toolTip: "Fill color",            btnType: ButtonType.ColorButton,  icon: "fill-drip",ignoreClick: true, scripts: {script: '{ return setFillColor(value, _readOnly_);}'} },
             { title: "Width",  toolTip: "Stroke width",          btnType: ButtonType.NumberButton,                   ignoreClick: true, scripts: {script: '{ return setStrokeWidth(value, _readOnly_);}'}, numBtnType: NumButtonType.Slider, numBtnMin: 1},
             { title: "Color",  toolTip: "Stroke color",          btnType: ButtonType.ColorButton,  icon: "pen",      ignoreClick: true, scripts: {script: '{ return setStrokeColor(value, _readOnly_);}'} },
         ];
@@ -674,6 +675,7 @@ export class CurrentUserUtils {
                     CollectionViewType.Grid, CollectionViewType.NoteTaking]),
               title: "Perspective",                    toolTip: "View",                 btnType: ButtonType.DropdownList,                                                                                    ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}},
             { title: "Back",    icon: "chevron-left",  toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton,         funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20,  scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
+            { title: "Num",     icon: "",           toolTip: "Frame Number",         btnType: ButtonType.TextButton,          funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement().currentFrame.toString()'}, width: 20,  scripts: {}},
             { title: "Fwd",     icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton,         funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20,  scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
             { title: "Fill",    icon: "fill-drip",     toolTip: "Background Fill Color",btnType: ButtonType.ColorButton,         funcs: {hidden: '!SelectionManager_selectedDocType()'},                     ignoreClick: true, width: 20,  scripts: { script:  'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected
             { title: "Header",  icon: "heading",       toolTip: "Header Color",         btnType: ButtonType.ColorButton,         funcs: {hidden: '!SelectionManager_selectedDocType()'},                     ignoreClick: true,             scripts: { script:  'return setHeaderColor(value, _readOnly_)'}},
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index ccd94c56e..d781a87ab 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -377,14 +377,14 @@ export namespace DragManager {
             }
             const rect = ele.getBoundingClientRect();
             const scaleX = rect.width / (ele.offsetWidth || rect.width);
-            const scaleY = ele.offsetHeight ? rect.height / (ele.offsetHeight || rect.height) : scaleX;
+            const scaleY = scaleX; //ele.offsetHeight ? rect.height / (ele.offsetHeight || rect.height) : scaleX;
 
             elesCont.left = Math.min(rect.left, elesCont.left);
             elesCont.top = Math.min(rect.top, elesCont.top);
             elesCont.right = Math.max(rect.right, elesCont.right);
             elesCont.bottom = Math.max(rect.bottom, elesCont.bottom);
-            xs.push(rect.left);
-            ys.push(rect.top);
+            xs.push(rect.left + (options?.offsetX || 0));
+            ys.push(rect.top + (options?.offsetY || 0));
             scaleXs.push(scaleX);
             scaleYs.push(scaleY);
             Object.assign(dragElement.style, {
@@ -401,9 +401,9 @@ export namespace DragManager {
                 transformOrigin: '0 0',
                 width: `${rect.width / scaleX}px`,
                 height: `${rect.height / scaleY}px`,
-                transform: `translate(${rect.left + (options?.offsetX || 0)}px, ${rect.top + (options?.offsetY || 0)}px) scale(${scaleX}, ${scaleY})`,
+                transform: `translate(${xs[0]}px, ${ys[0]}px) scale(${scaleX}, ${scaleY})`,
             });
-            dragLabel.style.transform = `translate(${rect.left + (options?.offsetX || 0)}px, ${rect.top + (options?.offsetY || 0) - 20}px)`;
+            dragLabel.style.transform = `translate(${xs[0]}px, ${ys[0] - 20}px)`;
 
             if (docsToDrag.length) {
                 const pdfBox = dragElement.getElementsByTagName('canvas');
@@ -543,8 +543,8 @@ export namespace DragManager {
             const moveVec = { x: x - lastPt.x, y: y - lastPt.y };
             lastPt = { x, y };
 
-            dragLabel.style.transform = `translate(${xs[0] + moveVec.x + (options?.offsetX || 0)}px, ${ys[0] + moveVec.y + (options?.offsetY || 0) - 20}px)`;
-            dragElements.map((dragElement, i) => (dragElement.style.transform = `translate(${(xs[i] += moveVec.x) + (options?.offsetX || 0)}px, ${(ys[i] += moveVec.y) + (options?.offsetY || 0)}px)  scale(${scaleXs[i]}, ${scaleYs[i]})`));
+            dragElements.map((dragElement, i) => (dragElement.style.transform = `translate(${(xs[i] += moveVec.x)}px, ${(ys[i] += moveVec.y)}px)  scale(${scaleXs[i]}, ${scaleYs[i]})`));
+            dragLabel.style.transform = `translate(${xs[0]}px, ${ys[0] - 20}px)`;
         };
         const upHandler = (e: PointerEvent) => {
             clearTimeout(startWindowDragTimer);
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index e5de7a0c5..2671aea56 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -23,9 +23,9 @@
 import React = require('react');
 import { action, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
-import { Doc, WidthSym } from '../../fields/Doc';
+import { Doc, HeightSym, WidthSym } from '../../fields/Doc';
 import { InkData, InkField, InkTool } from '../../fields/InkField';
-import { Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
+import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
 import { TraceMobx } from '../../fields/util';
 import { OmitKeys, returnFalse, setupMoveUpEvents } from '../../Utils';
 import { CognitiveServices } from '../cognitive_services/CognitiveServices';
@@ -42,12 +42,13 @@ import { InkTangentHandles } from './InkTangentHandles';
 import { DocComponentView } from './nodes/DocumentView';
 import { FieldView, FieldViewProps } from './nodes/FieldView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import { INK_MASK_SIZE } from './global/globalCssVariables.scss';
 import './InkStroke.scss';
 import Color = require('color');
 
 @observer
 export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
-    static readonly MaskDim = 50000; // choose a really big number to make sure mask fits over container (which in theory can be arbitrarily big)
+    static readonly MaskDim = INK_MASK_SIZE; // choose a really big number to make sure mask fits over container (which in theory can be arbitrarily big)
     public static LayoutString(fieldStr: string) {
         return FieldView.LayoutString(InkingStroke, fieldStr);
     }
@@ -353,9 +354,18 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         const endMarker = StrCast(this.layoutDoc.strokeEndMarker);
         const markerScale = NumCast(this.layoutDoc.strokeMarkerScale, 1);
         const closed = InkingStroke.IsClosed(inkData);
-        const fillColor = StrCast(this.layoutDoc.fillColor, 'transparent');
+        const isInkMask = BoolCast(this.layoutDoc.isInkMask);
+        const fillColor = isInkMask ? '#aaaaaa' : StrCast(this.layoutDoc.fillColor, 'transparent');
         const strokeColor = !closed && fillColor && fillColor !== 'transparent' ? fillColor : StrCast(this.layoutDoc.color);
 
+        // bcz: Hack!! Not really sure why, but having fractional values for width/height of mask ink strokes causes the dragging clone (see DragManager) to be offset from where it should be.
+        if (isInkMask && (this.layoutDoc[WidthSym]() !== Math.round(this.layoutDoc[WidthSym]()) || this.layoutDoc[HeightSym]() !== Math.round(this.layoutDoc[HeightSym]()))) {
+            setTimeout(() => {
+                this.layoutDoc._width = Math.round(NumCast(this.layoutDoc[WidthSym]()));
+                this.layoutDoc._height = Math.round(NumCast(this.layoutDoc[HeightSym]()));
+            });
+        }
+
         // Visually renders the polygonal line made by the user.
         const inkLine = InteractionUtils.CreatePolyline(
             inkData,
@@ -430,7 +440,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                     className="inkStroke"
                     style={{
                         transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
-                        mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
+                        // mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
                         cursor: this.props.isSelected() ? 'default' : undefined,
                     }}
                     {...(!closed ? interactions : {})}>
@@ -467,7 +477,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                         className="inkStroke"
                         style={{
                             transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
-                            mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
+                            mixBlendMode: 'unset',
                             cursor: this.props.isSelected() ? 'default' : undefined,
                             position: 'absolute',
                         }}
@@ -489,6 +499,9 @@ export function SetActiveBezierApprox(bezier: string): void {
 export function SetActiveInkColor(value: string) {
     ActiveInkPen() && (ActiveInkPen().activeInkColor = value);
 }
+export function SetActiveIsInkMask(value: boolean) {
+    ActiveInkPen() && (ActiveInkPen().activeIsInkMask = value);
+}
 export function SetActiveFillColor(value: string) {
     ActiveInkPen() && (ActiveInkPen().activeFillColor = value);
 }
@@ -513,6 +526,9 @@ export function ActiveInkColor(): string {
 export function ActiveFillColor(): string {
     return StrCast(ActiveInkPen()?.activeFillColor, '');
 }
+export function ActiveIsInkMask(): boolean {
+    return BoolCast(ActiveInkPen()?.activeIsInkMask, false);
+}
 export function ActiveArrowStart(): string {
     return StrCast(ActiveInkPen()?.activeArrowStart, '');
 }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index e96f65548..c166594e5 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -457,6 +457,7 @@ export class MainView extends React.Component {
                 fa.faVolumeDown,
                 fa.faSquareRootAlt,
                 fa.faVolumeMute,
+                fa.faUserCircle,
             ]
         );
         this.initAuthenticationRouters();
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 8432619de..eb55650e4 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -1025,7 +1025,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
                                         <FontAwesomeIcon icon={'caret-left'} size={'lg'} />
                                     </div>
                                 </Tooltip>
-                                <Tooltip key="num" title={<div className="dash-tooltip">Toggle View All</div>} placement="bottom">
+                                <Tooltip key="num" title={<div className="dash-tooltip">Frame number</div>} placement="bottom">
                                     <div
                                         className="numKeyframe"
                                         style={{ color: this.props.docView.ComponentView?.getKeyFrameEditing?.() ? 'white' : 'black', backgroundColor: this.props.docView.ComponentView?.getKeyFrameEditing?.() ? '#5B9FDD' : '#AEDDF8' }}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index f30faab79..ff4b1e2ce 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -292,11 +292,11 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     pinDoc.presActiveFrame = pinProps?.activeFrame;
                     pinDoc.title = doc.title + ' (move)';
                     pinDoc.presMovement = PresMovement.Pan;
-                    if (pinDoc.isInkMask) {
-                        pinDoc.presHideAfter = true;
-                        pinDoc.presHideBefore = true;
-                        pinDoc.presMovement = PresMovement.None;
-                    }
+                }
+                if (pinDoc.isInkMask) {
+                    pinDoc.presHideAfter = true;
+                    pinDoc.presHideBefore = true;
+                    pinDoc.presMovement = PresMovement.None;
                 }
                 if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true;
                 PresBox.Instance?._selectedArray.clear();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 3d85d32a0..ee01c341b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -45,6 +45,7 @@ export interface PoolData {
 export interface ViewDefResult {
     ele: JSX.Element;
     bounds?: ViewDefBounds;
+    inkMask?: boolean;
 }
 function toLabel(target: FieldResult<Field>) {
     if (typeof target === 'number' || Number(target)) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 79e063f7f..010132aa5 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -1,4 +1,4 @@
-@import "../../global/globalCssVariables";
+@import '../../global/globalCssVariables';
 
 .collectionfreeformview-none {
     position: inherit;
@@ -20,8 +20,19 @@
     pointer-events: none;
 }
 
+.collectionfreeformview-mask {
+    mix-blend-mode: multiply;
+    z-index: 5000;
+    width: $INK_MASK_SIZE;
+    height: $INK_MASK_SIZE;
+    transform: translate($INK_MASK_SIZE_HALF, $INK_MASK_SIZE_HALF);
+    background-color: rgba(0, 0, 0, 0.7);
+    pointer-events: none;
+    position: absolute;
+}
+
 .collectionfreeformview-viewdef {
-    >.collectionFreeFormDocumentView-container {
+    > .collectionFreeFormDocumentView-container {
         pointer-events: none;
 
         .contentFittingDocumentDocumentView-previewDoc {
@@ -210,13 +221,13 @@
         }
     }
 
-    .collectionfreeformview>.jsx-parser {
+    .collectionfreeformview > .jsx-parser {
         position: inherit;
         height: 100%;
         width: 100%;
     }
 
-    >.jsx-parser {
+    > .jsx-parser {
         z-index: 0;
     }
 
@@ -268,6 +279,6 @@
 
 .pullpane-indicator {
     z-index: 99999;
-    background-color: rgba($color: #000000, $alpha: .4);
+    background-color: rgba($color: #000000, $alpha: 0.4);
     position: absolute;
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 82b377dfa..f3074543b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -34,7 +34,7 @@ import { COLLECTION_BORDER_WIDTH } from '../../../views/global/globalCssVariable
 import { Timeline } from '../../animationtimeline/Timeline';
 import { ContextMenu } from '../../ContextMenu';
 import { GestureOverlay } from '../../GestureOverlay';
-import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
+import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
 import { LightboxView } from '../../LightboxView';
 import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView';
 import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from '../../nodes/DocumentView';
@@ -121,7 +121,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @observable ChildDrag: DocumentView | undefined; // child document view being dragged.  needed to update drop areas of groups when a group item is dragged.
 
     @computed get views() {
-        return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele);
+        const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask).map(ele => ele.ele);
+        const renderableEles = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && !ele.inkMask).map(ele => ele.ele);
+        if (viewsMask.length) renderableEles.push(<div className="collectionfreeformview-mask">{viewsMask}</div>);
+        return renderableEles;
     }
     @computed get fitToContentVals() {
         return {
@@ -579,6 +582,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     ActiveArrowEnd(),
                     ActiveDash(),
                     points,
+                    ActiveIsInkMask(),
                     {
                         title: 'ink stroke',
                         x: B.x - ActiveInkWidth() / 2,
@@ -1519,6 +1523,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 elements.push({
                     ele: this.getChildDocView(entry[1]),
                     bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica),
+                    inkMask: BoolCast(entry[1].pair.layout.isInkMask),
                 })
             );
 
diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss
index a14634bdc..c5968d9a7 100644
--- a/src/client/views/global/globalCssVariables.scss
+++ b/src/client/views/global/globalCssVariables.scss
@@ -1,4 +1,4 @@
-@import url("https://fonts.googleapis.com/css2?family=Roboto&display=swap");
+@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
 // colors
 $white: #ffffff;
 $off-white: #fdfdfd;
@@ -16,8 +16,10 @@ $yellow: #f5d747;
 
 $close-red: #e48282;
 
-$drop-shadow: "#32323215";
+$drop-shadow: '#32323215';
 
+$INK_MASK_SIZE: 50000;
+$INK_MASK_SIZE_HALF: -25000px;
 
 //padding
 $minimum-padding: 4px;
@@ -28,8 +30,7 @@ $large-padding: 32px;
 $icon-size: 28px;
 
 // fonts
-$sans-serif: "Roboto",
-sans-serif;
+$sans-serif: 'Roboto', sans-serif;
 $large-header: 16px;
 $body-text: 12px;
 $small-text: 9px;
@@ -54,7 +55,6 @@ $standard-border-radius: 3px;
 // shadow
 $standard-box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
 
-
 $dashboardselector-height: 32px;
 $mainTextInput-zindex: 999; // then text input overlay so that it's context menu will appear over decorations, etc
 $docDecorations-zindex: 998; // then doc decorations appear over everything else
@@ -79,4 +79,5 @@ $TREE_BULLET_WIDTH: 20px;
     DFLT_IMAGE_NATIVE_DIM: $DFLT_IMAGE_NATIVE_DIM;
     LEFT_MENU_WIDTH: $LEFT_MENU_WIDTH;
     TREE_BULLET_WIDTH: $TREE_BULLET_WIDTH;
-}
\ No newline at end of file
+    INK_MASK_SIZE: $INK_MASK_SIZE;
+}
diff --git a/src/client/views/global/globalCssVariables.scss.d.ts b/src/client/views/global/globalCssVariables.scss.d.ts
index 59c2b3585..76259113c 100644
--- a/src/client/views/global/globalCssVariables.scss.d.ts
+++ b/src/client/views/global/globalCssVariables.scss.d.ts
@@ -1,6 +1,5 @@
-
 interface IGlobalScss {
-    contextMenuZindex: string;  // context menu shows up over everything
+    contextMenuZindex: string; // context menu shows up over everything
     SCHEMA_DIVIDER_WIDTH: string;
     COLLECTION_BORDER_WIDTH: string;
     MINIMIZED_ICON_SIZE: string;
@@ -11,7 +10,8 @@ interface IGlobalScss {
     DFLT_IMAGE_NATIVE_DIM: string;
     LEFT_MENU_WIDTH: string;
     TREE_BULLET_WIDTH: string;
+    INK_MASK_SIZE: number;
 }
 declare const globalCssVariables: IGlobalScss;
 
-export = globalCssVariables;
\ No newline at end of file
+export = globalCssVariables;
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index e154e8445..78f351f4f 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -38,11 +38,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     get displayName() {
         return 'CollectionFreeFormDocumentView(' + this.rootDoc.title + ')';
     } // this makes mobx trace() statements more descriptive
-    get maskCentering() {
-        return this.props.Document.isInkMask ? InkingStroke.MaskDim / 2 : 0;
-    }
     get transform() {
-        return `translate(${this.X - this.maskCentering}px, ${this.Y - this.maskCentering}px) rotate(${NumCast(this.Document.jitterRotation, this.props.jitterRotation)}deg)`;
+        return `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Document.jitterRotation, this.props.jitterRotation)}deg)`;
     }
     get X() {
         return this.dataProvider ? this.dataProvider.x : NumCast(this.Document.x);
@@ -194,7 +191,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             PanelHeight: this.panelHeight,
         };
         const background = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
-        const mixBlendMode = (StrCast(this.layoutDoc.mixBlendMode) as any) || (typeof background === 'string' && background && !background.startsWith('linear') && DashColor(background).alpha() !== 1 ? 'multiply' : undefined);
+        const mixBlendMode = undefined; // (StrCast(this.layoutDoc.mixBlendMode) as any) || (typeof background === 'string' && background && !background.startsWith('linear') && DashColor(background).alpha() !== 1 ? 'multiply' : undefined);
         return (
             <div
                 className={'collectionFreeFormDocumentView-container'}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f9ef85595..bcc55eab4 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1640,8 +1640,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     render() {
         TraceMobx();
-        const xshift = () => (this.props.Document.isInkMask && !this.props.LayoutTemplateString && !this.props.LayoutTemplate?.() ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
-        const yshift = () => (this.props.Document.isInkMask && !this.props.LayoutTemplateString && !this.props.LayoutTemplate?.() ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
+        const xshift = () => (Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
+        const yshift = () => (Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
         const isPresTreeElement: boolean = this.props.treeViewDoc?.type === DocumentType.PRES;
         const isButton: boolean = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear;
         return (
@@ -1652,7 +1652,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                         ref={this.ContentRef}
                         style={{
                             transition: this.props.dataTransition,
-                            position: this.props.Document.isInkMask ? 'absolute' : undefined,
+                            //position: this.props.Document.isInkMask ? 'absolute' : undefined,
                             transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`,
                             width: isButton || isPresTreeElement ? '100%' : xshift() ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`,
                             height:
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index d3b95e25a..cb68c1ac3 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -1,6 +1,7 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
+import Color from 'color';
 import { action, computed, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -21,7 +22,7 @@ import { DocComponent } from '../../DocComponent';
 import { EditableView } from '../../EditableView';
 import { GestureOverlay } from '../../GestureOverlay';
 import { Colors } from '../../global/globalEnums';
-import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
+import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, SetActiveIsInkMask } from '../../InkingStroke';
 import { InkTranscription } from '../../InkTranscription';
 import { StyleProp } from '../../StyleProvider';
 import { FieldView, FieldViewProps } from '.././FieldView';
@@ -498,8 +499,6 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                 </div>
             );
 
-        const buttonText = StrCast(this.rootDoc.buttonText);
-
         // TODO:glr Add label of button type
         let button: JSX.Element | null = this.defaultButton;
 
@@ -508,7 +507,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                 button = (
                     <div className={`menuButton ${this.type}`} style={{ color, backgroundColor, opacity: 1, gridAutoColumns: `${NumCast(this.rootDoc._height)} auto` }}>
                         <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
-                        {buttonText ? <div className="button-text">{buttonText}</div> : null}
+                        {StrCast(this.rootDoc.buttonText) ? <div className="button-text">{StrCast(this.rootDoc.buttonText)}</div> : null}
                         {label}
                     </div>
                 );
@@ -563,11 +562,13 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                 break;
         }
 
-        return !this.layoutDoc.toolTip || this.type === ButtonType.DropdownList || this.type === ButtonType.ColorButton || this.type === ButtonType.NumberButton || this.type === ButtonType.EditableText ? (
-            button
-        ) : button !== null ? (
-            <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>{button}</Tooltip>
-        ) : null;
+        const retval =
+            !this.layoutDoc.toolTip || this.type === ButtonType.DropdownList || this.type === ButtonType.ColorButton || this.type === ButtonType.NumberButton || this.type === ButtonType.EditableText ? (
+                button
+            ) : button !== null ? (
+                <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>{button}</Tooltip>
+            ) : null;
+        return retval;
     }
 }
 
@@ -860,6 +861,21 @@ ScriptingGlobals.add(function setActiveTool(tool: string, checkResult?: boolean)
     }
 });
 
+// toggle: Set overlay status of selected document
+ScriptingGlobals.add(function setIsInkMask(checkResult?: boolean) {
+    const selected = SelectionManager.Docs().lastElement();
+    if (checkResult) {
+        if (selected?.type === DocumentType.INK) {
+            return BoolCast(selected.isInkMask) ? Colors.MEDIUM_BLUE : 'transparent';
+        }
+        return ActiveIsInkMask() ? Colors.MEDIUM_BLUE : 'transparent';
+    }
+    SetActiveIsInkMask(!ActiveIsInkMask());
+    SelectionManager.Docs()
+        .filter(doc => doc.type === DocumentType.INK)
+        .map(doc => (doc.isInkMask = !doc.isInkMask));
+});
+
 // toggle: Set overlay status of selected document
 ScriptingGlobals.add(function setFillColor(color?: string, checkResult?: boolean) {
     const selected = SelectionManager.Docs().lastElement();
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 3bbdce1e4..627f35e71 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -313,10 +313,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     //it'll also execute the necessary actions if presentation is playing.
     public gotoDocument = action((index: number, from?: Doc, group?: boolean) => {
         Doc.UnBrushAllDocs();
+
         if (index >= 0 && index < this.childDocs.length) {
             this.rootDoc._itemIndex = index;
             const activeItem: Doc = this.activeItem;
             const targetDoc: Doc = this.targetDoc;
+            if (activeItem.presActiveFrame !== undefined) {
+                const context = DocCast(DocCast(activeItem.presentationTargetDoc).context);
+                context && CollectionFreeFormViewChrome.gotoKeyFrame(context, NumCast(activeItem.presActiveFrame));
+            }
             if (from?.mediaStopTriggerList && this.layoutDoc.presStatus !== PresStatus.Edit) {
                 DocListCast(from.mediaStopTriggerList).forEach(this.stopTempMedia);
             }
@@ -516,20 +521,26 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             if (tagDoc === this.layoutDoc.presCollection) {
                 tagDoc.opacity = 1;
             } else {
-                if (itemIndexes.length > 1 && curDoc.presHideBefore && curInd !== 0) {
-                } else if (curDoc.presHideBefore) {
-                    if (index > this.itemIndex) {
-                        tagDoc.opacity = 0;
-                    } else if (index === this.itemIndex || !curDoc.presHideAfter) {
+                if (curDoc.presHideBefore) {
+                    if (itemIndexes.length > 1 && curInd !== 0) {
                         tagDoc.opacity = 1;
+                    } else {
+                        if (index > this.itemIndex) {
+                            tagDoc.opacity = 0;
+                        } else if (index === this.itemIndex || !curDoc.presHideAfter) {
+                            tagDoc.opacity = 1;
+                        }
                     }
                 }
-                if (itemIndexes.length > 1 && curDoc.presHideAfter && curInd !== itemIndexes.length - 1) {
-                } else if (curDoc.presHideAfter) {
-                    if (index < this.itemIndex) {
-                        tagDoc.opacity = 0;
-                    } else if (index === this.itemIndex || !curDoc.presHideBefore) {
+                if (curDoc.presHideAfter) {
+                    if (itemIndexes.length > 1 && curInd !== itemIndexes.length - 1) {
                         tagDoc.opacity = 1;
+                    } else {
+                        if (index < this.itemIndex) {
+                            tagDoc.opacity = 0;
+                        } else if (index === this.itemIndex || !curDoc.presHideBefore) {
+                            tagDoc.opacity = 1;
+                        }
                     }
                 }
             }
@@ -821,11 +832,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this.gotoDocument(this.childDocs.indexOf(doc), this.activeItem);
         if (doc.presPinView || doc.presentationTargetDoc === this.layoutDoc.presCollection) setTimeout(() => this.updateCurrentPresentation(context), 0);
         else this.updateCurrentPresentation(context);
-
-        if (this.activeItem.presActiveFrame !== undefined) {
-            const context = DocCast(DocCast(this.activeItem.presentationTargetDoc).context);
-            context && CollectionFreeFormViewChrome.gotoKeyFrame(context, NumCast(this.activeItem.presActiveFrame));
-        }
     };
 
     //Command click
diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts
index 114d5fc2f..a074098c1 100644
--- a/src/fields/InkField.ts
+++ b/src/fields/InkField.ts
@@ -1,22 +1,21 @@
-import { Bezier } from "bezier-js";
-import { createSimpleSchema, list, object, serializable } from "serializr";
-import { ScriptingGlobals } from "../client/util/ScriptingGlobals";
-import { Deserializable } from "../client/util/SerializationHelper";
-import { Copy, ToScriptString, ToString } from "./FieldSymbols";
-import { ObjectField } from "./ObjectField";
+import { Bezier } from 'bezier-js';
+import { createSimpleSchema, list, object, serializable } from 'serializr';
+import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
+import { Deserializable } from '../client/util/SerializationHelper';
+import { Copy, ToScriptString, ToString } from './FieldSymbols';
+import { ObjectField } from './ObjectField';
 
 // Helps keep track of the current ink tool in use.
 export enum InkTool {
-    None = "none",
-    Pen = "pen",
-    Highlighter = "highlighter",
-    Eraser = "eraser",
-    Stamp = "stamp",
-    Write = "write",
-    PresentationPin = 'presentationpin'
+    None = 'none',
+    Pen = 'pen',
+    Highlighter = 'highlighter',
+    Eraser = 'eraser',
+    Stamp = 'stamp',
+    Write = 'write',
+    PresentationPin = 'presentationpin',
 }
 
-
 // Defines a point in an ink as a pair of x- and y-coordinates.
 export interface PointData {
     X: number;
@@ -54,15 +53,16 @@ export interface HandleLine {
 }
 
 const pointSchema = createSimpleSchema({
-    X: true, Y: true
+    X: true,
+    Y: true,
 });
 
 const strokeDataSchema = createSimpleSchema({
     pathData: list(object(pointSchema)),
-    "*": true
+    '*': true,
 });
 
-@Deserializable("ink")
+@Deserializable('ink')
 export class InkField extends ObjectField {
     @serializable(list(object(strokeDataSchema)))
     readonly inkData: InkData;
@@ -85,11 +85,11 @@ export class InkField extends ObjectField {
     }
 
     [ToScriptString]() {
-        return "new InkField([" + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}`) + "])";
+        return 'new InkField([' + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}}`) + '])';
     }
     [ToString]() {
-        return "InkField";
+        return 'InkField';
     }
 }
 
-ScriptingGlobals.add("InkField", InkField);
\ No newline at end of file
+ScriptingGlobals.add('InkField', InkField);
-- 
cgit v1.2.3-70-g09d2


From 542a057edbdd4661cfb5ef5d07058aa93b47a113 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 24 Aug 2022 11:16:46 -0400
Subject: cleaned up some of the pin with view code.  added a pin button to
 menu bar when tab is selected to avoid needing to use context menu

---
 src/client/util/CurrentUserUtils.ts                |   5 +-
 src/client/util/SelectionManager.ts                |   3 +
 src/client/views/collections/CollectionMenu.tsx    |  44 ---
 src/client/views/collections/TabDocView.tsx        |  39 +--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   3 +
 src/client/views/nodes/button/FontIconBox.tsx      |  96 ++----
 .../views/nodes/formattedText/RichTextMenu.tsx     |   4 +-
 src/client/views/nodes/trails/PresBox.tsx          | 358 +++++----------------
 8 files changed, 124 insertions(+), 428 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 17d58595c..99a8c895f 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -267,7 +267,7 @@ export class CurrentUserUtils {
             {key: "Script",     creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
             // {key: "DataViz",    creator: opts => Docs.Create.DataVizDocument(opts),         opts: { _width: 300, _height: 300 }},
             {key: "Header",     creator: headerTemplate,                                    opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true,}},
-            {key: "Presentation",creator: Docs.Create.PresDocument,                         opts: { _width: 400, _height: 500, _viewType: CollectionViewType.Stacking, targetDropAction: "alias" as any, _chromeHidden: true, boxShadow: "0 0" }},
+            {key: "Presentation",creator: Docs.Create.PresDocument,                         opts: { _width: 400, _height: 500, _viewType: CollectionViewType.Stacking, targetDropAction: "alias" as any, treeViewHideTitle: true,  _chromeHidden: true, boxShadow: "0 0" }},
             {key: "Tab",        creator: opts => Docs.Create.FreeformDocument([], opts),    opts: { _width: 500, _height: 800, _backgroundGridShow: true, }},
             {key: "Slide",      creator: opts => Docs.Create.TreeDocument([], opts),        opts: { _width: 300, _height: 200, _viewType: CollectionViewType.Tree, 
                                                                                                 treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true, 
@@ -675,8 +675,9 @@ export class CurrentUserUtils {
                     CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map,
                     CollectionViewType.Grid, CollectionViewType.NoteTaking]),
               title: "Perspective",                    toolTip: "View",                 btnType: ButtonType.DropdownList,                                                                                    ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}},
+            { title: "Pin",     icon: "map-pin",       toolTip: "Pin View to Trail",    btnType: ButtonType.ClickButton,         funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "tab")'},                        width: 20,  scripts: { onClick: 'pinWithView(_readOnly_)'}},
             { title: "Back",    icon: "chevron-left",  toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton,         funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20,  scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
-            { title: "Num",     icon: "",              toolTip: "Frame Number",         btnType: ButtonType.TextButton,          funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement().currentFrame.toString()'}, width: 20,  scripts: {}},
+            { title: "Num",     icon: "",              toolTip: "Frame Number",         btnType: ButtonType.TextButton,          funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement()?.currentFrame.toString()'}, width: 20,  scripts: {}},
             { title: "Fwd",     icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton,         funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20,  scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
             { title: "Fill",    icon: "fill-drip",     toolTip: "Background Fill Color",btnType: ButtonType.ColorButton,         funcs: {hidden: '!SelectionManager_selectedDocType()'},                     ignoreClick: true, width: 20,  scripts: { script:  'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected
             { title: "Header",  icon: "heading",       toolTip: "Header Color",         btnType: ButtonType.ColorButton,         funcs: {hidden: '!SelectionManager_selectedDocType()'},                     ignoreClick: true,             scripts: { script:  'return setHeaderColor(value, _readOnly_)'}},
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 1c84af94a..7a555d5f8 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -101,6 +101,9 @@ export namespace SelectionManager {
     }
 }
 ScriptingGlobals.add(function SelectionManager_selectedDocType(docType?: DocumentType, colType?: CollectionViewType, checkContext?: boolean) {
+    if (colType === ('tab' as any)) {
+        return SelectionManager.Views().lastElement()?.props.renderDepth === 0;
+    }
     let selected = (sel => (checkContext ? DocCast(sel?.context) : sel))(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement());
     return docType ? selected?.type === docType : colType ? selected?.viewType === colType : true;
 });
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index eb55650e4..0dc30e0fd 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -586,49 +586,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
         );
     }
 
-    @undoBatch
-    @action
-    pinWithView = (targetDoc: Opt<Doc>) => {
-        if (targetDoc) {
-            TabDocView.PinDoc(targetDoc);
-            const presArray: Doc[] = PresBox.Instance?.sortArray();
-            const size: number = PresBox.Instance?._selectedArray.size;
-            const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined;
-            const activeDoc = presSelected ? PresBox.Instance?.childDocs[PresBox.Instance?.childDocs.indexOf(presSelected) + 1] : PresBox.Instance?.childDocs[PresBox.Instance?.childDocs.length - 1];
-            if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking || targetDoc._viewType === CollectionViewType.NoteTaking) {
-                const scroll = targetDoc._scrollTop;
-                activeDoc.presPinView = true;
-                activeDoc.presPinViewScroll = scroll;
-            } else if ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG || targetDoc.type === DocumentType.MAP) {
-                const x = targetDoc._panX;
-                const y = targetDoc._panY;
-                const scale = targetDoc._viewScale;
-                activeDoc.presPinView = true;
-                activeDoc.presPinViewX = x;
-                activeDoc.presPinViewY = y;
-                activeDoc.presPinViewScale = scale;
-            } else if (targetDoc.type === DocumentType.VID) {
-                activeDoc.presPinView = true;
-            } else if (targetDoc.type === DocumentType.COMPARISON) {
-                const width = targetDoc._clipWidth;
-                activeDoc.presPinClipWidth = width;
-                activeDoc.presPinView = true;
-            }
-        }
-    };
-
-    @computed
-    get pinWithViewButton() {
-        const presPinWithViewIcon = <img src={`/assets/pinWithView.png`} style={{ margin: 'auto', width: 19 }} />;
-        return !this.selectedDoc ? null : (
-            <Tooltip title={<div className="dash-tooltip">{'Pin with current view'}</div>} placement="top">
-                <button className="antimodeMenu-button" style={{ justifyContent: 'center' }} onClick={() => this.pinWithView(this.selectedDoc)}>
-                    {presPinWithViewIcon}
-                </button>
-            </Tooltip>
-        );
-    }
-
     @undoBatch
     onAlias = () => {
         if (this.selectedDoc && this.selectedDocumentView) {
@@ -722,7 +679,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
                         {this.aliasButton}
                         {/* {this.pinButton} */}
                         {this.toggleOverlayButton}
-                        {this.pinWithViewButton}
                         <div className="collectionMenu-divider" key="divider2"></div>
                         {this.subChrome}
                         <div className="collectionMenu-divider" key="divider3"></div>
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index e147f34d2..73574bdb3 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -242,43 +242,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 const size: number = PresBox.Instance?._selectedArray.size;
                 const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined;
                 const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], null);
-                // If pinWithView option set then update scale and x / y props of slide
-                if (pinProps?.pinWithView) {
-                    const viewProps = pinProps.pinWithView;
-                    pinDoc.presPinView = true;
-                    pinDoc.presPinViewX = viewProps.bounds.left + viewProps.bounds.width / 2;
-                    pinDoc.presPinViewY = viewProps.bounds.top + viewProps.bounds.height / 2;
-                    pinDoc.presPinViewScale = viewProps.scale;
-                    pinDoc.contentBounds = new List<number>([viewProps.bounds.left, viewProps.bounds.top, viewProps.bounds.left + viewProps.bounds.width, viewProps.bounds.top + viewProps.bounds.height]);
-                }
-                if (pinProps?.pinDocView) {
-                    const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(pinDoc.type as any) || pinDoc._viewType === CollectionViewType.Stacking;
-                    const pannable: boolean = (pinDoc.type === DocumentType.COL && doc._viewType === CollectionViewType.Freeform) || doc.type === DocumentType.IMG;
-                    if (scrollable) {
-                        const scroll = doc._scrollTop;
-                        pinDoc.presPinView = true;
-                        pinDoc.presPinViewScroll = scroll;
-                    } else if ([DocumentType.AUDIO, DocumentType.VID].includes(doc.type as any)) {
-                        pinDoc.presPinView = true;
-                        pinDoc.presStartTime = doc._currentTimecode;
-                        pinDoc.presEndTime = NumCast(doc._currentTimecode) + 0.1;
-                    } else if (pannable) {
-                        pinDoc.presPinView = true;
-                        pinDoc.presPinViewX = pinDoc._panX;
-                        pinDoc.presPinViewY = pinDoc._panY;
-                        pinDoc.presPinViewScale = pinDoc._viewScale;
-                        const pw = NumCast(pinProps.panelWidth);
-                        const ph = NumCast(pinProps.panelHeight);
-                        const ps = NumCast(pinDoc._viewScale);
-                        if (pw && ph && ps) {
-                            pinDoc.contentBounds = new List<number>([NumCast(pinDoc.panX) - pw / 2 / ps, NumCast(pinDoc.panY) - ph / 2 / ps, NumCast(pinDoc.panX) + pw / 2 / ps, NumCast(pinDoc.panY) + ph / 2 / ps]);
-                        }
-                    } else if (doc.type === DocumentType.COMPARISON) {
-                        const width = doc._clipWidth;
-                        pinDoc.presPinClipWidth = width;
-                        pinDoc.presPinView = true;
-                    }
-                }
+
+                PresBox.pinDocView(pinDoc, pinProps);
                 pinDoc.onClick = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)');
                 Doc.AddDocToList(curPres, 'data', pinDoc, presSelected);
                 if (!pinProps?.audioRange && duration !== undefined) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 03beaf65e..052cbd3bb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -2213,3 +2213,6 @@ ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) {
 ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) {
     !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true);
 });
+ScriptingGlobals.add(function pinWithView(readOnly: boolean) {
+    !readOnly && SelectionManager.Views().forEach(view => TabDocView.PinDoc(view.rootDoc, { pinDocView: true, panelWidth: view.props.PanelWidth(), panelHeight: view.props.PanelHeight() }));
+});
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index fc5bf86f4..c72b5ca9b 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -254,16 +254,13 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
         const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
 
         const script = ScriptCast(this.rootDoc.script);
-        if (!script) {
-            return null;
-        }
 
         let noviceList: string[] = [];
         let text: string | undefined;
         let dropdown = true;
         let icon: IconProp = 'caret-down';
         try {
-            if (script.script.originalScript.startsWith('setView')) {
+            if (script?.script.originalScript.startsWith('setView')) {
                 const selected = SelectionManager.Docs().lastElement();
                 if (selected) {
                     if (StrCast(selected.type) === DocumentType.COL) {
@@ -279,7 +276,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                     text = 'User Default';
                 }
                 noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking];
-            } else if (script.script.originalScript.startsWith('setFont')) {
+            } else if (script?.script.originalScript.startsWith('setFont')) {
                 const editorView = RichTextMenu.Instance?.TextView?.EditorView;
                 text = StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
                 noviceList = ['Roboto', 'Times New Roman', 'Arial', 'Georgia', 'Comic Sans MS', 'Tahoma', 'Impact', 'Crimson Text'];
@@ -289,11 +286,9 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
         }
 
         // Get items to place into the list
-        const list = this.buttonList.map(value => {
-            if (Doc.noviceMode && !noviceList.includes(value)) {
-                return;
-            }
-            return (
+        const list = this.buttonList
+            .filter(value => !Doc.noviceMode || noviceList.includes(value))
+            .map(value => (
                 <div
                     className="list-item"
                     key={`${value}`}
@@ -304,8 +299,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                     onClick={() => script.script.run({ value }).result}>
                     {value[0].toUpperCase() + value.slice(1)}
                 </div>
-            );
-        });
+            ));
 
         const label =
             !this.label || !FontIconBox.GetShowLabels() ? null : (
@@ -451,7 +445,6 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
     @computed get defaultButton() {
         const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
         const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
-        const active: string = StrCast(this.rootDoc.dropDownOpen);
         return (
             <div className={`menuButton ${this.type}`} onContextMenu={this.specificContextMenu} style={{ backgroundColor: 'transparent', borderBottomLeftRadius: this.dropdown ? 0 : undefined }}>
                 <div className="menuButton-wrap">
@@ -487,89 +480,50 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
     render() {
         const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
         const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
-        const label =
-            !this.label || !FontIconBox.GetShowLabels() ? null : (
-                <div className="fontIconBox-label" style={{ color, backgroundColor }}>
-                    {this.label}
-                </div>
-            );
-
-        const menuLabel =
+        const label = (noBackground: boolean = false) =>
             !this.label || !FontIconBox.GetShowLabels() ? null : (
-                <div className="fontIconBox-label" style={{ color, backgroundColor: 'transparent' }}>
+                <div className="fontIconBox-label" style={{ color, backgroundColor: noBackground ? 'transparent' : backgroundColor }}>
                     {this.label}
                 </div>
             );
-
         // TODO:glr Add label of button type
-        let button: JSX.Element | null = this.defaultButton;
+        let button: JSX.Element = this.defaultButton;
 
+        // prettier-ignore
         switch (this.type) {
-            case ButtonType.TextButton:
-                button = (
+            case ButtonType.DropdownList: return this.dropdownListButton;
+            case ButtonType.ColorButton: return this.colorButton; 
+            case ButtonType.NumberButton: return this.numberButton; 
+            case ButtonType.EditableText: return this.editableText;
+            case ButtonType.DropdownButton: button = this.dropdownButton; break;
+            case ButtonType.ToggleButton: button = this.toggleButton; break;
+            case ButtonType.TextButton: button = (
                     <div className={`menuButton ${this.type}`} style={{ color, backgroundColor, opacity: 1, gridAutoColumns: `${NumCast(this.rootDoc._height)} auto` }}>
                         {this.Icon(color)}
                         {StrCast(this.rootDoc.buttonText) ? <div className="button-text">{StrCast(this.rootDoc.buttonText)}</div> : null}
-                        {label}
-                    </div>
-                );
-                // button = <TextButton {...buttonProps}></TextButton>
-                break;
-            case ButtonType.EditableText:
-                button = this.editableText;
-                break;
-            case ButtonType.NumberButton:
-                button = this.numberButton;
-                break;
-            case ButtonType.DropdownButton:
-                button = this.dropdownButton;
-                break;
-            case ButtonType.DropdownList:
-                button = this.dropdownListButton;
-                break;
-            case ButtonType.ColorButton:
-                button = this.colorButton;
-                break;
-            case ButtonType.ToolButton:
-                button = (
-                    <div className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')}`} style={{ opacity: 1, backgroundColor, color }}>
-                        {this.Icon(color)}
-                        {label}
+                        {label()}
                     </div>
                 );
                 break;
-            case ButtonType.ToggleButton:
-                button = this.toggleButton;
-                // button = <ToggleButton {...buttonProps}></ToggleButton>
-                break;
             case ButtonType.ClickButton:
-                button = (
-                    <div className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')}`} style={{ color, backgroundColor, opacity: 1 }}>
+            case ButtonType.ToolButton: button = (
+                    <div className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')}`} style={{ backgroundColor, color, opacity: 1 }}>
                         {this.Icon(color)}
-                        {label}
+                        {label()}
                     </div>
                 );
                 break;
-            case ButtonType.MenuButton:
-                button = (
+            case ButtonType.MenuButton: button = (
                     <div className={`menuButton ${this.type}`} style={{ color, backgroundColor }}>
                         {this.Icon(color)}
-                        {menuLabel}
+                        {label(true)}
                         <FontIconBadge value={Cast(this.Document.badgeValue, 'string', null)} />
                     </div>
                 );
                 break;
-            default:
-                break;
         }
 
-        const retval =
-            !this.layoutDoc.toolTip || this.type === ButtonType.DropdownList || this.type === ButtonType.ColorButton || this.type === ButtonType.NumberButton || this.type === ButtonType.EditableText ? (
-                button
-            ) : button !== null ? (
-                <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>{button}</Tooltip>
-            ) : null;
-        return retval;
+        return !this.layoutDoc.toolTip ? button : <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>{button}</Tooltip>;
     }
 }
 
@@ -698,7 +652,7 @@ ScriptingGlobals.add(function setFontHighlight(color?: string, checkResult?: boo
 ScriptingGlobals.add(function setFontSize(size: string | number, checkResult?: boolean) {
     const editorView = RichTextMenu.Instance?.TextView?.EditorView;
     if (checkResult) {
-        return RichTextMenu.Instance.fontSize.replace('px', '');
+        return RichTextMenu.Instance?.fontSize.replace('px', '');
     }
     if (typeof size === 'number') size = size.toString();
     if (size && Number(size).toString() === size) size += 'px';
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 2a77210ae..0cbe60c0c 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -16,7 +16,7 @@ import { SelectionManager } from '../../../util/SelectionManager';
 import { undoBatch, UndoManager } from '../../../util/UndoManager';
 import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
 import { FieldViewProps } from '../FieldView';
-import { FormattedTextBox, FormattedTextBoxProps } from './FormattedTextBox';
+import { FormattedTextBox } from './FormattedTextBox';
 import { updateBullets } from './ProsemirrorExampleTransfer';
 import './RichTextMenu.scss';
 import { schema } from './schema_rts';
@@ -29,7 +29,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     private _linkToRef = React.createRef<HTMLInputElement>();
 
     @observable public view?: EditorView;
-    public editorProps: (FieldViewProps & FormattedTextBoxProps) | undefined;
+    public editorProps: FieldViewProps | undefined;
 
     public _brushMap: Map<string, Set<Mark>> = new Map();
 
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 05e09361b..eb40089ec 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -358,20 +358,24 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (bestTarget) this._navTimer = PresBox.navigateToDoc(bestTarget, activeItem, false);
     };
 
+    static pinDataTypes(target: Doc) {
+        const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(target.type as any) || target._viewType === CollectionViewType.Stacking;
+        const pannable = [DocumentType.IMG].includes(target.type as any) || (target.type === DocumentType.COL && target._viewType === CollectionViewType.Freeform);
+        const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(target.type as any);
+        const clippable = [DocumentType.COMPARISON].includes(target.type as any);
+        return { scrollable, pannable, temporal, clippable };
+    }
     // navigates to the bestTarget document by making sure it is on screen,
     // then it applies the view specs stored in activeItem to
     @action
     static navigateToDoc(bestTarget: Doc, activeItem: Doc, jumpToDoc: boolean) {
-        if (bestTarget.type === DocumentType.PDF || bestTarget.type === DocumentType.WEB || bestTarget.type === DocumentType.RTF || bestTarget._viewType === CollectionViewType.Stacking) {
-            bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s';
-            bestTarget._scrollTop = activeItem.presPinViewScroll;
-        } else if (bestTarget.type === DocumentType.COMPARISON) {
-            bestTarget._clipWidth = activeItem.presPinClipWidth;
-        } else if ([DocumentType.AUDIO, DocumentType.VID].includes(bestTarget.type as any)) {
-            bestTarget._currentTimecode = activeItem.presStartTime;
-        } else {
+        bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s';
+        const { scrollable, pannable, temporal, clippable } = this.pinDataTypes(bestTarget);
+        if (clippable) bestTarget._clipWidth = activeItem.presPinClipWidth;
+        if (temporal) bestTarget._currentTimecode = activeItem.presStartTime;
+        if (scrollable) bestTarget._scrollTop = activeItem.presPinViewScroll;
+        if (pannable) {
             const contentBounds = Cast(activeItem.contentBounds, listSpec('number'));
-            bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s';
             if (contentBounds) {
                 bestTarget._panX = (contentBounds[0] + contentBounds[2]) / 2;
                 bestTarget._panY = (contentBounds[1] + contentBounds[3]) / 2;
@@ -388,6 +392,43 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return setTimeout(() => (bestTarget._viewTransition = undefined), activeItem.presTransition ? NumCast(activeItem.presTransition) + 10 : 510);
     }
 
+    /// copies values from the targetDoc (which is the prototype of the pinDoc) to
+    /// reserved fields on the pinDoc so that those values can be restored to the
+    /// target doc when navigating to it.
+    @action
+    static pinDocView(pinDoc: Doc, pinProps: PinProps | undefined) {
+        if (pinProps?.pinWithView) {
+            // If pinWithView option set then update scale and x / y props of slide
+            const bounds = pinProps.pinWithView.bounds;
+            pinDoc.presPinView = true;
+            pinDoc.presPinViewX = bounds.left + bounds.width / 2;
+            pinDoc.presPinViewY = bounds.top + bounds.height / 2;
+            pinDoc.presPinViewScale = pinProps.pinWithView.scale;
+            pinDoc.contentBounds = new List<number>([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]);
+        }
+        if (pinProps?.pinDocView) {
+            const { scrollable, pannable, temporal, clippable } = this.pinDataTypes(pinDoc);
+            pinDoc.presPinView = (pinProps?.pinWithView ? true : false) || scrollable || temporal || pannable || clippable;
+
+            if (scrollable) pinDoc.presPinViewScroll = pinDoc._scrollTop;
+            else if (clippable) pinDoc.presPinClipWidth = pinDoc._clipWidth;
+            else if (temporal) pinDoc.presEndTime = NumCast((pinDoc.presStartTime = pinDoc._currentTimecode)) + 0.1;
+            else if (pannable) {
+                const panX = NumCast(pinDoc._panX);
+                const panY = NumCast(pinDoc._panY);
+                const pw = NumCast(pinProps.panelWidth);
+                const ph = NumCast(pinProps.panelHeight);
+                const ps = NumCast(pinDoc._viewScale);
+                if (pw && ph && ps) {
+                    pinDoc.contentBounds = new List<number>([panX - pw / 2 / ps, panY - ph / 2 / ps, panX + pw / 2 / ps, panY + ph / 2 / ps]);
+                }
+                pinDoc.presPinViewX = panX;
+                pinDoc.presPinViewY = panY;
+                pinDoc.presPinViewScale = ps;
+            }
+        }
+    }
+
     /**
      * This method makes sure that cursor navigates to the element that
      * has the option open and last in the group.
@@ -1540,26 +1581,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
     }
 
-    @computed get effectDirection(): string {
-        let effect = '';
+    @computed get effectDirection() {
+        // prettier-ignore
         switch (this.activeItem.presEffectDirection) {
-            case 'left':
-                effect = 'Enter from left';
-                break;
-            case 'right':
-                effect = 'Enter from right';
-                break;
-            case 'top':
-                effect = 'Enter from top';
-                break;
-            case 'bottom':
-                effect = 'Enter from bottom';
-                break;
-            default:
-                effect = 'Enter from center';
-                break;
+            case 'left': return 'Enter from left'; 
+            case 'right': return 'Enter from right'; 
+            case 'top':  return'Enter from top';
+            case 'bottom': return 'Enter from bottom';
         }
-        return effect;
+        return 'Enter from center';
     }
 
     @undoBatch
@@ -1582,179 +1612,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         });
     };
 
-    @computed get presPinViewOptionsDropdown() {
-        const activeItem: Doc = this.activeItem;
-        const targetDoc: Doc = this.targetDoc;
-        const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ margin: 'auto', width: 16, filter: 'invert(1)' }} />;
-        return (
-            <>
-                {this.panable || this.scrollable || this.targetDoc.type === DocumentType.COMPARISON ? 'Pinned view' : null}
-                <div className="ribbon-doubleButton">
-                    <Tooltip
-                        title={
-                            <>
-                                <div className="dash-tooltip">{activeItem.presPinView ? 'Turn off pin with view' : 'Turn on pin with view'}</div>
-                            </>
-                        }>
-                        <div
-                            className="ribbon-toggle"
-                            style={{ width: 20, padding: 0, backgroundColor: activeItem.presPinView ? Colors.LIGHT_BLUE : '' }}
-                            onClick={() => {
-                                activeItem.presPinView = !activeItem.presPinView;
-                                targetDoc.presPinView = activeItem.presPinView;
-                                if (activeItem.presPinView) {
-                                    if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking) {
-                                        const scroll = targetDoc._scrollTop;
-                                        activeItem.presPinView = true;
-                                        activeItem.presPinViewScroll = scroll;
-                                    } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) {
-                                        activeItem.presStartTime = targetDoc._currentTimecode;
-                                        activeItem.presEndTime = NumCast(targetDoc._currentTimecode) + 0.1;
-                                    } else if ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG) {
-                                        const x = targetDoc._panX;
-                                        const y = targetDoc._panY;
-                                        const scale = targetDoc._viewScale;
-                                        activeItem.presPinView = true;
-                                        activeItem.presPinViewX = x;
-                                        activeItem.presPinViewY = y;
-                                        activeItem.presPinViewScale = scale;
-                                    } else if (targetDoc.type === DocumentType.COMPARISON) {
-                                        const width = targetDoc._clipWidth;
-                                        activeItem.presPinClipWidth = width;
-                                        activeItem.presPinView = true;
-                                    }
-                                }
-                            }}>
-                            {presPinWithViewIcon}
-                        </div>
-                    </Tooltip>
-                    {activeItem.presPinView ? (
-                        <Tooltip
-                            title={
-                                <>
-                                    <div className="dash-tooltip">{'Update the pinned view with the view of the selected document'}</div>
-                                </>
-                            }>
-                            <div
-                                className="ribbon-button"
-                                onClick={() => {
-                                    if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF) {
-                                        const scroll = targetDoc._scrollTop;
-                                        activeItem.presPinViewScroll = scroll;
-                                    } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) {
-                                        activeItem.presStartTime = targetDoc._currentTimecode;
-                                        activeItem.presStartTime = NumCast(targetDoc._currentTimecode) + 0.1;
-                                    } else if (targetDoc.type === DocumentType.COMPARISON) {
-                                        const clipWidth = targetDoc._clipWidth;
-                                        activeItem.presPinClipWidth = clipWidth;
-                                    } else {
-                                        const x = targetDoc._panX;
-                                        const y = targetDoc._panY;
-                                        const scale = targetDoc._viewScale;
-                                        activeItem.presPinViewX = x;
-                                        activeItem.presPinViewY = y;
-                                        activeItem.presPinViewScale = scale;
-                                    }
-                                }}>
-                                Update
-                            </div>
-                        </Tooltip>
-                    ) : null}
-                </div>
-            </>
-        );
-    }
-
-    @computed get panOptionsDropdown() {
-        const activeItem: Doc = this.activeItem;
-        const targetDoc: Doc = this.targetDoc;
-        return (
-            <>
-                {this.panable ? (
-                    <div style={{ display: activeItem.presPinView ? 'block' : 'none' }}>
-                        <div className="ribbon-doubleButton" style={{ marginRight: 10 }}>
-                            <div className="presBox-subheading">Pan X</div>
-                            <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}>
-                                <input
-                                    className="presBox-input"
-                                    style={{ textAlign: 'left', width: 50 }}
-                                    type="number"
-                                    value={NumCast(activeItem.presPinViewX)}
-                                    onKeyDown={e => e.stopPropagation()}
-                                    onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
-                                        const val = e.target.value;
-                                        activeItem.presPinViewX = Number(val);
-                                    })}
-                                />
-                            </div>
-                        </div>
-                        <div className="ribbon-doubleButton" style={{ marginRight: 10 }}>
-                            <div className="presBox-subheading">Pan Y</div>
-                            <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}>
-                                <input
-                                    className="presBox-input"
-                                    style={{ textAlign: 'left', width: 50 }}
-                                    type="number"
-                                    value={NumCast(activeItem.presPinViewY)}
-                                    onKeyDown={e => e.stopPropagation()}
-                                    onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
-                                        const val = e.target.value;
-                                        activeItem.presPinViewY = Number(val);
-                                    })}
-                                />
-                            </div>
-                        </div>
-                        <div className="ribbon-doubleButton" style={{ marginRight: 10 }}>
-                            <div className="presBox-subheading">Scale</div>
-                            <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}>
-                                <input
-                                    className="presBox-input"
-                                    style={{ textAlign: 'left', width: 50 }}
-                                    type="number"
-                                    value={NumCast(activeItem.presPinViewScale)}
-                                    onKeyDown={e => e.stopPropagation()}
-                                    onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
-                                        const val = e.target.value;
-                                        activeItem.presPinViewScale = Number(val);
-                                    })}
-                                />
-                            </div>
-                        </div>
-                    </div>
-                ) : null}
-            </>
-        );
-    }
-
-    @computed get scrollOptionsDropdown() {
-        const activeItem: Doc = this.activeItem;
-        const targetDoc: Doc = this.targetDoc;
-        return (
-            <>
-                {this.scrollable ? (
-                    <div style={{ display: activeItem.presPinView ? 'block' : 'none' }}>
-                        <div className="ribbon-doubleButton" style={{ marginRight: 10 }}>
-                            <div className="presBox-subheading">Scroll</div>
-                            <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}>
-                                <input
-                                    className="presBox-input"
-                                    style={{ textAlign: 'left', width: 50 }}
-                                    type="number"
-                                    value={NumCast(activeItem.presPinViewScroll)}
-                                    onKeyDown={e => e.stopPropagation()}
-                                    onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
-                                        const val = e.target.value;
-                                        activeItem.presPinViewScroll = Number(val);
-                                    })}
-                                />
-                            </div>
-                        </div>
-                    </div>
-                ) : null}
-            </>
-        );
-    }
-
     @computed get mediaStopSlides() {
         const activeItem: Doc = this.activeItem;
         const list = this.childDocs.map((doc, i) => {
@@ -2102,42 +1959,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     createTemplate = (layout: string, input?: string) => {
-        const activeItem: Doc = this.activeItem;
-        const targetDoc: Doc = this.targetDoc;
-        let x = 0;
-        let y = 0;
-        if (activeItem && targetDoc) {
-            x = NumCast(targetDoc.x);
-            y = NumCast(targetDoc.y) + NumCast(targetDoc._height) + 20;
-        }
-        let doc = undefined;
-        const title = Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 58, _fontSize: '24pt' });
-        const subtitle = Docs.Create.TextDocument('Click to change subtitle', { title: 'Slide subtitle', _width: 380, _height: 50, x: 10, y: 118, _fontSize: '16pt' });
-        const header = Docs.Create.TextDocument('Click to change header', { title: 'Slide header', _width: 380, _height: 65, x: 10, y: 80, _fontSize: '20pt' });
-        const contentTitle = Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 10, _fontSize: '24pt' });
-        const content = Docs.Create.TextDocument('Click to change text', { title: 'Slide text', _width: 380, _height: 145, x: 10, y: 70, _fontSize: '14pt' });
-        const content1 = Docs.Create.TextDocument('Click to change text', { title: 'Column 1', _width: 185, _height: 140, x: 10, y: 80, _fontSize: '14pt' });
-        const content2 = Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, _fontSize: '14pt' });
+        const x = this.activeItem && this.targetDoc ? NumCast(this.targetDoc.x) : 0;
+        const y = this.activeItem && this.targetDoc ? NumCast(this.targetDoc.y) + NumCast(this.targetDoc._height) + 20 : 0;
+        const title = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 58, _fontSize: '24pt' });
+        const subtitle = () => Docs.Create.TextDocument('Click to change subtitle', { title: 'Slide subtitle', _width: 380, _height: 50, x: 10, y: 118, _fontSize: '16pt' });
+        const header = () => Docs.Create.TextDocument('Click to change header', { title: 'Slide header', _width: 380, _height: 65, x: 10, y: 80, _fontSize: '20pt' });
+        const contentTitle = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 10, _fontSize: '24pt' });
+        const content = () => Docs.Create.TextDocument('Click to change text', { title: 'Slide text', _width: 380, _height: 145, x: 10, y: 70, _fontSize: '14pt' });
+        const content1 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 1', _width: 185, _height: 140, x: 10, y: 80, _fontSize: '14pt' });
+        const content2 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, _fontSize: '14pt' });
+        // prettier-ignore
         switch (layout) {
-            case 'blank':
-                doc = Docs.Create.FreeformDocument([], { title: input ? input : 'Blank slide', _width: 400, _height: 225, x: x, y: y });
-                break;
-            case 'title':
-                doc = Docs.Create.FreeformDocument([title, subtitle], { title: input ? input : 'Title slide', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y });
-                break;
-            case 'header':
-                doc = Docs.Create.FreeformDocument([header], { title: input ? input : 'Section header', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y });
-                break;
-            case 'content':
-                doc = Docs.Create.FreeformDocument([contentTitle, content], { title: input ? input : 'Title and content', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y });
-                break;
-            case 'twoColumns':
-                doc = Docs.Create.FreeformDocument([contentTitle, content1, content2], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y });
-                break;
-            default:
-                break;
+            case 'blank': return Docs.Create.FreeformDocument([], { title: input ? input : 'Blank slide', _width: 400, _height: 225, x, y });
+            case 'title': return Docs.Create.FreeformDocument([title(), subtitle()], { title: input ? input : 'Title slide', _width: 400, _height: 225, _fitContentsToBox: true, x, y });
+            case 'header': return Docs.Create.FreeformDocument([header()], { title: input ? input : 'Section header', _width: 400, _height: 225, _fitContentsToBox: true, x, y });
+            case 'content': return Docs.Create.FreeformDocument([contentTitle(), content()], { title: input ? input : 'Title and content', _width: 400, _height: 225, _fitContentsToBox: true, x, y });
+            case 'twoColumns': return Docs.Create.FreeformDocument([contentTitle(), content1(), content2()], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _fitContentsToBox: true, x, y })
         }
-        return doc;
     };
 
     // Dropdown that appears when the user wants to begin presenting (either minimize or sidebar view)
@@ -2201,42 +2039,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     /**
      * Returns the collection type as a string for headers
      */
-    @computed get stringType(): string {
-        const activeItem: Doc = this.activeItem;
-        const targetDoc: Doc = this.targetDoc;
-        let type: string = '';
-        if (activeItem) {
-            switch (targetDoc.type) {
-                case DocumentType.PDF:
-                    type = 'PDF';
-                    break;
-                case DocumentType.RTF:
-                    type = 'Text node';
-                    break;
-                case DocumentType.COL:
-                    type = 'Collection';
-                    break;
-                case DocumentType.AUDIO:
-                    type = 'Audio';
-                    break;
-                case DocumentType.VID:
-                    type = 'Video';
-                    break;
-                case DocumentType.IMG:
-                    type = 'Image';
-                    break;
-                case DocumentType.WEB:
-                    type = 'Web page';
-                    break;
-                case DocumentType.MAP:
-                    type = 'Map';
-                    break;
-                default:
-                    type = 'Other node';
-                    break;
+    @computed get stringType() {
+        if (this.activeItem) {
+            // prettier-ignore
+            switch (this.targetDoc.type) {
+                case DocumentType.PDF: return 'PDF';
+                case DocumentType.RTF: return 'Text node';
+                case DocumentType.COL: return 'Collection';
+                case DocumentType.AUDIO: return 'Audio';
+                case DocumentType.VID: return 'Video';
+                case DocumentType.IMG: return 'Image';
+                case DocumentType.WEB: return 'Web page';
+                case DocumentType.MAP: return 'Map';
+                default: return 'Other node';
             }
         }
-        return type;
+        return '';
     }
 
     @observable private openActiveColorPicker: boolean = false;
@@ -2845,10 +2663,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             case DocumentType.PDF || DocumentType.RTF || DocumentType.WEB:
                 this.updateList(activeItem.frameList);
                 break;
-            case DocumentType.COL:
-                break;
-            default:
-                break;
         }
     };
 
-- 
cgit v1.2.3-70-g09d2


From 9263422913f30b54922f3c0d7290e36d4a509455 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 26 Aug 2022 12:43:00 -0400
Subject: extending pres trails to allow ink animation without using keyframes.

---
 src/client/views/MainView.tsx                      |  2 +-
 src/client/views/collections/CollectionMenu.tsx    | 20 +++----
 src/client/views/collections/TabDocView.tsx        |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  1 -
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 10 ++--
 src/client/views/nodes/trails/PresBox.tsx          | 67 +++++++++++++++-------
 6 files changed, 61 insertions(+), 41 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 06be4d194..515faa316 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -154,10 +154,10 @@ export class MainView extends React.Component {
         if (!MainView.Live) {
             DocServer.setPlaygroundFields([
                 'dataTransition',
+                'viewTransition',
                 'treeViewOpen',
                 'showSidebar',
                 'sidebarWidthPercent',
-                'viewTransition',
                 'panX',
                 'panY',
                 'fitWidth',
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 0dc30e0fd..6a0f69359 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -726,18 +726,16 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
     }
 
     public static gotoKeyFrame(doc: Doc, newFrame: number) {
-        if (!doc) {
-            return;
-        }
-        const dataField = doc[Doc.LayoutFieldKey(doc)];
-        const childDocs = DocListCast(dataField);
-        const currentFrame = Cast(doc._currentFrame, 'number', null);
-        if (currentFrame === undefined) {
-            doc._currentFrame = 0;
-            CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
+        if (doc) {
+            const childDocs = DocListCast(doc[Doc.LayoutFieldKey(doc)]);
+            const currentFrame = Cast(doc._currentFrame, 'number', null);
+            if (currentFrame === undefined) {
+                doc._currentFrame = 0;
+                CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
+            }
+            CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0);
+            doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame);
         }
-        CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0);
-        doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame);
     }
 
     @undoBatch
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 73574bdb3..f7b48adf6 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -243,7 +243,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined;
                 const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], null);
 
-                PresBox.pinDocView(pinDoc, pinProps);
+                PresBox.pinDocView(pinDoc, pinProps, doc);
                 pinDoc.onClick = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)');
                 Doc.AddDocToList(curPres, 'data', pinDoc, presSelected);
                 if (!pinProps?.audioRange && duration !== undefined) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c44b33ed0..210370d39 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -278,7 +278,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000), false); // get non-default values for everything else
                 vals.x = x + NumCast(pvals.x) - dropPos[0];
                 vals.y = y + NumCast(pvals.y) - dropPos[1];
-                vals._scrollTop = this.Document.editScrollProgressivize ? pvals._scrollTop : undefined;
                 CollectionFreeFormDocumentView.setValues(NumCast(this.Document._currentFrame), d, vals);
             } else {
                 d.x = x + NumCast(d.x) - dropPos[0];
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index efaecb8d2..82557d36b 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -90,14 +90,14 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     public static getValues(doc: Doc, time: number, fillIn: boolean = true) {
         return CollectionFreeFormDocumentView.animFields.reduce((p, val) => {
-            p[val.key] = Cast(`${val}-indexed`, listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : []).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as number);
+            p[val.key] = Cast(doc[`${val.key}-indexed`], listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : []).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as number);
             return p;
         }, {} as { [val: string]: Opt<number> });
     }
 
     public static getStringValues(doc: Doc, time: number) {
         return CollectionFreeFormDocumentView.animStringFields.reduce((p, val) => {
-            p[val] = Cast(`${val}-indexed`, listSpec('string'), [StrCast(doc[val])]).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as string);
+            p[val] = Cast(doc[`${val}-indexed`], listSpec('string'), [StrCast(doc[val])]).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as string);
             return p;
         }, {} as { [val: string]: Opt<string> });
     }
@@ -140,8 +140,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         );
     }
 
-    public static gotoKeyframe(docs: Doc[]) {
-        docs.forEach(doc => (doc._viewTransition = doc.dataTransition = 'all 1s'));
+    public static gotoKeyframe(docs: Doc[], duration = 1000) {
+        docs.forEach(doc => (doc._viewTransition = doc.dataTransition = `all ${duration}ms`));
         setTimeout(
             () =>
                 docs.forEach(doc => {
@@ -226,7 +226,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             PanelWidth: this.panelWidth,
             PanelHeight: this.panelHeight,
         };
-        const mixBlendMode = undefined; // (StrCast(this.layoutDoc.mixBlendMode) as any) || (typeof background === 'string' && background && !background.startsWith('linear') && DashColor(background).alpha() !== 1 ? 'multiply' : undefined);
         return (
             <div
                 className={'collectionFreeFormDocumentView-container'}
@@ -238,7 +237,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     transformOrigin: '50% 50%',
                     transition: this.dataProvider?.transition ?? (this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition)),
                     zIndex: this.ZInd,
-                    mixBlendMode: mixBlendMode,
                     display: this.ZInd === -99 ? 'none' : undefined,
                 }}>
                 {this.props.renderCutoffProvider(this.props.Document) ? (
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index eb40089ec..f254eaba6 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -6,7 +6,7 @@ import { observer } from 'mobx-react';
 import { ColorState, SketchPicker } from 'react-color';
 import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
 import { Doc, DocListCast, DocListCastAsync, FieldResult } from '../../../../fields/Doc';
-import { InkTool } from '../../../../fields/InkField';
+import { InkField, InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
 import { listSpec } from '../../../../fields/Schema';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
@@ -19,8 +19,7 @@ import { SelectionManager } from '../../../util/SelectionManager';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { undoBatch, UndoManager } from '../../../util/UndoManager';
 import { CollectionDockingView } from '../../collections/CollectionDockingView';
-import { MarqueeViewBounds } from '../../collections/collectionFreeForm';
-import { CollectionFreeFormViewChrome } from '../../collections/CollectionMenu';
+import { CollectionFreeFormView, MarqueeViewBounds } from '../../collections/collectionFreeForm';
 import { CollectionView } from '../../collections/CollectionView';
 import { TabDocView } from '../../collections/TabDocView';
 import { ViewBoxBaseComponent } from '../../DocComponent';
@@ -30,6 +29,8 @@ import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentVie
 import { FieldView, FieldViewProps } from '../FieldView';
 import './PresBox.scss';
 import { PresEffect, PresMovement, PresStatus } from './PresEnums';
+import { Copy } from '../../../../fields/FieldSymbols';
+import { CollectionFreeFormViewChrome } from '../../collections/CollectionMenu';
 
 export interface PinProps {
     audioRange?: boolean;
@@ -317,8 +318,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const activeItem: Doc = this.activeItem;
             const targetDoc: Doc = this.targetDoc;
             if (activeItem.presActiveFrame !== undefined) {
+                const transTime = NumCast(activeItem.presDuration, 500);
                 const context = DocCast(DocCast(activeItem.presentationTargetDoc).context);
-                context && CollectionFreeFormViewChrome.gotoKeyFrame(context, NumCast(activeItem.presActiveFrame));
+                if (context) {
+                    const contextView = DocumentManager.Instance.getFirstDocumentView(context);
+                    if (contextView?.ComponentView) {
+                        CollectionFreeFormDocumentView.gotoKeyframe((contextView.ComponentView as CollectionFreeFormView).childDocs.slice(), transTime);
+                        context._currentFrame = NumCast(activeItem.presActiveFrame);
+                    }
+                }
             }
             if (from?.mediaStopTriggerList && this.layoutDoc.presStatus !== PresStatus.Edit) {
                 DocListCast(from.mediaStopTriggerList).forEach(this.stopTempMedia);
@@ -353,8 +361,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     navigateToView = (targetDoc: Doc, activeItem: Doc) => {
         clearTimeout(this._navTimer);
         const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document;
-        if (bestTarget) console.log(bestTarget.title, bestTarget.type);
-        else console.log('no best target');
         if (bestTarget) this._navTimer = PresBox.navigateToDoc(bestTarget, activeItem, false);
     };
 
@@ -363,17 +369,29 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const pannable = [DocumentType.IMG].includes(target.type as any) || (target.type === DocumentType.COL && target._viewType === CollectionViewType.Freeform);
         const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(target.type as any);
         const clippable = [DocumentType.COMPARISON].includes(target.type as any);
-        return { scrollable, pannable, temporal, clippable };
+        const dataview = [DocumentType.INK].includes(target.type as any) && target.activeFrame === undefined;
+        return { scrollable, pannable, temporal, clippable, dataview };
     }
     // navigates to the bestTarget document by making sure it is on screen,
     // then it applies the view specs stored in activeItem to
     @action
     static navigateToDoc(bestTarget: Doc, activeItem: Doc, jumpToDoc: boolean) {
-        bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s';
-        const { scrollable, pannable, temporal, clippable } = this.pinDataTypes(bestTarget);
+        const transTime = NumCast(activeItem.presDuration, 500);
+        const presTransitionTime = `all ${transTime}ms`;
+        bestTarget._viewTransition = presTransitionTime;
+        const { scrollable, pannable, temporal, clippable, dataview } = this.pinDataTypes(bestTarget);
         if (clippable) bestTarget._clipWidth = activeItem.presPinClipWidth;
         if (temporal) bestTarget._currentTimecode = activeItem.presStartTime;
         if (scrollable) bestTarget._scrollTop = activeItem.presPinViewScroll;
+        if (dataview) {
+            bestTarget._dataTransition = presTransitionTime;
+            bestTarget.data = (activeItem.presData as any as InkField)[Copy]();
+            bestTarget.x = NumCast(activeItem.presX);
+            bestTarget.y = NumCast(activeItem.presY);
+            bestTarget.width = NumCast(activeItem.presWidth);
+            bestTarget.height = NumCast(activeItem.presHeight);
+        }
+
         if (pannable) {
             const contentBounds = Cast(activeItem.contentBounds, listSpec('number'));
             if (contentBounds) {
@@ -389,14 +407,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 bestTarget._viewScale = activeItem.presPinViewScale;
             }
         }
-        return setTimeout(() => (bestTarget._viewTransition = undefined), activeItem.presTransition ? NumCast(activeItem.presTransition) + 10 : 510);
+        return setTimeout(() => {
+            bestTarget._viewTransition = undefined;
+            if (dataview) bestTarget._dataTransition = undefined;
+        }, transTime + 10);
     }
 
     /// copies values from the targetDoc (which is the prototype of the pinDoc) to
     /// reserved fields on the pinDoc so that those values can be restored to the
     /// target doc when navigating to it.
     @action
-    static pinDocView(pinDoc: Doc, pinProps: PinProps | undefined) {
+    static pinDocView(pinDoc: Doc, pinProps: PinProps | undefined, targetDoc: Doc) {
         if (pinProps?.pinWithView) {
             // If pinWithView option set then update scale and x / y props of slide
             const bounds = pinProps.pinWithView.bounds;
@@ -407,13 +428,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             pinDoc.contentBounds = new List<number>([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]);
         }
         if (pinProps?.pinDocView) {
-            const { scrollable, pannable, temporal, clippable } = this.pinDataTypes(pinDoc);
-            pinDoc.presPinView = (pinProps?.pinWithView ? true : false) || scrollable || temporal || pannable || clippable;
+            const { scrollable, pannable, temporal, clippable, dataview } = this.pinDataTypes(pinDoc);
+            pinDoc.presPinView = (pinProps?.pinWithView ? true : false) || scrollable || temporal || pannable || clippable || dataview || pinProps.activeFrame !== undefined;
 
             if (scrollable) pinDoc.presPinViewScroll = pinDoc._scrollTop;
-            else if (clippable) pinDoc.presPinClipWidth = pinDoc._clipWidth;
-            else if (temporal) pinDoc.presEndTime = NumCast((pinDoc.presStartTime = pinDoc._currentTimecode)) + 0.1;
-            else if (pannable) {
+            if (clippable) pinDoc.presPinClipWidth = pinDoc._clipWidth;
+            if (temporal) pinDoc.presEndTime = NumCast((pinDoc.presStartTime = pinDoc._currentTimecode)) + 0.1;
+            if (dataview) {
+                pinDoc.presData = (targetDoc.data as InkField)[Copy]();
+                pinDoc.presX = NumCast(targetDoc.x);
+                pinDoc.presY = NumCast(targetDoc.y);
+                pinDoc.presWidth = NumCast(targetDoc.width);
+                pinDoc.presHeight = NumCast(targetDoc.height);
+            }
+            if (pannable) {
                 const panX = NumCast(pinDoc._panX);
                 const panY = NumCast(pinDoc._panY);
                 const pw = NumCast(pinProps.panelWidth);
@@ -493,8 +521,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         // After navigating to the document, if it is added as a presPinView then it will
         // adjust the pan and scale to that of the pinView when it was added.
         if (activeItem.presPinView) {
-            console.log(targetDoc.title);
-            console.log('presPinView in PresBox.tsx:420');
             // if targetDoc is not displayed but one of its aliases is, then we need to modify that alias, not the original target
             this.navigateToView(targetDoc, activeItem);
         }
@@ -866,10 +892,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     //Regular click
     @action
     selectElement = async (doc: Doc) => {
-        const context = Cast(doc.context, Doc, null);
         this.gotoDocument(this.childDocs.indexOf(doc), this.activeItem);
-        if (doc.presPinView || doc.presentationTargetDoc === this.layoutDoc.presCollection) setTimeout(() => this.updateCurrentPresentation(context), 0);
-        else this.updateCurrentPresentation(context);
+        if (doc.presPinView || doc.presentationTargetDoc === this.layoutDoc.presCollection) setTimeout(() => this.updateCurrentPresentation(DocCast(doc.context)), 0);
+        else this.updateCurrentPresentation(DocCast(doc.context));
     };
 
     //Command click
-- 
cgit v1.2.3-70-g09d2


From f847c6d554f9dcecbd6c3024f712510f341daf67 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 12 Sep 2022 11:36:15 -0400
Subject: made annotation pen available in lightbox.

---
 src/client/views/GestureOverlay.scss            |   5 +-
 src/client/views/GestureOverlay.tsx             |  43 +++++-----
 src/client/views/LightboxView.scss              |  80 ++++++++++++-------
 src/client/views/LightboxView.tsx               | 101 ++++++++++++++----------
 src/client/views/MainView.tsx                   |  48 +++++------
 src/client/views/collections/CollectionMenu.tsx |   1 -
 src/client/views/nodes/DocumentView.tsx         |   9 +--
 7 files changed, 159 insertions(+), 128 deletions(-)

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

diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss
index f5cbbffb1..bfe2d5c64 100644
--- a/src/client/views/GestureOverlay.scss
+++ b/src/client/views/GestureOverlay.scss
@@ -1,8 +1,9 @@
 .gestureOverlay-cont {
-    width: 100vw;
-    height: 100vh;
+    width: 100%;
+    height: 100%;
     position: absolute;
     touch-action: none;
+    top: 0;
 
     .pointerBubbles {
         width: 100%;
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 6f28ef9eb..850688e7e 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -1,7 +1,6 @@
 import React = require('react');
 import * as fitCurve from 'fit-curve';
-import { action, computed, observable, runInAction } from 'mobx';
-import { observer } from 'mobx-react';
+import { action, computed, observable, runInAction, trace } from 'mobx';
 import { Doc } from '../../fields/Doc';
 import { InkData, InkTool } from '../../fields/InkField';
 import { List } from '../../fields/List';
@@ -41,9 +40,13 @@ import { RadialMenu } from './nodes/RadialMenu';
 import HorizontalPalette from './Palette';
 import { Touchable } from './Touchable';
 import TouchScrollableMenu, { TouchScrollableMenuItem } from './TouchScrollableMenu';
+import { observer } from 'mobx-react';
 
+interface GestureOverlayProps {
+    isActive: boolean;
+}
 @observer
-export class GestureOverlay extends Touchable {
+export class GestureOverlay extends Touchable<GestureOverlayProps> {
     static Instance: GestureOverlay;
 
     @observable public InkShape: string = '';
@@ -83,7 +86,7 @@ export class GestureOverlay extends Touchable {
 
     protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
-    constructor(props: Readonly<{}>) {
+    constructor(props: any) {
         super(props);
 
         GestureOverlay.Instance = this;
@@ -495,7 +498,7 @@ export class GestureOverlay extends Touchable {
             }
 
             this._strokes = [];
-            this._points = [];
+            this._points.length = 0;
             this._possibilities = [];
             document.removeEventListener('touchend', this.handleHandUp);
         }
@@ -619,14 +622,14 @@ export class GestureOverlay extends Touchable {
 
         // get the two targets at the ends of the line
         const ep1 = this._points[0];
-        const ep2 = this._points[this._points.length - 1];
+        const ep2 = this._points.lastElement();
         const target1 = document.elementFromPoint(ep1.X, ep1.Y);
         const target2 = document.elementFromPoint(ep2.X, ep2.Y);
 
         const ge = new CustomEvent<GestureUtils.GestureEvent>('dashOnGesture', {
             bubbles: true,
             detail: {
-                points: this._points,
+                points: this._points.slice(),
                 gesture: GestureUtils.Gestures.Line,
                 bounds: B,
             },
@@ -652,8 +655,8 @@ export class GestureOverlay extends Touchable {
             if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) {
                 switch (this.Tool) {
                     case ToolglassTools.InkToText:
-                        this._strokes.push(new Array(...this._points));
-                        this._points = [];
+                        this._strokes.push(this._points.slice());
+                        this._points.length = 0;
                         CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes).then(results => {
                             const wordResults = results.filter((r: any) => r.category === 'line');
                             const possibilities: string[] = [];
@@ -675,7 +678,7 @@ export class GestureOverlay extends Touchable {
                         break;
                     case ToolglassTools.IgnoreGesture:
                         this.dispatchGesture(GestureUtils.Gestures.Stroke);
-                        this._points = [];
+                        this._points.length = 0;
                         break;
                 }
             }
@@ -683,7 +686,7 @@ export class GestureOverlay extends Touchable {
             else if (this.InkShape) {
                 this.makePolygon(this.InkShape, false);
                 this.dispatchGesture(GestureUtils.Gestures.Stroke);
-                this._points = [];
+                this._points.length = 0;
                 if (!CollectionFreeFormViewChrome.Instance?._keepPrimitiveMode) {
                     this.InkShape = '';
                     Doc.ActiveTool = InkTool.None;
@@ -743,15 +746,16 @@ export class GestureOverlay extends Touchable {
                         (controlPoints[0].X - controlPoints.lastElement().X) * (controlPoints[0].X - controlPoints.lastElement().X) + (controlPoints[0].Y - controlPoints.lastElement().Y) * (controlPoints[0].Y - controlPoints.lastElement().Y)
                     );
                     if (controlPoints.length > 4 && dist < 10) controlPoints[controlPoints.length - 1] = controlPoints[0];
-                    this._points = controlPoints;
+                    this._points.length = 0;
+                    this._points.push(...controlPoints);
                     this.dispatchGesture(GestureUtils.Gestures.Stroke);
                     // TODO: nda - check inks to group here
                     checkInksToGroup();
                 }
-                this._points = [];
+                this._points.length = 0;
             }
         } else {
-            this._points = [];
+            this._points.length = 0;
         }
         CollectionFreeFormViewChrome.Instance?.primCreated();
     };
@@ -803,7 +807,7 @@ export class GestureOverlay extends Touchable {
                 }
             }
         }
-        this._points = [];
+        this._points.length = 0;
         switch (shape) {
             //must push an extra point in the end so InteractionUtils knows pointer is up.
             //must be (points[0].X,points[0]-1)
@@ -922,7 +926,7 @@ export class GestureOverlay extends Touchable {
                 new CustomEvent<GestureUtils.GestureEvent>('dashOnGesture', {
                     bubbles: true,
                     detail: {
-                        points: stroke ?? this._points,
+                        points: stroke ?? this._points.slice(),
                         gesture: gesture as any,
                         bounds: this.getBounds(stroke ?? this._points),
                         text: data,
@@ -1067,8 +1071,8 @@ export class GestureOverlay extends Touchable {
 
     render() {
         return (
-            <div className="gestureOverlay-cont" ref={this._overlayRef} onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}>
-                {this.showMobileInkOverlay ? <MobileInkOverlay /> : <></>}
+            <div className="gestureOverlay-cont" style={{ pointerEvents: this.props.isActive ? 'all' : 'none' }} ref={this._overlayRef} onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}>
+                {this.showMobileInkOverlay ? <MobileInkOverlay /> : null}
                 {this.elements}
 
                 <div
@@ -1091,7 +1095,8 @@ export class GestureOverlay extends Touchable {
                         pointerEvents: 'none',
                         touchAction: 'none',
                         display: this.showBounds ? 'unset' : 'none',
-                    }}></div>
+                    }}
+                />
                 <TouchScrollableMenu options={this._possibilities} bounds={this.svgBounds} selectedIndex={this._selectedIndex} x={this._menuX} y={this._menuY} />
             </div>
         );
diff --git a/src/client/views/LightboxView.scss b/src/client/views/LightboxView.scss
index 5d42cd97f..ae5dc9902 100644
--- a/src/client/views/LightboxView.scss
+++ b/src/client/views/LightboxView.scss
@@ -1,35 +1,55 @@
-
-    .lightboxView-navBtn {
-        margin: auto;
-        position: absolute;
-        right: 10;
-        top: 10;
-        background: transparent;
-        border-radius: 8;
-        color:white;
-        opacity: 0.7;
-        width: 35; 
-        &:hover {
-            opacity: 1;
-        }
+.lightboxView-navBtn {
+    margin: auto;
+    position: absolute;
+    right: 10;
+    top: 10;
+    background: transparent;
+    border-radius: 8;
+    color: white;
+    opacity: 0.7;
+    width: 25;
+    flex-direction: column;
+    display: flex;
+    &:hover {
+        opacity: 1;
     }
-    .lightboxView-tabBtn {
-        margin: auto;
-        position: absolute;
-        right: 35;
-        top: 10;
-        background: transparent;
-        border-radius: 8;
-        color:white;
-        opacity: 0.7;
-        width: 35;
-        &:hover {
-            opacity: 1;
-        }
+}
+.lightboxView-tabBtn {
+    margin: auto;
+    position: absolute;
+    right: 38;
+    top: 10;
+    background: transparent;
+    border-radius: 8;
+    color: white;
+    opacity: 0.7;
+    width: 25;
+    flex-direction: column;
+    display: flex;
+    &:hover {
+        opacity: 1;
+    }
+}
+.lightboxView-penBtn {
+    margin: auto;
+    position: absolute;
+    right: 70;
+    top: 10;
+    background: transparent;
+    border-radius: 8;
+    color: white;
+    opacity: 0.7;
+    width: 25;
+    flex-direction: column;
+    display: flex;
+    &:hover {
+        opacity: 1;
     }
+}
 .lightboxView-frame {
-    position: absolute; 
-    top: 0; left: 0;
+    position: absolute;
+    top: 0;
+    left: 0;
     width: 100%;
     height: 100%;
     background: #000000bb;
@@ -51,4 +71,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 25354a09d..5613e82fb 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -4,6 +4,7 @@ import { observer } from 'mobx-react';
 import 'normalize.css';
 import * as React from 'react';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { InkTool } from '../../fields/InkField';
 import { Cast, NumCast, StrCast } from '../../fields/Types';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../Utils';
 import { DocUtils } from '../documents/Documents';
@@ -14,6 +15,7 @@ import { Transform } from '../util/Transform';
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline';
 import { TabDocView } from './collections/TabDocView';
+import { GestureOverlay } from './GestureOverlay';
 import './LightboxView.scss';
 import { DocumentView } from './nodes/DocumentView';
 import { DefaultStyleProvider, wavyBorderPath } from './StyleProvider';
@@ -267,45 +269,48 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                         clipPath: `path('${Doc.UserDoc().renderStyle === 'comic' ? wavyBorderPath(this.lightboxWidth(), this.lightboxHeight()) : undefined}')`,
                     }}>
                     {/* <CollectionMenu /> TODO:glr This is where it would go*/}
-                    <DocumentView
-                        ref={action((r: DocumentView | null) => {
-                            LightboxView._docView = r !== null ? r : undefined;
-                            r &&
-                                setTimeout(
-                                    action(() => {
-                                        const target = LightboxView._docTarget;
-                                        const doc = LightboxView._doc;
-                                        const targetView = target && DocumentManager.Instance.getLightboxDocumentView(target);
-                                        if (doc === r.props.Document && (!target || target === doc)) r.ComponentView?.shrinkWrap?.();
-                                        //else target?.focus(target, { willZoom: true, scale: 0.9, instant: true }); // bcz: why was this here?  it breaks smooth navigation in lightbox using 'next' button
-                                    })
-                                );
-                        })}
-                        Document={LightboxView.LightboxDoc}
-                        DataDoc={undefined}
-                        LayoutTemplate={LightboxView.LightboxDocTemplate}
-                        addDocument={undefined}
-                        isDocumentActive={returnFalse}
-                        isContentActive={returnTrue}
-                        addDocTab={this.addDocTab}
-                        pinToPres={TabDocView.PinDoc}
-                        rootSelected={returnTrue}
-                        docViewPath={returnEmptyDoclist}
-                        docFilters={this.docFilters}
-                        removeDocument={undefined}
-                        styleProvider={DefaultStyleProvider}
-                        ScreenToLocalTransform={this.lightboxScreenToLocal}
-                        PanelWidth={this.lightboxWidth}
-                        PanelHeight={this.lightboxHeight}
-                        focus={DocUtils.DefaultFocus}
-                        whenChildContentsActiveChanged={emptyFunction}
-                        bringToFront={emptyFunction}
-                        docRangeFilters={returnEmptyFilter}
-                        searchFilterDocs={returnEmptyDoclist}
-                        ContainingCollectionView={undefined}
-                        ContainingCollectionDoc={undefined}
-                        renderDepth={0}
-                    />
+
+                    <GestureOverlay isActive={true}>
+                        <DocumentView
+                            ref={action((r: DocumentView | null) => {
+                                LightboxView._docView = r !== null ? r : undefined;
+                                r &&
+                                    setTimeout(
+                                        action(() => {
+                                            const target = LightboxView._docTarget;
+                                            const doc = LightboxView._doc;
+                                            const targetView = target && DocumentManager.Instance.getLightboxDocumentView(target);
+                                            if (doc === r.props.Document && (!target || target === doc)) r.ComponentView?.shrinkWrap?.();
+                                            //else target?.focus(target, { willZoom: true, scale: 0.9, instant: true }); // bcz: why was this here?  it breaks smooth navigation in lightbox using 'next' button
+                                        })
+                                    );
+                            })}
+                            Document={LightboxView.LightboxDoc}
+                            DataDoc={undefined}
+                            LayoutTemplate={LightboxView.LightboxDocTemplate}
+                            addDocument={undefined}
+                            isDocumentActive={returnFalse}
+                            isContentActive={returnTrue}
+                            addDocTab={this.addDocTab}
+                            pinToPres={TabDocView.PinDoc}
+                            rootSelected={returnTrue}
+                            docViewPath={returnEmptyDoclist}
+                            docFilters={this.docFilters}
+                            removeDocument={undefined}
+                            styleProvider={DefaultStyleProvider}
+                            ScreenToLocalTransform={this.lightboxScreenToLocal}
+                            PanelWidth={this.lightboxWidth}
+                            PanelHeight={this.lightboxHeight}
+                            focus={DocUtils.DefaultFocus}
+                            whenChildContentsActiveChanged={emptyFunction}
+                            bringToFront={emptyFunction}
+                            docRangeFilters={returnEmptyFilter}
+                            searchFilterDocs={returnEmptyDoclist}
+                            ContainingCollectionView={undefined}
+                            ContainingCollectionDoc={undefined}
+                            renderDepth={0}
+                        />
+                    </GestureOverlay>
                 </div>
 
                 {this.navBtn(
@@ -332,6 +337,15 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                     this.future()?.length.toString()
                 )}
                 <LightboxTourBtn navBtn={this.navBtn} future={this.future} stepInto={this.stepInto} tourMap={this.tourMap} />
+                <div
+                    className="lightboxView-navBtn"
+                    title={'toggle fit width'}
+                    onClick={e => {
+                        e.stopPropagation();
+                        LightboxView.LightboxDoc!._fitWidth = !LightboxView.LightboxDoc!._fitWidth;
+                    }}>
+                    <FontAwesomeIcon icon={LightboxView.LightboxDoc?._fitWidth ? 'arrows-alt-h' : 'arrows-alt-v'} size="2x" />
+                </div>
                 <div
                     className="lightboxView-tabBtn"
                     title={'open in tab'}
@@ -345,13 +359,14 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                     <FontAwesomeIcon icon={'file-download'} size="2x" />
                 </div>
                 <div
-                    className="lightboxView-navBtn"
-                    title={'toggle fit width'}
+                    className="lightboxView-penBtn"
+                    title="toggle pen annotation"
+                    style={{ background: Doc.ActiveTool === InkTool.Pen ? 'white' : undefined }}
                     onClick={e => {
                         e.stopPropagation();
-                        LightboxView.LightboxDoc!._fitWidth = !LightboxView.LightboxDoc!._fitWidth;
+                        Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen;
                     }}>
-                    <FontAwesomeIcon icon={LightboxView.LightboxDoc?._fitWidth ? 'arrows-alt-h' : 'arrows-alt-v'} size="2x" />
+                    <FontAwesomeIcon color={Doc.ActiveTool === InkTool.Pen ? 'black' : 'white'} icon={'pen'} size="2x" />
                 </div>
             </div>
         );
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 45281ed69..d7b526d22 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -86,7 +86,7 @@ export class MainView extends React.Component {
         return this._hideUI ? 0 : 27;
     } //  27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js
     @computed private get topOfDashUI() {
-        return this._hideUI ? 0 : Number(DASHBOARD_SELECTOR_HEIGHT.replace('px', ''));
+        return this._hideUI || LightboxView.LightboxDoc ? 0 : Number(DASHBOARD_SELECTOR_HEIGHT.replace('px', ''));
     }
     @computed private get topOfHeaderBarDoc() {
         return this.topOfDashUI;
@@ -610,7 +610,7 @@ export class MainView extends React.Component {
 
     @computed get dockingContent() {
         return (
-            <GestureOverlay>
+            <GestureOverlay isActive={LightboxView.LightboxDoc ? false : true}>
                 <div
                     key="docking"
                     className={`mainView-dockingContent${this._leftMenuFlyoutWidth ? '-flyout' : ''}`}
@@ -657,21 +657,16 @@ export class MainView extends React.Component {
         const locationFields = doc._viewType === CollectionViewType.Docking ? ['dashboard'] : location.split(':');
         const locationParams = locationFields.length > 1 ? locationFields[1] : '';
         if (doc.dockingConfig) return DashboardView.openDashboard(doc);
+        // prettier-ignore
         switch (locationFields[0]) {
-            case 'dashboard':
-                return DashboardView.openDashboard(doc);
-            case 'close':
-                return CollectionDockingView.CloseSplit(doc, locationParams);
-            case 'fullScreen':
-                return CollectionDockingView.OpenFullScreen(doc);
-            case 'lightbox':
-                return LightboxView.AddDocTab(doc, location);
-            case 'toggle':
-                return CollectionDockingView.ToggleSplit(doc, locationParams);
-            case 'inPlace':
-            case 'add':
             default:
-                return CollectionDockingView.AddSplit(doc, locationParams);
+            case 'inPlace':
+            case 'add':        return CollectionDockingView.AddSplit(doc, locationParams);
+            case 'dashboard':  return DashboardView.openDashboard(doc);
+            case 'close':      return CollectionDockingView.CloseSplit(doc, locationParams);
+            case 'fullScreen': return CollectionDockingView.OpenFullScreen(doc);
+            case 'lightbox':   return LightboxView.AddDocTab(doc, location);
+            case 'toggle':     return CollectionDockingView.ToggleSplit(doc, locationParams);
         }
     };
 
@@ -716,7 +711,7 @@ export class MainView extends React.Component {
 
     @computed get leftMenuPanel() {
         return (
-            <div key="menu" className="mainView-leftMenuPanel">
+            <div key="menu" className="mainView-leftMenuPanel" style={{ display: LightboxView.LightboxDoc ? 'none' : undefined }}>
                 <DocumentView
                     Document={Doc.MyLeftSidebarMenu}
                     DataDoc={undefined}
@@ -991,19 +986,16 @@ export class MainView extends React.Component {
                 {LinkDocPreview.LinkInfo ? <LinkDocPreview {...LinkDocPreview.LinkInfo} /> : null}
 
                 {((page: string) => {
+                    // prettier-ignore
                     switch (page) {
-                        case 'dashboard':
                         default:
-                            return (
-                                <>
-                                    <div style={{ position: 'relative', display: this._hideUI || LightboxView.LightboxDoc ? 'none' : undefined, zIndex: 2001 }}>
-                                        <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} />
-                                    </div>
-                                    {this.mainDashboardArea}
-                                </>
-                            );
-                        case 'home':
-                            return <DashboardView />;
+                        case 'dashboard': return (<>
+                                        <div key="dashdiv" style={{ position: 'relative', display: this._hideUI || LightboxView.LightboxDoc ? 'none' : undefined, zIndex: 2001 }}>
+                                            <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} />
+                                        </div>
+                                        {this.mainDashboardArea}
+                                    </> );
+                        case 'home': return <DashboardView />;
                     }
                 })(Doc.ActivePage)}
 
@@ -1020,7 +1012,7 @@ export class MainView extends React.Component {
                 <InkTranscription />
                 {this.snapLines}
                 <div className="mainView-webRef" ref={this.makeWebRef} />
-                <LightboxView PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
+                <LightboxView key="lightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
             </div>
         );
     }
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 6a0f69359..46e8494ab 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -34,7 +34,6 @@ import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocum
 import { DocumentView } from '../nodes/DocumentView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
-import { PresBox } from '../nodes/trails/PresBox';
 import { DefaultStyleProvider } from '../StyleProvider';
 import { CollectionDockingView } from './CollectionDockingView';
 import { CollectionLinearView } from './collectionLinear';
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 113574a64..e628c2e44 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,8 +1,9 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
 import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
+import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
 import { Document } from '../../../fields/documentSchemas';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
@@ -10,7 +11,7 @@ import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
 import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { AudioField } from '../../../fields/URLField';
 import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
 import { MobileInterface } from '../../../mobile/MobileInterface';
@@ -20,6 +21,7 @@ import { DocServer } from '../../DocServer';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 import { Networking } from '../../Network';
+import { DictationManager } from '../../util/DictationManager';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager, dropActionType } from '../../util/DragManager';
 import { InteractionUtils } from '../../util/InteractionUtils';
@@ -37,7 +39,6 @@ import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { DocComponent } from '../DocComponent';
 import { EditableView } from '../EditableView';
-import { InkingStroke } from '../InkingStroke';
 import { LightboxView } from '../LightboxView';
 import { StyleProp } from '../StyleProvider';
 import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView';
@@ -52,8 +53,6 @@ import { RadialMenu } from './RadialMenu';
 import { ScriptingBox } from './ScriptingBox';
 import { PresBox } from './trails/PresBox';
 import React = require('react');
-import { DictationManager } from '../../util/DictationManager';
-import { Tooltip } from '@material-ui/core';
 const { Howl } = require('howler');
 
 interface Window {
-- 
cgit v1.2.3-70-g09d2

