From 9031708067ee16d7b7e6b2689f45ee54ea5f1e4a Mon Sep 17 00:00:00 2001
From: dinhanhtruong <70963346+dinhanhtruong@users.noreply.github.com>
Date: Tue, 17 Aug 2021 14:14:26 -0400
Subject: Added empty tabs message

todo: create buttons for recent tabs when all tabs in a dashboard are closed
---
 src/client/views/MainView.tsx | 1 +
 1 file changed, 1 insertion(+)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index b0b8d7f41..ba8f2a4c8 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -448,6 +448,7 @@ export class MainView extends React.Component {
             </div>;
     }
 
+
     expandFlyout = action((button: Doc) => {
         this._flyoutWidth = (this._flyoutWidth || 250);
         this._sidebarContent.proto = button.target as any;
-- 
cgit v1.2.3-70-g09d2


From 1395ce3fbf73bf5df5ed4add744c333cfc8008c9 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 15 Sep 2021 15:43:24 -0400
Subject: another windows fix for contextmenus on web pages

---
 src/client/views/MainView.tsx     |  1 +
 src/client/views/nodes/WebBox.tsx | 30 ++++++++++++++++++------------
 2 files changed, 19 insertions(+), 12 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 5bda9f6bf..d854f118f 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -214,6 +214,7 @@ export class MainView extends React.Component {
                 }
             }
         }, false);
+        document.oncontextmenu = () => false;
     }
 
     initAuthenticationRouters = async () => {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index fe5070fa4..cce71329d 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -224,15 +224,20 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
 
         // bcz: hack - iframe grabs all events which messes up how we handle contextMenus.  So this super naively simulates the event stack to get the specific menu items and the doc view menu items.
-        if (e.button === 2 && ContextMenu.Instance.getItems().length) {
-            ContextMenu.Instance.displayMenu(e.pageX, e.pageY);
-        }
-        else {
-            ContextMenu.Instance.closeMenu();
-            if (e.button === 2) {
+        if (e.button === 2) {
+            e.preventDefault();
+            e.stopPropagation();
+            const where = this.props.ScreenToLocalTransform().inverse().transformPoint(e.pageX, e.pageY);
+            if (ContextMenu.Instance.getItems().length) {
+                ContextMenu.Instance.displayMenu(where[0], where[1]);
+            }
+            else {
+                ContextMenu.Instance.closeMenu();
                 this.specificContextMenu(e);
-                this.props.docViewPath().lastElement().docView?.onContextMenu(undefined, e.pageX, e.pageY);
+                this.props.docViewPath().lastElement().docView?.onContextMenu(undefined, where[0], where[1]);
             }
+        } else {
+            ContextMenu.Instance.closeMenu();
         }
     }
 
@@ -511,11 +516,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @computed get annotationLayer() {
         TraceMobx();
-        return <div className="webBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}>
-            {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
-                <Annotation {...this.props} fieldKey={this.annotationKey} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />)
-            }
-        </div>;
+        return !this.inlineTextAnnotations.length ? (null) :
+            <div className="webBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}>
+                {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
+                    <Annotation {...this.props} fieldKey={this.annotationKey} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />)
+                }
+            </div>;
 
     }
     @observable _showSidebar = false;
-- 
cgit v1.2.3-70-g09d2


From eb50e46332c2f0b48c7cb165d55245aab317ef08 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 21 Sep 2021 19:43:32 -0400
Subject: fixed up some darkScheme css, enabled comic mode for developers,
 enabled opening up fields on dashboards in myDocuments

---
 src/client/documents/Documents.ts                  |  2 +-
 src/client/util/CurrentUserUtils.ts                | 30 ++++++++++------
 src/client/util/SelectionManager.ts                | 14 ++------
 src/client/util/SettingsManager.tsx                | 19 +++++-----
 src/client/views/ContextMenu.scss                  |  1 -
 src/client/views/DocumentDecorations.scss          | 28 +++++++++++----
 src/client/views/DocumentDecorations.tsx           | 42 +++++++++++++---------
 src/client/views/MainView.scss                     | 12 +++----
 src/client/views/MainView.tsx                      | 14 ++++----
 src/client/views/StyleProvider.tsx                 |  5 +--
 .../views/collections/CollectionSchemaView.tsx     |  2 +-
 src/client/views/collections/TreeView.tsx          |  9 +++--
 .../CollectionFreeFormLayoutEngines.tsx            |  3 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 ++-
 .../collectionSchema/CollectionSchemaView.tsx      |  7 ++--
 src/client/views/nodes/DocumentView.tsx            |  6 ++--
 src/client/views/nodes/button/FontIconBox.tsx      |  2 --
 .../views/nodes/formattedText/DashDocView.tsx      |  3 +-
 src/mobile/MobileInterface.tsx                     |  4 +--
 19 files changed, 118 insertions(+), 89 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index fafbc4a7d..206f65bfd 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -862,7 +862,7 @@ export namespace Docs {
         export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
             const tabs = TreeDocument(documents, { title: "On-Screen Tabs", childDontRegisterViews: true, freezeChildren: "remove|add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", _fitWidth: true, system: true, isFolder: true });
             const all = TreeDocument([], { title: "Off-Screen Tabs", childDontRegisterViews: true, freezeChildren: "add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", system: true, isFolder: true });
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List([tabs, all]), { freezeChildren: "remove|add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id);
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List([tabs, all]), { freezeChildren: "remove|add", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id);
         }
 
         export function DirectoryImportDocument(options: DocumentOptions = {}) {
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index d5dc9e2be..568a9ddbd 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -20,6 +20,7 @@ import { Networking } from "../Network";
 import { CollectionDockingView } from "../views/collections/CollectionDockingView";
 import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView";
 import { CollectionView, CollectionViewType } from "../views/collections/CollectionView";
+import { TreeView } from "../views/collections/TreeView";
 import { Colors } from "../views/global/globalEnums";
 import { MainView } from "../views/MainView";
 import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox";
@@ -38,7 +39,6 @@ import { ColorScheme } from "./SettingsManager";
 import { SharingManager } from "./SharingManager";
 import { SnappingManager } from "./SnappingManager";
 import { UndoManager } from "./UndoManager";
-import { TreeView } from "../views/collections/TreeView";
 
 interface Button {
     title?: string;
@@ -816,16 +816,19 @@ export class CurrentUserUtils {
                 _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", treeViewType: "fileSystem", isFolder: true, system: true,
                 explainer: "This is your collection of dashboards. A dashboard represents the tab configuration of your workspace. To manage documents as folders, go to the Files."
             }));
-            // const toggleTheme = ScriptField.MakeScript(`Doc.UserDoc().darkScheme = !Doc.UserDoc().darkScheme`);
-            // const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
-            // const snapshotDashboard = ScriptField.MakeScript(`snapshotDashboard()`);
+            const toggleDarkTheme = ScriptField.MakeScript(`this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`);
+            const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
+            const snapshotDashboard = ScriptField.MakeScript(`snapshotDashboard()`);
             const shareDashboard = ScriptField.MakeScript(`shareDashboard(self)`);
             const removeDashboard = ScriptField.MakeScript('removeDashboard(self)');
-            (doc.myDashboards as any as Doc).childContextMenuScripts = new List<ScriptField>([newDashboard!, shareDashboard!, removeDashboard!]);
-            (doc.myDashboards as any as Doc).childContextMenuLabels = new List<string>(["Create New Dashboard", "Share Dashboard", "Remove Dashboard"]);
-            (doc.myDashboards as any as Doc).childContextMenuIcons = new List<string>(["plus", "user-friends", "times"]);
-            // (doc.myDashboards as any as Doc).childContextMenuScripts = new List<ScriptField>([newDashboard!, toggleTheme!, toggleComic!, snapshotDashboard!, shareDashboard!, removeDashboard!]);
-            // (doc.myDashboards as any as Doc).childContextMenuLabels = new List<string>(["Create New Dashboard", "Toggle Theme Colors", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard"]);
+            const developerFilter = ScriptField.MakeFunction('!IsNoviceMode()');
+            // (doc.myDashboards as any as Doc).childContextMenuScripts = new List<ScriptField>([newDashboard!, shareDashboard!, removeDashboard!]);
+            // (doc.myDashboards as any as Doc).childContextMenuLabels = new List<string>(["Create New Dashboard", "Share Dashboard", "Remove Dashboard"]);
+            // (doc.myDashboards as any as Doc).childContextMenuIcons = new List<string>(["plus", "user-friends", "times"]);
+            (doc.myDashboards as any as Doc).childContextMenuScripts = new List<ScriptField>([newDashboard!, toggleDarkTheme!, toggleComic!, snapshotDashboard!, shareDashboard!, removeDashboard!]);
+            (doc.myDashboards as any as Doc).childContextMenuLabels = new List<string>(["Create New Dashboard", "Toggle Dark Theme", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard"]);
+            (doc.myDashboards as any as Doc).childContextMenuIcons = new List<string>(["plus", "chalkboard", "tv", "camera", "users", "times"]);
+            (doc.myDashboards as any as Doc).childContextMenuFilters = new List<ScriptField>([undefined as any, developerFilter, developerFilter, developerFilter, undefined as any, undefined as any]);
         }
         return doc.myDashboards as any as Doc;
     }
@@ -1302,7 +1305,6 @@ export class CurrentUserUtils {
             }, { fireImmediately: true });
         // Document properties on load
         doc.system = true;
-        doc.darkScheme = ColorScheme.Dark;
         doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode;
         doc.title = Doc.CurrentUserEmail;
         doc._raiseWhenDragged = true;
@@ -1321,7 +1323,6 @@ export class CurrentUserUtils {
         doc.fontColor = StrCast(doc.fontColor, "black");
         doc.fontHighlight = StrCast(doc.fontHighlight, "");
         doc.defaultAclPrivate = BoolCast(doc.defaultAclPrivate, false);
-        doc.activeCollectionBackground = StrCast(doc.activeCollectionBackground, "white");
         doc.activeCollectionNestedBackground = Cast(doc.activeCollectionNestedBackground, "string", null);
         doc.noviceMode = BoolCast(doc.noviceMode, true);
         doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); //
@@ -1358,6 +1359,10 @@ export class CurrentUserUtils {
         // });
         setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500);
         doc.fieldInfos = await Docs.setupFieldInfos();
+        if (doc.activeDashboard instanceof Doc) {
+            // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
+            doc.activeDashboard.colorScheme = doc.activeDashboard.colorScheme === ColorScheme.Light ? undefined : doc.activeDashboard.colorScheme;
+        }
         return doc;
     }
 
@@ -1637,4 +1642,7 @@ Scripting.addGlobal(function makeTopLevelFolder() {
     const folder = Docs.Create.TreeDocument([], { title: "Untitled folder", _stayInCollection: true, isFolder: true });
     TreeView._editTitleOnLoad = { id: folder[Id], parent: undefined };
     return Doc.AddDocToList(Doc.UserDoc().myFilesystem as Doc, "data", folder);
+});
+Scripting.addGlobal(function toggleComicMode() {
+    Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic";
 });
\ No newline at end of file
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index bac13373c..e507ec3bf 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -2,7 +2,6 @@ import { action, observable, ObservableMap } from "mobx";
 import { computedFn } from "mobx-utils";
 import { Doc, Opt } from "../../fields/Doc";
 import { DocumentType } from "../documents/DocumentTypes";
-import { CollectionSchemaView } from "../views/collections/collectionSchema/CollectionSchemaView";
 import { CollectionViewType } from "../views/collections/CollectionView";
 import { DocumentView } from "../views/nodes/DocumentView";
 
