From aab4148a23a6505f103564fff305ac4c97b64479 Mon Sep 17 00:00:00 2001
From: monoguitari <113245090+monoguitari@users.noreply.github.com>
Date: Wed, 23 Aug 2023 13:15:31 -0400
Subject: Basic implementation of micahels branching trail on master

---
 src/client/views/collections/collectionLinear/CollectionLinearView.tsx | 2 ++
 1 file changed, 2 insertions(+)

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

diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 2254b2e5f..707986ec3 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -22,6 +22,7 @@ import { CollectionSubView } from '../CollectionSubView';
 import './CollectionLinearView.scss';
 import { Button, Toggle, ToggleType, Type } from 'browndash-components';
 import { Colors } from '../../global/globalEnums';
+import { BranchingTrailManager } from '../../../util/BranchingTrailManager';
 
 /**
  * CollectionLinearView is the class for rendering the horizontal collection
@@ -145,6 +146,7 @@ export class CollectionLinearView extends CollectionSubView() {
             case '<LinkingUI>':          return this.getLinkUI();
             case '<CurrentlyPlayingUI>': return this.getCurrentlyPlayingUI();
             case '<UndoStack>':          return <UndoStack key={doc[Id]} width={200} height={40} inline={true} />;
+            case '<Branching>':          return Doc.UserDoc().isBranchingMode  ? <BranchingTrailManager /> : null;
         }
 
         const nested = doc._type_collection === CollectionViewType.Linear;
-- 
cgit v1.2.3-70-g09d2


From 349e586b01b18c641a4c753b709f4217a3f3e528 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 29 Aug 2023 12:55:49 -0400
Subject: cleanup

---
 src/client/util/CurrentUserUtils.ts                |  6 ++--
 src/client/views/OverlayView.tsx                   |  2 --
 src/client/views/UndoStack.tsx                     |  2 +-
 src/client/views/collections/TreeView.scss         |  4 +--
 .../collectionLinear/CollectionLinearView.tsx      |  2 +-
 .../views/nodes/RecordingBox/RecordingView.tsx     |  3 --
 src/client/views/nodes/VideoBox.tsx                |  4 ---
 src/client/views/nodes/trails/PresBox.tsx          | 12 +++----
 src/client/views/nodes/trails/PresElementBox.tsx   | 37 +++++++---------------
 9 files changed, 21 insertions(+), 51 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 873361587..b705bde7f 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -20,6 +20,7 @@ import { DashboardView } from "../views/DashboardView";
 import { Colors } from "../views/global/globalEnums";
 import { OpenWhere } from "../views/nodes/DocumentView";
 import { ButtonType } from "../views/nodes/FontIconBox/FontIconBox";
+import { ImportElementBox } from "../views/nodes/importBox/ImportElementBox";
 import { OverlayView } from "../views/OverlayView";
 import { DragManager, dropActionType } from "./DragManager";
 import { MakeTemplate } from "./DropConverter";
@@ -28,7 +29,6 @@ import { LinkManager } from "./LinkManager";
 import { ScriptingGlobals } from "./ScriptingGlobals";
 import { ColorScheme, SettingsManager } from "./SettingsManager";
 import { UndoManager } from "./UndoManager";
-import { ImportElementBox } from "../views/nodes/importBox/ImportElementBox";
 
 interface Button {
     // DocumentOptions fields a button can set
@@ -746,10 +746,8 @@ export class CurrentUserUtils {
     static setupContextMenuBtn(params:Button, menuDoc:Doc):Doc {
         const menuBtnDoc = DocListCast(menuDoc?.data).find(doc => doc.title === params.title);
         const subMenu = params.subMenu;
-        // Doc.UserDoc().workspaceRecordings = new List<Doc>;
         if (Doc.UserDoc().currentRecording) {
-            //@ts-ignore
-            Doc.RemFromMyOverlay(Doc.UserDoc().currentRecording);
+            Doc.RemFromMyOverlay(DocCast(Doc.UserDoc().currentRecording));
         }
         Doc.UserDoc().isRecording = false;
         Doc.UserDoc().isRecPlayback = false;
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 7d65914b3..5d95c5fda 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -1,4 +1,3 @@
-
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
@@ -145,7 +144,6 @@ export class OverlayView extends React.Component {
 
     @action
     addWindow(contents: JSX.Element, options: OverlayElementOptions): OverlayDisposer {
-        console.log("adding window");
         const remove = action(() => {
             const index = this._elements.indexOf(contents);
             if (index !== -1) this._elements.splice(index, 1);
diff --git a/src/client/views/UndoStack.tsx b/src/client/views/UndoStack.tsx
index cdc389efe..47853b5e4 100644
--- a/src/client/views/UndoStack.tsx
+++ b/src/client/views/UndoStack.tsx
@@ -46,7 +46,7 @@ export class UndoStack extends React.Component<UndoStackProps> {
                                 .reverse()
                                 .map((name, i) => (
                                     <div className="undoStack-resultContainer" key={i}>
-                                        <div className="undoStack-commandString" style={{ fontWeight: 'bold', color: 'red' }}>
+                                        <div className="undoStack-commandString" style={{ fontWeight: 'bold', color: SettingsManager.userBackgroundColor }}>
                                             {StrCast(name).replace(/[^\.]*\./, '')}
                                         </div>
                                     </div>
diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss
index d3ba23b4e..0cc11bf1c 100644
--- a/src/client/views/collections/TreeView.scss
+++ b/src/client/views/collections/TreeView.scss
@@ -23,7 +23,7 @@
     .treeView-bulletIcons {
         width: 100%;
         height: 100%;
-        // position: absolute;
+        // changes start here.
 
         .treeView-expandIcon {
             display: none;
@@ -42,6 +42,7 @@
                 display: unset;
             }
         }
+        // end changes
         position: relative;
         display: flex;
         flex-direction: row;
@@ -140,7 +141,6 @@
             filter: opacity(0.2) !important;
         }
     }
-    
 
     //align-items: center;
     ::-webkit-scrollbar {
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 0854bc611..3481d5130 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -210,7 +210,7 @@ export class CollectionLinearView extends CollectionSubView() {
                 text={Cast(this.props.Document.icon, 'string', null)}
                 icon={Cast(this.props.Document.icon, 'string', null) ? undefined : <FontAwesomeIcon color={SettingsManager.userColor} icon={isExpanded ? 'minus' : 'plus'} />}
                 color={SettingsManager.userColor}
-                // background={SettingsManager.userVariantColor}
+                background={SettingsManager.userVariantColor}
                 type={Type.TERT}
                 onPointerDown={e => e.stopPropagation()}
                 toggleType={ToggleType.BUTTON}
diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx
index 755f1adc0..f7ed82643 100644
--- a/src/client/views/nodes/RecordingBox/RecordingView.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx
@@ -9,7 +9,6 @@ import { Networking } from '../../../Network';
 import { Presentation, TrackMovements } from '../../../util/TrackMovements';
 import { ProgressBar } from './ProgressBar';
 import './RecordingView.scss';
-import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
 
 export interface MediaSegment {
     videoChunks: any[];
@@ -164,9 +163,7 @@ export function RecordingView(props: IRecordingViewProps) {
     // if this is called, then we're done recording all the segments
     const finish = () => {
         // call stop on the video recorder if active
-        console.log(videoRecorder.current?.state);
         videoRecorder.current?.state !== 'inactive' && videoRecorder.current?.stop();
-        console.log("this it")
         // end the streams (audio/video) to remove recording icon
         const stream = videoElementRef.current!.srcObject;
         stream instanceof MediaStream && stream.getTracks().forEach(track => track.stop());
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 5c2d09b0c..56508abf6 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -191,10 +191,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 this._finished = false;
                 start = this.timeline.trimStart;
             }
-            if (this.dataDoc._data_presentation) {
-                console.log("ninja")
-            }
-            console.log(this.dataDoc.data_presentation)
             try {
                 this._audioPlayer && this.player && (this._audioPlayer.currentTime = this.player?.currentTime);
                 update && this.player && this.playFrom(start, undefined, true);
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index b0101d9a7..02132b7b3 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -303,7 +303,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     // Called when the user activates 'next' - to move to the next part of the pres. trail
     @action
     next = () => {
-        console.log("next");
+        console.log('next');
         const progressiveReveal = (first: boolean) => {
             const presIndexed = Cast(this.activeItem?.presentation_indexed, 'number', null);
             if (presIndexed !== undefined) {
@@ -345,7 +345,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             // before moving onto next slide, run the subroutines :)
             const currentDoc = this.childDocs[this.itemIndex];
             //could i do this.childDocs[this.itemIndex] for first arg?
-            this.runSubroutines(TreeView.GetRunningChildren.get(currentDoc)?.() , this.childDocs[this.itemIndex + 1]);
+            this.runSubroutines(TreeView.GetRunningChildren.get(currentDoc)?.(), this.childDocs[this.itemIndex + 1]);
 
             this.nextSlide(curLast + 1 === this.childDocs.length ? (this.layoutDoc.presLoop ? 0 : curLast) : curLast + 1);
             progressiveReveal(true); // shows first progressive document, but without a transition effect
@@ -386,7 +386,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     //it'll also execute the necessary actions if presentation is playing.
     @undoBatch
     public gotoDocument = action((index: number, from?: Doc, group?: boolean, finished?: () => void) => {
-        console.log("going to document");
+        console.log('going to document');
         Doc.UnBrushAllDocs();
         if (index >= 0 && index < this.childDocs.length) {
             this.rootDoc._itemIndex = index;
@@ -747,7 +747,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
         console.log(pinDoc.presData);
     }
-    
+
     /**
      * This method makes sure that cursor navigates to the element that
      * has the option open and last in the group.
@@ -759,7 +759,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     navigateToActiveItem = (afterNav?: () => void) => {
         const activeItem: Doc = this.activeItem;
         const targetDoc: Doc = this.targetDoc;
-        // BranchingTrailManager.Instance.observeDocumentChange(targetDoc, this);
         const finished = () => {
             afterNav?.();
             console.log('Finish Slide Nav: ' + targetDoc.title);
@@ -1149,17 +1148,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     //Regular click
     @action
     selectElement = (doc: Doc, noNav = false) => {
-        // BranchingTrailManager.Instance.observeDocumentChange(doc, this);
         CollectionStackedTimeline.CurrentlyPlaying?.map((clip, i) => clip?.ComponentView?.Pause?.());
         if (noNav) {
             const index = this.childDocs.indexOf(doc);
             if (index >= 0 && index < this.childDocs.length) {
                 this.rootDoc._itemIndex = index;
             }
-            console.log("no nav")
         } else {
             this.gotoDocument(this.childDocs.indexOf(doc), this.activeItem);
-            console.log('e bitch')
         }
         this.updateCurrentPresentation(DocCast(doc.embedContainer));
     };
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index d90f96249..121eb87d5 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -316,15 +316,15 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         console.log('reverting');
         // console.log("reverting to previosly saved\n");
         // console.log(this.prevTarget);
-        console.log("Content continuously updating");
+        console.log('Content continuously updating');
         const target = DocCast(activeItem.annotationOn) ?? activeItem;
-        console.log(presTargetDoc.pinData)
+        console.log(presTargetDoc.pinData);
         PresBox.reversePin(activeItem, target);
         // console.log("new target\n");
-        // console.log(target); 
+        // console.log(target);
         // PresBox.pinDocView(activeItem, { pinData: PresBox.pinDataTypes(this.prevTarget) }, this.prevTarget ? this.prevTarget : target);
         //figure out how to make it go back to the previously saved one
-    }
+    };
 
     /**
      * Method called for updating the view of the currently selected document
@@ -388,7 +388,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     @undoBatch
     @action
-    startRecording =    (e: React.MouseEvent, activeItem: Doc) => {
+    startRecording = (e: React.MouseEvent, activeItem: Doc) => {
         e.stopPropagation();
         if (PresElementBox.videoIsRecorded(activeItem)) {
             // if we already have an existing recording
@@ -453,7 +453,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const hasChildren: boolean = Cast(this.rootDoc?.hasChildren, 'boolean') || false;
 
         const items: JSX.Element[] = [];
-       
+
         items.push(
             <Tooltip key="slide" title={<div className="dash-tooltip">Update captured doc layout</div>}>
                 <div
@@ -464,31 +464,16 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 </div>
             </Tooltip>
         );
-        // items.push(
-        //     <Tooltip key="flex" title={<div className="dash-tooltip">Update captured doc content</div>}>
-        //         <div
-        //             className="slideButton"
-        //             onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.updateCapturedViewContents(targetDoc, activeItem))}
-        //             style={{ opacity: activeItem.config_pinData || activeItem.config_pinView ? 1 : 0.5, fontWeight: 700, display: 'flex' }}>
-        //             C
-        //         </div>
-        //     </Tooltip>
-        // );
         items.push(
             <Tooltip key="flex" title={<div className="dash-tooltip">Update captured doc content</div>}>
                 <div
-                    className="slideButton" 
-                    style={{fontWeight: 700, display: 'flex'}}
-                >
-                 <MultiToggle  type={Type.PRIM} items={[
-                        {icon: <FontAwesomeIcon icon="rotate-left" color="white" size='sm'/>, tooltip: "Save data to presentation", val: 'revert',
-                        onPointerDown: e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.updateCapturedViewContents(targetDoc, activeItem))},
-                        {icon: <FontAwesomeIcon icon="floppy-disk" color="white" size='sm'/>, tooltip: "Continously update content", val: "floppy-disk",
-                        onPointerDown: e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.revertToPreviouslySaved(targetDoc, activeItem))},
-                    ]} />
+                    className="slideButton"
+                    onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.updateCapturedViewContents(targetDoc, activeItem))}
+                    style={{ opacity: activeItem.config_pinData || activeItem.config_pinView ? 1 : 0.5, fontWeight: 700, display: 'flex' }}>
+                    C
                 </div>
             </Tooltip>
-        )
+        );
         items.push(
             <Tooltip key="slash" title={<div className="dash-tooltip">{this.recordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}</div>}>
                 <div className="slideButton" onClick={e => (this.recordingIsInOverlay ? this.hideRecording(e, true) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}>
-- 
cgit v1.2.3-70-g09d2


From 7c218639c75e22e1270d4198fb940b439175deee Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 31 Aug 2023 11:50:14 -0400
Subject: reworked recording workspace UI and switched to recording window, not
 webcam

---
 src/client/util/CurrentUserUtils.ts                |  58 +++---
 src/client/util/DragManager.ts                     |   7 +-
 src/client/views/DashboardView.tsx                 |   8 +-
 .../collectionLinear/CollectionLinearView.tsx      |   4 +-
 src/client/views/nodes/DocumentView.tsx            |   2 +-
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |  90 ++++-----
 .../views/nodes/RecordingBox/RecordingBox.tsx      | 215 ++++++++++-----------
 src/client/views/nodes/ScreenshotBox.tsx           |   3 +
 src/client/views/topbar/TopBar.tsx                 |  63 ++++--
 src/fields/Doc.ts                                  |   3 +
 10 files changed, 234 insertions(+), 219 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 51ef51508..04f1ff0d2 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -18,6 +18,7 @@ import { CollectionViewType, DocumentType } from "../documents/DocumentTypes";
 import { TreeViewType } from "../views/collections/CollectionTreeView";
 import { DashboardView } from "../views/DashboardView";
 import { Colors } from "../views/global/globalEnums";
+import { media_state } from "../views/nodes/AudioBox";
 import { OpenWhere } from "../views/nodes/DocumentView";
 import { ButtonType } from "../views/nodes/FontIconBox/FontIconBox";
 import { ImportElementBox } from "../views/nodes/importBox/ImportElementBox";
@@ -51,7 +52,7 @@ interface Button {
 
     // fields that do not correspond to DocumentOption fields
     scripts?: { script?: string; onClick?: string; onDoubleClick?: string }
-    funcs?: { [key:string]: string };
+    funcs?: { [key:string]: any};
     subMenu?: Button[];
 }
 
@@ -591,7 +592,7 @@ export class CurrentUserUtils {
     static createToolButton = (opts: DocumentOptions) => Docs.Create.FontIconDocument({
         btnType: ButtonType.ToolButton, _forceActive: true, _layout_hideContextMenu: true, 
         _dropPropertiesToRemove: new List<string>([ "_layout_hideContextMenu"]), 
-        _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, isSystem: true, ...opts,
+        /*_nativeWidth: 40, _nativeHeight: 40, */ _width: 40, _height: 40, isSystem: true, ...opts,
     })
 
     /// initializes the required buttons in the expanding button menu at the bottom of the Dash window