@@ -13,12 +12,10 @@ export namespace SelectionManager {
         @observable IsDragging: boolean = false;
         SelectedViews: ObservableMap<DocumentView, Doc> = new ObservableMap();
         @observable SelectedSchemaDocument: Doc | undefined;
-        @observable SelectedSchemaCollection: CollectionSchemaView | undefined;
 
         @action
-        SelectSchemaView(collectionView: Opt<CollectionSchemaView>, doc: Opt<Doc>) {
+        SelectSchemaViewDoc(doc: Opt<Doc>) {
             manager.SelectedSchemaDocument = doc;
-            manager.SelectedSchemaCollection = collectionView;
         }
         @action
         SelectView(docView: DocumentView, ctrlPressed: boolean): void {
@@ -33,7 +30,6 @@ export namespace SelectionManager {
             } else if (!ctrlPressed && Array.from(manager.SelectedViews.entries()).length > 1) {
                 Array.from(manager.SelectedViews.keys()).map(dv => dv !== docView && dv.props.whenChildContentsActiveChanged(false));
                 manager.SelectedSchemaDocument = undefined;
-                manager.SelectedSchemaCollection = undefined;
                 manager.SelectedViews.clear();
                 manager.SelectedViews.set(docView, docView.rootDoc);
             }
@@ -47,7 +43,6 @@ export namespace SelectionManager {
         }
         @action
         DeselectAll(): void {
-            manager.SelectedSchemaCollection = undefined;
             manager.SelectedSchemaDocument = undefined;
             Array.from(manager.SelectedViews.keys()).map(dv => dv.props.whenChildContentsActiveChanged(false));
             manager.SelectedViews.clear();
@@ -62,8 +57,8 @@ export namespace SelectionManager {
     export function SelectView(docView: DocumentView, ctrlPressed: boolean): void {
         manager.SelectView(docView, ctrlPressed);
     }
-    export function SelectSchemaView(colSchema: Opt<CollectionSchemaView>, document: Opt<Doc>): void {
-        manager.SelectSchemaView(colSchema, document);
+    export function SelectSchemaViewDoc(document: Opt<Doc>): void {
+        manager.SelectSchemaViewDoc(document);
     }
 
     const IsSelectedCache = computedFn(function isSelected(doc: DocumentView) {  // wrapping get() in a computedFn only generates mobx() invalidations when the return value of the function for the specific get parameters has changed
@@ -96,9 +91,6 @@ export namespace SelectionManager {
     export function SelectedSchemaDoc(): Doc | undefined {
         return manager.SelectedSchemaDocument;
     }
-    export function SelectedSchemaCollection(): CollectionSchemaView | undefined {
-        return manager.SelectedSchemaCollection;
-    }
     export function Docs(): Doc[] {
         return Array.from(manager.SelectedViews.values()).filter(doc => doc?._viewType !== CollectionViewType.Docking);
     }
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index bd91db779..6a26dfdc7 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -19,9 +19,9 @@ export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
 
 export enum ColorScheme {
-    Dark = "Dark",
-    Light = "Light",
-    System = "Match System"
+    Dark = "-Dark",
+    Light = "-Light",
+    System = "-MatchSystem"
 }
 
 @observer
@@ -38,7 +38,7 @@ export class SettingsManager extends React.Component<{}> {
     @observable activeTab = "Accounts";
 
     @computed get backgroundColor() { return Doc.UserDoc().activeCollectionBackground; }
-    @computed get colorScheme() { return Doc.UserDoc().colorScheme; }
+    @computed get colorScheme() { return CurrentUserUtils.ActiveDashboard.colorScheme; }
 
     constructor(props: {}) {
         super(props);
@@ -81,16 +81,16 @@ export class SettingsManager extends React.Component<{}> {
         const scheme: ColorScheme = (e.currentTarget as any).value;
         switch (scheme) {
             case ColorScheme.Light:
-                Doc.UserDoc().colorScheme = ColorScheme.Light;
+                CurrentUserUtils.ActiveDashboard.colorScheme = undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
                 addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "#d3d3d3 !important" });
                 break;
             case ColorScheme.Dark:
-                Doc.UserDoc().colorScheme = ColorScheme.Dark;
+                CurrentUserUtils.ActiveDashboard.colorScheme = ColorScheme.Dark;
                 addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "black !important" });
                 break;
             case ColorScheme.System: default:
                 window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
-                    Doc.UserDoc().colorScheme = e.matches ? ColorScheme.Dark : ColorScheme.Light;
+                    CurrentUserUtils.ActiveDashboard.colorScheme = e.matches ? ColorScheme.Dark : undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
                 });
                 break;
         }
@@ -119,6 +119,7 @@ export class SettingsManager extends React.Component<{}> {
         </div>;
 
         const colorSchemes = [ColorScheme.Light, ColorScheme.Dark, ColorScheme.System];
+        const schemeMap = ["Light", "Dark", "Match system"];
 
         return <div className="colors-content">
             <div className="preferences-color">
@@ -132,8 +133,8 @@ export class SettingsManager extends React.Component<{}> {
             <div className="preferences-colorScheme">
                 <div className="preferences-color-text">Color Scheme</div>
                 <div className="preferences-color-controls">
-                    <select className="scheme-select" onChange={this.changeColorScheme} defaultValue={StrCast(Doc.UserDoc().colorScheme)}>
-                        {colorSchemes.map(scheme => <option key={scheme} value={scheme}> {scheme} </option>)}
+                    <select className="scheme-select" onChange={this.changeColorScheme} defaultValue={StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme)}>
+                        {colorSchemes.map((scheme, i) => <option key={scheme} value={scheme}> {schemeMap[i]} </option>)}
                     </select>
                 </div>
             </div>
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index 47ae0424b..ea24dbf6d 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -8,7 +8,6 @@
     flex-direction: column;
     background: whitesmoke;
     border-radius: 3px;
-    border: solid $light-gray 1px;
 }
 
 // .contextMenu-item:first-child {
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index a9f50f81b..d8ad47ecb 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -2,10 +2,14 @@
 
 $linkGap: 3px;
 
+.documentDecorations-Dark,
 .documentDecorations {
   position: absolute;
   z-index: 2000;
 }
+.documentDecorations-Dark {
+    background: dimgray;
+}
 .documentDecorations-container {
     z-index: $docDecorations-zindex;
     position: absolute;
@@ -50,12 +54,17 @@ $linkGap: 3px;
         pointer-events: auto;
         background: $medium-gray;
         opacity: 0.1;
-
         &:hover {
             opacity: 1;
         }
     }
 
+    .documentDecorations-resizer-Dark
+    {
+        background: $light-gray;
+        opacity: 0.2;
+    }
+
     .documentDecorations-topLeftResizer,
     .documentDecorations-leftResizer,
     .documentDecorations-bottomLeftResizer {
@@ -221,6 +230,7 @@ $linkGap: 3px;
     cursor: ns-resize;
   }
 
+    .documentDecorations-title-Dark,
     .documentDecorations-title {
         opacity: 1;
         grid-column-start: 2;
@@ -233,14 +243,22 @@ $linkGap: 3px;
         height: 22px;
         position: absolute;
 
-        .documentDecorations-titleSpan {
+        .documentDecorations-titleSpan, 
+        .documentDecorations-titleSpan-Dark  {
             width: 100%;
             border-radius: 8px;
-            background: #ffffffcf;
+            background: #ffffffa0;
             position: absolute;
             display: inline-block;
             cursor: move;
         }
+        .documentDecorations-titleSpan-Dark {
+            background: hsla(0, 0%, 0%, 0.412);
+        }
+    }
+    .documentDecorations-title-Dark {
+        color: white;
+        background: black;
     }
 
   .documentDecorations-contextMenu {
@@ -439,10 +457,6 @@ $linkGap: 3px;
   }
 }
 
-.documentDecorations-darkScheme {
-  background: dimgray;
-}
-
 #template-list {
   position: absolute;
   top: 25px;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index d785d5419..e9a54d6a5 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -8,7 +8,7 @@ import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, Field, HeightSym, WidthSy
 import { Document } from '../../fields/documentSchemas';
 import { InkField } from "../../fields/InkField";
 import { ScriptField } from '../../fields/ScriptField';
-import { Cast, NumCast } from "../../fields/Types";
+import { Cast, NumCast, StrCast } from "../../fields/Types";
 import { GetEffectiveAcl } from '../../fields/util';
 import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../Utils";
 import { Docs } from "../documents/Documents";
@@ -27,6 +27,8 @@ import { LightboxView } from './LightboxView';
 import { DocumentView } from "./nodes/DocumentView";
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import React = require("react");
+import { dark } from '@material-ui/core/styles/createPalette';
+import { color } from 'd3-color';
 
 @observer
 export class DocumentDecorations extends React.Component<{ PanelWidth: number, PanelHeight: number, boundsLeft: number, boundsTop: number }, { value: string }> {
@@ -241,7 +243,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         InkStrokeProperties.Instance?._lock && SelectionManager.Views().filter(dv => dv.rootDoc.type === DocumentType.INK)
             .forEach(dv => fixedAspect = Doc.NativeAspect(dv.rootDoc));
 
-        if (fixedAspect && (this._resizeHdlId === "documentDecorations-bottomRightResizer" || this._resizeHdlId === "documentDecorations-topLeftResizer")) { // need to generalize for bl and tr drag handles
+        const resizeHdl = this._resizeHdlId.split(" ")[0];
+        if (fixedAspect && (resizeHdl === "documentDecorations-bottomRightResizer" || resizeHdl === "documentDecorations-topLeftResizer")) { // need to generalize for bl and tr drag handles
             const project = (p: number[], a: number[], b: number[]) => {
                 const atob = [b[0] - a[0], b[1] - a[1]];
                 const atop = [p[0] - a[0], p[1] - a[1]];
@@ -264,7 +267,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         this._snapY = thisPt.y;
         let dragBottom = false, dragRight = false, dragBotRight = false;
         let dX = 0, dY = 0, dW = 0, dH = 0;
-        switch (this._resizeHdlId) {
+        switch (this._resizeHdlId.split(" ")[0]) {
             case "": break;
             case "documentDecorations-topLeftResizer":
                 dX = -1;
@@ -437,11 +440,17 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                 </div>
             </Tooltip>);
 
+        const colorScheme = StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme);
         const titleArea = this._edtingTitle ?
-            <input ref={this._keyinput} className="documentDecorations-title" style={{ width: `calc(100% - ${seldoc?.props.hideResizeHandles ? 0 : 20}px` }} type="text" name="dynbox" autoComplete="on" value={this._accumulatedTitle}
-                onBlur={e => this.titleBlur()} onChange={action(e => this._accumulatedTitle = e.target.value)} onKeyPress={this.titleEntered} /> :
+            <input ref={this._keyinput} className={`documentDecorations-title${colorScheme}`}
+                style={{ width: `calc(100% - ${seldoc?.props.hideResizeHandles ? 0 : 20}px` }}
+                type="text" name="dynbox" autoComplete="on"
+                value={this._accumulatedTitle}
+                onBlur={e => this.titleBlur()}
+                onChange={action(e => this._accumulatedTitle = e.target.value)}
+                onKeyPress={this.titleEntered} /> :
             <div className="documentDecorations-title" style={{ width: `calc(100% - ${seldoc?.props.hideResizeHandles ? 0 : 20}px` }} key="title" onPointerDown={this.onTitleDown} >
-                <span className="documentDecorations-titleSpan">{`${this.selectionTitle}`}</span>
+                <span className={`documentDecorations-titleSpan${colorScheme}`}>{`${this.selectionTitle}`}</span>
             </div>;
 
         let inMainMenuPanel = false;
@@ -457,8 +466,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight));
 
         const useRotation = seldoc.rootDoc.type === DocumentType.INK;
+        const resizerScheme = colorScheme ? "documentDecorations-resizer" + colorScheme : "";
 
-        return (<div className="documentDecorations" style={{ background: CurrentUserUtils.ActiveDashboard?.darkScheme ? "dimgray" : "" }} >
+        return (<div className={`documentDecorations${colorScheme}`}>
             <div className="documentDecorations-background" style={{
                 width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
                 height: (bounds.b - bounds.y + this._resizeBorderWidth) + "px",
@@ -481,15 +491,15 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                         <>
                             {SelectionManager.Views().length !== 1 || hideTitle ? (null) :
                                 topBtn("iconify", `window-${seldoc.finalLayoutKey.includes("icon") ? "restore" : "minimize"}`, undefined, this.onIconifyClick, `${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`)}
-                            <div key="tl" className="documentDecorations-topLeftResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
-                            <div key="t" className="documentDecorations-topResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
-                            <div key="tr" className="documentDecorations-topRightResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
-                            <div key="l" className="documentDecorations-leftResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
-                            <div key="c" className="documentDecorations-centerCont"></div>
-                            <div key="r" className="documentDecorations-rightResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
-                            <div key="bl" className="documentDecorations-bottomLeftResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
-                            <div key="b" className="documentDecorations-bottomResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
-                            <div key="br" className="documentDecorations-bottomRightResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                            <div key="tl" className={`documentDecorations-topLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                            <div key="t" className={`documentDecorations-topResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                            <div key="tr" className={`documentDecorations-topRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                            <div key="l" className={`documentDecorations-leftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                            <div key="c" className={`documentDecorations-centerCont ${resizerScheme}`}></div>
+                            <div key="r" className={`documentDecorations-rightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                            <div key="bl" className={`documentDecorations-bottomLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                            <div key="b" className={`documentDecorations-bottomResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                            <div key="br" className={`documentDecorations-bottomRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
 
                             {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) :
                                 topBtn("selector", "arrow-alt-circle-up", undefined, this.onSelectorClick, "tap to select containing document")}
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 4f871f5ec..7fa841002 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -41,7 +41,7 @@
 }
 
 .mainView-container,
-.mainView-container-dark {
+.mainView-container-Dark {
     width: 100%;
     height: 100%;
     position: absolute;
@@ -65,7 +65,7 @@
     }
 }
 
-.mainView-container-dark {
+.mainView-container-Dark {
     color: $light-gray;
 
     .lm_goldenlayout {
@@ -91,7 +91,7 @@
 
     .contextMenu-cont,
     .contextMenu-item {
-        background: $medium-gray;
+        background: $dark-gray;
     }
 
     .contextMenu-item:hover {
@@ -144,7 +144,7 @@
     }
 }
 
-.mainView-innerContent, .mainView-innerContent-dark {
+.mainView-innerContent, .mainView-innerContent-Dark {
     display: contents;
     flex-direction: row;
     position: relative;
@@ -175,7 +175,7 @@
 .mainView-libraryHandle {
     background-color: $light-gray;
 }
-.mainView-innerContent-dark 
+.mainView-innerContent-Dark 
 {
     .propertiesView {
         background-color: #252525;
@@ -198,7 +198,7 @@
         background: #353535;
     }
 }
-.mainView-container-dark {
+.mainView-container-Dark {
     .contextMenu-cont {
         background: $medium-gray;
         color: $white;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index d854f118f..c99ba447c 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -26,7 +26,7 @@ import { HistoryUtil } from '../util/History';
 import { Hypothesis } from '../util/HypothesisUtils';
 import { Scripting } from '../util/Scripting';
 import { SelectionManager } from '../util/SelectionManager';
-import { SettingsManager } from '../util/SettingsManager';
+import { SettingsManager, ColorScheme } from '../util/SettingsManager';
 import { SharingManager } from '../util/SharingManager';
 import { SnappingManager } from '../util/SnappingManager';
 import { Transform } from '../util/Transform';
@@ -88,7 +88,7 @@ export class MainView extends React.Component {
     @computed private get topOfMainDocContent() { return this.topOfMainDoc + this.dashboardTabHeight; }
     @computed private get leftScreenOffsetOfMainDocView() { return this.leftMenuWidth() - 2; }
     @computed private get userDoc() { return Doc.UserDoc(); }
-    @computed private get darkScheme() { return BoolCast(CurrentUserUtils.ActiveDashboard?.darkScheme); }
+    @computed private get colorScheme() { return StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme); }
     @computed private get mainContainer() { return this.userDoc ? CurrentUserUtils.ActiveDashboard : CurrentUserUtils.GuestDashboard; }
     @computed public get mainFreeform(): Opt<Doc> { return (docs => (docs?.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); }
 
@@ -430,10 +430,10 @@ export class MainView extends React.Component {
         const transform = this._leftMenuFlyoutWidth ? 'translate(-28px, 0px)' : undefined;
         return <>
             {this.leftMenuPanel}
-            <div key="inner" className={`mainView-innerContent${this.darkScheme ? "-dark" : ""}`}>
+            <div key="inner" className={`mainView-innerContent${this.colorScheme}`}>
                 {this.flyout}
                 <div className="mainView-libraryHandle" style={{ display: !this._leftMenuFlyoutWidth ? "none" : undefined }} onPointerDown={this.onFlyoutPointerDown} >
-                    <FontAwesomeIcon icon="chevron-left" color={this.darkScheme ? "white" : "black"} style={{ opacity: "50%" }} size="sm" />
+                    <FontAwesomeIcon icon="chevron-left" color={this.colorScheme === ColorScheme.Dark ? "white" : "black"} style={{ opacity: "50%" }} size="sm" />
                 </div>
                 <div className="mainView-innerContainer" style={{ width: `calc(100% - ${width}px)`, transform: transform }}>
                     <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} />
@@ -441,7 +441,7 @@ export class MainView extends React.Component {
                     {this.dockingContent}
 
                     <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this._leftMenuFlyoutWidth ? 0 : this.propertiesWidth() - 1 }}>
-                        <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? "chevron-left" : "chevron-right"} color={this.darkScheme ? Colors.WHITE : Colors.BLACK} size="sm" />
+                        <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? "chevron-left" : "chevron-right"} color={this.colorScheme === ColorScheme.Dark ? Colors.WHITE : Colors.BLACK} size="sm" />
                     </div>
                     <div className="properties-container">
                         {this.propertiesWidth() < 10 ? (null) : <PropertiesView styleProvider={DefaultStyleProvider} width={this.propertiesWidth()} height={this.propertiesHeight()} />}
@@ -459,7 +459,7 @@ export class MainView extends React.Component {
                     this._dashUIHeight = r.getBoundingClientRect().height;
                 })).observe(r);
             }} style={{
-                color: this.darkScheme ? "rgb(205,205,205)" : "black",
+                color: this.colorScheme === ColorScheme.Dark ? "rgb(205,205,205)" : "black",
                 height: `calc(100% - ${this.topOfDashUI}px)`,
                 width: "100%",
             }} >
@@ -601,7 +601,7 @@ export class MainView extends React.Component {
     }
 
     render() {
-        return (<div className={"mainView-container" + (this.darkScheme ? "-dark" : "")}
+        return (<div className={`mainView-container${this.colorScheme}`}
             onScroll={() => ((ele) => ele.scrollTop = ele.scrollLeft = 0)(document.getElementById("root")!)}
             ref={r => {
                 r && new _global.ResizeObserver(action(() => { this._windowWidth = r.getBoundingClientRect().width; this._windowHeight = r.getBoundingClientRect().height; })).observe(r);
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 3c88a4830..1eb7a222e 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -22,6 +22,7 @@ import "./StyleProvider.scss";
 import React = require("react");
 import Color = require('color');
 import { lightOrDark } from '../../Utils';
+import { ColorScheme } from '../util/SettingsManager';
 
 export enum StyleLayers {
     Background = "background"
@@ -49,7 +50,7 @@ export enum StyleProp {
     FontSize = "fontSize",                // size of text font
 }
 
-function darkScheme() { return BoolCast(CurrentUserUtils.ActiveDashboard?.darkScheme); }
+function darkScheme() { return CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark; }
 
 function toggleBackground(doc: Doc) {
     UndoManager.RunInBatch(() => runInAction(() => {
@@ -143,7 +144,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                                     doc.annotationOn ? "#00000015" : // faint interior for collections on PDFs, images, etc
                                         StrCast((props?.renderDepth || 0) > 0 ?
                                             Doc.UserDoc().activeCollectionNestedBackground :
-                                            Doc.UserDoc().activeCollectionBackground)));
+                                            Doc.UserDoc().activeCollectionBackground ?? (darkScheme() ? Colors.BLACK : Colors.WHITE))));
                     break;
                 //if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)";
                 default: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.WHITE); break;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 6a22acae8..3ea190a98 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -353,7 +353,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     @action setFocused = (doc: Doc) => this._focusedTable = doc;
 
     @action setPreviewDoc = (doc: Opt<Doc>) => {
-        SelectionManager.SelectSchemaView(this, doc);
+        SelectionManager.SelectSchemaViewDoc(doc);
         this._previewDoc = doc;
     }
 
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 97de097e0..a3da0e0e4 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -481,13 +481,16 @@ export class TreeView extends React.Component<TreeViewProps> {
     }
 
     @computed get validExpandViewTypes() {
-        if (this.doc.viewType === CollectionViewType.Docking) return [this.fieldKey];
+        if (this.props.treeView.dashboardMode && Doc.UserDoc().noviceMode) {
+            return [this.doc.viewType === CollectionViewType.Docking ? this.fieldKey : "layout"];
+        }
         const annos = () => DocListCast(this.doc[this.fieldKey + "-annotations"]).length ? "annotations" : "";
         const links = () => DocListCast(this.doc.links).length ? "links" : "";
-        const data = () => this.childDocs && !this.props.treeView.dashboardMode ? this.fieldKey : "";
+        const data = () => this.childDocs ? this.fieldKey : "";
         const aliases = () => this.props.treeView.dashboardMode ? "" : "aliases";
         const fields = () => Doc.UserDoc().noviceMode ? "" : "fields";
-        return [data(), "layout", ...(this.props.treeView.fileSysMode ? [aliases(), links(), annos()] : []), fields()].filter(m => m);
+        const layout = this.doc.viewType === CollectionViewType.Docking ? [] : ["layout"];
+        return [data(), ...layout, ...(this.props.treeView.fileSysMode ? [aliases(), links(), annos()] : []), fields()].filter(m => m);
     }
     @action
     expandNextviewType = () => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 37444a9dc..b3c57d33a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -7,6 +7,7 @@ import { Cast, NumCast, StrCast } from "../../../../fields/Types";
 import { aggregateBounds } from "../../../../Utils";
 import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
 import React = require("react");
+import { ColorScheme } from "../../../util/SettingsManager";
 
 export interface ViewDefBounds {
     type: string;
@@ -361,7 +362,7 @@ export function computeTimelineLayout(
         groupNames.push({ type: "text", text: toLabel(Math.ceil(maxTime)), x: Math.ceil(maxTime - minTime) * scaling, y: 0, height: fontHeight, fontSize, payload: undefined });
     }
 
-    const divider = { type: "div", color: CurrentUserUtils.ActiveDashboard?.darkScheme ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
+    const divider = { type: "div", color: CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
     return normalizeResults(panelDim, fontHeight, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider]);
 
     function layoutDocsAtTime(keyDocs: Doc[], key: number) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 94cf1c5a6..0b12f6c21 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -51,6 +51,7 @@ import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import Color = require("color");
+import { ColorScheme } from "../../../util/SettingsManager";
 
 export const panZoomSchema = createSchema({
     _panX: "number",
@@ -1419,6 +1420,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const renderGridSpace = gridSpace * this.zoomScaling();
         const w = this.props.PanelWidth() + 2 * renderGridSpace;
         const h = this.props.PanelHeight() + 2 * renderGridSpace;
+        const strokeStyle = CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "rgba(255,255,255,0.5)" : "rgba(0, 0,0,0.5)";
         return <canvas className="collectionFreeFormView-grid" width={w} height={h} style={{ transform: `translate(${shiftX}px, ${shiftY}px)` }}
             ref={(el) => {
                 const ctx = el?.getContext('2d');
@@ -1429,7 +1431,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]);
                     ctx.clearRect(0, 0, w, h);
                     if (ctx) {
-                        ctx.strokeStyle = "rgba(0, 0, 0, 0.5)";
+                        ctx.strokeStyle = strokeStyle;
                         ctx.beginPath();
                         for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) {
                             ctx.moveTo(x, Cy - h);
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index dfe99ffc8..12493ecc1 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -337,8 +337,9 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
             {this.renderTypes(this._col)}
             {this.renderColors(this._col)}
             <div className="collectionSchema-headerMenu-group">
-                <button onClick={() => { this.deleteColumn(this._col.heading); }}
-                >Hide Column</button>
+                <button onClick={() => { this.deleteColumn(this._col.heading); }}>
+                    Hide Column
+                </button>
             </div>
         </div>;
     }
@@ -353,7 +354,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     @action setFocused = (doc: Doc) => this._focusedTable = doc;
 
     @action setPreviewDoc = (doc: Opt<Doc>) => {
-        SelectionManager.SelectSchemaView(this, doc);
+        SelectionManager.SelectSchemaViewDoc(doc);
         this._previewDoc = doc;
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3e15ed661..5d0b91b91 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -25,7 +25,6 @@ import { InteractionUtils } from '../../util/InteractionUtils';
 import { LinkManager } from '../../util/LinkManager';
 import { Scripting } from '../../util/Scripting';
 import { SelectionManager } from "../../util/SelectionManager";
-import { ColorScheme } from "../../util/SettingsManager";
 import { SharingManager } from '../../util/SharingManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from "../../util/Transform";
@@ -50,6 +49,7 @@ import { ScriptingBox } from "./ScriptingBox";
 import { PresBox } from './trails/PresBox';
 import React = require("react");
 import { IconProp } from "@fortawesome/fontawesome-svg-core";
+import { ColorScheme } from "../../util/SettingsManager";
 const { Howl } = require('howler');
 
 interface Window {
@@ -1061,9 +1061,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     render() {
         TraceMobx();
         const highlightIndex = this.props.LayoutTemplateString ? (Doc.IsHighlighted(this.props.Document) ? 6 : 0) : Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
-        const highlightColor = (Doc.UserDoc().colorScheme === ColorScheme.Dark ?
-            ["transparent", "#65350c", "#65350c", "yellow", "magenta", "cyan", "orange"] :
-            ["transparent", "#4476F7", "#4476F7", "yellow", "magenta", "cyan", "orange"])[highlightIndex];
+        const highlightColor = ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "yellow", "magenta", "cyan", "orange"][highlightIndex];
         const highlightStyle = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"][highlightIndex];
         const excludeTypes = !this.props.treeViewDoc ? [DocumentType.FONTICON, DocumentType.INK] : [DocumentType.FONTICON];
         let highlighting = !this.props.disableDocBrushing && highlightIndex && !excludeTypes.includes(this.layoutDoc.type as any) && this.layoutDoc._viewType !== CollectionViewType.Linear;
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index adcac0aef..520034c3c 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -14,7 +14,6 @@ import { WebField } from '../../../../fields/URLField';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { Scripting } from "../../../util/Scripting";
 import { SelectionManager } from '../../../util/SelectionManager';
-import { ColorScheme } from '../../../util/SettingsManager';
 import { UndoManager, undoBatch } from '../../../util/UndoManager';
 import { CollectionViewType } from '../../collections/CollectionView';
 import { ContextMenu } from '../../ContextMenu';
@@ -463,7 +462,6 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
     render() {
         const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
         const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
-        const dark: boolean = Doc.UserDoc().colorScheme === ColorScheme.Dark;
 
         const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
             <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor, position: "absolute" }}>
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index e519de1c5..149836e93 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -12,6 +12,7 @@ import { FormattedTextBox } from "./FormattedTextBox";
 import React = require("react");
 import * as ReactDOM from 'react-dom';
 import { observer } from "mobx-react";
+import { ColorScheme } from "../../../util/SettingsManager";
 
 export class DashDocView {
     _fieldWrapper: HTMLSpanElement; // container for label and value
@@ -20,7 +21,7 @@ export class DashDocView {
         this._fieldWrapper = document.createElement("span");
         this._fieldWrapper.style.position = "relative";
         this._fieldWrapper.style.textIndent = "0";
-        this._fieldWrapper.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (CurrentUserUtils.ActiveDashboard.darkScheme ? "dimGray" : "lightGray"));
+        this._fieldWrapper.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "dimGray" : "lightGray"));
         this._fieldWrapper.style.width = node.attrs.width;
         this._fieldWrapper.style.height = node.attrs.height;
         this._fieldWrapper.style.display = node.attrs.hidden ? "none" : "inline-block";
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 404e828ea..652804126 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -17,7 +17,7 @@ import { Docs, DocumentOptions, DocUtils } from '../client/documents/Documents';
 import { DocumentType } from "../client/documents/DocumentTypes";
 import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
 import { Scripting } from '../client/util/Scripting';
-import { SettingsManager } from '../client/util/SettingsManager';
+import { SettingsManager, ColorScheme } from '../client/util/SettingsManager';
 import { Transform } from '../client/util/Transform';
 import { UndoManager } from "../client/util/UndoManager";
 import { TabDocView } from '../client/views/collections/TabDocView';
@@ -403,7 +403,7 @@ export class MobileInterface extends React.Component {
         const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
         const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, "row");
 
-        const toggleTheme = ScriptField.MakeScript(`self.darkScheme = !self.darkScheme`);
+        const toggleTheme = ScriptField.MakeScript(`self.colorScheme = self.colorScheme ? undefined: ${ColorScheme.Dark}}`);
         const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
         const cloneDashboard = ScriptField.MakeScript(`cloneDashboard()`);
         dashboardDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!, toggleComic!, cloneDashboard!]);
-- 
cgit v1.2.3-70-g09d2


From 12ae56962397a786397158af9e442a955883d16f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 22 Sep 2021 03:30:58 -0400
Subject: fixed runtime bug inside toggleBold().  removed print statements.

---
 src/client/documents/Documents.ts                  |   3 +-
 src/client/util/CurrentUserUtils.ts                |   3 +-
 src/client/views/MainView.tsx                      |   1 -
 .../collectionLinear/CollectionLinearView.tsx      |   1 -
 src/client/views/nodes/button/FontIconBox.tsx      | 126 +++++---------
 .../views/nodes/formattedText/FormattedTextBox.tsx |  11 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     | 186 +++++++++++----------
 src/client/views/nodes/formattedText/marks_rts.ts  |   6 +-
 8 files changed, 153 insertions(+), 184 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 206f65bfd..8ac647b99 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -141,8 +141,8 @@ export class DocumentOptions {
     _columnWidth?: number;
     _columnsHideIfEmpty?: boolean; // whether stacking view column headings should be hidden
     _fontSize?: string;
-    _fontWeight?: number;
     _fontFamily?: string;
+    _fontWeight?: string;
     _pivotField?: string; // field key used to determine headings for sections in stacking, masonry, pivot views
     _curPage?: number; // current page of a PDF or other? paginated document
     _currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video)  value is in seconds
@@ -1188,7 +1188,6 @@ export namespace DocUtils {
                 description: ":" + StrCast(note.title),
                 event: undoBatch((args: { x: number, y: number }) => {
                     const textDoc = Docs.Create.TextDocument("", {
-                        _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize),
                         _width: 200, x, y, _autoHeight: note._autoHeight !== false,
                         title: StrCast(note.title) + "#" + (note.aliasCount = NumCast(note.aliasCount) + 1)
                     });
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 5683a8c21..297d4b241 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1535,8 +1535,7 @@ export class CurrentUserUtils {
     public static GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, maxHeight?: number, backgroundColor?: string) {
         const tbox = Docs.Create.TextDocument("", {
             _xMargin: noMargins ? 0 : undefined, _yMargin: noMargins ? 0 : undefined, annotationOn, docMaxAutoHeight: maxHeight, backgroundColor: backgroundColor,
-            _width: width || 200, _height: height || 100, x: x, y: y, _fitWidth: true, _autoHeight: true, _fontSize: StrCast(Doc.UserDoc().fontSize),
-            _fontFamily: StrCast(Doc.UserDoc().fontFamily), title
+            _width: width || 200, _height: height || 100, x: x, y: y, _fitWidth: true, _autoHeight: true, title
         });
         const template = Doc.UserDoc().defaultTextLayout;
         if (template instanceof Doc) {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index c99ba447c..fbd3fece2 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -471,7 +471,6 @@ export class MainView extends React.Component {
         this._leftMenuFlyoutWidth = (this._leftMenuFlyoutWidth || 250);
         this._sidebarContent.proto = button.target as any;
         this.LastButton = button;
-        console.log(button.title);
     });
 
     closeFlyout = action(() => {
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 7fe95fef0..18a715edf 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -109,7 +109,6 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
     }
 
     myContextMenu = (e: React.MouseEvent) => {
-        console.log("STOPPING");
         e.stopPropagation();
         e.preventDefault();
     }
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 520034c3c..6a782b105 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -113,7 +113,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
 
         // Script for checking the outcome of the toggle
         const checkScript: string = StrCast(this.rootDoc.script) + "(0, true)";
-        const checkResult: number = ScriptField.MakeScript(checkScript)?.script.run().result;
+        const checkResult: number = ScriptField.MakeScript(checkScript)?.script.run().result || 0;
 
 
         if (numBtnType === NumButtonType.Slider) {
@@ -158,7 +158,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
                 <div
                     className={`menuButton ${this.type} ${numBtnType}`}
                 >
-                    <div className={`button`} onClick={action((e) => setValue(checkResult - 1))}>
+                    <div className={`button`} onClick={action((e) => setValue(Number(checkResult) - 1))}>
                         <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={"minus"} />
                     </div>
                     <div
@@ -177,7 +177,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
                             onChange={action((e) => setValue(Number(e.target.value)))}
                         />
                     </div>
-                    <div className={`button`} onClick={action((e) => setValue(checkResult + 1))}>
+                    <div className={`button`} onClick={action((e) => setValue(Number(checkResult) + 1))}>
                         <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={"plus"} />
                     </div>
 
@@ -261,8 +261,8 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
             }
             noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking];
         } else if (script === 'setFont') {
-            const selected = SelectionManager.Docs().lastElement();
-            text = StrCast((selected?.type === DocumentType.RTF ? selected : Doc.UserDoc())._fontFamily);
+            const editorView = RichTextMenu.Instance?.TextView?.EditorView;
+            text = StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
             noviceList = ["Roboto", "Times New Roman", "Arial", "Georgia",
                 "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"];
         }
@@ -337,7 +337,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
         const colorBox = (func: (color: ColorState) => void) => <SketchPicker
             disableAlpha={false}
             onChange={func}
-            color={boolResult ? boolResult : "#FFFFFF"}
+            color={boolResult ?? "transparent"}
             presetColors={colorOptions} />;
         const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
             <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor, position: "absolute" }}>
@@ -360,7 +360,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
                 onClick={() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen}
                 onPointerDown={e => e.stopPropagation()}>
                 <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
-                <div className="colorButton-color" style={{ backgroundColor: boolResult ? boolResult : "#FFFFFF" }} />
+                <div className="colorButton-color" style={{ backgroundColor: boolResult ?? "transparent" }} />
                 {label}
                 {/* {dropdownCaret} */}
                 {this.rootDoc.dropDownOpen ?
@@ -387,7 +387,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
 
         // Button label
         const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
-            <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor, position: "absolute" }}>
+            <div className="fontIconBox-label" style={{ color, backgroundColor, position: "absolute" }}>
                 {this.label}
             </div>;
 
@@ -406,7 +406,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
         } else {
             return (
                 <div className={`menuButton ${this.type}`}
-                    style={{ opacity: 1, backgroundColor: backgroundColor, color: color }}>
+                    style={{ opacity: 1, backgroundColor, color }}>
                     <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
                     {label}
                 </div>
@@ -571,11 +571,7 @@ Scripting.addGlobal(function setView(view: string) {
 Scripting.addGlobal(function setBackgroundColor(color?: string, checkResult?: boolean) {
     const selected = SelectionManager.Docs().lastElement();
     if (checkResult) {
-        if (selected) {
-            return selected._backgroundColor;
-        } else {
-            return "#FFFFFF";
-        }
+        return selected?._backgroundColor ?? "transparent";
     }
     if (selected) selected._backgroundColor = color;
     Doc.UserDoc()._fontColor = color;
@@ -596,7 +592,7 @@ Scripting.addGlobal(function toggleOverlay(checkResult?: boolean) {
     const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
     if (checkResult && selected) {
         if (NumCast(selected.Document.z) >= 1) return Colors.MEDIUM_BLUE;
-        else return "transparent";
+        return "transparent";
     }
     selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log("[FontIconBox.tsx] toggleOverlay failed");
 });
@@ -617,16 +613,18 @@ Scripting.addGlobal(function toggleOverlay(checkResult?: boolean) {
 Scripting.addGlobal(function setFont(font: string, checkResult?: boolean) {
     SelectionManager.Docs().map(doc => doc._fontFamily = font);
     const editorView = RichTextMenu.Instance.TextView?.EditorView;
-    editorView?.state && RichTextMenu.Instance.setFontFamily(font, editorView);
-    Doc.UserDoc()._fontFamily = font;
-    return Doc.UserDoc()._fontFamily;
+    if (checkResult) {
+        return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
+    }
+    if (editorView) RichTextMenu.Instance.setFontFamily(font);
+    else Doc.UserDoc().fontFamily = font;
 });
 
 Scripting.addGlobal(function getActiveTextInfo(info: "family" | "size" | "color" | "highlight") {
     const editorView = RichTextMenu.Instance.TextView?.EditorView;
     const style = editorView?.state && RichTextMenu.Instance.getActiveFontStylesOnSelection();
     switch (info) {
-        case "family": return style?.activeColors[0];
+        case "family": return style?.activeFamilies[0];
         case "size": return style?.activeSizes[0];
         case "color": return style?.activeColors[0];
         case "highlight": return style?.activeHighlights[0];
@@ -643,7 +641,7 @@ Scripting.addGlobal(function setAlignment(align: "left" | "right" | "center", ch
             active = StrCast(Doc.UserDoc().textAlign);
         }
         if (active === align) return Colors.MEDIUM_BLUE;
-        else return "transparent";
+        return "transparent";
     }
     SelectionManager.Docs().map(doc => doc.textAlign = align);
     switch (align) {
@@ -667,38 +665,25 @@ Scripting.addGlobal(function setBulletList(mapStyle: "bullet" | "decimal", check
     if (checkResult) {
         const active = editorView?.state && RichTextMenu.Instance.getActiveListStyle();
         if (active === mapStyle) return Colors.MEDIUM_BLUE;
-        else return "transparent";
+        return "transparent";
     }
     if (editorView) {
         const active = editorView?.state && RichTextMenu.Instance.getActiveListStyle();
-        if (active === mapStyle) {
-            editorView?.state && RichTextMenu.Instance.changeListType(editorView.state.schema.nodes.ordered_list.create({ mapStyle: "" }));
-        } else {
-            editorView?.state && RichTextMenu.Instance.changeListType(editorView.state.schema.nodes.ordered_list.create({ mapStyle: mapStyle }));
-        }
+        editorView?.state && RichTextMenu.Instance.changeListType(
+            editorView.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? "" : mapStyle }));
     }
 });
 
 // toggle: Set overlay status of selected document
 Scripting.addGlobal(function setFontColor(color?: string, checkResult?: boolean) {
-    const selected = SelectionManager.Docs().lastElement();
     const editorView = RichTextMenu.Instance.TextView?.EditorView;
 
     if (checkResult) {
-        if (selected) {
-            return selected._fontColor;
-        } else {
-            return Doc.UserDoc()._fontColor;
-        }
+        return editorView ? RichTextMenu.Instance.fontColor : Doc.UserDoc().fontColor;
     }
 
-    if (selected) {
-        selected._fontColor = color;
-        if (color) {
-            editorView?.state && RichTextMenu.Instance.setColor(color, editorView, editorView?.dispatch);
-        }
-    }
-    Doc.UserDoc()._fontColor = color;
+    if (editorView) color && RichTextMenu.Instance.setColor(color, editorView, editorView?.dispatch);
+    else Doc.UserDoc()._fontColor = color;
 });
 
 // toggle: Set overlay status of selected document
@@ -707,11 +692,7 @@ Scripting.addGlobal(function setFontHighlight(color?: string, checkResult?: bool
     const editorView = RichTextMenu.Instance.TextView?.EditorView;
 
     if (checkResult) {
-        if (selected) {
-            return selected._fontHighlight;
-        } else {
-            return Doc.UserDoc()._fontHighlight;
-        }
+        return (selected ?? Doc.UserDoc())._fontHighlight;
     }
     if (selected) {
         selected._fontColor = color;
@@ -722,62 +703,43 @@ Scripting.addGlobal(function setFontHighlight(color?: string, checkResult?: bool
     Doc.UserDoc()._fontHighlight = color;
 });
 
-
-
 // toggle: Set overlay status of selected document
-Scripting.addGlobal(function setFontSize(size: string, checkResult?: boolean) {
+Scripting.addGlobal(function setFontSize(size: string | number, checkResult?: boolean) {
+    if (typeof size === "number") size = size.toString();
+    if (size && Number(size).toString() === size) size += "px";
+    const editorView = RichTextMenu.Instance.TextView?.EditorView;
     if (checkResult) {
-        const size: number = parseInt(StrCast(Doc.UserDoc()._fontSize), 10);
-        return size;
+        return (editorView ? RichTextMenu.Instance.fontSize : StrCast(Doc.UserDoc().fontSize, "10px")).replace("px", "");
     }
-    const editorView = RichTextMenu.Instance.TextView?.EditorView;
-    editorView?.state && RichTextMenu.Instance.setFontSize(Number(size), editorView);
-    Doc.UserDoc()._fontSize = size + "px";
+    if (editorView) RichTextMenu.Instance.setFontSize(size);
+    else Doc.UserDoc()._fontSize = size;
 });
 
 Scripting.addGlobal(function toggleBold(checkResult?: boolean) {
+    const editorView = RichTextMenu.Instance?.TextView?.EditorView;
     if (checkResult) {
-        const editorView = RichTextMenu.Instance.TextView?.EditorView;
-        if (editorView) {
-            const bold: boolean = editorView?.state && RichTextMenu.Instance.getBoldStatus();
-            if (bold) return Colors.MEDIUM_BLUE;
-            else return "transparent";
-        }
-        else return "transparent";
+        return (editorView ? RichTextMenu.Instance.bold : Doc.UserDoc().fontWeight === "bold") ? Colors.MEDIUM_BLUE : "transparent";
     }
-    const editorView = RichTextMenu.Instance.TextView?.EditorView;
-    if (editorView) {
-        editorView?.state && RichTextMenu.Instance.toggleBold(editorView, true);
-    }
-    SelectionManager.Docs().filter(doc => StrCast(doc.type) === DocumentType.RTF).map(doc => doc.bold = !doc.bold);
-    Doc.UserDoc().bold = !Doc.UserDoc().bold;
-    return Doc.UserDoc().bold;
+    if (editorView) RichTextMenu.Instance?.toggleBold();
+    else Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === "bold" ? undefined : "bold";
 });
 
 Scripting.addGlobal(function toggleUnderline(checkResult?: boolean) {
+    const editorView = RichTextMenu.Instance?.TextView?.EditorView;
     if (checkResult) {
-        return "transparent";
-    }
-    const editorView = RichTextMenu.Instance.TextView?.EditorView;
-    if (editorView) {
-        editorView?.state && RichTextMenu.Instance.toggleUnderline(editorView, true);
+        return (editorView ? RichTextMenu.Instance.underline : Doc.UserDoc().textDecoration === "underline") ? Colors.MEDIUM_BLUE : "transparent";
     }
-    SelectionManager.Docs().filter(doc => StrCast(doc.type) === DocumentType.RTF).map(doc => doc.underline = !doc.underline);
-    Doc.UserDoc().underline = !Doc.UserDoc().underline;
-    return Doc.UserDoc().underline;
+    if (editorView) RichTextMenu.Instance?.toggleUnderline();
+    else Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === "underline" ? undefined : "underline";
 });
 
 Scripting.addGlobal(function toggleItalic(checkResult?: boolean) {
+    const editorView = RichTextMenu.Instance?.TextView?.EditorView;
     if (checkResult) {
-        return "transparent";
-    }
-    const editorView = RichTextMenu.Instance.TextView?.EditorView;
-    if (editorView) {
-        editorView?.state && RichTextMenu.Instance.toggleItalic(editorView, true);
+        return (editorView ? RichTextMenu.Instance.italics : Doc.UserDoc().fontStyle === "italics") ? Colors.MEDIUM_BLUE : "transparent";
     }
-    SelectionManager.Docs().filter(doc => StrCast(doc.type) === DocumentType.RTF).map(doc => doc.italic = !doc.italic);
-    Doc.UserDoc().italic = !Doc.UserDoc().italic;
-    return Doc.UserDoc().italic;
+    if (editorView) RichTextMenu.Instance?.toggleItalics();
+    else Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === "italics" ? undefined : "italics";
 });
 
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 617fa0bee..16aa4de6c 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1168,7 +1168,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         selectOnLoad && this._editorView!.focus();
         // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
         if (this._editorView && !this._editorView.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark)) {
-            this._editorView.state.storedMarks = [...(this._editorView.state.storedMarks ?? []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })];
+            this._editorView.state.storedMarks = [...(this._editorView.state.storedMarks ?? []),
+            schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }),
+            ...(Doc.UserDoc().fontColor !== "transparent" && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []),
+            ...(Doc.UserDoc().fontStyle === "italics" ? [schema.mark(schema.marks.em)] : []),
+            ...(Doc.UserDoc().textDecoration === "underline" ? [schema.mark(schema.marks.underline)] : []),
+            ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: StrCast(Doc.UserDoc().fontFamily) })] : []),
+            ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: StrCast(Doc.UserDoc().fontSize, "") })] : []),
+            ...(Doc.UserDoc().fontWeight === "bold" ? [schema.mark(schema.marks.strong)] : [])];
         }
     }
 
@@ -1596,7 +1603,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                         background: this.props.background ? this.props.background : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
                         color: this.props.color ? this.props.color : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
                         fontSize: this.props.fontSize ? this.props.fontSize : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize),
-                        fontWeight: Cast(this.layoutDoc._fontWeight, "number", null),
+                        fontWeight: Cast(this.layoutDoc._fontWeight, "string", null) as any,
                         fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"),
                         pointerEvents: interactive ? undefined : "none",
                     }}
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 9904a7939..23a3d80a4 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -1,8 +1,7 @@
 import React = require("react");
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { Tooltip } from "@material-ui/core";
-import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx";
+import { action, IReactionDisposer, observable, reaction, runInAction, computed } from "mobx";
 import { observer } from "mobx-react";
 import { lift, wrapIn } from "prosemirror-commands";
 import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos } from "prosemirror-model";
@@ -10,10 +9,7 @@ import { wrapInList } from "prosemirror-schema-list";
 import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
 import { Doc } from "../../../../fields/Doc";
-import { DarkPastelSchemaPalette, PastelSchemaPalette } from '../../../../fields/SchemaHeaderField';
 import { Cast, StrCast } from "../../../../fields/Types";
-import { TraceMobx } from "../../../../fields/util";
-import { unimplementedFunction, Utils } from "../../../../Utils";
 import { DocServer } from "../../../DocServer";
 import { LinkManager } from "../../../util/LinkManager";
 import { SelectionManager } from "../../../util/SelectionManager";
@@ -29,7 +25,7 @@ const { toggleMark } = require("prosemirror-commands");
 
 @observer
 export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
-    static Instance: RichTextMenu;
+    @observable static Instance: RichTextMenu;
     public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable
     private _linkToRef = React.createRef<HTMLInputElement>();
 
@@ -39,22 +35,22 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
     public _brushMap: Map<string, Set<Mark>> = new Map();
 
     @observable private collapsed: boolean = false;
-    @observable private boldActive: boolean = false;
-    @observable private italicsActive: boolean = false;
-    @observable private underlineActive: boolean = false;
-    @observable private strikethroughActive: boolean = false;
-    @observable private subscriptActive: boolean = false;
-    @observable private superscriptActive: boolean = false;
-
-    @observable private activeFontSize: string = "";
-    @observable private activeFontFamily: string = "";
+    @observable private _boldActive: boolean = false;
+    @observable private _italicsActive: boolean = false;
+    @observable private _underlineActive: boolean = false;
+    @observable private _strikethroughActive: boolean = false;
+    @observable private _subscriptActive: boolean = false;
+    @observable private _superscriptActive: boolean = false;
+
+    @observable private _activeFontSize: string = "13px";
+    @observable private _activeFontFamily: string = "";
     @observable private activeListType: string = "";
     @observable private activeAlignment: string = "left";
 
     @observable private brushMarks: Set<Mark> = new Set();
     @observable private showBrushDropdown: boolean = false;
 
-    @observable private activeFontColor: string = "black";
+    @observable private _activeFontColor: string = "black";
     @observable private showColorDropdown: boolean = false;
 
     @observable private activeHighlightColor: string = "transparent";
@@ -67,10 +63,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
     _delayHide = false;
     constructor(props: Readonly<{}>) {
         super(props);
-        RichTextMenu.Instance = this;
-        this._canFade = false;
-        //this.Pinned = BoolCast(Doc.UserDoc()["menuRichText-pinned"]);
-        runInAction(() => this.Pinned = true);
+        runInAction(() => {
+            RichTextMenu.Instance = this;
+            this._canFade = false;
+            //this.Pinned = BoolCast(Doc.UserDoc()["menuRichText-pinned"]);
+            this.Pinned = true;
+        });
     }
 
     componentDidMount() {
@@ -81,6 +79,14 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
         this._reaction?.();
     }
 
+    @computed get bold() { return this._boldActive; }
+    @computed get underline() { return this._underlineActive; }
+    @computed get italics() { return this._italicsActive; }
+    @computed get strikeThrough() { return this._strikethroughActive; }
+    @computed get fontColor() { return this._activeFontColor; }
+    @computed get fontFamily() { return this._activeFontFamily; }
+    @computed get fontSize() { return this._activeFontSize; }
+
     public delayHide = () => this._delayHide = true;
 
     @action
@@ -110,10 +116,10 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
 
         this.activeListType = this.getActiveListStyle();
         this.activeAlignment = this.getActiveAlignment();
-        this.activeFontFamily = !activeFamilies.length ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
-        this.activeFontSize = !activeSizes.length ? "13px" : activeSizes.length === 1 ? String(activeSizes[0]) : "...";
-        this.activeFontColor = !activeColors.length ? "black" : activeColors.length === 1 ? String(activeColors[0]) : "...";
-        this.activeHighlightColor = !activeHighlights.length ? "" : activeHighlights.length === 1 ? String(activeHighlights[0]) : "...";
+        this._activeFontFamily = !activeFamilies.length ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
+        this._activeFontSize = !activeSizes.length ? "13px" : activeSizes[0];
+        this._activeFontColor = !activeColors.length ? "black" : activeColors.length > 0 ? String(activeColors[0]) : "...";
+        this.activeHighlightColor = !activeHighlights.length ? "" : activeHighlights.length > 0 ? String(activeHighlights[0]) : "...";
 
         // update link in current selection
         this.getTextLinkTargetTitle().then(targetTitle => this.setCurrentLink(targetTitle));
@@ -125,7 +131,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
             if (node?.type === schema.nodes.ordered_list) {
                 let attrs = node.attrs;
                 if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, fontFamily: mark.attrs.family };
-                if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, fontSize: `${mark.attrs.fontSize}px` };
+                if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, fontSize: mark.attrs.fontSize };
                 if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, fontColor: mark.attrs.color };
                 const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema);
                 dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from))));
@@ -142,17 +148,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
         }
     }
 
-    getBoldStatus() {
-        if (this.view && this.TextView.props.isSelected(true)) {
-            const path = (this.view.state.selection.$from as any).path;
-            for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) {
-                if (path[i]?.type === this.view.state.schema.nodes.paragraph || path[i]?.type === this.view.state.schema.nodes.heading) {
-                    return path[i].attrs.strong;
-                }
-            }
-        }
-    }
-
     // finds font sizes and families in selection
     getActiveAlignment() {
         if (this.view && this.TextView.props.isSelected(true)) {
@@ -193,25 +188,23 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
         if (this.TextView.props.isSelected(true)) {
             const state = this.view.state;
             const pos = this.view.state.selection.$from;
-            const ref_node = this.reference_node(pos);
-            if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) {
-                const marks = Array.from(ref_node.marks);
-                marks.push(...(this.view.state.storedMarks as any));
-                marks.forEach(m => {
-                    m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family);
-                    m.type === state.schema.marks.pFontColor && activeColors.push(m.attrs.color);
-                    m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "px");
-                    m.type === state.schema.marks.marker && activeHighlights.push(String(m.attrs.highlight));
+            const marks: Mark<any>[] = [];
+            if (state.selection.empty) {
+                const ref_node = this.reference_node(pos);
+                marks.push(...[...(ref_node !== this.view.state.doc && ref_node?.isText ?
+                    [...(state.storedMarks ?? []), ...Array.from(ref_node.marks)] : [])]);
+            } else {
+                state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+                    node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark));
                 });
             }
-            !activeFamilies.length && (activeFamilies.push(StrCast(this.TextView.layoutDoc._fontFamily, StrCast(Doc.UserDoc().fontFamily))));
-            !activeSizes.length && (activeSizes.push(StrCast(this.TextView.layoutDoc._fontSize, StrCast(Doc.UserDoc().fontSize))));
-            !activeColors.length && (activeColors.push(StrCast(this.TextView.layoutDoc.color, StrCast(Doc.UserDoc().fontColor))));
+            marks.forEach(m => {
+                m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family);
+                m.type === state.schema.marks.pFontColor && activeColors.push(m.attrs.color);
+                m.type === state.schema.marks.pFontSize && activeSizes.push(m.attrs.fontSize);
+                m.type === state.schema.marks.marker && activeHighlights.push(String(m.attrs.highlight));
+            });
         }
-        !activeFamilies.length && (activeFamilies.push(StrCast(Doc.UserDoc().fontFamily)));
-        !activeSizes.length && (activeSizes.push(StrCast(Doc.UserDoc().fontSize)));
-        !activeColors.length && (activeColors.push(StrCast(Doc.UserDoc().fontColor, "black")));
-        !activeHighlights.length && (activeHighlights.push(StrCast(Doc.UserDoc().fontHighlight, "")));
         return { activeFamilies, activeSizes, activeColors, activeHighlights };
     }
 
@@ -251,11 +244,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
                     return [];
                 }
                 activeMarks = markGroup.filter(mark_type => {
-                    if (mark_type === state.schema.marks.pFontSize) {
-                        return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name);
-                    }
+                    // if (mark_type === state.schema.marks.pFontSize) {
+                    //     return mark.isINSet
+                    //     ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name);
+                    // }
                     const mark = state.schema.mark(mark_type);
-                    return ref_node.marks.includes(mark);
+                    return mark.isInSet(ref_node.marks);
                 });
             }
         }
@@ -270,56 +264,66 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
     setActiveMarkButtons(activeMarks: MarkType[] | undefined) {
         if (!activeMarks) return;
 
-        this.boldActive = false;
-        this.italicsActive = false;
-        this.underlineActive = false;
-        this.strikethroughActive = false;
-        this.subscriptActive = false;
-        this.superscriptActive = false;
+        this._boldActive = false;
+        this._italicsActive = false;
+        this._underlineActive = false;
+        this._strikethroughActive = false;
+        this._subscriptActive = false;
+        this._superscriptActive = false;
 
         activeMarks.forEach(mark => {
             switch (mark.name) {
-                case "strong": this.boldActive = true; break;
-                case "em": this.italicsActive = true; break;
-                case "underline": this.underlineActive = true; break;
-                case "strikethrough": this.strikethroughActive = true; break;
-                case "subscript": this.subscriptActive = true; break;
-                case "superscript": this.superscriptActive = true; break;
+                case "strong": this._boldActive = true; break;
+                case "em": this._italicsActive = true; break;
+                case "underline": this._underlineActive = true; break;
+                case "strikethrough": this._strikethroughActive = true; break;
+                case "subscript": this._subscriptActive = true; break;
+                case "superscript": this._superscriptActive = true; break;
             }
         });
     }
 