@@ -693,7 +694,6 @@ export class CurrentUserUtils {
             { title: "URL",     toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, scripts: { script: '{ return webSetURL(value, _readOnly_); }'} },
         ];
     }
-
     static contextMenuTools():Button[] {
         return [
             { btnList: new List<string>([CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Tree,
@@ -701,11 +701,11 @@ export class CurrentUserUtils {
                     CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel,
                     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: "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,         expertMode: false,                                                                                                                             width: 30,  scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}},
             { title: "Fill",    icon: "fill-drip",     toolTip: "Background Fill Color",btnType: ButtonType.ColorButton,         expertMode: false,                                                                                                          ignoreClick: true, width: 30,  scripts: { script:  'return setBackgroundColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}}, // Only when a document is selected
             { title: "Header",  icon: "heading",       toolTip: "Header Color",         btnType: ButtonType.ColorButton,         expertMode: true,                                                                                                           ignoreClick: true,             scripts: { script:  'return setHeaderColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}},
-            { title: "Overlay", icon: "layer-group",   toolTip: "Overlay",              btnType: ButtonType.ToggleButton,        expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'},       scripts: { onClick:  '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform
+            { title: "Overlay", icon: "layer-group",   toolTip: "Overlay",              btnType: ButtonType.ToggleButton,        expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'},       scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform
             { title: "Back",    icon: "chevron-left",  toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton,         expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 30,  scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
             { title: "Num",     icon:"",               toolTip: "Frame Number (click to toggle edit mode)",    btnType: ButtonType.TextButton,          expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
             { title: "Fwd",     icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton,         expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 30,  scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
@@ -714,16 +714,8 @@ export class CurrentUserUtils {
             { title: "Doc",     icon: "Doc",           toolTip: "Freeform Doc tools",   subMenu: CurrentUserUtils.freeTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
             { title: "View",    icon: "View",          toolTip: "View tools",           subMenu: CurrentUserUtils.viewTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
             { title: "Web",     icon: "Web",           toolTip: "Web functions",        subMenu: CurrentUserUtils.webTools(),    expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected
-            { title: "Schema",  icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema,   funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Schema is selected
-            { title: "Video",   icon: 'video',         toolTip: "Dictate",              btnType: ButtonType.ToggleButton,        expertMode: false,                                       funcs: {hidden: `getIsRecording()`},                               ignoreClick: true,             scripts: { onClick:  'return toggleRecording(_readOnly_)'}},
-            { title: "StopRec", icon: "stop",          toolTip: "Stop",                 btnType: ButtonType.ToggleButton,        expertMode: false,                                       funcs: {hidden: `!getIsRecording()`},                              ignoreClick: true,             scripts: { onClick: `return toggleRecording(_readOnly_)`}},
-            { title: "Dropdown",                       toolTip: "Workspace Recordings", btnType: ButtonType.DropdownList,        expertMode: false,                                       funcs: {hidden: `renderDropdown()`, btnList: `getWorkspaceRecordings()`},                      ignoreClick: true,             scripts: { script: `toggleRecPlayback(value)`}},
-            { title: "Play Rec",icon: "play",          toolTip: "Play recording",       btnType: ButtonType.ToggleButton,        expertMode: false,                                       funcs: {hidden: `!getIsWorkspaceRecPlaying()`},                            ignoreClick: true,             scripts: { onClick: `return playWorkspaceRec(getCurrentRecording())`}},
-            { title: "Pause Rec",icon: "pause",        toolTip: "Pause recording",      btnType: ButtonType.ToggleButton,        expertMode: false,                                       funcs: {hidden: `!getIsWorkspaceRecPaused()`},                            ignoreClick: true,             scripts: { onClick: `return pauseWorkspaceRec(getCurrentRecording())`}},
-            { title: "Stop Rec", icon: "stop",         toolTip: "Stop recording",       btnType: ButtonType.ToggleButton,        expertMode: false,                                       funcs: {hidden: `!getIsRecPlayback()`},                            ignoreClick: true,             scripts: { onClick: `return closeWorkspaceRec(getCurrentRecording())`}},
-            { title: "Add doc", icon: "down",          toolTip: "add to doc",           btnType: ButtonType.ToggleButton,        expertMode: false,                                       funcs: {hidden: `!getIsRecPlayback()`},                            ignoreClick: true,             scripts: { onClick: `addRectoWorkspace(getCurrentRecording())`}},
-            { title: "Delete Rec", icon: "trash",      toolTip: "delete selected recording", btnType: ButtonType.ToggleButton,   expertMode: false,                                       funcs: {hidden: `!getIsRecPlayback()`},                                                         ignoreClick: true,             scripts: { onClick: `removeWorkspaceRec(getCurrentRecording())`}}
-        ];
+            { title: "Schema",  icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(),expertMode: false,toolType:CollectionViewType.Schema,funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Schema is selected
+           ];
     }
 
     /// initializes a context menu button for the top bar context menu
@@ -749,19 +741,17 @@ export class CurrentUserUtils {
         if (Doc.UserDoc().currentRecording) {
             Doc.RemFromMyOverlay(DocCast(Doc.UserDoc().currentRecording));
         }
-        Doc.UserDoc().isRecording = false;
-        Doc.UserDoc().isRecPlayback = false;
+        Doc.UserDoc().workspaceRecordingState = undefined;
+        Doc.UserDoc().workspaceReplayingState = undefined;
         Doc.UserDoc().currentRecording = undefined;
-        Doc.UserDoc().isPlaybackPlaying = false;
-        Doc.UserDoc().isWorkspaceRecPlaying = false;
-        Doc.UserDoc().isWorkspaceRecPaused = false;
+        Doc.UserDoc().workspaceRecordingState = undefined;
         if (!subMenu) { // button does not have a sub menu
             return this.setupContextMenuButton(params, menuBtnDoc);
         }  
         // linear view
         const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List<string>(['width', "linearView_IsOpen"]),
             childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true,
-            linearView_SubMenu: true, linearView_Expandable: params.btnType !== ButtonType.MultiToggleButton};
+            linearView_SubMenu: true, linearView_Expandable: true};
         
         const items = (menuBtnDoc?:Doc) => !menuBtnDoc ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menuBtnDoc) );
         const creator = params.btnType === ButtonType.MultiToggleButton ? this.multiToggleList : this.linearButtonList;
@@ -773,11 +763,34 @@ export class CurrentUserUtils {
 
     /// Initializes all the default buttons for the top bar context menu
     static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") {
-        const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", undoIgnoreFields:new List<string>(['width', "linearView_IsOpen"]), flexGap: 0, childDragAction: 'embed', childDontRegisterViews: true,  linearView_IsOpen: true, ignoreClick: true, linearView_Expandable: false, _height: 35 };
+        const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", undoIgnoreFields:new List<string>(['width', "linearView_IsOpen"]), flexGap: 0, childDragAction: 'embed', childDontRegisterViews: true,  linearView_IsOpen: true, ignoreClick: true, linearView_Expandable: true, _height: 35 };
         const ctxtMenuBtnsDoc = DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, undefined);
         const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => this.setupContextMenuBtn(params, ctxtMenuBtnsDoc) );
         return DocUtils.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts, ctxtMenuBtns);
     }
+    /// Initializes all the default buttons for the top bar context menu
+    static setupTopbarButtons(doc: Doc, field="myTopBarBtns") {
+        const dockedBtns = DocCast(doc[field]);
+        const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string|undefined}, funcs?: {[key:string]:any}) =>
+            DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ?? 
+                            CurrentUserUtils.createToolButton(opts), scripts, funcs);
+        
+        const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet
+            { opts: { title: "Replicate",icon:"camera",toolTip: "Copy dashboard layout",btnType: ButtonType.ClickButton, expertMode: true},                                                                                               scripts: { onClick:  `snapshotDashboard()`}},
+            { opts: { title: "Recordings",            toolTip: "Workspace Recordings", btnType: ButtonType.DropdownList,expertMode: false, ignoreClick: true, width: 100}, funcs: {hidden: `false`, btnList:`getWorkspaceRecordings()`}, scripts: { script:  `{ return replayWorkspace(value, _readOnly_); }`}},
+            { opts: { title: "Stop Rec",icon: "stop", toolTip: "Stop recording",       btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `!isWorkspaceRecording()`},                                                  scripts: { onClick: `stopWorkspaceRecording()`}},
+            { opts: { title: "Play",    icon: "play", toolTip: "Play recording",       btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`},                       scripts: { onClick: `resumeWorkspaceReplaying(getCurrentRecording())`}},
+            { opts: { title: "Pause",   icon: "pause",toolTip: "Pause playback",       btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Playing}"`},                      scripts: { onClick: `pauseWorkspaceReplaying(getCurrentRecording())`}},
+            { opts: { title: "Stop",    icon: "stop", toolTip: "Stop playback",        btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`},                       scripts: { onClick: `stopWorkspaceReplaying(getCurrentRecording())`}},
+            { opts: { title: "Delete",  icon: "trash",toolTip: "delete selected rec",  btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`},                       scripts: { onClick: `removeWorkspaceReplaying(getCurrentRecording())`}}
+        ];
+        const btns = btnDescs.map(desc => dockBtn({_width: desc.opts.width??30, _height: 30, defaultDoubleClick: 'ignore', undoIgnoreFields: new List<string>(['opacity']), _dragOnlyWithinContainer: true, ...desc.opts}, desc.scripts, desc.funcs));
+        const dockBtnsReqdOpts:DocumentOptions = {
+            title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDragAction: 'move',
+            childDontRegisterViews: true, linearView_IsOpen: true, linearView_Expandable: true, ignoreClick: true
+        };
+        return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns);
+    }
 
     /// collection of documents rendered in the overlay layer above all tabs and other UI
     static setupOverlays(doc: Doc, field = "myOverlayDocs") {
@@ -891,6 +904,7 @@ export class CurrentUserUtils {
         this.setupOverlays(doc);  // sets up the overlay panel where documents and other widgets can be added to float over the rest of the dashboard
         this.setupPublished(doc);  // sets up the list doc of all docs that have been published (meaning that they can be auto-linked by typing their title into another text box)
         this.setupContextMenuButtons(doc); // set up the row of buttons at the top of the dashboard that change depending on what is selected
+        this.setupTopbarButtons(doc);
         this.setupDockedButtons(doc);  // the bottom bar of font icons
         this.setupLeftSidebarMenu(doc); // the left-side column of buttons that open their contents in a flyout panel on the left
         this.setupDocTemplates(doc); // sets up the template menu of templates
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 065f17139..6c0641943 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -243,13 +243,10 @@ export namespace DragManager {
             return dropDoc;
         };
         const finishDrag = async (e: DragCompleteEvent) => {
-            Doc.UserDoc().isAddRecToDocMode = false;
             Doc.RemFromMyOverlay(DocCast(Doc.UserDoc().currentRecording));
             Doc.UserDoc().currentRecording = undefined;
-            Doc.UserDoc().isRecPlayback = false;
-            Doc.UserDoc().isWorkspaceRecPlaying = false;
-            Doc.UserDoc().isWorkspaceRecPaused = false;
-            Doc.UserDoc().isAddRecToDocMode = false;
+            Doc.UserDoc().isWorkspaceReplaying = false;
+            Doc.UserDoc().workspaceRecordingState = undefined;
             Cast(Doc.UserDoc().workspaceRecordings, listSpec(Doc), null)?.splice(recordingIndex, 1);
             const docDragData = e.docDragData;
             dropEvent?.(); // glr: optional additional function to be called - in this case with presentation trails
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 4387c6e96..3ef6c0814 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -18,7 +18,7 @@ import { CollectionViewType } from '../documents/DocumentTypes';
 import { HistoryUtil } from '../util/History';
 import { ScriptingGlobals } from '../util/ScriptingGlobals';
 import { SharingManager } from '../util/SharingManager';
-import { undoBatch } from '../util/UndoManager';
+import { undoBatch, UndoManager } from '../util/UndoManager';
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import { CollectionView } from './collections/CollectionView';
 import { ContextMenu } from './ContextMenu';
@@ -493,6 +493,8 @@ ScriptingGlobals.add(function resetDashboard(dashboard: Doc) {
 ScriptingGlobals.add(function addToDashboards(dashboard: Doc) {
     DashboardView.openDashboard(Doc.MakeEmbedding(dashboard));
 }, 'adds Dashboard to set of Dashboards');
-ScriptingGlobals.add(function snapshotDashboard() {
-    DashboardView.snapshotDashboard();
+ScriptingGlobals.add(async function snapshotDashboard() {
+    const batch = UndoManager.StartBatch('snapshot');
+    await DashboardView.snapshotDashboard();
+    batch.end();
 }, 'creates a snapshot copy of a dashboard');
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 3481d5130..0cf7d4411 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -229,8 +229,8 @@ export class CollectionLinearView extends CollectionSubView() {
                 <div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu} style={{ minHeight: this.dimension(), pointerEvents: 'all' }}>
                     {
                         <>
-                            {menuOpener}
-                            {!this.layoutDoc.linearView_IsOpen ? null : (
+                            {!this.layoutDoc.linearView_Expandable ? null :menuOpener}
+                            {!this.layoutDoc.linearView_IsOpen && !this.layoutDoc.linearView_alwaysOpen ? null : (
                                 <div
                                     className="collectionLinearView-content"
                                     style={{
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 8a0706c30..998024cea 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -798,7 +798,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             !more && moreItems.length && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'compass' });
         }
         const constantItems: ContextMenuProps[] = [];
-        if (!Doc.IsSystem(this.rootDoc)) {
+        if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._type_collection !== CollectionViewType.Docking) {
             constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => Doc.Zip(this.props.Document) });
             (this.rootDoc._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: 'users' });
             if (this.props.removeDocument && Doc.ActiveDashboard !== this.props.Document) {
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index bb2069cc9..8ef9cd792 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -180,55 +180,45 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
      * Dropdown list
      */
     @computed get dropdownListButton() {
-        const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
         const script = ScriptCast(this.rootDoc.script);
 
         let noviceList: string[] = [];
         let text: string | undefined;
-        let dropdown = true;
         let getStyle: (val: string) => any = () => {};
         let icon: IconProp = 'caret-down';
-        let isViewDropdown: boolean = script?.script.originalScript.startsWith('setView');
-        try {
-            if (isViewDropdown) {
-                const selectedDocs: Doc[] = SelectionManager.Docs();
-                const selected = SelectionManager.Docs().lastElement();
-                if (selected) {
-                    if (StrCast(selected.type) === DocumentType.COL) {
-                        text = StrCast(selected._type_collection);
+        const isViewDropdown = script?.script.originalScript.startsWith('setView');
+        if (isViewDropdown) {
+            const selected = SelectionManager.Docs();
+            if (selected.lastElement()) {
+                if (StrCast(selected.lastElement().type) === DocumentType.COL) {
+                    text = StrCast(selected.lastElement()._type_collection);
+                } else {
+                    if (selected.length > 1) {
+                        text = selected.length + ' selected';
                     } else {
-                        dropdown = false;
-                        if (selectedDocs.length > 1) {
-                            text = selectedDocs.length + ' selected';
-                        } else {
-                            text = Utils.cleanDocumentType(StrCast(selected.type) as DocumentType);
-                            icon = Doc.toIcon(selected);
-                        }
-                        return (
-                            <Popup
-                                icon={<FontAwesomeIcon size={'1x'} icon={icon} />}
-                                text={text}
-                                type={Type.TERT}
-                                color={SettingsManager.userColor}
-                                background={SettingsManager.userVariantColor}
-                                popup={<SelectedDocView selectedDocs={selectedDocs} />}
-                                fillWidth
-                            />
-                        );
+                        text = Utils.cleanDocumentType(StrCast(selected.lastElement().type) as DocumentType);
+                        icon = Doc.toIcon(selected.lastElement());
                     }
-                } else {
-                    dropdown = false;
-                    return <Button text="None Selected" type={Type.TERT} color={SettingsManager.userColor} background={SettingsManager.userVariantColor} fillWidth inactive />;
+                    return (
+                        <Popup
+                            icon={<FontAwesomeIcon size={'1x'} icon={icon} />}
+                            text={text}
+                            type={Type.TERT}
+                            color={SettingsManager.userColor}
+                            background={SettingsManager.userVariantColor}
+                            popup={<SelectedDocView selectedDocs={selected} />}
+                            fillWidth
+                        />
+                    );
                 }
-                noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking, CollectionViewType.NoteTaking];
             } else {
-                text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
-                getStyle = (val: string) => {
-                    return { fontFamily: val };
-                };
+                return <Button text="None Selected" type={Type.TERT} color={SettingsManager.userColor} background={SettingsManager.userVariantColor} fillWidth inactive />;
             }
-        } catch (e) {
-            console.log(e);
+            noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking, CollectionViewType.NoteTaking];
+        } else {
+            text = script?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: '', _readOnly_: true }).result;
+            // text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
+            getStyle = (val: string) => ({ fontFamily: val });
         }
 
         // Get items to place into the list
@@ -243,19 +233,17 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
             }));
 
         return (
-            <div style={{ color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }}>
-                <Dropdown
-                    selectedVal={text}
-                    setSelectedVal={undoable(val => script.script.run({ this: this.layoutDoc, self: this.rootDoc, val }), `dropdown select ${this.label}`)}
-                    color={SettingsManager.userColor}
-                    background={isViewDropdown ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor}
-                    type={Type.TERT}
-                    dropdownType={DropdownType.SELECT}
-                    items={list}
-                    tooltip={this.label}
-                    fillWidth
-                />
-            </div>
+            <Dropdown
+                selectedVal={text ?? 'Record Workspace'}
+                setSelectedVal={undoable(val => script.script.run({ this: this.layoutDoc, self: this.rootDoc, val }), `dropdown select ${this.label}`)}
+                color={SettingsManager.userColor}
+                background={SettingsManager.userVariantColor}
+                type={Type.TERT}
+                dropdownType={DropdownType.SELECT}
+                items={list}
+                tooltip={this.label}
+                fillWidth
+            />
         );
     }
 
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index 9dd673591..b1b044c25 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -1,11 +1,10 @@
 import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { formatTime } from '../../../../Utils';
 import { Doc, DocListCast } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
 import { listSpec } from '../../../../fields/Schema';
-import { BoolCast, Cast, DocCast } from '../../../../fields/Types';
+import { BoolCast, Cast, DocCast, StrCast } from '../../../../fields/Types';
 import { VideoField } from '../../../../fields/URLField';
 import { Upload } from '../../../../server/SharedMediaTypes';
 import { Docs } from '../../../documents/Documents';
@@ -22,6 +21,8 @@ import { FieldView, FieldViewProps } from '../FieldView';
 import { VideoBox } from '../VideoBox';
 import { RecordingView } from './RecordingView';
 import { DateField } from '../../../../fields/DateField';
+import { media_state } from '../AudioBox';
+import { List } from '../../../../fields/List';
 
 @observer
 export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -41,9 +42,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @observable videoDuration: number | undefined = undefined;
 
     @action
-    setVideoDuration = (duration: number) => {
-        this.videoDuration = duration;
-    };
+    setVideoDuration = (duration: number) => (this.videoDuration = duration);
 
     @action
     setResult = (info: Upload.AccessPathInfo, presentation?: Presentation) => {
@@ -61,76 +60,68 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
             this.dataDoc[this.fieldKey + '_presentation'] = JSON.stringify(presCopy);
         }
     };
-
-    /**
-     * This method toggles whether or not we are currently using the RecordingBox to record with the topbar button
-     * @param _readOnly_
-     * @returns
-     */
     @undoBatch
     @action
-    public static toggleWorkspaceRecording(_readOnly_: boolean) {
-        if (_readOnly_) return RecordingBox.screengrabber ? true : false;
-        if (RecordingBox.screengrabber) {
+    public static WorkspaceStopRecording() {
+        const remDoc = RecordingBox.screengrabber?.rootDoc;
+        if (remDoc) {
             //if recordingbox is true; when we press the stop button. changed vals temporarily to see if changes happening
-            console.log('grabbing screen!');
-            RecordingBox.screengrabber.Pause?.();
-            const remDoc = RecordingBox.screengrabber.rootDoc;
+            RecordingBox.screengrabber?.Pause?.();
             setTimeout(() => {
                 RecordingBox.screengrabber?.Finish?.();
-                RecordingBox.screengrabber!.rootDoc.overlayX = 70; //was 100
-                RecordingBox.screengrabber!.rootDoc.overlayY = 590;
-                console.log(RecordingBox.screengrabber);
+                remDoc.overlayX = 70; //was 100
+                remDoc.overlayY = 590;
                 RecordingBox.screengrabber = undefined;
             }, 100);
             //could break if recording takes too long to turn into videobox. If so, either increase time on setTimeout below or find diff place to do this
-            setTimeout(() => {
-                Doc.RemFromMyOverlay(remDoc);
-            }, 1000);
-            Doc.UserDoc().isRecording = false;
-            Doc.AddDocToList(Doc.UserDoc(), 'workspaceRecordings', RecordingBox.screengrabber.rootDoc);
-        } else {
-            //when we first press mic
-            const screengrabber = Docs.Create.WebCamDocument('', {
-                title: `${Doc.ActiveDashboard?.title ?? ''} ${new DateField()}`,
-                _width: 205,
-                _height: 115,
-            });
-            screengrabber.overlayX = 70; //was -400
-            screengrabber.overlayY = 590; //was 0
-            screengrabber[Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true;
-            Doc.AddToMyOverlay(screengrabber); //just adds doc to overlay
-            DocumentManager.Instance.AddViewRenderedCb(screengrabber, docView => {
-                RecordingBox.screengrabber = docView.ComponentView as RecordingBox;
-                RecordingBox.screengrabber.Record?.();
-            });
-            Doc.UserDoc().isRecording = true;
+            setTimeout(() => Doc.RemFromMyOverlay(remDoc), 1000);
+            Doc.UserDoc().workspaceRecordingState = media_state.Paused;
+            Doc.AddDocToList(Doc.UserDoc(), 'workspaceRecordings', remDoc);
         }
     }
 
+    /**
+     * This method toggles whether or not we are currently using the RecordingBox to record with the topbar button
+     * @param _readOnly_
+     * @returns
+     */
+    @undoBatch
+    @action
+    public static WorkspaceStartRecording() {
+        const screengrabber = Docs.Create.ScreenshotDocument({
+            title: `${Doc.ActiveDashboard?.title ?? ''} ${new DateField()}`,
+            _width: 205,
+            _height: 115,
+        });
+        screengrabber.overlayX = 70; //was -400
+        screengrabber.overlayY = 590; //was 0
+        screengrabber[Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true;
+        Doc.AddToMyOverlay(screengrabber); //just adds doc to overlay
+        DocumentManager.Instance.AddViewRenderedCb(screengrabber, docView => {
+            RecordingBox.screengrabber = docView.ComponentView as RecordingBox;
+            RecordingBox.screengrabber.Record?.();
+        });
+        Doc.UserDoc().workspaceRecordingState = media_state.Recording;
+    }
+
     /**
      * This method changes the menu depending on whether or not we are in playback mode
      * @param value  RecordingBox rootdoc
      */
     @undoBatch
     @action
-    public static toggleWorkspaceRecPlayback(value: Doc) {
+    public static replayWorkspace(value: Doc) {
         Doc.UserDoc().currentRecording = value;
-        console.log(value);
         value.overlayX = 70;
         value.overlayY = 590;
-        if (!Doc.UserDoc().isAddRecToDocMode) {
-            Doc.UserDoc().isRecPlayback = true;
-            Doc.UserDoc().isWorkspaceRecPlaying = true;
-            Doc.AddToMyOverlay(value);
-            DocumentManager.Instance.AddViewRenderedCb(value, docView => {
-                Doc.UserDoc().currentRecording = docView.rootDoc;
-                SelectionManager.SelectSchemaViewDoc(value);
-            });
-        } else {
-            let recordingIndex = DocListCast(Doc.UserDoc().workspaceRecordings).indexOf(value);
-            DragManager.StartDropdownDrag([document.createElement('div')], new DragManager.DocumentDragData([value]), 1, 1, recordingIndex);
-        }
+        Doc.AddToMyOverlay(value);
+        DocumentManager.Instance.AddViewRenderedCb(value, docView => {
+            Doc.UserDoc().currentRecording = docView.rootDoc;
+            SelectionManager.SelectSchemaViewDoc(value);
+            RecordingBox.resumeWorkspaceReplaying(value);
+        });
+        // let recordingIndex = DocListCast(Doc.UserDoc().workspaceRecordings).indexOf(value);
+        // DragManager.StartDropdownDrag([document.createElement('div')], new DragManager.DocumentDragData([value]), 1, 1, recordingIndex);
     }
 
     /**
@@ -140,62 +131,55 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @undoBatch
     @action
     public static addRecToWorkspace(value: RecordingBox) {
-        console.log('adding rec to doc');
-        console.log(value);
         let ffView = Array.from(DocumentManager.Instance.DocumentViews).find(view => view.ComponentView instanceof CollectionFreeFormView);
         (ffView?.ComponentView as CollectionFreeFormView).props.addDocument?.(value.rootDoc);
-        let recordingIndex = DocListCast(Doc.UserDoc().workspaceRecordings).indexOf(value.rootDoc);
-        console.log(recordingIndex);
-        Cast(Doc.UserDoc().workspaceRecordings, listSpec(Doc), null)?.splice(recordingIndex, 1);
-        Doc.UserDoc().isAddRecToDocMode = false;
+        Doc.RemoveDocFromList(Doc.UserDoc(), 'workspaceRecordings', value.rootDoc);
         Doc.RemFromMyOverlay(value.rootDoc);
         Doc.UserDoc().currentRecording = undefined;
-        Doc.UserDoc().isRecPlayback = false;
-        Doc.UserDoc().isAddRecToDocMode = false;
-        Doc.UserDoc().isWorkspaceRecPlaying = false;
-        Doc.UserDoc().isWorkspaceRecPaused = false;
+        Doc.UserDoc().workspaceReplayingState = undefined;
+        Doc.UserDoc().workspaceRecordingState = undefined;
     }
 
     @undoBatch
     @action
-    public static playWorkspaceRec(value: VideoBox) {
-        value.Play();
-        Doc.UserDoc().isWorkspaceRecPlaying = false;
-        Doc.UserDoc().isWorkspaceRecPaused = true;
+    public static resumeWorkspaceReplaying(doc: Doc) {
+        const docView = DocumentManager.Instance.getDocumentView(doc);
+        const videoBox = docView?.ComponentView as VideoBox;
+        if (videoBox) {
+            videoBox.Play();
+            Doc.UserDoc().workspaceReplayingState = media_state.Playing;
+        }
     }
 
     @undoBatch
     @action
-    public static pauseWorkspaceRec(value: VideoBox) {
-        value.Pause();
-        Doc.UserDoc().isWorkspaceRecPlaying = true;
-        Doc.UserDoc().isWorkspaceRecPaused = false;
+    public static pauseWorkspaceReplaying(doc: Doc) {
+        const docView = DocumentManager.Instance.getDocumentView(doc);
+        const videoBox = docView?.ComponentView as VideoBox;
+        if (videoBox) {
+            videoBox.Pause();
+        }
+        Doc.UserDoc().workspaceReplayingState = media_state.Paused;
     }
 
     @undoBatch
     @action
-    public static closeWorkspaceRec(value: VideoBox) {
-        value.Pause();
-        Doc.RemFromMyOverlay(value.rootDoc);
+    public static stopWorkspaceReplaying(value: Doc) {
+        Doc.RemFromMyOverlay(value);
         Doc.UserDoc().currentRecording = undefined;
-        Doc.UserDoc().isRecPlayback = false;
-        Doc.UserDoc().isWorkspaceRecPlaying = false;
-        Doc.UserDoc().isWorkspaceRecPaused = false;
-        Doc.RemFromMyOverlay(value.rootDoc);
+        Doc.UserDoc().workspaceReplayingState = undefined;
+        Doc.UserDoc().workspaceRecordingState = undefined;
+        Doc.RemFromMyOverlay(value);
     }
 
     @undoBatch
     @action
-    public static removeWorkspaceRec(value: VideoBox) {
-        let recordingIndex = DocListCast(Doc.UserDoc().workspaceRecordings).indexOf(value.rootDoc);
-        Cast(Doc.UserDoc().workspaceRecordings, listSpec(Doc), null)?.splice(recordingIndex, 1);
-        Doc.UserDoc().isAddRecToDocMode = false;
-        Doc.RemFromMyOverlay(value.rootDoc);
+    public static removeWorkspaceReplaying(value: Doc) {
+        Doc.RemoveDocFromList(Doc.UserDoc(), 'workspaceRecordings', value);
+        Doc.RemFromMyOverlay(value);
         Doc.UserDoc().currentRecording = undefined;
-        Doc.UserDoc().isRecPlayback = false;
-        Doc.UserDoc().isAddRecToDocMode = false;
-        Doc.UserDoc().isWorkspaceRecPlaying = false;
-        Doc.UserDoc().isWorkspaceRecPaused = false;
+        Doc.UserDoc().workspaceReplayingState = undefined;
+        Doc.UserDoc().workspaceRecordingState = undefined;
     }
 
     Record: undefined | (() => void);
@@ -225,47 +209,44 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
     static screengrabber: RecordingBox | undefined;
 }
 
-ScriptingGlobals.add(function toggleRecording(_readOnly_: boolean) {
-    RecordingBox.toggleWorkspaceRecording(_readOnly_);
-});
-ScriptingGlobals.add(function toggleRecPlayback(value: Doc) {
-    RecordingBox.toggleWorkspaceRecPlayback(value);
+ScriptingGlobals.add(function startWorkspaceRecording() {
+    RecordingBox.WorkspaceStartRecording();
 });
-ScriptingGlobals.add(function addRectoWorkspace(value: RecordingBox) {
-    RecordingBox.addRecToWorkspace(value);
+ScriptingGlobals.add(function stopWorkspaceRecording() {
+    RecordingBox.WorkspaceStopRecording();
 });
 
-ScriptingGlobals.add(function playWorkspaceRec(value: VideoBox) {
-    RecordingBox.playWorkspaceRec(value);
+ScriptingGlobals.add(function stopWorkspaceReplaying(value: Doc) {
+    RecordingBox.stopWorkspaceReplaying(value);
 });
-ScriptingGlobals.add(function pauseWorkspaceRec(value: VideoBox) {
-    RecordingBox.pauseWorkspaceRec(value);
-});
-ScriptingGlobals.add(function closeWorkspaceRec(value: VideoBox) {
-    RecordingBox.closeWorkspaceRec(value);
-});
-ScriptingGlobals.add(function removeWorkspaceRec(value: VideoBox) {
-    RecordingBox.removeWorkspaceRec(value);
+ScriptingGlobals.add(function removeWorkspaceReplaying(value: Doc) {
+    RecordingBox.removeWorkspaceReplaying(value);
 });
 
+ScriptingGlobals.add(function getCurrentRecording() {
+    return Doc.UserDoc().currentRecording;
+});
 ScriptingGlobals.add(function getWorkspaceRecordings() {
-    return Doc.UserDoc().workspaceRecordings;
+    return new List<any>(['Record Workspace', ...DocListCast(Doc.UserDoc().workspaceRecordings)]);
 });
-ScriptingGlobals.add(function getIsRecording() {
-    return Doc.UserDoc().isRecording;
+ScriptingGlobals.add(function isWorkspaceRecording() {
+    return Doc.UserDoc().workspaceRecordingState === media_state.Recording;
 });
-ScriptingGlobals.add(function getIsRecPlayback() {
-    return Doc.UserDoc().isRecPlayback;
+ScriptingGlobals.add(function isWorkspaceReplaying() {
+    return Doc.UserDoc().workspaceReplayingState;
 });
-ScriptingGlobals.add(function getCurrentRecording() {
-    return Doc.UserDoc().currentRecording;
+ScriptingGlobals.add(function replayWorkspace(value: Doc | string, _readOnly_: boolean) {
+    if (_readOnly_) return DocCast(Doc.UserDoc().currentRecording) ?? 'Record Workspace';
+    if (typeof value === 'string') RecordingBox.WorkspaceStartRecording();
+    else RecordingBox.replayWorkspace(value);
 });
-ScriptingGlobals.add(function getIsWorkspaceRecPlaying() {
-    return Doc.UserDoc().isWorkspaceRecPlaying;
+ScriptingGlobals.add(function pauseWorkspaceReplaying(value: Doc, _readOnly_: boolean) {
+    RecordingBox.pauseWorkspaceReplaying(value);
 });
-ScriptingGlobals.add(function getIsWorkspaceRecPaused() {
-    return Doc.UserDoc().isWorkspaceRecPaused;
+ScriptingGlobals.add(function resumeWorkspaceReplaying(value: Doc, _readOnly_: boolean) {
+    RecordingBox.resumeWorkspaceReplaying(value);
 });
+
 ScriptingGlobals.add(function renderDropdown() {
     if (!Doc.UserDoc().workspaceRecordings || DocListCast(Doc.UserDoc().workspaceRecordings).length === 0) {
         return true;
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index fa19caae1..4ebc93165 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -219,6 +219,9 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         // }
         return null;
     }
+    Record = () => !this._screenCapture && this.toggleRecording();
+    Pause = () => this._screenCapture && this.toggleRecording();
+
     toggleRecording = async () => {
         if (!this._screenCapture) {
             this._audioRec = new MediaRecorder(await navigator.mediaDevices.getUserMedia({ audio: true }));
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 5b097e639..55d94406a 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -1,13 +1,14 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button, IconButton, isDark, Size, Toggle, Type } from 'browndash-components';
+import { Button, IconButton, isDark, Size, Type } from 'browndash-components';
 import { action, computed, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { FaBug, FaCamera } from 'react-icons/fa';
+import { FaBug } from 'react-icons/fa';
 import { Doc, DocListCast } from '../../../fields/Doc';
 import { AclAdmin, DashVersion } from '../../../fields/DocSymbols';
 import { StrCast } from '../../../fields/Types';
 import { GetEffectiveAcl } from '../../../fields/util';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils';
 import { CurrentUserUtils } from '../../util/CurrentUserUtils';
 import { DocumentManager } from '../../util/DocumentManager';
 import { PingManager } from '../../util/PingManager';
@@ -15,12 +16,14 @@ import { ReportManager } from '../../util/reportManager/ReportManager';
 import { ServerStats } from '../../util/ServerStats';
 import { SettingsManager } from '../../util/SettingsManager';
 import { SharingManager } from '../../util/SharingManager';
-import { UndoManager } from '../../util/UndoManager';
+import { Transform } from '../../util/Transform';
 import { CollectionDockingView } from '../collections/CollectionDockingView';
+import { CollectionLinearView } from '../collections/collectionLinear';
 import { ContextMenu } from '../ContextMenu';
 import { DashboardView } from '../DashboardView';
 import { Colors } from '../global/globalEnums';
-import { DocumentView } from '../nodes/DocumentView';
+import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
+import { DefaultStyleProvider } from '../StyleProvider';
 import './TopBar.scss';
 
 /**
@@ -91,6 +94,43 @@ export class TopBar extends React.Component {
         );
     }
 
+    @computed get dashMenuButtons() {
+        const selDoc = Doc.MyTopBarBtns;
+        return !(selDoc instanceof Doc) ? null : (
+            <div className="collectionMenu-contMenuButtons" style={{ height: '100%' }}>
+                <CollectionLinearView
+                    Document={selDoc}
+                    DataDoc={undefined}
+                    fieldKey="data"
+                    dropAction="embed"
+                    setHeight={returnFalse}
+                    styleProvider={DefaultStyleProvider}
+                    rootSelected={returnTrue}
+                    bringToFront={emptyFunction}
+                    select={emptyFunction}
+                    isContentActive={returnTrue}
+                    isAnyChildContentActive={returnFalse}
+                    isSelected={returnFalse}
+                    docViewPath={returnEmptyDoclist}
+                    moveDocument={returnFalse}
+                    addDocument={returnFalse}
+                    addDocTab={DocumentViewInternal.addDocTabFunc}
+                    pinToPres={emptyFunction}
+                    removeDocument={returnFalse}
+                    ScreenToLocalTransform={Transform.Identity}
+                    PanelWidth={() => 200}
+                    PanelHeight={() => 30}
+                    renderDepth={0}
+                    focus={emptyFunction}
+                    whenChildContentsActiveChanged={emptyFunction}
+                    childFilters={returnEmptyFilter}
+                    childFiltersByRanges={returnEmptyFilter}
+                    searchFilterDocs={returnEmptyDoclist}
+                />
+            </div>
+        );
+    }
+
     /**
      * Returns the center of the topbar
      * This part of the topbar contains everything related to the current dashboard including:
@@ -118,23 +158,10 @@ export class TopBar extends React.Component {
                     style={{ fontWeight: 700, fontSize: '1rem' }}
                     onClick={(e: React.MouseEvent) => {
                         const dashView = Doc.ActiveDashboard && DocumentManager.Instance.getDocumentView(Doc.ActiveDashboard);
-                        ContextMenu.Instance.addItem({ description: 'Open Dashboard View', event: this.navigateToHome, icon: 'edit' });
                         dashView?.showContextMenu(e.clientX + 20, e.clientY + 30);
                     }}
                 />
-                {!Doc.noviceMode && (
-                    <IconButton
-                        tooltip="Work on a copy of the dashboard layout"
-                        size={Size.SMALL}
-                        color={this.color}
-                        onClick={async () => {
-                            const batch = UndoManager.StartBatch('snapshot');
-                            await DashboardView.snapshotDashboard();
-                            batch.end();
-                        }}
-                        icon={<FaCamera />}
-                    />
-                )}
+                {!Doc.noviceMode && this.dashMenuButtons}
             </div>
         ) : null;
     }
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index ad0e548ed..2a1dfbfc7 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -220,6 +220,9 @@ export class Doc extends RefField {
     public static get MyContextMenuBtns() {
         return DocCast(Doc.UserDoc().myContextMenuBtns);
     }
+    public static get MyTopBarBtns() {
+        return DocCast(Doc.UserDoc().myTopBarBtns);
+    }
     public static get MyRecentlyClosed() {
         return DocCast(Doc.UserDoc().myRecentlyClosed);
     }
-- 
cgit v1.2.3-70-g09d2


From b7295d4747d8eb01d8213c97442cc4916aec77c4 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 31 Aug 2023 12:16:09 -0400
Subject: from last again

---
 src/client/util/DragManager.ts                     |  4 +---
 .../collectionLinear/CollectionLinearView.tsx      |  4 ++--
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |  2 +-
 .../views/nodes/RecordingBox/RecordingBox.tsx      |  3 ---
 src/client/views/nodes/trails/PresBox.tsx          |  2 --
 src/client/views/nodes/trails/PresElementBox.tsx   | 24 +++++++---------------
 6 files changed, 11 insertions(+), 28 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 4ab033555..6d6eaebec 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -4,17 +4,15 @@ import { Doc, Field, Opt, StrListCast } from '../../fields/Doc';
 import { List } from '../../fields/List';
 import { PrefetchProxy } from '../../fields/Proxy';
 import { ScriptField } from '../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, ScriptCast, StrCast } from '../../fields/Types';
+import { ScriptCast, StrCast } from '../../fields/Types';
 import { emptyFunction, Utils } from '../../Utils';
 import { Docs, DocUtils } from '../documents/Documents';
 import * as globalCssVariables from '../views/global/globalCssVariables.scss';
-import { Colors } from '../views/global/globalEnums';
 import { DocumentView } from '../views/nodes/DocumentView';
 import { ScriptingGlobals } from './ScriptingGlobals';
 import { SelectionManager } from './SelectionManager';
 import { SnappingManager } from './SnappingManager';
 import { UndoManager } from './UndoManager';
-import { listSpec } from '../../fields/Schema';
 
 export type dropActionType = 'embed' | 'copy' | 'move' | 'add' | 'same' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call dropPropertiesToRemove
 
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 0cf7d4411..47a98bdd1 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -229,8 +229,8 @@ export class CollectionLinearView extends CollectionSubView() {
                 <div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu} style={{ minHeight: this.dimension(), pointerEvents: 'all' }}>
                     {
                         <>
-                            {!this.layoutDoc.linearView_Expandable ? null :menuOpener}
-                            {!this.layoutDoc.linearView_IsOpen && !this.layoutDoc.linearView_alwaysOpen ? null : (
+                            {!this.layoutDoc.linearView_Expandable ? null : menuOpener}
+                            {!this.layoutDoc.linearView_IsOpen ? null : (
                                 <div
                                     className="collectionLinearView-content"
                                     style={{
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 8ef9cd792..9bc9f2b52 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -234,7 +234,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
 
         return (
             <Dropdown
-                selectedVal={text ?? 'Record Workspace'}
+                selectedVal={text}
                 setSelectedVal={undoable(val => script.script.run({ this: this.layoutDoc, self: this.rootDoc, val }), `dropdown select ${this.label}`)}
                 color={SettingsManager.userColor}
                 background={SettingsManager.userVariantColor}
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index 1b2f63fa2..e9060a605 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -138,7 +138,6 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
         Doc.UserDoc().workspaceRecordingState = undefined;
     }
 
-    @undoBatch
     @action
     public static resumeWorkspaceReplaying(doc: Doc) {
         const docView = DocumentManager.Instance.getDocumentView(doc);
@@ -149,7 +148,6 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
     }
 
-    @undoBatch
     @action
     public static pauseWorkspaceReplaying(doc: Doc) {
         const docView = DocumentManager.Instance.getDocumentView(doc);
@@ -160,7 +158,6 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
         Doc.UserDoc().workspaceReplayingState = media_state.Paused;
     }
 
-    @undoBatch
     @action
     public static stopWorkspaceReplaying(value: Doc) {
         Doc.RemFromMyOverlay(value);
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 02132b7b3..7bb1b80a3 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -303,7 +303,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     // Called when the user activates 'next' - to move to the next part of the pres. trail
     @action
     next = () => {
-        console.log('next');
         const progressiveReveal = (first: boolean) => {
             const presIndexed = Cast(this.activeItem?.presentation_indexed, 'number', null);
             if (presIndexed !== undefined) {
@@ -386,7 +385,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     //it'll also execute the necessary actions if presentation is playing.
     @undoBatch
     public gotoDocument = action((index: number, from?: Doc, group?: boolean, finished?: () => void) => {
-        console.log('going to document');
         Doc.UnBrushAllDocs();
         if (index >= 0 && index < this.childDocs.length) {
             this.rootDoc._itemIndex = index;
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 121eb87d5..82ed9e8d5 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -6,7 +6,7 @@ import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
 import { Height, Width } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { List } from '../../../../fields/List';
-import { Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
+import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { CollectionViewType } from '../../../documents/DocumentTypes';
@@ -313,17 +313,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @undoBatch
     @action
     revertToPreviouslySaved = (presTargetDoc: Doc, activeItem: Doc) => {
-        console.log('reverting');
-        // console.log("reverting to previosly saved\n");
-        // console.log(this.prevTarget);
-        console.log('Content continuously updating');
         const target = DocCast(activeItem.annotationOn) ?? activeItem;
-        console.log(presTargetDoc.pinData);
         PresBox.reversePin(activeItem, target);
-        // console.log("new target\n");
-        // console.log(target);
-        // PresBox.pinDocView(activeItem, { pinData: PresBox.pinDataTypes(this.prevTarget) }, this.prevTarget ? this.prevTarget : target);
-        //figure out how to make it go back to the previously saved one
     };
 
     /**
@@ -426,7 +417,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @action
     lfg = (e: React.MouseEvent) => {
         e.stopPropagation();
-        console.log('lfg called');
         // TODO: fix this bug
         const { toggleChildrenRun } = this.rootDoc;
         TreeView.ToggleChildrenRun.get(this.rootDoc)?.();
@@ -445,12 +435,12 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     @computed get presButtons() {
-        const presBox = this.presBox; //presBox
-        const presBoxColor: string = StrCast(presBox?._backgroundColor);
-        const presColorBool: boolean = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
-        const targetDoc: Doc = this.targetDoc;
-        const activeItem: Doc = this.rootDoc;
-        const hasChildren: boolean = Cast(this.rootDoc?.hasChildren, 'boolean') || false;
+        const presBox = this.presBox;
+        const presBoxColor = StrCast(presBox?._backgroundColor);
+        const presColorBool = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
+        const targetDoc = this.targetDoc;
+        const activeItem = this.rootDoc;
+        const hasChildren = BoolCast(this.rootDoc?.hasChildren);
 
         const items: JSX.Element[] = [];
 
-- 
cgit v1.2.3-70-g09d2


From e847215964bc02cf402eace30d6c9e19f5f0f0cf Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 7 Sep 2023 01:28:27 -0400
Subject: truncate link descriptions over link lines.  Fix schema view issues
 with multiline inputs.  fix '#' field assignment for title bar of docs. 
 fixed dashFieldView to read fields from texstbox by fixing FindDocByTitle to
 not match undefind.  Don't end link_description input on Enter to allow
 multiline inputs.

---
 src/client/DocServer.ts                            |  8 +++--
 src/client/views/EditableView.scss                 |  4 ++-
 src/client/views/EditableView.tsx                  | 40 +++++++++++-----------
 src/client/views/PropertiesView.tsx                |  8 ++---
 .../CollectionFreeFormLinkView.tsx                 |  4 +--
 .../collectionLinear/CollectionLinearView.tsx      | 10 +++---
 src/client/views/linking/LinkMenuItem.tsx          |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  5 ++-
 .../views/nodes/formattedText/RichTextRules.ts     |  2 +-
 9 files changed, 42 insertions(+), 41 deletions(-)

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

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 5fdea131b..fc8a9f3d6 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -32,9 +32,11 @@ export namespace DocServer {
     let _cache: { [id: string]: RefField | Promise<Opt<RefField>> } = {};
 
     export function FindDocByTitle(title: string) {
-        const foundDocId = Array.from(Object.keys(_cache))
-            .filter(key => _cache[key] instanceof Doc)
-            .find(key => (_cache[key] as Doc).title === title);
+        const foundDocId =
+            title &&
+            Array.from(Object.keys(_cache))
+                .filter(key => _cache[key] instanceof Doc)
+                .find(key => (_cache[key] as Doc).title === title);
 
         return foundDocId ? (_cache[foundDocId] as Doc) : undefined;
     }
diff --git a/src/client/views/EditableView.scss b/src/client/views/EditableView.scss
index 0955ba8ff..f7c03caf9 100644
--- a/src/client/views/EditableView.scss
+++ b/src/client/views/EditableView.scss
@@ -3,7 +3,8 @@
     overflow-wrap: break-word;
     word-wrap: break-word;
     hyphens: auto;
-    overflow: hidden;
+    overflow: auto;
+    height: 100%;
     min-width: 20;
     text-overflow: ellipsis;
 }
@@ -11,6 +12,7 @@
 .editableView-container-editing-oneLine {
     width: 100%;
     height: max-content;
+    overflow: hidden;
 
     span {
         p {
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 147921596..ca4ffaf3a 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -231,7 +231,7 @@ export class EditableView extends React.Component<EditableProps> {
                     onChange: this.props.autosuggestProps.onChange,
                 }}
             />
-        ) : (
+        ) : this.props.oneLine !== false && this.props.GetValue()?.toString().indexOf('\n') === -1 ? (
             <input
                 className="editableView-input"
                 ref={r => (this._inputref = r)}
@@ -247,31 +247,31 @@ export class EditableView extends React.Component<EditableProps> {
                 onClick={this.stopPropagation}
                 onPointerUp={this.stopPropagation}
             />
+        ) : (
+            <textarea
+                className="editableView-input"
+                ref={r => (this._inputref = r)}
+                style={{ display: this.props.display, overflow: 'auto', fontSize: this.props.fontSize, minHeight: `min(100%, ${(this.props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20, background: this.props.background }}
+                placeholder={this.props.placeholder}
+                onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)}
+                defaultValue={this.props.GetValue()}
+                autoFocus={true}
+                onChange={this.onChange}
+                onKeyDown={this.onKeyDown}
+                onKeyPress={this.stopPropagation}
+                onPointerDown={this.stopPropagation}
+                onClick={this.stopPropagation}
+                onPointerUp={this.stopPropagation}
+            />
         );
-        // ) : (
-        //     <textarea
-        //         className="editableView-input"
-        //         ref={r => (this._inputref = r)}
-        //         style={{ display: this.props.display, overflow: 'auto', fontSize: this.props.fontSize, minHeight: `min(100%, ${(this.props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20, background: this.props.background }}
-        //         placeholder={this.props.placeholder}
-        //         onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)}
-        //         defaultValue={this.props.GetValue()}
-        //         autoFocus={true}
-        //         onChange={this.onChange}
-        //         onKeyDown={this.onKeyDown}
-        //         onKeyPress={this.stopPropagation}
-        //         onPointerDown={this.stopPropagation}
-        //         onClick={this.stopPropagation}
-        //         onPointerUp={this.stopPropagation}
-        //     />
-        // );
     }
 
     render() {
-        if (this._editing && this.props.GetValue() !== undefined) {
+        const gval = this.props.GetValue()?.replace(/\n/g, '\\r\\n');
+        if (this._editing && gval !== undefined) {
             return this.props.sizeToContent ? (
                 <div style={{ display: 'grid', minWidth: 100 }}>
-                    <div style={{ display: 'inline-block', position: 'relative', height: 0, width: '100%', overflow: 'hidden' }}>{this.props.GetValue()}</div>
+                    <div style={{ display: 'inline-block', position: 'relative', height: 0, width: '100%', overflow: 'hidden' }}>{gval}</div>
                     {this.renderEditor()}
                 </div>
             ) : (
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 6f1461fea..72ff906f6 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -1342,10 +1342,10 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     };
 
     onDescriptionKey = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
-        if (e.key === 'Enter') {
-            this.setDescripValue(this.description);
-            document.getElementById('link_description_input')?.blur();
-        }
+        // if (e.key === 'Enter') {
+        //     this.setDescripValue(this.description);
+        //     document.getElementById('link_description_input')?.blur();
+        // }
     };
 
     onSelectOutRelationship = () => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index f940eb4bf..ea225e52f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -256,7 +256,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         const linkColorList = Doc.UserDoc().link_ColorList as List<string>;
         const linkRelationshipSizes = Doc.UserDoc().link_relationshipSizes as List<number>;
         const currRelationshipIndex = linkRelationshipList.indexOf(linkRelationship);
-        const linkDescription = Field.toString(link.link_description as any as Field);
+        const linkDescription = Field.toString(link.link_description as any as Field).split('\n')[0];
 
         const linkSize = Doc.noviceMode || currRelationshipIndex === -1 || currRelationshipIndex >= linkRelationshipSizes.length ? -1 : linkRelationshipSizes[currRelationshipIndex];
 
@@ -306,7 +306,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
                 {textX === undefined || !linkDescription ? null : (
                     <text filter={`url(#${link[Id] + 'background'})`} className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown}>
                         <tspan>&nbsp;</tspan>
-                        <tspan dy="2">{linkDescription}</tspan>
+                        <tspan dy="2">{linkDescription.substring(0, 50) + (linkDescription.length > 50 ? '...' : '')}</tspan>
                         <tspan dy="2">&nbsp;</tspan>
                     </text>
                 )}
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 47a98bdd1..faf7501c4 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -1,5 +1,6 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
+import { Toggle, ToggleType, Type } from 'browndash-components';
 import { action, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -7,23 +8,20 @@ import { Doc, Opt } from '../../../../fields/Doc';
 import { Height, Width } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnTrue, StopEvent, Utils } from '../../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnTrue, Utils } from '../../../../Utils';
 import { CollectionViewType } from '../../../documents/DocumentTypes';
+import { BranchingTrailManager } from '../../../util/BranchingTrailManager';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager, dropActionType } from '../../../util/DragManager';
+import { SettingsManager } from '../../../util/SettingsManager';
 import { Transform } from '../../../util/Transform';
 import { DocumentLinksButton } from '../../nodes/DocumentLinksButton';
 import { DocumentView } from '../../nodes/DocumentView';
 import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup';
-import { StyleProp } from '../../StyleProvider';
 import { UndoStack } from '../../UndoStack';
 import { CollectionStackedTimeline } from '../CollectionStackedTimeline';
 import { CollectionSubView } from '../CollectionSubView';
 import './CollectionLinearView.scss';
-import { Button, Toggle, ToggleType, Type } from 'browndash-components';
-import { Colors } from '../../global/globalEnums';
-import { BranchingTrailManager } from '../../../util/BranchingTrailManager';
-import { SettingsManager } from '../../../util/SettingsManager';
 
 /**
  * CollectionLinearView is the class for rendering the horizontal collection
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 0a9d7543b..bf2a4e1a9 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -197,7 +197,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                                     {this.props.linkDoc.linksToAnnotation && Cast(this.props.destinationDoc.data, WebField)?.url.href === this.props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)}
                                 </p>
                             </div>
-                            {!this.props.linkDoc.link_description ? null : <p className="linkMenu-description">{StrCast(this.props.linkDoc.link_description)}</p>}
+                            {!this.props.linkDoc.link_description ? null : <p className="linkMenu-description">{StrCast(this.props.linkDoc.link_description).split('\n')[0].substring(0, 50)}</p>}
                         </div>
 
                         <div className="linkMenu-item-buttons">
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index dcb2d9d51..ae9977d7a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1149,14 +1149,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     display={'block'}
                     fontSize={10}
                     GetValue={() => {
-                        this.props.select(false);
                         return showTitle.split(';').length === 1 ? showTitle + '=' + Field.toString(targetDoc[showTitle.split(';')[0]] as any as Field) : '#' + showTitle;
                     }}
                     SetValue={undoBatch((input: string) => {
                         if (input?.startsWith('#')) {
-                            if (this.props.layout_showTitle) {
+                            if (this.rootDoc.layout_showTitle) {
                                 this.rootDoc._layout_showTitle = input?.substring(1) ? input.substring(1) : undefined;
-                            } else {
+                            } else if (!this.props.layout_showTitle) {
                                 Doc.UserDoc().layout_showTitle = input?.substring(1) ? input.substring(1) : 'author_date';
                             }
                         } else {
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 8bafc2cef..5d92f63ef 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -279,7 +279,7 @@ export class RichTextRules {
                     const num = value.match(/^[0-9.]$/);
                     this.Document[DocData][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value;
                 }
-                const target = DocServer.FindDocByTitle(docTitle);
+                const target = DocServer.FindDocByTitle(docTitle) ?? this.Document;
                 if (target) {
                     const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target[Id], hideKey: false });
                     return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true);
-- 
cgit v1.2.3-70-g09d2