-    toggleBold = (view: EditorView, forceBool?: boolean) => {
-        const mark = view.state.schema.mark(view.state.schema.marks.strong, { strong: forceBool });
-        this.setMark(mark, view.state, view.dispatch, false);
-        view.focus();
+    toggleBold = () => {
+        if (this.view) {
+            const mark = this.view.state.schema.mark(this.view.state.schema.marks.strong);
+            this.setMark(mark, this.view.state, this.view.dispatch, false);
+            this.view.focus();
+        }
     }
 
-    toggleUnderline = (view: EditorView, forceBool?: boolean) => {
-        const mark = view.state.schema.mark(view.state.schema.marks.underline, { underline: forceBool });
-        this.setMark(mark, view.state, view.dispatch, false);
-        view.focus();
+    toggleUnderline = () => {
+        if (this.view) {
+            const mark = this.view.state.schema.mark(this.view.state.schema.marks.underline);
+            this.setMark(mark, this.view.state, this.view.dispatch, false);
+            this.view.focus();
+        }
     }
 
-    toggleItalic = (view: EditorView, forceBool?: boolean) => {
-        const mark = view.state.schema.mark(view.state.schema.marks.em, { em: forceBool });
-        this.setMark(mark, view.state, view.dispatch, false);
-        view.focus();
+    toggleItalics = () => {
+        if (this.view) {
+            const mark = this.view.state.schema.mark(this.view.state.schema.marks.em);
+            this.setMark(mark, this.view.state, this.view.dispatch, false);
+            this.view.focus();
+        }
     }
 
 
-    setFontSize = (size: number, view: EditorView) => {
-        const fmark = view.state.schema.marks.pFontSize.create({ fontSize: size });
-        this.setMark(fmark, view.state, (tx: any) => view.dispatch(tx.addStoredMark(fmark)), true);
-        view.focus();
-        this.updateMenu(view, undefined, this.props);
+    setFontSize = (fontSize: string) => {
+        if (this.view) {
+            const fmark = this.view.state.schema.marks.pFontSize.create({ fontSize });
+            this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
+            this.view.focus();
+            this.updateMenu(this.view, undefined, this.props);
+        }
     }
 
-    setFontFamily = (family: string, view: EditorView) => {
-        const fmark = view.state.schema.marks.pFontFamily.create({ family: family });
-        this.setMark(fmark, view.state, (tx: any) => view.dispatch(tx.addStoredMark(fmark)), true);
-        view.focus();
-        this.updateMenu(view, undefined, this.props);
+    setFontFamily = (family: string) => {
+        if (this.view) {
+            const fmark = this.view.state.schema.marks.pFontFamily.create({ family: family });
+            this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
+            this.view.focus();
+            this.updateMenu(this.view, undefined, this.props);
+        }
     }
 
     setHighlight(color: String, view: EditorView, dispatch: any) {
@@ -330,7 +334,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps>   {
     }
 
     setColor(color: String, view: EditorView, dispatch: any) {
-        const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color });
+        const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color });
         if (view.state.selection.empty) {
             dispatch(view.state.tr.addStoredMark(colorMark));
             return false;
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 655ee7e44..6103a28d6 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -61,13 +61,13 @@ export const marks: { [index: string]: MarkSpec } = {
 
     /** FONT SIZES */
     pFontSize: {
-        attrs: { fontSize: { default: 10 } },
+        attrs: { fontSize: { default: "10px" } },
         parseDOM: [{
             tag: "span", getAttrs(dom: any) {
-                return { fontSize: dom.style.fontSize ? Number(dom.style.fontSize.replace("px", "")) : "" };
+                return { fontSize: dom.style.fontSize ? dom.style.fontSize.toString() : "" };
             }
         }],
-        toDOM: (node) => node.attrs.fontSize ? ['span', { style: `font-size: ${node.attrs.fontSize}px;` }] : ['span', 0]
+        toDOM: (node) => node.attrs.fontSize ? ['span', { style: `font-size: ${node.attrs.fontSize};` }] : ['span', 0]
     },
 
     /* FONTS */
-- 
cgit v1.2.3-70-g09d2


From 13ec8c89d7fb809a9ceefa3c25e332b0f4457593 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 22 Sep 2021 11:28:46 -0400
Subject: fixed dropdowns to be over documentdecorations by moving top menu bar
 out of maincontent area and giving it a higher z-index than DocDecorations. 
 fixed toggle behavior of shape drawing modes.  made color dropdown state be
 runtime only (doesn't write to data model) -- need to decide if that's what
 we want.

---
 src/client/views/MainView.tsx                 | 14 +++---
 src/client/views/nodes/button/FontIconBox.tsx | 68 +++++++++++++--------------
 2 files changed, 42 insertions(+), 40 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index fbd3fece2..35c5801e5 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -12,7 +12,7 @@ import { Doc, DocListCast, Opt } from '../../fields/Doc';
 import { List } from '../../fields/List';
 import { PrefetchProxy } from '../../fields/Proxy';
 import { ScriptField } from '../../fields/ScriptField';
-import { BoolCast, PromiseValue, StrCast } from '../../fields/Types';
+import { PromiseValue, StrCast } from '../../fields/Types';
 import { TraceMobx } from '../../fields/util';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick, Utils } from '../../Utils';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
@@ -26,7 +26,7 @@ import { HistoryUtil } from '../util/History';
 import { Hypothesis } from '../util/HypothesisUtils';
 import { Scripting } from '../util/Scripting';
 import { SelectionManager } from '../util/SelectionManager';
-import { SettingsManager, ColorScheme } from '../util/SettingsManager';
+import { ColorScheme, SettingsManager } from '../util/SettingsManager';
 import { SharingManager } from '../util/SharingManager';
 import { SnappingManager } from '../util/SnappingManager';
 import { Transform } from '../util/Transform';
@@ -101,7 +101,7 @@ export class MainView extends React.Component {
     propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, CurrentUserUtils.propertiesWidth || 0));
     propertiesHeight = () => this._dashUIHeight;
     mainDocViewWidth = () => this._dashUIWidth - this.propertiesWidth() - this.leftMenuWidth();
-    mainDocViewHeight = () => this._dashUIHeight - this.topMenuHeight();
+    mainDocViewHeight = () => this._dashUIHeight;
 
     componentDidMount() {
         document.getElementById("root")?.addEventListener("scroll", e => ((ele) => ele.scrollLeft = ele.scrollTop = 0)(document.getElementById("root")!));
@@ -436,7 +436,6 @@ export class MainView extends React.Component {
                     <FontAwesomeIcon icon="chevron-left" color={this.colorScheme === ColorScheme.Dark ? "white" : "black"} style={{ opacity: "50%" }} size="sm" />
                 </div>
                 <div className="mainView-innerContainer" style={{ width: `calc(100% - ${width}px)`, transform: transform }}>
-                    <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} />
 
                     {this.dockingContent}
 
@@ -460,7 +459,7 @@ export class MainView extends React.Component {
                 })).observe(r);
             }} style={{
                 color: this.colorScheme === ColorScheme.Dark ? "rgb(205,205,205)" : "black",
-                height: `calc(100% - ${this.topOfDashUI}px)`,
+                height: `calc(100% - ${this.topOfDashUI + this.topMenuHeight()}px)`,
                 width: "100%",
             }} >
                 {this.mainInnerContent}
@@ -613,11 +612,14 @@ export class MainView extends React.Component {
             <GroupManager />
             <GoogleAuthenticationManager />
             <DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} />
-            <ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
+            <ComponentDecorations boundsLeft={0} boundsTop={this.topOfMainDocContent} />
             {this.topbar}
             {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
             {DocumentLinksButton.LinkEditorDocView ? <LinkMenu docView={DocumentLinksButton.LinkEditorDocView} changeFlyout={emptyFunction} /> : (null)}
             {LinkDocPreview.LinkInfo ? <LinkDocPreview {...LinkDocPreview.LinkInfo} /> : (null)}
+            <div style={{ position: "relative", display: LightboxView.LightboxDoc ? "none" : undefined, zIndex: 2001 }} >
+                <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} />
+            </div>
             <GestureOverlay >
                 {this.mainDashboardArea}
             </GestureOverlay>
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 368699dea..511df8786 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -5,7 +5,7 @@ import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { ColorState, SketchPicker } from 'react-color';
-import { Doc, StrListCast } from '../../../../fields/Doc';
+import { Doc, StrListCast, WidthSym, HeightSym } from '../../../../fields/Doc';
 import { InkTool } from '../../../../fields/InkField';
 import { createSchema, makeInterface } from '../../../../fields/Schema';
 import { ScriptField } from '../../../../fields/ScriptField';
@@ -318,35 +318,35 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
         );
     }
 
+    @observable colorPickerClosed: boolean = true;
     @computed get colorScript() {
         const script = StrCast(this.rootDoc.script);
         return ScriptField.MakeScript(script + '(colValue, checkResult)', { colValue: "string", checkResult: "boolean" });
     }
-    colorPicker = (boolResult: string) => {
-        const click = (value: ColorState) => {
+
+    colorPicker = (curColor: string) => {
+        const change = (value: ColorState) => {
             const s = this.colorScript;
             s && undoBatch(() => s.script.run({ colValue: Utils.colorString(value), checkResult: false }).result)();
         };
-        const colorOptions: string[] = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505',
+        const presets = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505',
             '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B',
             '#FFFFFF', '#f1efeb', "transparent"];
         return <SketchPicker
-            disableAlpha={false}
-            onChange={click}
-            color={boolResult}
-            presetColors={colorOptions} />;
+            onChange={change}
+            color={curColor}
+            presetColors={presets} />
     }
     /**
      * Color button
      */
     @computed get colorButton() {
-        const active = StrCast(this.rootDoc.dropDownOpen);
         const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
         const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
-        const boolResult = this.colorScript?.script.run({ colValue: undefined, checkResult: true }).result;
+        const curColor = this.colorScript?.script.run({ colValue: undefined, checkResult: true }).result ?? "transparent";
 
         const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
-            <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor, position: "absolute" }}>
+            <div className="fontIconBox-label" style={{ color, backgroundColor, position: "absolute" }}>
                 {this.label}
             </div>;
 
@@ -355,26 +355,27 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
             style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}>
             <FontAwesomeIcon icon={'caret-down'} color={color} size="sm" />
         </div>;
-
+        setTimeout(() => this.colorPicker(curColor)); // cause an update to the color picker rendered in MainView
         return (
-            <div className={`menuButton ${this.type} ${active}`}
+            <div className={`menuButton ${this.type} ${this.colorPickerClosed}`}
                 style={{ color: color, borderBottomLeftRadius: this.dropdown ? 0 : undefined }}
-                onClick={() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen}
+                onClick={action(() => this.colorPickerClosed = !this.colorPickerClosed)}
                 onPointerDown={e => e.stopPropagation()}>
                 <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
-                <div className="colorButton-color" style={{ backgroundColor: boolResult ?? "transparent" }} />
+                <div className="colorButton-color" style={{ backgroundColor: curColor }} />
                 {label}
                 {/* {dropdownCaret} */}
-                {this.rootDoc.dropDownOpen ?
+                {this.colorPickerClosed ? (null) :
                     <div>
                         <div className="menuButton-dropdownBox"
                             onPointerDown={e => e.stopPropagation()}
                             onClick={e => e.stopPropagation()}>
-                            {this.colorPicker(boolResult ?? "transparent")}
+                            {this.colorPicker(curColor)}
                         </div>
-                        <div className="dropbox-background" onClick={(e) => { e.stopPropagation(); this.rootDoc.dropDownOpen = false; }} />
-                    </div>
-                    : null}
+                        <div className="dropbox-background" onClick={action((e) => {
+                            e.stopPropagation(); this.colorPickerClosed = true;
+                        })} />
+                    </div>}
             </div>
         );
     }
@@ -754,24 +755,23 @@ Scripting.addGlobal(function toggleItalic(checkResult?: boolean) {
 
 Scripting.addGlobal(function setActiveInkTool(tool: string, checkResult?: boolean) {
     if (checkResult) {
-        if (Doc.UserDoc().activeInkTool === tool && GestureOverlay.Instance.InkShape === "" || GestureOverlay.Instance.InkShape === tool) return Colors.MEDIUM_BLUE;
-        else return "transparent";
+        return (Doc.UserDoc().activeInkTool === tool && GestureOverlay.Instance?.InkShape === "" || GestureOverlay.Instance?.InkShape === tool) ?
+            Colors.MEDIUM_BLUE : "transparent";
     }
-    if (tool === "circle") {
-        Doc.UserDoc().activeInkTool = "pen";
-        GestureOverlay.Instance.InkShape = tool;
-    } else if (tool === "square") {
-        Doc.UserDoc().activeInkTool = "pen";
-        GestureOverlay.Instance.InkShape = tool;
-    } else if (tool === "line") {
-        Doc.UserDoc().activeInkTool = "pen";
-        GestureOverlay.Instance.InkShape = tool;
-    } else if (tool) {
-        if (Doc.UserDoc().activeInkTool === tool && GestureOverlay.Instance.InkShape === "" || GestureOverlay.Instance.InkShape === tool) {
-            GestureOverlay.Instance.InkShape = "";
+    if (["circle", "square", "line"].includes(tool)) {
+        if (GestureOverlay.Instance.InkShape === tool) {
+            Doc.UserDoc().activeInkTool = InkTool.None;
+            GestureOverlay.Instance.InkShape = InkTool.None;
+        } else {
+            Doc.UserDoc().activeInkTool = InkTool.Pen;
+            GestureOverlay.Instance.InkShape = tool;
+        }
+    } else if (tool) { // pen
+        if (Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance.InkShape) {
             Doc.UserDoc().activeInkTool = InkTool.None;
         } else {
             Doc.UserDoc().activeInkTool = tool;
+            GestureOverlay.Instance.InkShape = "";
         }
     } else {
         Doc.UserDoc().activeInkTool = InkTool.None;
-- 
cgit v1.2.3-70-g09d2


From e7e67ff7e3170b6eb5bf05ffe284ca08f4802459 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 25 Sep 2021 12:30:35 -0400
Subject: added a hacky fix to what seems to be a Chrome bug when auto
 expanding the left flyout panel

---
 src/client/views/MainView.tsx                                   | 6 ++++++
 src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 3 +--
 2 files changed, 7 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 35c5801e5..7edcd6217 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -467,7 +467,13 @@ export class MainView extends React.Component {
     }
 
     expandFlyout = action((button: Doc) => {
+        // bcz: What's going on here!?  
+        // Chrome(not firefox) seems to have a bug when the flyout expands and there's a zoomed freeform tab.  All of the div below the CollectionFreeFormView's main div
+        // generate the wrong value from getClientRectangle() -- specifically they return an 'x' that is the flyout's width greater than it should be.
+        // interactively adjusting the flyout fixes the problem.  So does programmatically changing the value after a timeout to something *fractionally* different (ie, 1.5, not 1);)
         this._leftMenuFlyoutWidth = (this._leftMenuFlyoutWidth || 250);
+        setTimeout(action(() => this._leftMenuFlyoutWidth += 0.5), 0);
+
         this._sidebarContent.proto = button.target as any;
         this.LastButton = button;
     });
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 81f6307d1..24a7d77e0 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -646,8 +646,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
     render() {
         return <div className="marqueeView"
             style={{
-                overflow: //(!this.props.ContainingCollectionView && this.props.isAnnotationOverlay) ? "visible" :
-                    StrCast(this.props.Document._overflow),
+                overflow: StrCast(this.props.Document._overflow),
                 cursor: "hand"
             }}
             onDragOver={e => e.preventDefault()}
-- 
cgit v1.2.3-70-g09d2


From dab30019adba202ba2266acf046ad3c41f90c6e2 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 27 Sep 2021 14:06:51 -0400
Subject: videoBox fitsWidth properly in lightbox.  documentdecorations less
 hacky for deciding when to allow nativewidth/height changes by adding props
 to document type templates.  fixed height of properties pane & updated CSS
 for leftflyout & properties pane.  marquees no longer oveflow image/video
 boxes.

---
 src/client/documents/Documents.ts        | 12 +++++++++---
 src/client/views/DocumentDecorations.tsx | 29 +++++++++++------------------
 src/client/views/GlobalKeyHandler.ts     |  2 +-
 src/client/views/MainView.scss           |  9 ++++-----
 src/client/views/MainView.tsx            | 12 ++++++------
 src/client/views/nodes/ImageBox.scss     |  1 +
 src/client/views/nodes/ImageBox.tsx      |  4 ++--
 src/client/views/nodes/VideoBox.scss     | 15 ++++++++++++++-
 src/client/views/nodes/VideoBox.tsx      | 22 +++++++++++++++-------
 src/client/views/nodes/WebBox.tsx        |  8 ++++----
 10 files changed, 67 insertions(+), 47 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 8ac647b99..403620bdd 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -352,7 +352,7 @@ export namespace Docs {
         const TemplateMap: TemplateMap = new Map([
             [DocumentType.RTF, {
                 layout: { view: FormattedTextBox, dataField: "text" },
-                options: { _height: 150, _xMargin: 10, _yMargin: 10, links: ComputedField.MakeFunction("links(self)") as any }
+                options: { _height: 150, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: ComputedField.MakeFunction("links(self)") as any }
             }],
             [DocumentType.SEARCH, {
                 layout: { view: SearchBox, dataField: defaultDataKey },
@@ -372,7 +372,7 @@ export namespace Docs {
             }],
             [DocumentType.WEB, {
                 layout: { view: WebBox, dataField: defaultDataKey },
-                options: { _height: 300, _fitWidth: true, links: ComputedField.MakeFunction("links(self)") as any }
+                options: { _height: 300, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: ComputedField.MakeFunction("links(self)") as any }
             }],
             [DocumentType.COL, {
                 layout: { view: CollectionView, dataField: defaultDataKey },
@@ -392,7 +392,7 @@ export namespace Docs {
             }],
             [DocumentType.PDF, {
                 layout: { view: PDFBox, dataField: defaultDataKey },
-                options: { _curPage: 1, _fitWidth: true, links: ComputedField.MakeFunction("links(self)") as any }
+                options: { _curPage: 1, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: ComputedField.MakeFunction("links(self)") as any }
             }],
             [DocumentType.IMPORT, {
                 layout: { view: DirectoryImportBox, dataField: defaultDataKey },
@@ -506,6 +506,12 @@ export namespace Docs {
             const prototypeIds = Object.values(DocumentType).filter(type => type !== DocumentType.NONE).map(type => type + suffix);
             // fetch the actual prototype documents from the server
             const actualProtos = Docs.newAccount ? {} : await DocServer.GetRefFields(prototypeIds);
+            Cast(actualProtos[DocumentType.WEB + suffix], Doc, null).nativeHeightUnfrozen = true; // to avoid having to recreate the DB
+            Cast(actualProtos[DocumentType.PDF + suffix], Doc, null).nativeHeightUnfrozen = true;
+            Cast(actualProtos[DocumentType.RTF + suffix], Doc, null).nativeHeightUnfrozen = true;
+            Cast(actualProtos[DocumentType.WEB + suffix], Doc, null).nativeDimModifiable = true; // to avoid having to recreate the DB
+            Cast(actualProtos[DocumentType.PDF + suffix], Doc, null).nativeDimModifiable = true;
+            Cast(actualProtos[DocumentType.RTF + suffix], Doc, null).nativeDimModifiable = true;
 
             // update this object to include any default values: DocumentOptions for all prototypes
             prototypeIds.map(id => {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index e9a54d6a5..bd61c9f60 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -319,12 +319,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                 let height = (doc._height || (nheight / nwidth * width));
                 height = !height || isNaN(height) ? 20 : height;
                 const scale = docView.props.ScreenToLocalTransform().Scale;
-                const canModifyNativeDim = e.ctrlKey || doc.allowReflow;
+                const modifyNativeDim = (e.ctrlKey || doc.forceReflow) && doc.nativeDimModifiable;
                 if (nwidth && nheight) {
                     if (nwidth / nheight !== width / height && !dragBottom) {
                         height = nheight / nwidth * width;
                     }
-                    if (canModifyNativeDim && !dragBottom) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction
+                    if (modifyNativeDim && !dragBottom) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction
                         if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth;
                         else dW = dH * nwidth / nheight;
                     }
@@ -334,34 +334,27 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                 doc.x = (doc.x || 0) + dX * (actualdW - width);
                 doc.y = (doc.y || 0) + dY * (actualdH - height);
                 const fixedAspect = (nwidth && nheight);
-                if (canModifyNativeDim && [DocumentType.IMG, DocumentType.SCREENSHOT, DocumentType.VID].includes(doc.type as DocumentType)) {
-                    dW !== 0 && runInAction(() => {
-                        const dataDoc = doc[DataSym];
-                        const nw = Doc.NativeWidth(dataDoc);
-                        const nh = Doc.NativeHeight(dataDoc);
-                        Doc.SetNativeWidth(dataDoc, nw + (dW > 0 ? 10 : -10));
-                        Doc.SetNativeHeight(dataDoc, nh + (dW > 0 ? 10 : -10) * nh / nw);
-                    });
-                }
-                else if (fixedAspect) {
-                    if ((Math.abs(dW) > Math.abs(dH) && (!dragBottom || !canModifyNativeDim)) || dragRight) {
-                        if (dragRight && canModifyNativeDim) {
+                if (fixedAspect) {
+                    if ((Math.abs(dW) > Math.abs(dH) && (!dragBottom || !modifyNativeDim)) || dragRight) {
+                        if (dragRight && modifyNativeDim) {
                             doc._nativeWidth = actualdW / (doc._width || 1) * Doc.NativeWidth(doc);
                         } else {
                             if (!doc._fitWidth) doc._height = nheight / nwidth * actualdW;
-                            else if (!canModifyNativeDim || dragBotRight) doc._height = actualdH;
+                            else if (!modifyNativeDim || dragBotRight) doc._height = actualdH;
                         }
                         doc._width = actualdW;
                     }
                     else {
-                        if (dragBottom && (canModifyNativeDim || docView.layoutDoc._fitWidth)) { // frozen web pages and others that fitWidth can't grow horizontally to match a vertical resize so the only choice is to change the nativeheight even if the ctrl key isn't used
+                        if (dragBottom && (modifyNativeDim ||
+                            (docView.layoutDoc.nativeHeightUnfrozen && docView.layoutDoc._fitWidth))) { // frozen web pages, PDFs, and some RTFS have frozen nativewidth/height.  But they are marked to allow their nativeHeight to be explicitly modified with fitWidth and vertical resizing. (ie, with fitWidth they can't grow horizontally to match a vertical resize so it makes more sense to change their nativeheight even if the ctrl key isn't used)
                             doc._nativeHeight = actualdH / (doc._height || 1) * Doc.NativeHeight(doc);
                             doc._autoHeight = false;
                         } else {
                             if (!doc._fitWidth) doc._width = nwidth / nheight * actualdH;
-                            else if (!canModifyNativeDim || dragBotRight) doc._width = actualdW;
+                            else if (!modifyNativeDim || dragBotRight) doc._width = actualdW;
                         }
-                        doc._height = actualdH;
+                        if (!modifyNativeDim) doc._height = Math.min(nheight / nwidth * NumCast(doc._width), actualdH);
+                        else doc._height = actualdH;
                     }
                 } else {
                     dH && (doc._height = actualdH);
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 574d28b8f..364bf05e2 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -141,7 +141,7 @@ export class KeyManager {
             case "delete":
             case "backspace":
                 if (document.activeElement?.tagName !== "INPUT" && document.activeElement?.tagName !== "TEXTAREA") {
-                    const selected = SelectionManager.Views().filter(dv => !dv.topMost);;
+                    const selected = SelectionManager.Views().filter(dv => !dv.topMost);
                     UndoManager.RunInBatch(() => {
                         SelectionManager.DeselectAll();
                         selected.map(dv => !dv.props.Document._stayInCollection && dv.props.removeDocument?.(dv.props.Document));
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 7fa841002..c3cdb7dde 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -109,9 +109,9 @@
 
 .properties-container {
     height: 100%;
-    position: relative;
-    left: 100%;
-    top: calc(-100% - 36px);
+    position: absolute;
+    right: 0;
+    top: 0;
     z-index: 3000;
 }
 
@@ -298,9 +298,8 @@
     width: var(--flyoutHandleWidth);
     height: 55px;
     top: 50%;
-    left: -10px;
     border-radius: 8px;
-    position: relative;
+    position: absolute;
     z-index: 41; // lm_maximised has a z-index of 40 and this needs to be above that
     touch-action: none;
     cursor: grab;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 7edcd6217..3f7df705f 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -426,23 +426,23 @@ export class MainView extends React.Component {
     }
 
     @computed get mainInnerContent() {
-        const width = this.propertiesWidth() + this._leftMenuFlyoutWidth + this.leftMenuWidth();
-        const transform = this._leftMenuFlyoutWidth ? 'translate(-28px, 0px)' : undefined;
+        const leftMenuFlyoutWidth = this._leftMenuFlyoutWidth + this.leftMenuWidth();
+        const width = this.propertiesWidth() + leftMenuFlyoutWidth;
         return <>
             {this.leftMenuPanel}
             <div key="inner" className={`mainView-innerContent${this.colorScheme}`}>
                 {this.flyout}
-                <div className="mainView-libraryHandle" style={{ display: !this._leftMenuFlyoutWidth ? "none" : undefined }} onPointerDown={this.onFlyoutPointerDown} >
+                <div className="mainView-libraryHandle" style={{ left: leftMenuFlyoutWidth - 10 /* ~half width of handle */, display: !this._leftMenuFlyoutWidth ? "none" : undefined }} onPointerDown={this.onFlyoutPointerDown} >
                     <FontAwesomeIcon icon="chevron-left" color={this.colorScheme === ColorScheme.Dark ? "white" : "black"} style={{ opacity: "50%" }} size="sm" />
                 </div>
-                <div className="mainView-innerContainer" style={{ width: `calc(100% - ${width}px)`, transform: transform }}>
+                <div className="mainView-innerContainer" style={{ width: `calc(100% - ${width}px)` }}>
 
                     {this.dockingContent}
 
-                    <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this._leftMenuFlyoutWidth ? 0 : this.propertiesWidth() - 1 }}>
+                    <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this.propertiesWidth() - 1 }}>
                         <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? "chevron-left" : "chevron-right"} color={this.colorScheme === ColorScheme.Dark ? Colors.WHITE : Colors.BLACK} size="sm" />
                     </div>
-                    <div className="properties-container">
+                    <div className="properties-container" style={{ width: this.propertiesWidth() }}>
                         {this.propertiesWidth() < 10 ? (null) : <PropertiesView styleProvider={DefaultStyleProvider} width={this.propertiesWidth()} height={this.propertiesHeight()} />}
                     </div>
                 </div>
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 9142a68ed..4238f6d29 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -13,6 +13,7 @@
         width: 100%;
         pointer-events: none;
         mix-blend-mode: multiply; // bcz: makes text fuzzy!
+        overflow: hidden;
     }
 
     .imageBox-fader {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index b927e98db..89f70985c 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -354,15 +354,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             }} >
             <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
                 renderDepth={this.props.renderDepth + 1}
-                isAnnotationOverlay={true}
                 fieldKey={this.annotationKey}
                 CollectionView={undefined}
+                isAnnotationOverlay={true}
                 annotationLayerHostsContent={true}
                 PanelWidth={this.props.PanelWidth}
                 PanelHeight={this.props.PanelHeight}
                 ScreenToLocalTransform={this.screenToLocalTransform}
-                scaling={returnOne}
                 select={emptyFunction}
+                scaling={returnOne}
                 whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                 removeDocument={this.removeDocument}
                 moveDocument={this.moveDocument}
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index cdd36eb3b..4871599b8 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -14,6 +14,9 @@
     height: 100%;
     position: relative;
     .videoBox-viewer {
+        display:flex;
+        flex-direction: column;
+        height: 100%;
         border-radius: inherit;
         opacity: 0.99;  // hack!  overcomes some kind of Chrome weirdness where buttons (e.g., snapshot) disappear at some point as the video is resized larger
     }
@@ -26,7 +29,17 @@
     .videoBox-stackPanel {
         z-index: -1;
         width: 100%;
-        position: absolute; 
+        position: relative; 
+    }
+
+    .videoBox-annotationLayer {
+        position: relative;
+        transform-origin: left top;
+        top: 0;
+        width: 100%;
+        pointer-events: none;
+        mix-blend-mode: multiply; // bcz: makes text fuzzy!
+        overflow: hidden;
     }
 }
 
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index e3ba638b3..440ccf638 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -550,7 +550,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     }
 
     @computed get annotationLayer() {
-        return <div className="imageBox-annotationLayer" style={{ transition: this.transition, height: `${this.heightPercent}%` }} ref={this._annotationLayer} />;
+        return <div className="videoBox-annotationLayer" style={{ transition: this.transition, height: `${this.heightPercent}%` }} ref={this._annotationLayer} />;
     }
 
     marqueeDown = (e: React.PointerEvent) => {
@@ -568,10 +568,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         this.props.select(true);
     });
 
+    @computed get fitWidth() { return this.props.docViewPath?.().lastElement().fitWidth; }
     contentFunc = () => [this.youtubeVideoId ? this.youtubeContent : this.content];
     scaling = () => this.props.scaling?.() || 1;
-    panelWidth = () => this.props.PanelWidth() * this.heightPercent / 100;
-    panelHeight = () => this.layoutDoc._fitWidth ? this.panelWidth() / (Doc.NativeAspect(this.rootDoc) || 1) : this.props.PanelHeight() * this.heightPercent / 100;
+    panelWidth = (): number => this.fitWidth ? this.props.PanelWidth() : (Doc.NativeAspect(this.rootDoc) || 1) * this.panelHeight();
+    panelHeight = (): number => this.fitWidth ? this.panelWidth() / (Doc.NativeAspect(this.rootDoc) || 1) : this.heightPercent / 100 * this.props.PanelHeight();
     screenToLocalTransform = () => {
         const offset = (this.props.PanelWidth() - this.panelWidth()) / 2 / this.scaling();
         return this.props.ScreenToLocalTransform().translate(-offset, 0).scale(100 / this.heightPercent);
@@ -585,10 +586,17 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         return (<div className="videoBox" onContextMenu={this.specificContextMenu} ref={this._mainCont}
             style={{
                 pointerEvents: this.props.layerProvider?.(this.layoutDoc) === false ? "none" : undefined,
-                borderRadius
+                borderRadius,
+                overflow: this.props.docViewPath?.().lastElement().fitWidth ? "auto" : undefined
             }} onWheel={e => { e.stopPropagation(); e.preventDefault(); }}>
             <div className="videoBox-viewer" onPointerDown={this.marqueeDown} >
-                <div style={{ position: "absolute", transition: this.transition, width: this.panelWidth(), height: this.panelHeight(), top: 0, left: `${(100 - this.heightPercent) / 2}%` }}>
+                <div style={{
+                    position: "absolute", transition: this.transition,
+                    width: this.panelWidth(),
+                    height: this.panelHeight(),
+                    top: 0,
+                    left: (this.props.PanelWidth() - this.panelWidth()) / 2
+                }}>
                     <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
                         renderDepth={this.props.renderDepth + 1}
                         fieldKey={this.annotationKey}
@@ -608,9 +616,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                         {this.contentFunc}
                     </CollectionFreeFormView>
                 </div>
-                {this.uIButtons}
                 {this.annotationLayer}
-                {this.renderTimeline}
                 {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
                     <MarqueeAnnotator
                         rootDoc={this.rootDoc}
@@ -625,6 +631,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                         annotationLayer={this._annotationLayer.current}
                         mainCont={this._mainCont.current}
                     />}
+                {this.renderTimeline}
+                {this.uIButtons}
             </div>
         </div >);
     }
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 8e6586735..37d716993 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -430,9 +430,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         if (!cm.findByDescription("Options...")) {
             !Doc.UserDoc().noviceMode && funcs.push({ description: (this.layoutDoc.useCors ? "Don't Use" : "Use") + " Cors", event: () => this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" });
             funcs.push({
-                description: (!this.layoutDoc.allowReflow ? "Allow" : "Prevent") + " Reflow", event: () => {
-                    const nw = !this.layoutDoc.allowReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.scaling?.() || 1);
-                    this.layoutDoc.allowReflow = !nw;
+                description: (!this.layoutDoc.forceReflow ? "Force" : "Prevent") + " Reflow", event: () => {
+                    const nw = !this.layoutDoc.forceReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.scaling?.() || 1);
+                    this.layoutDoc.forceReflow = !nw;
                     if (nw) {
                         Doc.SetInPlace(this.layoutDoc, this.fieldKey + "-nativeWidth", nw, true);
                     }
@@ -536,7 +536,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @computed get content() {
         const interactive = !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && this.props.pointerEvents !== "none" && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting;
         return <div className={"webBox-cont" + (interactive ? "-interactive" : "")}
-            style={{ width: !this.layoutDoc.allowReflow ? NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) || `100%` : "100%", }}>
+            style={{ width: !this.layoutDoc.forceReflow ? NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) || `100%` : "100%", }}>
             {this.urlContent}
         </div>;
     }
-- 
cgit v1.2.3-70-g09d2


From 5f95911a504a47c867198fccc32a75bf22d26056 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 29 Sep 2021 15:15:21 -0400
Subject: added snapping to close curve or to self-snap a vertex to its curve. 
 fixed ink decorations from being clipped when zoomed.  fixed crash with
 zero-length tangent

---
 src/client/views/InkControlPtHandles.tsx | 18 ++++++----
 src/client/views/InkStrokeProperties.ts  | 61 ++++++++++++++++++++++++++++++--
 src/client/views/InkingStroke.tsx        | 26 ++++----------
 src/client/views/MainView.tsx            |  2 +-
 4 files changed, 78 insertions(+), 29 deletions(-)

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

diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index f80aca268..249a0fa64 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -42,10 +42,13 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
             setupMoveUpEvents(this, e,
                 action((e: PointerEvent, down: number[], delta: number[]) => {
                     if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("drag ink ctrl pt");
-                    InkStrokeProperties.Instance?.moveControl(-delta[0] * screenScale, -delta[1] * screenScale, controlIndex);
+                    InkStrokeProperties.Instance?.moveControl(delta[0] * screenScale, delta[1] * screenScale, controlIndex);
                     return false;
                 }),
                 action(() => {
+                    if (this.controlUndo) {
+                        InkStrokeProperties.Instance?.snapControl(this.props.inkDoc, controlIndex);
+                    }
                     this.controlUndo?.end();
                     this.controlUndo = undefined;
                     UndoManager.FilterBatches(["data", "x", "y", "width", "height"]);
@@ -117,20 +120,21 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
         }
 
         const screenSpaceLineWidth = this.props.screenSpaceLineWidth;
+        const closed = inkData.lastElement().X === inkData[0].X && inkData.lastElement().Y === inkData[0].Y;
         const nearestScreenPt = this.props.nearestScreenPt();
         const TagType = (broken?: boolean) => broken ? "rect" : "circle";
         const hdl = (control: { X: number, Y: number, I: number }, scale: number, color: string) => {
             const broken = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number"))?.includes(control.I);
-            const Tag = TagType(broken) as keyof JSX.IntrinsicElements;
-            return <circle key={control.I.toString() + scale}
-                x={control.X - screenSpaceLineWidth * 3 * scale}
-                y={control.Y - screenSpaceLineWidth * 3 * scale}
+            const Tag = TagType((control.I === 0 || control.I === inkData.length - 1) && !closed) as keyof JSX.IntrinsicElements;
+            return <Tag key={control.I.toString() + scale}
+                x={control.X - screenSpaceLineWidth * 2 * scale}
+                y={control.Y - screenSpaceLineWidth * 2 * scale}
                 cx={control.X}
                 cy={control.Y}
                 r={screenSpaceLineWidth * 2 * scale}
                 opacity={this.controlUndo ? 0.15 : 1}
-                height={screenSpaceLineWidth * 6 * scale}
-                width={screenSpaceLineWidth * 6 * scale}
+                height={screenSpaceLineWidth * 4 * scale}
+                width={screenSpaceLineWidth * 4 * scale}
                 strokeWidth={screenSpaceLineWidth / 2}
                 stroke={Colors.MEDIUM_BLUE}
                 fill={broken ? Colors.MEDIUM_BLUE : color}
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 3770eb7c1..ac5cdfee2 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -189,7 +189,7 @@ export class InkStrokeProperties {
             const order = controlIndex % 4;
             const closed = ink.lastElement().X === ink[0].X && ink.lastElement().Y === ink[0].Y;
 
-            return ink.map((pt, i) => {
+            const newpts = ink.map((pt, i) => {
                 const leftHandlePoint = order === 0 && i === controlIndex + 1;
                 const rightHandlePoint = order === 0 && controlIndex !== 0 && i === controlIndex - 2;
                 if (controlIndex === i ||
@@ -201,12 +201,68 @@ export class InkStrokeProperties {
                     (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 1) ||
                     (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 2) ||
                     ((ink[0].X === ink[ink.length - 1].X) && (ink[0].Y === ink[ink.length - 1].Y) && (i === 0 || i === ink.length - 1) && (controlIndex === 0 || controlIndex === ink.length - 1))) {
-                    return ({ X: pt.X - deltaX / xScale, Y: pt.Y - deltaY / yScale });
+                    return ({ X: pt.X + deltaX / xScale, Y: pt.Y + deltaY / yScale });
                 }
                 return pt;
             });
+            return newpts;
         })
 
+
+    public static nearestPtToStroke(ctrlPoints: { X: number, Y: number }[], refPt: { X: number, Y: number }, excludeSegs?: number[]) {
+        var distance = Number.MAX_SAFE_INTEGER;
+        var nearestT = -1;
+        var nearestSeg = -1;
+        var nearestPt = { X: 0, Y: 0 };
+        for (var i = 0; i < ctrlPoints.length - 3; i += 4) {
+            if (excludeSegs?.includes(i)) continue;
+            const array = [ctrlPoints[i], ctrlPoints[i + 1], ctrlPoints[i + 2], ctrlPoints[i + 3]];
+            const point = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).project({ x: refPt.X, y: refPt.Y });
+            if (point.t !== undefined) {
+                const dist = Math.sqrt((point.x - refPt.X) * (point.x - refPt.X) + (point.y - refPt.Y) * (point.y - refPt.Y));
+                if (dist < distance) {
+                    distance = dist;
+                    nearestT = point.t;
+                    nearestSeg = i;
+                    nearestPt = { X: point.x, Y: point.y };
+                }
+            }
+        }
+        return { distance, nearestT, nearestSeg, nearestPt };
+    }
+
+    /**
+     * Handles the movement/scaling of a control point.
+     */
+    snapControl = (inkDoc: Doc, controlIndex: number) => {
+        const ink = Cast(inkDoc.data, InkField)?.inkData;
+        if (ink) {
+            const closed = ink.lastElement().X === ink[0].X && ink.lastElement().Y === ink[0].Y;
+
+            // figure out which segments we don't want to snap to - avoid the dragged control point's segment and the next and prev segments (when they exist -- ie not for endpoints of unclosed curve)
+            const thisseg = Math.floor(controlIndex / 4) * 4;
+            const which = controlIndex % 4;
+            const nextseg = which > 1 && (closed || controlIndex < ink.length - 1) ? (thisseg + 4) % ink.length : -1;
+            const prevseg = which < 2 && (closed || controlIndex > 0) ? (thisseg - 4 + ink.length) % ink.length : -1;
+            const refPt = ink[controlIndex];
+            const { nearestPt } = InkStrokeProperties.nearestPtToStroke(ink, refPt, [thisseg, prevseg, nextseg]);
+
+            // nearestPt is in inkDoc coordinates -- we need to compute the distance in screen coordinates.
+            // so we scale the X & Y distances by the internal ink scale factor and then transform the final distance by the ScreenToLocal.Scale of the inkDoc itself.
+            const oldXrange = (xs => ({ coord: NumCast(inkDoc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X));
+            const oldYrange = (ys => ({ coord: NumCast(inkDoc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y));
+            const ptsXscale = NumCast(inkDoc._width) / (oldXrange.max - oldXrange.min);
+            const ptsYscale = NumCast(inkDoc._height) / (oldYrange.max - oldYrange.min);
+            const near = Math.sqrt((nearestPt.X - refPt.X) * (nearestPt.X - refPt.X) * ptsXscale * ptsXscale +
+                (nearestPt.Y - refPt.Y) * (nearestPt.Y - refPt.Y) * ptsYscale * ptsYscale);
+
+            if (near / (this.selectedInk?.lastElement().props.ScreenToLocalTransform().Scale || 1) < 10) {
+                return this.moveControl((nearestPt.X - ink[controlIndex].X) * ptsXscale, (nearestPt.Y - ink[controlIndex].Y) * ptsYscale, controlIndex)
+            }
+        }
+        return false;
+    }
+
     /**
      * Snaps a control point with broken tangency back to synced rotation.
      * @param handleIndexA The handle point that retains its current position.
@@ -247,6 +303,7 @@ export class InkStrokeProperties {
     angleBetweenTwoVectors = (vectorA: PointData, vectorB: PointData) => {
         const magnitudeA = Math.sqrt(vectorA.X * vectorA.X + vectorA.Y * vectorA.Y);
         const magnitudeB = Math.sqrt(vectorB.X * vectorB.X + vectorB.Y * vectorB.Y);
+        if (magnitudeA === 0 || magnitudeB === 0) return 0;
         // Normalizing the vectors.
         vectorA = { X: vectorA.X / magnitudeA, Y: vectorA.Y / magnitudeA };
         vectorB = { X: vectorB.X / magnitudeB, Y: vectorB.Y / magnitudeB };
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 4cd7f7698..8b1b3ea32 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -130,25 +130,14 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint(
             (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
             (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] }));
-        var nearest = Number.MAX_SAFE_INTEGER;
-        this._nearestT = -1;
-        this._nearestSeg = -1;
-        this._nearestScrPt = { X: 0, Y: 0 };
-        for (var i = 0; i < screenPts.length - 3; i += 4) {
-            const array = [screenPts[i], screenPts[i + 1], screenPts[i + 2], screenPts[i + 3]];
-            const point = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).project({ x: e.clientX, y: e.clientY });
-            if (point.t) {
-                const dist = (point.x - e.clientX) * (point.x - e.clientX) + (point.y - e.clientY) * (point.y - e.clientY);
-                if (dist < nearest) {
-                    nearest = dist;
-                    this._nearestT = point.t;
-                    this._nearestSeg = i;
-                    this._nearestScrPt = { X: point.x, Y: point.y };
-                }
-            }
-        }
+        const { distance, nearestT, nearestSeg, nearestPt } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY });
+
+        this._nearestT = nearestT;
+        this._nearestSeg = nearestSeg;
+        this._nearestScrPt = nearestPt;
     }
 
+
     nearestScreenPt = () => this._nearestScrPt;
     componentUI = (boundsLeft: number, boundsTop: number) => {
         const inkDoc = this.props.Document;
@@ -159,11 +148,10 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint(
             (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
             (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] }));
-        const screenOrigin = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
         const screenHdlPts = screenPts;
 
         return <div className="inkstroke-UI" style={{
-            clip: `rect(${boundsTop - screenOrigin[1]}px, 10000px, 10000px, ${boundsLeft - screenOrigin[0]}px)`
+            clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)`
         }} >
             {InteractionUtils.CreatePolyline(screenPts, 0, 0, Colors.MEDIUM_BLUE, screenInkWidth[0], screenSpaceCenterlineStrokeWidth,
                 StrCast(inkDoc.strokeBezier), StrCast(inkDoc.fillColor, "none"),
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 3f7df705f..6d0d5eb39 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -618,7 +618,7 @@ export class MainView extends React.Component {
             <GroupManager />
             <GoogleAuthenticationManager />
             <DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} />
-            <ComponentDecorations boundsLeft={0} boundsTop={this.topOfMainDocContent} />
+            <ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
             {this.topbar}
             {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
             {DocumentLinksButton.LinkEditorDocView ? <LinkMenu docView={DocumentLinksButton.LinkEditorDocView} changeFlyout={emptyFunction} /> : (null)}
-- 
cgit v1.2.3-70-g09d2

