From 2e76877dc1c9c5b1c226f5bd0394d17cabfec0b4 Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Mon, 17 May 2021 18:01:44 -0400
Subject: trying dynamic off screen docs

---
 .../views/collections/CollectionDockingView.tsx    | 43 ++++++++++++++--------
 1 file changed, 28 insertions(+), 15 deletions(-)

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 388f9a909..819667834 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -4,7 +4,7 @@ import { action, IReactionDisposer, observable, reaction, runInAction } from "mo
 import { observer } from "mobx-react";
 import * as ReactDOM from 'react-dom';
 import * as GoldenLayout from "../../../client/goldenLayout";
-import { Doc, DocListCast, Opt, DocListCastAsync } from "../../../fields/Doc";
+import { Doc, DocListCast, Opt, DocListCastAsync, DataSym } from "../../../fields/Doc";
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
@@ -24,6 +24,7 @@ import React = require("react");
 import { DocumentType } from '../../documents/DocumentTypes';
 import { listSpec } from '../../../fields/Schema';
 import { LightboxView } from '../LightboxView';
+import { inheritParentAcls } from '../../../fields/util';
 const _global = (window /* browser */ || global /* node */) as any;
 
 @observer
@@ -160,6 +161,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         }
         const instance = CollectionDockingView.Instance;
         if (!instance) return false;
+        else Doc.AddDocToList(instance.props.Document[DataSym], "data-all", document);
         const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName);
 
         if (!pullSide && stack) {
@@ -381,15 +383,22 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         setTimeout(async () => {
             const sublists = await DocListCastAsync(this.props.Document[this.props.fieldKey]);
             const tabs = sublists && Cast(sublists[0], Doc, null);
-            const other = sublists && Cast(sublists[1], Doc, null);
+            // const other = sublists && Cast(sublists[1], Doc, null);
             const tabdocs = await DocListCastAsync(tabs?.data);
-            const otherdocs = await DocListCastAsync(other?.data);
-            tabs && (Doc.GetProto(tabs).data = new List<Doc>(docs));
-            const otherSet = new Set<Doc>();
-            otherdocs?.filter(doc => !docs.includes(doc)).forEach(doc => otherSet.add(doc));
-            tabdocs?.filter(doc => !docs.includes(doc) && doc.type !== DocumentType.KVP).forEach(doc => otherSet.add(doc));
-            const vals = Array.from(otherSet.values()).filter(val => val instanceof Doc).map(d => d).filter(d => d.type !== DocumentType.KVP);
-            other && (Doc.GetProto(other).data = new List<Doc>(vals));
+            // const otherdocs = await DocListCastAsync(other?.data);
+            if (tabs) {
+                tabs.data = new List<Doc>(docs);
+                // DocListCast(tabs.aliases).forEach(tab => tab !== tabs && (tab.data = new List<Doc>(docs)));
+            }
+            // const otherSet = new Set<Doc>();
+            // otherdocs?.filter(doc => !docs.includes(doc)).forEach(doc => otherSet.add(doc));
+            // tabdocs?.filter(doc => !docs.includes(doc) && doc.type !== DocumentType.KVP).forEach(doc => otherSet.add(doc));
+            // const vals = Array.from(otherSet.values()).filter(val => val instanceof Doc).map(d => d).filter(d => d.type !== DocumentType.KVP);
+            // this.props.Document[DataSym][this.props.fieldKey + "-all"] = new List<Doc>([...docs, ...vals]);
+            // if (other) {
+            //     other.data = new List<Doc>(vals);
+            //     // DocListCast(other.aliases).forEach(tab => tab !== other && (tab.data = new List<Doc>(vals)));
+            // }
         }, 0);
     }
 
@@ -399,7 +408,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele));
     }
     tabCreated = (tab: any) => {
-        tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab?.(tab);  // have to explicitly initialize tabs that reuse contents from previous abs (ie, when dragging a tab around a new tab is created for the old content)
+        tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab?.(tab);  // have to explicitly initialize tabs that reuse contents from previous tabs (ie, when dragging a tab around a new tab is created for the old content)
     }
 
     stackCreated = (stack: any) => {
@@ -407,9 +416,11 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
             if (e.target === stack.header?.element[0] && e.button === 2) {
                 const emptyPane = CurrentUserUtils.EmptyPane;
                 emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
-                CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], {
-                    _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _fitWidth: true, title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`
-                }), "", stack);
+                const docToAdd = Docs.Create.FreeformDocument([], {
+                    _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _fitWidth: true, title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`,
+                });
+                this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd);
+                CollectionDockingView.AddSplit(docToAdd, "", stack);
             }
         });
 
@@ -430,9 +441,11 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
                 // stack.config.fixed = !stack.config.fixed;  // force the stack to have a fixed size
                 const emptyPane = CurrentUserUtils.EmptyPane;
                 emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
-                CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], {
+                const docToAdd = Docs.Create.FreeformDocument([], {
                     _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _fitWidth: true, title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`
-                }), "", stack);
+                });
+                this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd);
+                CollectionDockingView.AddSplit(docToAdd, "", stack);
             }));
     }
 
-- 
cgit v1.2.3-70-g09d2


From 77c16aa9f00a9d1b5e31ddf5a68d56f183f13691 Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Thu, 20 May 2021 12:47:46 -0400
Subject: dashboard sharing, aliases now work? needs more testing

---
 src/client/util/CurrentUserUtils.ts                   | 19 +++++++++++++------
 .../views/collections/CollectionDockingView.tsx       |  6 +++++-
 2 files changed, 18 insertions(+), 7 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index a8327ae93..e143887fa 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1255,12 +1255,20 @@ Scripting.addGlobal(function shareDashboard(dashboard: Doc) {
     SharingManager.Instance.open(undefined, dashboard);
 },
     "opens sharing dialog for Dashboard");
-Scripting.addGlobal(function addToDashboards(dashboard: Doc) {
+Scripting.addGlobal(async function addToDashboards(dashboard: Doc) {
     const dashboardAlias = Doc.MakeAlias(dashboard);
+    const dockingConfig = JSON.parse(StrCast(dashboardAlias.dockingConfig));
+    dockingConfig.content = [];
+    dashboardAlias.dockingConfig = JSON.stringify(dockingConfig);
+
+    const allDocs = await DocListCastAsync(dashboard[DataSym]["data-all"]);
+
+    dashboardAlias["data-all"] = new List<Doc>((allDocs || []).map(doc => Doc.MakeAlias(doc)));
+
     dashboardAlias.data = new List<Doc>(DocListCast(dashboard.data).map(tabFolder => Doc.MakeAlias(tabFolder)));
-    // DocListCast(dashboardAlias.data).forEach(tabFolder => {
-    //     tabFolder.data = new List<Doc>(DocListCast(tabFolder.data).map(tab => Doc.MakeAlias(tab)));
-    // });
+    DocListCast(dashboardAlias.data).forEach(doc => doc.dashboard = dashboardAlias);
+    DocListCast(dashboardAlias.data)[0].data = new List<Doc>();
+    DocListCast(dashboardAlias.data)[1].data = ComputedField.MakeFunction(`dynamicOffScreenDocs(self.dashboard)`) as any;
     Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", dashboardAlias);
     CurrentUserUtils.openDashboard(Doc.UserDoc(), dashboardAlias);
 },
@@ -1268,13 +1276,12 @@ Scripting.addGlobal(function addToDashboards(dashboard: Doc) {
 
 Scripting.addGlobal(function dynamicOffScreenDocs(dashboard: Doc) {
     if (dashboard[DataSym] instanceof Doc) {
-        const allDocs = DocListCast(dashboard[DataSym]["data-all"]);
+        const allDocs = DocListCast(dashboard["data-all"]);
         console.log(allDocs);
         const onScreenTab = DocListCast(dashboard.data)[0];
         const onScreenDocs = DocListCast(onScreenTab.data);
         return new List<Doc>(allDocs.reduce((result: Doc[], doc) => {
             !onScreenDocs.includes(doc) && (result.push(doc));
-            // console.log(doc);
             return result;
         }, []));
     }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 819667834..235fe8950 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -161,7 +161,11 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         }
         const instance = CollectionDockingView.Instance;
         if (!instance) return false;
-        else Doc.AddDocToList(instance.props.Document[DataSym], "data-all", document);
+        else {
+            const docList = DocListCast(instance.props.Document[DataSym]["data-all"]);
+            !docList.includes(document) && !docList.includes(document.aliasOf as Doc) && Doc.AddDocToList(instance.props.Document[DataSym], "data-all", document);
+            DocListCast(instance.props.Document[DataSym]["aliases"]).forEach(alias => !alias.aliasOf && alias !== instance.props.Document && Doc.AddDocToList(alias, "data-all", Doc.MakeAlias(document)));
+        }
         const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName);
 
         if (!pullSide && stack) {
-- 
cgit v1.2.3-70-g09d2


From b85e7819ae46558a659cac73817be0b812d6abbe Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Mon, 31 May 2021 20:18:19 -0400
Subject: sharing dashboard works? pending fixing remnant on screen tabs

---
 src/client/documents/Documents.ts                  | 17 +++---------
 src/client/util/CurrentUserUtils.ts                | 30 +++++++++++++++++++---
 .../views/collections/CollectionDockingView.tsx    |  9 ++++++-
 3 files changed, 38 insertions(+), 18 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 6fb6f70b3..70541b559 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -648,9 +648,8 @@ export namespace Docs {
          * constructor just generates a new GUID. This is currently used
          * only when creating a DockDocument from the current user's already existing
          * main document.
-         * @param layoutData whether the fieldKey field on the layout doc should store the data or the data doc
          */
-        function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = "data", protoId?: string, layoutData?: boolean) {
+        function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = "data", protoId?: string) {
             const viewKeys = ["x", "y", "system"]; // keys that should be addded to the view document even though they don't begin with an "_"
             const { omit: dataProps, extract: viewProps } = OmitKeys(options, viewKeys, "^_");
 
@@ -662,16 +661,8 @@ export namespace Docs {
             dataProps["acl-Override"] = "None";
             dataProps["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Add;
 
-            if (layoutData) {
-                viewProps[fieldKey] = data;
-                const list = new List<Doc>();
-                console.log(DocListCast(data));
-                DocListCast(data).forEach(doc => {
-                    list.push(...DocListCast(doc.data));
-                });
-                dataProps[fieldKey + "-all"] = list;
-            }
-            else dataProps[fieldKey] = data;
+            dataProps[fieldKey] = data;
+
             // so that the list of annotations is already initialised, prevents issues in addonly.
             // without this, if a doc has no annotations but the user has AddOnly privileges, they won't be able to add an annotation because they would have needed to create the field's list which they don't have permissions to do.
             dataProps[fieldKey + "-annotations"] = new List<Doc>();
@@ -902,7 +893,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 });
             const all = TreeDocument([], { title: "Off-Screen Tabs", childDontRegisterViews: true, freezeChildren: "add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", system: 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, undefined, undefined, 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);
         }
 
         export function DirectoryImportDocument(options: DocumentOptions = {}) {
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index e143887fa..90a2378fe 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1187,6 +1187,22 @@ export class CurrentUserUtils {
         const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
         const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, "row"); // add isFolder:true here?
         freeformDoc.context = dashboardDoc;
+
+        // switching the tabs from the datadoc to the regular doc
+        const dashboardTabs = dashboardDoc[DataSym].data;
+        dashboardDoc[DataSym].data = new List<Doc>();
+        dashboardDoc.data = dashboardTabs;
+
+        // collating all docs on the dashboard to make a data-all field
+        const allDocs = new List<Doc>();
+        const allDocs2 = new List<Doc>(); // Array.from, spread, splice all cause so stack or acl issues for some reason
+        DocListCast(dashboardTabs).forEach(doc => {
+            const tabDocs = DocListCast(doc.data);
+            allDocs.push(...tabDocs);
+            allDocs2.push(...tabDocs);
+        });
+        dashboardDoc[DataSym]["data-all"] = allDocs;
+        dashboardDoc["data-all"] = allDocs2;
         DocListCast(dashboardDoc.data).forEach(doc => doc.dashboard = dashboardDoc);
         DocListCast(dashboardDoc.data)[1].data = ComputedField.MakeFunction(`dynamicOffScreenDocs(self.dashboard)`) as any;
 
@@ -1257,14 +1273,18 @@ Scripting.addGlobal(function shareDashboard(dashboard: Doc) {
     "opens sharing dialog for Dashboard");
 Scripting.addGlobal(async function addToDashboards(dashboard: Doc) {
     const dashboardAlias = Doc.MakeAlias(dashboard);
-    const dockingConfig = JSON.parse(StrCast(dashboardAlias.dockingConfig));
-    dockingConfig.content = [];
-    dashboardAlias.dockingConfig = JSON.stringify(dockingConfig);
 
     const allDocs = await DocListCastAsync(dashboard[DataSym]["data-all"]);
 
+    // moves the data-all field from the datadoc to the layoutdoc, necessary for off screen docs tab to function properly
+    dashboard["data-all"] = new List<Doc>(allDocs);
     dashboardAlias["data-all"] = new List<Doc>((allDocs || []).map(doc => Doc.MakeAlias(doc)));
 
+    const dockingConfig = JSON.parse(StrCast(dashboardAlias.dockingConfig));
+    dockingConfig.content = [];
+    dashboardAlias.dockingConfig = JSON.stringify(dockingConfig);
+
+
     dashboardAlias.data = new List<Doc>(DocListCast(dashboard.data).map(tabFolder => Doc.MakeAlias(tabFolder)));
     DocListCast(dashboardAlias.data).forEach(doc => doc.dashboard = dashboardAlias);
     DocListCast(dashboardAlias.data)[0].data = new List<Doc>();
@@ -1274,10 +1294,12 @@ Scripting.addGlobal(async function addToDashboards(dashboard: Doc) {
 },
     "adds Dashboard to set of Dashboards");
 
+/**
+ * Dynamically computes which docs should be rendered in the off-screen tabs tree of a dashboard.
+ */
 Scripting.addGlobal(function dynamicOffScreenDocs(dashboard: Doc) {
     if (dashboard[DataSym] instanceof Doc) {
         const allDocs = DocListCast(dashboard["data-all"]);
-        console.log(allDocs);
         const onScreenTab = DocListCast(dashboard.data)[0];
         const onScreenDocs = DocListCast(onScreenTab.data);
         return new List<Doc>(allDocs.reduce((result: Doc[], doc) => {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 235fe8950..2e44b65e6 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -163,8 +163,15 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
         if (!instance) return false;
         else {
             const docList = DocListCast(instance.props.Document[DataSym]["data-all"]);
+            // adds the doc of the newly created tab to the data-all field if it doesn't already include that doc or one of its aliases
             !docList.includes(document) && !docList.includes(document.aliasOf as Doc) && Doc.AddDocToList(instance.props.Document[DataSym], "data-all", document);
-            DocListCast(instance.props.Document[DataSym]["aliases"]).forEach(alias => !alias.aliasOf && alias !== instance.props.Document && Doc.AddDocToList(alias, "data-all", Doc.MakeAlias(document)));
+            // adds an alias of the doc to the data-all field of the layoutdocs of the aliases
+            DocListCast(instance.props.Document[DataSym].aliases).forEach(alias => {
+                const aliasDocList = DocListCast(alias["data-all"]);
+                // if aliasDocList contains the alias, don't do anything
+                // otherwise add the original or an alias depending on whether the doc you're looking at is the current doc or a different alias
+                !DocListCast(document.aliases).some(a => aliasDocList.includes(a)) && Doc.AddDocToList(alias, "data-all", alias !== instance.props.Document ? Doc.MakeAlias(document) : document);
+            });
         }
         const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName);
 
-- 
cgit v1.2.3-70-g09d2


From b33e45f1f839b3c6eaf1076e605abacd1bc6883c Mon Sep 17 00:00:00 2001
From: geireann <geireann.lindfield@gmail.com>
Date: Thu, 29 Jul 2021 15:35:39 -0400
Subject: lots of updates!

---
 src/client/util/SettingsManager.tsx                |  79 ++++++--
 src/client/views/AntimodeMenu.scss                 |   2 +-
 src/client/views/MainView.scss                     |  27 ---
 src/client/views/MainView.tsx                      |  61 ++----
 src/client/views/_nodeModuleOverrides.scss         |  52 ++++-
 .../views/collections/CollectionDockingView.scss   |  98 +++++++---
 .../views/collections/CollectionDockingView.tsx    |   2 +-
 src/client/views/collections/TabDocView.scss       |  59 +++++-
 src/client/views/collections/TabDocView.tsx        | 107 +++++++----
 src/client/views/global/globalCssVariables.scss    |   4 +-
 src/client/views/global/globalEnums.tsx            |   4 +
 src/client/views/topbar/TopBar.scss                | 211 +++++++++++++++++++++
 src/client/views/topbar/TopBar.tsx                 |  58 ++++++
 src/fields/Doc.ts                                  |   6 +-
 14 files changed, 618 insertions(+), 152 deletions(-)
 create mode 100644 src/client/views/topbar/TopBar.scss
 create mode 100644 src/client/views/topbar/TopBar.tsx

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

diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 777394b05..3987497b8 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -18,6 +18,12 @@ const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
 
+export enum ColorScheme {
+    Dark = "Dark",
+    Light = "Light",
+    System = "Match System"
+}
+
 @observer
 export class SettingsManager extends React.Component<{}> {
     public static Instance: SettingsManager;
@@ -32,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; }
 
     constructor(props: {}) {
         super(props);
@@ -69,6 +75,28 @@ export class SettingsManager extends React.Component<{}> {
         else DocServer.Control.makeEditable();
     });
 
+    @undoBatch
+    @action
+    changeColorScheme = action((e: React.ChangeEvent) => {
+        const scheme: ColorScheme = (e.currentTarget as any).value;
+        switch (scheme) {
+            case ColorScheme.Light:
+                Doc.UserDoc().colorScheme = ColorScheme.Light;
+                addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "#d3d3d3 !important" });
+                break;
+            case ColorScheme.Dark:
+                Doc.UserDoc().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;
+                });
+                break;
+        }
+    });
+
+
     @computed get colorsContent() {
         const colorBox = (func: (color: ColorState) => void) => <SketchPicker onChange={func} color={StrCast(this.backgroundColor)}
             presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505',
@@ -90,8 +118,7 @@ export class SettingsManager extends React.Component<{}> {
             </Flyout>
         </div>;
 
-        const fontFamilies = ["Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"];
-        const fontSizes = ["7px", "8px", "9px", "10px", "12px", "14px", "16px", "18px", "20px", "24px", "32px", "48px", "72px"];
+        const colorSchemes = [ColorScheme.Light, ColorScheme.Dark, ColorScheme.System];
 
         return <div className="colors-content">
             <div className="preferences-color">
@@ -102,14 +129,11 @@ export class SettingsManager extends React.Component<{}> {
                 <div className="preferences-color-text">Border/Header Color</div>
                 {userColorFlyout}
             </div>
-            <div className="preferences-font">
-                <div className="preferences-font-text">Default Font</div>
-                <div className="preferences-font-controls">
-                    <select className="size-select" onChange={this.changeFontSize} value={StrCast(Doc.UserDoc().fontSize, "7px")}>
-                        {fontSizes.map(size => <option key={size} value={size} defaultValue={StrCast(Doc.UserDoc().fontSize)}> {size} </option>)}
-                    </select>
-                    <select className="font-select" onChange={this.changeFontFamily} value={StrCast(Doc.UserDoc().fontFamily, "Times New Roman")} >
-                        {fontFamilies.map(font => <option key={font} value={font} defaultValue={StrCast(Doc.UserDoc().fontFamily)}> {font} </option>)}
+            <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>
                 </div>
             </div>
@@ -132,6 +156,16 @@ export class SettingsManager extends React.Component<{}> {
                     checked={BoolCast(Doc.UserDoc()._raiseWhenDragged)} />
                 <div className="preferences-check">Raise on drag</div>
             </div>
+            <div>
+                <input type="checkbox" onChange={e => Doc.UserDoc()._showLabel = !Doc.UserDoc()._showLabel}
+                    checked={BoolCast(Doc.UserDoc()._showLabel)} />
+                <div className="preferences-check">Show tool button labels</div>
+            </div>
+            <div>
+                <input type="checkbox" onChange={e => Doc.UserDoc()._showMenuLabel = !Doc.UserDoc()._showMenuLabel}
+                    checked={BoolCast(Doc.UserDoc()._showMenuLabel)} />
+                <div className="preferences-check">Show menu button labels</div>
+            </div>
         </div>;
     }
 
@@ -149,6 +183,27 @@ export class SettingsManager extends React.Component<{}> {
         </div>;
     }
 
+    @computed get textContent() {
+
+        const fontFamilies = ["Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text", "Roboto"];
+        const fontSizes = ["7px", "8px", "9px", "10px", "12px", "14px", "16px", "18px", "20px", "24px", "32px", "48px", "72px"];
+
+        return (
+            <div className="tab-content appearances-content">
+                <div className="preferences-font">
+                    <div className="preferences-font-text">Default Font</div>
+                    <div className="preferences-font-controls">
+                        <select className="size-select" onChange={this.changeFontSize} value={StrCast(Doc.UserDoc().fontSize, "7px")}>
+                            {fontSizes.map(size => <option key={size} value={size} defaultValue={StrCast(Doc.UserDoc().fontSize)}> {size} </option>)}
+                        </select>
+                        <select className="font-select" onChange={this.changeFontFamily} value={StrCast(Doc.UserDoc().fontFamily, "Times New Roman")} >
+                            {fontFamilies.map(font => <option key={font} value={font} defaultValue={StrCast(Doc.UserDoc().fontFamily)}> {font} </option>)}
+                        </select>
+                    </div>
+                </div>
+            </div>);
+    }
+
     @action
     changeVal = (e: React.ChangeEvent, pass: string) => {
         const value = (e.target as any).value;
@@ -228,7 +283,7 @@ export class SettingsManager extends React.Component<{}> {
         // { title: "Accounts", ele: this.accountsContent }, { title: "Preferences", ele: this.preferencesContent }];
 
         const tabs = [{ title: "Accounts", ele: this.accountsContent }, { title: "Modes", ele: this.modesContent },
-        { title: "Appearance", ele: this.appearanceContent }];
+        { title: "Appearance", ele: this.appearanceContent }, { title: "Text", ele: this.textContent }];
 
         return <div className="settings-interface">
             <div className="settings-panel">
diff --git a/src/client/views/AntimodeMenu.scss b/src/client/views/AntimodeMenu.scss
index 2bac03af4..b509f9f54 100644
--- a/src/client/views/AntimodeMenu.scss
+++ b/src/client/views/AntimodeMenu.scss
@@ -6,7 +6,7 @@
     z-index: 10001;
     height: $antimodemenu-height;
     background: $dark-gray;
-    box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
+    // box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
     // border-radius: 0px 6px 6px 6px;
     z-index: 1001;
     display: flex;
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 07ca0257c..2069986ad 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -419,31 +419,4 @@
     display: block;
     width: 500px;
     height: 1000px;
-}
-
-.lm_drag_tab {
-    padding: 0;
-    width: 15px !important;
-    height: 15px !important;
-    position: relative !important;
-    display: inline-flex !important;
-    align-items: center;
-    top: 0 !important;
-    right: unset !important;
-    left: 0 !important;
-}
-.lm_close_tab {
-    padding: 0;
-    width: 15px !important;
-    height: 15px !important;
-    position: relative !important;
-    display: inline-flex !important;
-    align-items: center;
-    top: 0 !important;
-    right: unset !important;
-    left: 0 !important;
-}
-.lm_tab, .lm_tab_active {
-    display: flex !important;
-    padding-right: 0 !important;
 }
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f34851b00..7d6bfbd40 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -63,6 +63,7 @@ import { PreviewCursor } from './PreviewCursor';
 import { PropertiesView } from './PropertiesView';
 import { SearchBox } from './search/SearchBox';
 import { DefaultStyleProvider, DashboardStyleProvider, StyleProp } from './StyleProvider';
+import { TopBar } from './topbar/TopBar';
 const _global = (window /* browser */ || global /* node */) as any;
 
 @observer
@@ -78,7 +79,7 @@ export class MainView extends React.Component {
     @observable private _sidebarContent: any = this.userDoc?.sidebar;
     @observable private _flyoutWidth: number = 0;
 
-    @computed private get topOffset() { return (CollectionMenu.Instance?.Pinned ? 35 : 0) + Number(SEARCH_PANEL_HEIGHT.replace("px", "")); }
+    @computed private get topOffset() { return Number(SEARCH_PANEL_HEIGHT.replace("px", "")); } //TODO remove
     @computed private get leftOffset() { return this.menuPanelWidth() - 2; }
     @computed private get userDoc() { return Doc.UserDoc(); }
     @computed private get darkScheme() { return BoolCast(CurrentUserUtils.ActiveDashboard?.darkScheme); }
@@ -180,8 +181,8 @@ export class MainView extends React.Component {
             const targClass = targets[0].className.toString();
             if (SearchBox.Instance._searchbarOpen || SearchBox.Instance.open) {
                 const check = targets.some((thing) =>
-                (thing.className === "collectionSchemaView-searchContainer" || (thing as any)?.dataset.icon === "filter" ||
-                    thing.className === "collectionSchema-header-menuOptions"));
+                    (thing.className === "collectionSchemaView-searchContainer" || (thing as any)?.dataset.icon === "filter" ||
+                        thing.className === "collectionSchema-header-menuOptions"));
                 !check && SearchBox.Instance.resetSearch(true);
             }
             !targClass.includes("contextMenu") && ContextMenu.Instance.closeMenu();
@@ -242,8 +243,9 @@ export class MainView extends React.Component {
     }
 
     getPWidth = () => this._panelWidth - this.propertiesWidth();
-    getPHeight = () => this._panelHeight;
+    getPHeight = () => this._panelHeight - (CollectionMenu.Instance?.Pinned ? 35 : 0);
     getContentsHeight = () => this._panelHeight;
+    getMenuPanelHeight = () => this._panelHeight + (CollectionMenu.Instance?.Pinned ? 35 : 0);
 
     @computed get mainDocView() {
         return <DocumentView key="main"
@@ -275,10 +277,12 @@ export class MainView extends React.Component {
 
     @computed get dockingContent() {
         return <div key="docking" className={`mainContent-div${this._flyoutWidth ? "-flyout" : ""}`} onDrop={e => { e.stopPropagation(); e.preventDefault(); }}
+            // style={{ minWidth: `calc(100% - ${this._flyoutWidth + this.menuPanelWidth() + this.propertiesWidth()}px)`, width: `calc(100% - ${this._flyoutWidth + this.propertiesWidth()}px)` }}>
+            // FIXME update with property panel width
             style={{
                 minWidth: `calc(100% - ${this._flyoutWidth + this.menuPanelWidth() + this.propertiesWidth()}px)`,
                 transform: LightboxView.LightboxDoc ? "scale(0.0001)" : undefined,
-                width: `calc(100% - ${this._flyoutWidth + this.menuPanelWidth() + this.propertiesWidth()}px)`
+                //TODO:glr width: `calc(100% - ${this._flyoutWidth + this.menuPanelWidth() + this.propertiesWidth()}px)`
             }}>
             {!this.mainContainer ? (null) : this.mainDocView}
         </div>;
@@ -358,7 +362,7 @@ export class MainView extends React.Component {
                 removeDocument={returnFalse}
                 ScreenToLocalTransform={this.sidebarScreenToLocal}
                 PanelWidth={this.menuPanelWidth}
-                PanelHeight={this.getContentsHeight}
+                PanelHeight={this.getMenuPanelHeight}
                 renderDepth={0}
                 docViewPath={returnEmptyDoclist}
                 focus={DocUtils.DefaultFocus}
@@ -405,16 +409,19 @@ export class MainView extends React.Component {
             {this.menuPanel}
             <div key="inner" className={`mainView-innerContent${this.darkScheme ? "-dark" : ""}`}>
                 {this.flyout}
-                <div className="mainView-libraryHandle" style={{ display: !this._flyoutWidth ? "none" : undefined, }} onPointerDown={this.onFlyoutPointerDown} >
+                <div className="mainView-libraryHandle" style={{ display: !this._flyoutWidth ? "none" : undefined, left: this._flyoutWidth ? (this._flyoutWidth + this.menuPanelWidth() - 10) : -10 }} onPointerDown={this.onFlyoutPointerDown} >
                     <FontAwesomeIcon icon="chevron-left" color={this.darkScheme ? "white" : "black"} style={{ opacity: "50%" }} size="sm" />
                 </div>
+                <div className="mainView-innerContainer" style={{ width: `calc(100% - ${this.propertiesWidth() + this._flyoutWidth + this.menuPanelWidth()}px)` }}>
+                    <CollectionMenu />
 
-                {this.dockingContent}
+                    {this.dockingContent}
 
-                <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.darkScheme ? "white" : "black"} size="sm" />
+                    <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.darkScheme ? "white" : "black"} size="sm" />
+                    </div>
+                    {this.propertiesWidth() < 10 ? (null) : <PropertiesView styleProvider={DefaultStyleProvider} width={this.propertiesWidth()} height={this.getContentsHeight()} />}
                 </div>
-                {this.propertiesWidth() < 10 ? (null) : <PropertiesView styleProvider={DefaultStyleProvider} width={this.propertiesWidth()} height={this.getContentsHeight()} />}
             </div>
         </>;
     }
@@ -525,35 +532,8 @@ export class MainView extends React.Component {
 
     @computed get search() {
         TraceMobx();
-        return <div className="mainView-searchPanel">
-            <SearchBox Document={CurrentUserUtils.MySearchPanelDoc}
-                DataDoc={CurrentUserUtils.MySearchPanelDoc}
-                fieldKey="data"
-                dropAction="move"
-                isSelected={returnTrue}
-                isContentActive={returnTrue}
-                select={returnTrue}
-                setHeight={returnFalse}
-                addDocument={undefined}
-                addDocTab={this.addDocTabFunc}
-                pinToPres={emptyFunction}
-                rootSelected={returnTrue}
-                styleProvider={DefaultStyleProvider}
-                layerProvider={undefined}
-                removeDocument={undefined}
-                ScreenToLocalTransform={Transform.Identity}
-                PanelWidth={this.getPWidth}
-                PanelHeight={this.getPHeight}
-                renderDepth={0}
-                focus={DocUtils.DefaultFocus}
-                docViewPath={returnEmptyDoclist}
-                whenChildContentsActiveChanged={emptyFunction}
-                bringToFront={emptyFunction}
-                docFilters={returnEmptyFilter}
-                docRangeFilters={returnEmptyFilter}
-                searchFilterDocs={returnEmptyDoclist}
-                ContainingCollectionView={undefined}
-                ContainingCollectionDoc={undefined} />
+        return <div className="mainView-topbar">
+            <TopBar />
         </div>;
     }
 
@@ -605,7 +585,6 @@ export class MainView extends React.Component {
             <GoogleAuthenticationManager />
             <DocumentDecorations boundsLeft={this.leftOffset} boundsTop={this.topOffset} />
             {this.search}
-            <CollectionMenu />
             {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
             {DocumentLinksButton.LinkEditorDocView ? <LinkMenu docView={DocumentLinksButton.LinkEditorDocView} changeFlyout={emptyFunction} /> : (null)}
             {LinkDocPreview.LinkInfo ? <LinkDocPreview {...LinkDocPreview.LinkInfo} /> : (null)}
diff --git a/src/client/views/_nodeModuleOverrides.scss b/src/client/views/_nodeModuleOverrides.scss
index 56346b68b..cb59489c0 100644
--- a/src/client/views/_nodeModuleOverrides.scss
+++ b/src/client/views/_nodeModuleOverrides.scss
@@ -1,8 +1,49 @@
+@import "./global/globalCssVariables";
 // this file is for overriding all the css from installed node modules
 
 // goldenlayout stuff
 div .lm_header {
     background: $dark-gray;
+    overflow: hidden;
+}
+
+/* Width */
+.lm_header::-webkit-scrollbar {
+    -webkit-appearance: none;
+    display: none;
+}
+
+/* Width */
+.lm_header:hover::-webkit-scrollbar {
+    -webkit-appearance: none;
+    display: block;
+    height: 0px;
+}
+
+/* Track */
+.lm_header:hover::-webkit-scrollbar-track {
+    -webkit-appearance: none;
+    display: none;
+}
+
+/* Handle */
+.lm_header:hover::-webkit-scrollbar-thumb {
+    -webkit-appearance: none;
+    background: $dark-gray;
+}
+
+/* Handle on hover */
+.lm_header:hover::-webkit-scrollbar-thumb:hover {
+    -webkit-appearance: none;
+    background: $dark-gray;
+}
+
+.lm_tabs {
+    display: flex;
+    position: absolute;
+    width: calc(100% - 60px);
+    overflow: scroll;
+    background: $dark-gray;
 }
 
 .lm_tab {
@@ -15,7 +56,16 @@ div .lm_header {
 }
 
 .lm_header .lm_controls {
-    right: 1em !important;
+    align-items: center;
+    position: absolute;
+    background-color: #000000;
+    border-radius: 5px;
+    display: flex;
+    top: 2px;
+    justify-content: space-evenly;
+    right: 2px;
+    height: 18px;
+    width: 65px;
 }
 
 // @TODO the ril__navgiation buttons in the img gallery are a lil messed up but I can't figure out
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index a054f0ae1..b8180fe24 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -1,40 +1,46 @@
-@import "../../views/global/globalCssVariables.scss";
+@import "../global/globalCssVariables.scss";
 
 
 .lm_title {
-    margin-top: 3px;
-    border-radius: 5px;
-    border: solid 0px dimgray;
-    border-width: 2px 2px 0px;
-    height: 20px;
-    transform: translate(0px, -3px);
+    -webkit-appearance: none;
+    display: inline-block;
+    align-self: center;
+    align-items: center;
+    height: 100%;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    background: transparent;
+    border: solid 0px transparent;
     cursor: grab;
+    color: $black;
 }
 
 .lm_title.focus-visible {
+    -webkit-appearance: none;
     cursor: text;
 }
 
 .lm_title_wrap {
     overflow: hidden;
-    height: 19px;
-    margin-top: -2px;
-    display: inline-block;
+    align-items: center;
+    align-self: center;
+    background: transparent;
+    width: max-content;
+    height: 100%;
+    display: flex;
 }
 
 .lm_active .lm_title {
-    border: solid 1px lightgray;
-}
-
-.lm_header .lm_tab .lm_close_tab {
-    position: absolute;
-    text-align: center;
+    -webkit-appearance: none;
+    // font-weight: 700;
 }
 
 .lm_header .lm_tab {
-    padding-right: 20px;
-    margin-top: -1px;
-    border-bottom: 1px black;
+    padding: 0px;
+    opacity: 0.7;
+    box-shadow: none;
+    height: 19px;
+    // border-bottom: 1px black;
 
     .collectionDockingView-gear {
         display: none;
@@ -42,9 +48,13 @@
 }
 
 .lm_header .lm_tab.lm_active {
-    padding-right: 20px;
-    margin-top: 1px;
-    border-bottom: unset;
+    padding: 0;
+    opacity: 1;
+    margin: 0;
+    box-shadow: none;
+    height: 22px;
+    margin-right: 2px;
+    // border-bottom: unset;
 
     .collectionDockingView-gear {
         display: inline-block;
@@ -55,6 +65,41 @@
     display: inline;
 }
 
+.lm_drag_tab {
+    padding: 0;
+    width: 15px !important;
+    height: 15px !important;
+    position: relative !important;
+    display: inline-flex !important;
+    align-items: center;
+    top: 0 !important;
+    right: unset !important;
+    left: 0 !important;
+}
+
+.lm_close_tab {
+    padding: 0;
+    align-self: center;
+    margin-right: 5px;
+    background-color: black;
+    border-radius: 3px;
+    opacity: 1 !important;
+    width: 15px !important;
+    height: 15px !important;
+    position: relative !important;
+    display: inline-flex !important;
+    align-items: center;
+    top: 0 !important;
+    right: unset !important;
+    left: 0 !important;
+}
+
+.lm_tab,
+.lm_tab_active {
+    display: flex !important;
+    padding-right: 0 !important;
+}
+
 .collectiondockingview-container {
     width: 100%;
     height: 100%;
@@ -82,16 +127,17 @@
     }
 
     .lm_content {
-        background: white;
+        background: $white;
     }
 
     .lm_controls>li {
-        opacity: 0.6;
-        transform: scale(1.2);
+        opacity: 1;
+        transform: scale(1);
     }
 
     .lm_controls .lm_popout {
-        background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAUCAAAAABHICnvAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAHdElNRQfkCBsXMgbrEyzaAAAAT0lEQVQY02NgIAcIu8tgEW3/u4IDQ5B14/8LQlhFhckVFfCJjIyIOfP/QWpEZGSQJFS05s9fIPj3/z+YmseCTxS7CZS7DI+PsYcOjpAkDAA6H0KZxzDzlgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMC0wOC0yN1QyMzo1MDowNi0wNDowMDvgVpQAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjAtMDgtMjdUMjM6NTA6MDYtMDQ6MDBKve4oAAAAAElFTkSuQmCC)
+        transform: rotate(45deg);
+        background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAQUlEQVR4nHXOQQ4AMAgCQeT/f6aXpsGK3jSTuCVJAAr7iBdoAwCKd0nwfaAdHbYERw5b44+E8JoBjEYGMBq5gAYP3usUDu2IvoUAAAAASUVORK5CYII=);
     }
 
     .lm_maximised .lm_controls .lm_maximise {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 388f9a909..a8471f8e2 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -445,4 +445,4 @@ Scripting.addGlobal(function openInLightbox(doc: any) { LightboxView.AddDocTab(d
     "opens up document in a lightbox", "(doc: any)");
 Scripting.addGlobal(function openOnRight(doc: any) { return CollectionDockingView.AddSplit(doc, "right"); },
     "opens up document in tab on right side of the screen", "(doc: any)");
-Scripting.addGlobal(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.ReplaceTab(doc, "right", undefined, shiftKey); });
+Scripting.addGlobal(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.ReplaceTab(doc, "right", undefined, shiftKey); });
\ No newline at end of file
diff --git a/src/client/views/collections/TabDocView.scss b/src/client/views/collections/TabDocView.scss
index 9acbc4f85..a963f1cb9 100644
--- a/src/client/views/collections/TabDocView.scss
+++ b/src/client/views/collections/TabDocView.scss
@@ -1,19 +1,62 @@
 input.lm_title:focus,
-input.lm_title
-{
+input.lm_title {
     max-width: unset !important;
+    outline: none;
     transition-delay: unset;
-    width: 100%;
+    width: max-content;
     cursor: text;
 }
+
 input.lm_title {
     transition-delay: 0.35s;
-    width: 100px;
+    width: max-content;
     cursor: pointer;
 }
-.tabDocView-drag {
-    margin: auto;
+
+.lm_iconWrap {
+    display: flex;
+    color: black;
+    width: 15px;
+    height: 15px;
+    align-items: center;
+    align-self: center;
+    justify-content: center;
+    margin: 3px;
+    border-radius: 20%;
+
+    .moreInfoDot {
+        background-color: white;
+        border-radius: 100%;
+        width: 3px;
+        height: 3px;
+        margin: 0.5px;
+    }
+}
+
+.ffMenu {
+    display: grid;
+    grid-auto-rows: 35px;
+    grid-auto-columns: auto auto auto auto auto;
+    right: 10px;
+    bottom: 50px;
+    position: absolute;
+    min-height: 35px;
+    height: max-content;
+    border: solid 2px black;
+    border-radius: 5px;
+    background-color: #bddbe6;
+    width: max-content;
+    min-width: 35px;
+
+    .ffMenuButton {
+        display: flex;
+        width: 35px;
+        height: 35px;
+        align-items: center;
+        justify-content: center;
+    }
 }
+
 .miniMap-hidden,
 .miniMap {
     position: absolute;
@@ -37,6 +80,7 @@ input.lm_title {
         }
     }
 }
+
 .miniMap-hidden {
     position: absolute;
     bottom: 0;
@@ -46,7 +90,8 @@ input.lm_title {
     transform: translate(20px, 20px) rotate(45deg);
     border-radius: 30px;
     padding: 2px;
-    > svg {
+
+    >svg {
         margin-top: 3px;
         transform: translate(0px, 7px);
     }
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 7e2f7811e..0e67bebd8 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -1,3 +1,4 @@
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
 import 'golden-layout/src/css/goldenlayout-base.css';
@@ -9,9 +10,9 @@ import * as ReactDOM from 'react-dom';
 import { DataSym, Doc, DocListCast, DocListCastAsync, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
 import { Id } from '../../../fields/FieldSymbols';
 import { FieldId } from "../../../fields/RefField";
-import { Cast, NumCast, StrCast, BoolCast } from "../../../fields/Types";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, Utils } from "../../../Utils";
+import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, Utils } from "../../../Utils";
 import { DocServer } from "../../DocServer";
 import { DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -24,15 +25,15 @@ import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { LightboxView } from '../LightboxView';
 import { DocFocusOptions, DocumentView, DocumentViewProps } from "../nodes/DocumentView";
-import { FieldViewProps } from '../nodes/FieldView';
-import { PinProps, PresBox, PresMovement } from '../nodes/PresBox';
+import { PresBox, PinProps, PresMovement } from '../nodes/PresBox';
 import { DefaultLayerProvider, DefaultStyleProvider, StyleLayers, StyleProp } from '../StyleProvider';
 import { CollectionDockingView } from './CollectionDockingView';
 import { CollectionDockingViewMenu } from './CollectionDockingViewMenu';
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
-import { CollectionViewType, CollectionView } from './CollectionView';
+import { CollectionView, CollectionViewType } from './CollectionView';
 import "./TabDocView.scss";
 import React = require("react");
+import Color = require('color');
 const _global = (window /* browser */ || global /* node */) as any;
 
 interface TabDocViewProps {
@@ -52,6 +53,14 @@ export class TabDocView extends React.Component<TabDocViewProps> {
 
     @computed get layoutDoc() { return this._document && Doc.Layout(this._document); }
     @computed get tabColor() { return StrCast(this._document?._backgroundColor, StrCast(this._document?.backgroundColor, DefaultStyleProvider(this._document, undefined, StyleProp.BackgroundColor))); }
+    @computed get tabTextColor() { return this._document?.type === DocumentType.PRES ? "black" : StrCast(this._document?._color, StrCast(this._document?.color, DefaultStyleProvider(this._document, undefined, StyleProp.Color))); }
+    // @computed get renderBounds() {
+    //     const bounds = this._document ? Cast(this._document._renderContentBounds, listSpec("number"), [0, 0, this.returnMiniSize(), this.returnMiniSize()]) : [0, 0, 0, 0];
+    //     const xbounds = bounds[2] - bounds[0];
+    //     const ybounds = bounds[3] - bounds[1];
+    //     const dim = Math.max(xbounds, ybounds);
+    //     return { l: bounds[0] + xbounds / 2 - dim / 2, t: bounds[1] + ybounds / 2 - dim / 2, cx: bounds[0] + xbounds / 2, cy: bounds[1] + ybounds / 2, dim };
+    // }
 
     get stack() { return (this.props as any).glContainer.parent.parent; }
     get tab() { return (this.props as any).glContainer.tab; }
@@ -65,15 +74,25 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             tab.contentItem.config.fixed && (tab.contentItem.parent.config.fixed = true);
             tab.DashDoc = doc;
             CollectionDockingView.Instance.tabMap.add(tab);
-
+            const iconType: IconProp = Doc.toIcon(doc);
             // setup the title element and set its size according to the # of chars in the title.  Show the full title when clicked.
             const titleEle = tab.titleElement[0];
+            const iconWrap = document.createElement("div");
+            const closeWrap = document.createElement("div");
+
+
             titleEle.size = StrCast(doc.title).length + 3;
             titleEle.value = doc.title;
             titleEle.onchange = undoBatch(action((e: any) => {
                 titleEle.size = e.currentTarget.value.length + 3;
                 Doc.GetProto(doc).title = e.currentTarget.value;
             }));
+
+            const dragBtnDown = (e: React.PointerEvent) => {
+                setupMoveUpEvents(this, e, e => !e.defaultPrevented && DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY), returnFalse, emptyFunction);
+            };
+
+
             if (tab.element[0].children[1].children.length === 1) {
                 const toggle = document.createElement("div");
                 toggle.style.width = "10px";
@@ -83,18 +102,42 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 toggle.style.borderTopRightRadius = "7px";
                 toggle.style.position = "relative";
                 toggle.style.display = "inline-block";
-                toggle.style.background = "gray";
-                toggle.style.borderLeft = "solid 1px black";
+                toggle.style.background = "transparent";
                 toggle.onclick = (e: MouseEvent) => {
                     if (tab.contentItem === tab.header.parent.getActiveContentItem()) {
                         tab.DashDoc.activeLayer = tab.DashDoc.activeLayer ? undefined : StyleLayers.Background;
                     }
                 };
-                tab.element[0].style.borderTopRightRadius = "8px";
-                tab.element[0].children[1].appendChild(toggle);
-                tab._disposers.layerDisposer = reaction(() =>
-                    ({ layer: tab.DashDoc.activeLayer, color: this.tabColor }),
-                    ({ layer, color }) => toggle.style.background = !layer ? color : "dimgrey", { fireImmediately: true });
+                iconWrap.className = "lm_iconWrap";
+                iconWrap.id = "lm_iconWrap";
+                closeWrap.className = "lm_iconWrap";
+                closeWrap.id = "lm_closeWrap";
+                closeWrap.onclick = (e: MouseEvent) => {
+                    tab.header.parent.contentItem.remove();
+                    Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", tab.DashDoc, undefined, true, true);
+                };
+                const docIcon = <FontAwesomeIcon onPointerDown={dragBtnDown} icon={iconType} />;
+                const closeIcon = <FontAwesomeIcon icon={"times"} />;
+                ReactDOM.render(docIcon, iconWrap);
+                ReactDOM.render(closeIcon, closeWrap);
+                // tab.element[0].append(closeWrap);
+                tab.element[0].prepend(iconWrap);
+                tab._disposers.layerDisposer = reaction(() => ({ layer: tab.DashDoc.activeLayer, color: this.tabColor }),
+                    ({ layer, color }) => {
+                        const textColor = lightOrDark(this.tabColor); //not working with StyleProp.Color
+                        titleEle.style.color = textColor;
+                        titleEle.style.backgroundColor = "transparent";
+                        iconWrap.style.color = textColor;
+                        closeWrap.style.color = textColor;
+                        moreInfoDrag.style.backgroundColor = textColor;
+                        tab.element[0].style.background = !layer ? color : "dimgrey";
+                    }, { fireImmediately: true });
+                // TODO:glr fix
+                // tab.element[0].style.borderTopRightRadius = "8px";
+                // tab.element[0].children[1].appendChild(toggle);
+                // tab._disposers.layerDisposer = reaction(() =>
+                //     ({ layer: tab.DashDoc.activeLayer, color: this.tabColor }),
+                //     ({ layer, color }) => toggle.style.background = !layer ? color : "dimgrey", { fireImmediately: true });
             }
             // shifts the focus to this tab when another tab is dragged over it
             tab.element[0].onmouseenter = (e: MouseEvent) => {
@@ -103,13 +146,11 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     tab.setActive(true);
                 }
             };
-            const dragBtnDown = (e: React.PointerEvent) => {
-                setupMoveUpEvents(this, e, e => !e.defaultPrevented && DragManager.StartDocumentDrag([dragHdl], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY), returnFalse, emptyFunction);
-            };
+
 
             // select the tab document when the tab is directly clicked and activate the tab whenver the tab document is selected
             titleEle.onpointerdown = action((e: any) => {
-                if (e.target.className !== "lm_close_tab") {
+                if (e.target.className !== "lm_iconWrap") {
                     if (this.view) SelectionManager.SelectView(this.view, false);
                     else this._activated = true;
                     if (Date.now() - titleEle.lastClick < 1000) titleEle.select();
@@ -123,25 +164,25 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     const toggle = tab.element[0].children[1].children[0] as HTMLInputElement;
                     selected && tab.contentItem !== tab.header.parent.getActiveContentItem() &&
                         UndoManager.RunInBatch(() => tab.header.parent.setActiveContentItem(tab.contentItem), "tab switch");
-                    toggle.style.fontWeight = selected ? "bold" : "";
-                    toggle.style.textTransform = selected ? "uppercase" : "";
+                    // toggle.style.fontWeight = selected ? "bold" : "";
+                    // toggle.style.textTransform = selected ? "uppercase" : "";
                 }));
 
             //attach the selection doc buttons menu to the drag handle
             const stack = tab.contentItem.parent;
-            const dragHdl = document.createElement("div");
-            dragHdl.className = "lm_drag_tab";
+            const moreInfoDrag = document.createElement("div");
+            moreInfoDrag.className = "lm_iconWrap";
             tab._disposers.buttonDisposer = reaction(() => this.view, view =>
-                view && [ReactDOM.render(<span className="tabDocView-drag" onPointerDown={dragBtnDown}><CollectionDockingViewMenu views={() => [view]} Stack={stack} /></span>, dragHdl), tab._disposers.buttonDisposer?.()],
+                view && [ReactDOM.render(<span><CollectionDockingViewMenu views={() => [view]} Stack={stack} /></span>, moreInfoDrag), tab._disposers.buttonDisposer?.()],
                 { fireImmediately: true });
-            tab.reactComponents = [dragHdl];
-            tab.closeElement.before(dragHdl);
+            // tab.reactComponents = [moreInfoDrag];
+            // tab.element[0].children[3].before(moreInfoDrag);
 
             // highlight the tab when the tab document is brushed in any part of the UI
             tab._disposers.reactionDisposer = reaction(() => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }), ({ title, degree }) => {
                 titleEle.value = title;
-                titleEle.style.padding = degree ? 0 : 2;
-                titleEle.style.border = `${["gray", "gray", "gray"][degree]} ${["none", "dashed", "solid"][degree]} 2px`;
+                // titleEle.style.padding = degree ? 0 : 2;
+                // titleEle.style.border = `${["gray", "gray", "gray"][degree]} ${["none", "dashed", "solid"][degree]} 2px`;
             }, { fireImmediately: true });
 
             // clean up the tab when it is closed
@@ -221,9 +262,9 @@ export class TabDocView extends React.Component<TabDocViewProps> {
         })).observe(this.props.glContainer._element[0]);
         this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged);
         this.props.glContainer.tab?.isActive && this.onActiveContentItemChanged(undefined);
-        this._tabReaction = reaction(() => ({ selected: this.active(), title: this.tab?.titleElement[0] }),
-            ({ selected, title }) => title && (title.style.backgroundColor = selected ? "white" : ""),
-            { fireImmediately: true });
+        // this._tabReaction = reaction(() => ({ selected: this.active(), title: this.tab?.titleElement[0] }),
+        //     ({ selected, title }) => title && (title.style.backgroundColor = selected ? "white" : ""),
+        //     { fireImmediately: true });
     }
 
     componentWillUnmount() {
@@ -243,10 +284,10 @@ export class TabDocView extends React.Component<TabDocViewProps> {
     }
 
     // adds a tab to the layout based on the locaiton parameter which can be:
-    //  close[:{left,right,top,bottom}]  - e.g., "close" will close the tab, "close:left" will close the left tab, 
+    //  close[:{left,right,top,bottom}]  - e.g., "close" will close the tab, "close:left" will close the left tab,
     //  add[:{left,right,top,bottom}] - e.g., "add" will add a tab to the current stack, "add:right" will add a tab on the right
-    //  replace[:{left,right,top,bottom,<any string>}] - e.g., "replace" will replace the current stack contents, 
-    //                                  "replace:right" - will replace the stack on the right named "right" if it exists, or create a stack on the right with that name, 
+    //  replace[:{left,right,top,bottom,<any string>}] - e.g., "replace" will replace the current stack contents,
+    //                                  "replace:right" - will replace the stack on the right named "right" if it exists, or create a stack on the right with that name,
     //                                   "replace:monkeys" - will replace any tab that has the label 'monkeys', or a tab with that label will be created by default on the right
     //  inPlace - will add the document to any collection along the path from the document to the docking view that has a field isInPlaceContainer. if none is found, inPlace adds a tab to current stack
     addDocTab = (doc: Doc, location: string) => {
@@ -460,4 +501,4 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
                 </div>
             </div>;
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss
index ead5e166e..a8d4235bd 100644
--- a/src/client/views/global/globalCssVariables.scss
+++ b/src/client/views/global/globalCssVariables.scss
@@ -21,8 +21,6 @@ $large-padding: 32px;
 //icon sizes
 $icon-size: 28px;
 
-$antimodemenu-height: 36px;
-
 // fonts
 $sans-serif: "Noto Sans", sans-serif;
 $large-header: 16px;
@@ -33,6 +31,8 @@ $small-text: 9px;
 // misc values
 $border-radius: 0.3em;
 $search-thumnail-size: 130;
+$topbar-height: 32px;
+$antimodemenu-height: 36px;
 
 // dragged items
 $contextMenu-zindex: 100000; // context menu shows up over everything
diff --git a/src/client/views/global/globalEnums.tsx b/src/client/views/global/globalEnums.tsx
index 1e0381c33..2aeb8e338 100644
--- a/src/client/views/global/globalEnums.tsx
+++ b/src/client/views/global/globalEnums.tsx
@@ -31,4 +31,8 @@ export enum Padding {
 
 export enum IconSizes {
     ICON_SIZE = "28px",
+}
+
+export enum Borders {
+    STANDARD = "solid 1px #9F9F9F"
 }
\ No newline at end of file
diff --git a/src/client/views/topbar/TopBar.scss b/src/client/views/topbar/TopBar.scss
new file mode 100644
index 000000000..324b96dbd
--- /dev/null
+++ b/src/client/views/topbar/TopBar.scss
@@ -0,0 +1,211 @@
+@import "../global/globalCssVariables";
+
+.topbar-container {
+    display: flex;
+    flex-direction: column;
+    width: 100%;
+    position: relative;
+    font-size: 10px;
+    line-height: 1;
+    overflow-y: auto;
+    overflow-x: visible;
+    background: $dark-gray;
+    overflow: visible;
+    z-index: 1000;
+
+    .topbar-bar {
+        height: $topbar-height;
+        display: grid;
+        grid-auto-columns: 33.3% 33.3% 33.3%;
+        align-items: center;
+        background-color: $dark-gray;
+
+        .topbar-center {
+            grid-column: 2;
+            display: inline-flex;
+            justify-content: center;
+            align-items: center;
+
+            .topbar-lozenge-dashboard {
+                display: flex;
+
+                .topbar-dashboards {
+                    display: none;
+                }
+
+                .topbar-dashSelect {
+                    border: none;
+                    background-color: transparent;
+                    color: black;
+                    font-family: 'Roboto';
+                    font-size: 17;
+                    font-weight: 500;
+
+                    &:hover {
+                        cursor: pointer;
+                    }
+                }
+            }
+
+            .topbar-lozenge-dashboard:hover {
+                .topbar-dashboards {
+                    display: inline-flex;
+                }
+            }
+        }
+
+        .topBar-icon {
+            color: black;
+            cursor: pointer;
+            font-size: 15px;
+            height: 30;
+            width: 30;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            margin-right: 5px;
+            justify-self: center;
+            align-self: center;
+            border-radius: 100%;
+            transition: linear 0.1s;
+            background-color: #92adb900;
+        }
+
+        .topBar-icon:hover {
+            background-color: rgba(0, 0, 0, 0.15);
+        }
+
+        .topbar-right {
+            grid-column: 3;
+            position: relative;
+            display: flex;
+            justify-content: flex-end;
+
+            .topbar-lozenge-user,
+            .topbar-lozenge {
+                height: 23;
+                font-size: 12;
+                color: black;
+                font-family: 'Roboto';
+                font-weight: 400;
+                padding: 4px;
+                align-self: center;
+                margin-right: 7px;
+                display: flex;
+                align-items: center;
+                border: black 1px solid;
+
+                .topbar-logoff {
+                    border-radius: 3px;
+                    background: olivedrab;
+                    color: white;
+                    display: none;
+                    margin-left: 5px;
+                    padding: 1px 2px 1px 2px;
+                    cursor: pointer;
+                }
+
+                .topbar-logoff {
+                    background: red;
+                }
+
+                .topbar-dashSelect {
+                    border: none;
+                    background-color: transparent;
+                    color: black;
+                    font-family: 'Roboto';
+                    font-size: 17;
+                    font-weight: 500;
+
+                    &:hover {
+                        cursor: pointer;
+                    }
+                }
+            }
+
+            .topbar-lozenge-user:hover {
+                .topbar-logoff {
+                    display: inline-block;
+                }
+            }
+
+        }
+
+        .topbar-left {
+            grid-column: 1;
+            color: black;
+            font-family: 'Roboto';
+            position: relative;
+            display: flex;
+            width: 450;
+        }
+
+        .topbar-barChild {
+
+            &.topbar-collection {
+                flex: 0 1 auto;
+                margin-left: 2px;
+                margin-right: 2px
+            }
+
+            &.topbar-input {
+                margin:5px;
+                border-radius:20px;
+                border:$dark-gray;    
+                display: block;
+                width: 130px;
+                -webkit-transition: width 0.4s;
+                transition: width 0.4s;
+                /* align-self: stretch; */
+                outline: none;
+
+                &:focus {
+                    width: 500px;
+                    outline: none;
+                }
+            }
+
+            &.topbar-filter {
+                align-self: stretch;
+
+                button {
+                    transform: none;
+
+                    &:hover {
+                        transform: none;
+                    }
+                }
+            }
+
+            &.topbar-submit {
+                margin-left: 2px;
+                margin-right: 2px
+            }
+
+            &.topbar-close {
+                color: $white;
+                max-height: $topbar-height;
+            }
+        }
+    }
+}
+
+.topbar-results {
+    display: flex;
+    flex-direction: column;
+    top: 300px;
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+    overflow: visible;
+
+    .no-result {
+        width: 500px;
+        background: $light-gray;
+        padding: 10px;
+        height: 50px;
+        text-transform: uppercase;
+        text-align: left;
+        font-weight: bold;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
new file mode 100644
index 000000000..79239d4ea
--- /dev/null
+++ b/src/client/views/topbar/TopBar.tsx
@@ -0,0 +1,58 @@
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import * as React from 'react';
+import { Doc, DocListCast } from '../../../fields/Doc';
+import { Id } from '../../../fields/FieldSymbols';
+import { StrCast } from '../../../fields/Types';
+import { Utils } from '../../../Utils';
+import { CurrentUserUtils } from "../../util/CurrentUserUtils";
+import { ColorScheme, SettingsManager } from "../../util/SettingsManager";
+import { undoBatch } from "../../util/UndoManager";
+import "./TopBar.scss";
+import { Colors, Borders } from "../global/globalEnums";
+
+export const TopBar = () => {
+
+    const myDashboards = DocListCast(CurrentUserUtils.MyDashboards.data);
+    return (
+        <div style={{ pointerEvents: "all" }} className="topbar-container">
+            <div className="topbar-bar" style={{ background: Doc.UserDoc().colorScheme === ColorScheme.Dark ? Colors.DARK_GRAY : Colors.LIGHT_GRAY, borderBottom: Borders.STANDARD }}>
+                <div className="topbar-center" >
+                    <div className="topbar-lozenge-dashboard">
+                        <select className="topbar-dashSelect" onChange={e => CurrentUserUtils.openDashboard(Doc.UserDoc(), myDashboards[Number(e.target.value)])}
+                            value={myDashboards.indexOf(CurrentUserUtils.ActiveDashboard)}
+                            style={{ color: Doc.UserDoc().colorScheme === ColorScheme.Dark ? "white" : "black" }}>
+                            {myDashboards.map((dash, i) => <option key={dash[Id]} value={i}> {StrCast(dash.title)} </option>)}
+                        </select>
+                        <div className="topbar-dashboards">
+                            <div className="topbar-icon" onClick={undoBatch(() => CurrentUserUtils.createNewDashboard(Doc.UserDoc()))}
+                                style={{ color: Doc.UserDoc().colorScheme === ColorScheme.Dark ? "white" : "black" }}>
+                                <FontAwesomeIcon icon="plus"></FontAwesomeIcon>
+                            </div>
+                            <div className="topbar-icon" onClick={undoBatch(() => CurrentUserUtils.snapshotDashboard(Doc.UserDoc()))}
+                                style={{ color: Doc.UserDoc().colorScheme === ColorScheme.Dark ? "white" : "black" }}>
+                                <FontAwesomeIcon icon="camera"></FontAwesomeIcon>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div className="topbar-right" >
+                    <div className="topbar-icon"
+                        style={{ color: Doc.UserDoc().colorScheme === ColorScheme.Dark ? "white" : "black" }}>
+                        <FontAwesomeIcon icon="question-circle"></FontAwesomeIcon>
+                    </div>
+                    <div className="topbar-icon" onClick={() => SettingsManager.Instance.open()}
+                        style={{ color: Doc.UserDoc().colorScheme === ColorScheme.Dark ? "white" : "black" }}>
+                        <FontAwesomeIcon icon="cog"></FontAwesomeIcon>
+                    </div>
+                    <div className="topbar-lozenge-user">
+                        {`${Doc.CurrentUserEmail}`}
+                        <div className="topbar-logoff" onClick={() => window.location.assign(Utils.prepend("/logout"))}>
+                            Logoff
+                        </div>
+                    </div>
+                </div>
+
+            </div >
+        </div >
+    );
+}
\ No newline at end of file
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 464a8ad05..ee8d36f09 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -23,6 +23,7 @@ import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types";
 import { AudioField, ImageField, PdfField, VideoField, WebField } from "./URLField";
 import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from "./util";
 import JSZip = require("jszip");
+import { IconProp } from "@fortawesome/fontawesome-svg-core";
 
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
@@ -1184,7 +1185,10 @@ export namespace Doc {
             case DocumentType.IMG: return "image";
             case DocumentType.COMPARISON: return "columns";
             case DocumentType.RTF: return "sticky-note";
-            case DocumentType.COL: return !doc?.isFolder ? "folder" + (isOpen ? "-open" : "") : "chevron-" + (isOpen ? "down" : "right");
+            case DocumentType.COL:
+                const folder: IconProp = isOpen ? "folder-open" : "folder";
+                const chevron: IconProp = isOpen ? "chevron-down" : "chevron-right"
+                return !doc?.isFolder ? folder : chevron;
             case DocumentType.WEB: return "globe-asia";
             case DocumentType.SCREENSHOT: return "photo-video";
             case DocumentType.WEBCAM: return "video";
-- 
cgit v1.2.3-70-g09d2


From 4d21696c1b49c2c383adcd01974c18461123b83c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 17 Aug 2021 16:55:15 -0400
Subject: made dashboard context menu items context sensitive to whether
 they're called from MyDashboards, MySharedDocs or elsewhere.  Made
 treeViewOpen a playground field so that users don't interfere with each
 other.   Made Docking views not generate alias of their tabs when shared.

---
 src/client/util/CurrentUserUtils.ts                | 35 +++++++++++-----------
 src/client/views/MainView.tsx                      |  2 +-
 .../views/collections/CollectionDockingView.tsx    |  2 +-
 .../views/collections/CollectionTreeView.tsx       | 13 ++++++--
 src/client/views/collections/CollectionView.tsx    | 24 +++++++++------
 src/client/views/collections/TreeView.tsx          | 25 +++++++++++-----
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 7 files changed, 63 insertions(+), 40 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 34990e121..85762a73f 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -765,8 +765,13 @@ export class CurrentUserUtils {
                 _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", treeViewType: "fileSystem", isFolder: true, system: true
             }));
             const newDashboard = ScriptField.MakeScript(`createNewDashboard(Doc.UserDoc())`);
-            (doc.myDashboards as any as Doc).contextMenuScripts = new List<ScriptField>([newDashboard!]);
-            (doc.myDashboards as any as Doc).contextMenuLabels = new List<string>(["Create New Dashboard"]);
+            const toggleTheme = ScriptField.MakeScript(`Doc.UserDoc().darkScheme = !Doc.UserDoc().darkScheme`);
+            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!, 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"]);
         }
         return doc.myDashboards as any as Doc;
     }
@@ -933,6 +938,11 @@ export class CurrentUserUtils {
             }
             if (sharedDocs instanceof Doc) {
                 Doc.GetProto(sharedDocs).userColor = sharedDocs.userColor || "rgb(202, 202, 202)";
+                const addToDashboards = ScriptField.MakeScript(`addToDashboards(self)`);
+                const dashboardFilter = ScriptField.MakeFunction(`doc._viewType === '${CollectionViewType.Docking}'`, { doc: Doc.name });
+                sharedDocs.childContextMenuFilters = new List<ScriptField>([dashboardFilter!,]);
+                sharedDocs.childContextMenuScripts = new List<ScriptField>([addToDashboards!,]);
+                sharedDocs.childContextMenuLabels = new List<string>(["Add to Dashboards",]);
             }
             doc.mySharedDocs = new PrefetchProxy(sharedDocs);
         }
@@ -1228,15 +1238,6 @@ export class CurrentUserUtils {
 
         Doc.AddDocToList(myPresentations, "data", presentation);
         userDoc.activePresentation = presentation;
-        const toggleTheme = ScriptField.MakeScript(`Doc.UserDoc().darkScheme = !Doc.UserDoc().darkScheme`);
-        const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
-        const snapshotDashboard = ScriptField.MakeScript(`snapshotDashboard()`);
-        const createDashboard = ScriptField.MakeScript(`createNewDashboard()`);
-        const shareDashboard = ScriptField.MakeScript(`shareDashboard(self)`);
-        const addToDashboards = ScriptField.MakeScript(`addToDashboards(self)`);
-        const removeDashboard = ScriptField.MakeScript('removeDashboard(self)');
-        dashboardDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!, toggleComic!, snapshotDashboard!, createDashboard!, shareDashboard!, addToDashboards!, removeDashboard!]);
-        dashboardDoc.contextMenuLabels = new List<string>(["Toggle Theme Colors", "Toggle Comic Mode", "Snapshot Dashboard", "Create Dashboard", "Share Dashboard", "Add to Dashboards", "Remove Dashboard"]);
 
         Doc.AddDocToList(dashboards, "data", dashboardDoc);
         CurrentUserUtils.openDashboard(userDoc, dashboardDoc);
@@ -1306,17 +1307,15 @@ Scripting.addGlobal(async function addToDashboards(dashboard: Doc) {
     const allDocs = await DocListCastAsync(dashboard[DataSym]["data-all"]);
 
     // moves the data-all field from the datadoc to the layoutdoc, necessary for off screen docs tab to function properly
-    dashboard["data-all"] = new List<Doc>(allDocs);
-    dashboardAlias["data-all"] = new List<Doc>((allDocs || []).map(doc => Doc.MakeAlias(doc)));
-
-    const dockingConfig = JSON.parse(StrCast(dashboardAlias.dockingConfig));
-    dockingConfig.content = [];
-    dashboardAlias.dockingConfig = JSON.stringify(dockingConfig);
+    // dashboard["data-all"] = new List<Doc>(allDocs);
+    // dashboardAlias["data-all"] = new List<Doc>((allDocs || []).map(doc => Doc.MakeAlias(doc)));
 
+    // const dockingConfig = JSON.parse(StrCast(dashboardAlias.dockingConfig));
+    // dashboardAlias.dockingConfig = JSON.stringify(dockingConfig);
 
     dashboardAlias.data = new List<Doc>(DocListCast(dashboard.data).map(tabFolder => Doc.MakeAlias(tabFolder)));
     DocListCast(dashboardAlias.data).forEach(doc => doc.dashboard = dashboardAlias);
-    DocListCast(dashboardAlias.data)[0].data = new List<Doc>();
+    //new List<Doc>();
     DocListCast(dashboardAlias.data)[1].data = ComputedField.MakeFunction(`dynamicOffScreenDocs(self.dashboard)`) as any;
     Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", dashboardAlias);
     CurrentUserUtils.openDashboard(Doc.UserDoc(), dashboardAlias);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index b0b8d7f41..4d2a0a4c4 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -107,7 +107,7 @@ export class MainView extends React.Component {
         new InkStrokeProperties();
         this._sidebarContent.proto = undefined;
         if (!MainView.Live) {
-            DocServer.setPlaygroundFields(["dataTransition", "autoHeight", "showSidebar", "sidebarWidthPercent", "viewTransition",
+            DocServer.setPlaygroundFields(["dataTransition", "treeViewOpen", "autoHeight", "showSidebar", "sidebarWidthPercent", "viewTransition",
                 "panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "text-scrollHeight", "text-height", "hideMinimap",
                 "viewScale", "scrollTop", "hidden", "curPage", "viewType", "chromeHidden", "nativeWidth"]); // can play with these fields on someone else's
         }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index c0d39b2a2..cae08e1f4 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -170,7 +170,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
                 const aliasDocList = DocListCast(alias["data-all"]);
                 // if aliasDocList contains the alias, don't do anything
                 // otherwise add the original or an alias depending on whether the doc you're looking at is the current doc or a different alias
-                !DocListCast(document.aliases).some(a => aliasDocList.includes(a)) && Doc.AddDocToList(alias, "data-all", alias !== instance.props.Document ? Doc.MakeAlias(document) : document);
+                !DocListCast(document.aliases).some(a => aliasDocList.includes(a)) && Doc.AddDocToList(alias, "data-all", document);//alias !== instance.props.Document ? Doc.MakeAlias(document) : document);
             });
         }
         const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName);
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 3eece0086..c0553ca60 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,10 +1,10 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, reaction, IReactionDisposer, observable } from "mobx";
 import { observer } from "mobx-react";
-import { DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
+import { DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym, StrListCast } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
-import { Document } from '../../../fields/Schema';
+import { Document, listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
@@ -215,6 +215,11 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
             />
         </div>;
     }
+    childContextMenuItems = () => {
+        const customScripts = Cast(this.doc.childContextMenuScripts, listSpec(ScriptField), []);
+        const filterScripts = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []);
+        return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: filterScripts[i], label }));
+    }
     @computed get treeViewElements() {
         TraceMobx();
         const dropAction = StrCast(this.doc.childDropAction) as dropActionType;
@@ -247,7 +252,9 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
             this.whenChildContentsActiveChanged,
             this.props.dontRegisterView || Cast(this.props.Document.childDontRegisterViews, "boolean", null),
             this.observerHeight,
-            this.unobserveHeight);
+            this.unobserveHeight,
+            this.childContextMenuItems()
+        );
     }
     @computed get titleBar() {
         const hideTitle = this.props.treeViewHideTitle || this.doc.treeViewHideTitle;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index e65ebf075..a82128e47 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -70,6 +70,7 @@ export interface CollectionViewProps extends FieldViewProps {
     childDocumentsActive?: () => boolean;// whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode)
     childFitWidth?: () => boolean;
     childOpacity?: () => number;
+    childContextMenuItems?: () => { script: ScriptField, label: string }[];
     childHideTitle?: () => boolean; // whether to hide the documentdecorations title for children
     childHideDecorationTitle?: () => boolean;
     childLayoutTemplate?: () => (Doc | undefined);// specify a layout Doc template to use for children of the collection
@@ -186,15 +187,20 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
             }
             !Doc.UserDoc().noviceMode && optionItems.push({ description: `${this.rootDoc.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.rootDoc.isInPlaceContainer = !this.rootDoc.isInPlaceContainer, icon: "project-diagram" });
 
-            optionItems.push({
-                description: "Create Branch", event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), "add:right"), icon: "project-diagram"
-            });
-            optionItems.push({
-                description: "Pull Master", event: () => BranchTask(this.rootDoc, "pull"), icon: "project-diagram"
-            });
-            optionItems.push({
-                description: "Merge Branches", event: () => BranchTask(this.rootDoc, "merge"), icon: "project-diagram"
-            });
+            if (!Doc.UserDoc().noviceMode) {
+                optionItems.push({
+                    description: "Create Branch", event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), "add:right"), icon: "project-diagram"
+                });
+                optionItems.push({
+                    description: "Pull Master", event: () => BranchTask(this.rootDoc, "pull"), icon: "project-diagram"
+                });
+                optionItems.push({
+                    description: "Merge Branches", event: () => BranchTask(this.rootDoc, "merge"), icon: "project-diagram"
+                });
+            }
+            if (this.Document._viewType === CollectionViewType.Docking) {
+                optionItems.push({ description: "Create Dashboard", event: () => CurrentUserUtils.createNewDashboard(Doc.UserDoc()), icon: "project-diagram" });
+            }
 
             !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "hand-point-right" });
 
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 206e48aac..0598be49b 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -1,7 +1,7 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
 import { observer } from "mobx-react";
-import { DataSym, Doc, DocListCast, DocListCastOrNull, Field, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
+import { DataSym, Doc, DocListCast, DocListCastOrNull, Field, HeightSym, Opt, WidthSym, StrListCast } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { RichTextField } from '../../../fields/RichTextField';
@@ -54,6 +54,7 @@ export interface TreeViewProps {
     indentDocument?: (editTitle: boolean) => void;
     outdentDocument?: (editTitle: boolean) => void;
     ScreenToLocalTransform: () => Transform;
+    contextMenuItems: { script: ScriptField, filter: ScriptField, label: string }[];
     dontRegisterView?: boolean;
     styleProvider?: StyleProviderFunc | undefined;
     treeViewHideHeaderFields: () => boolean;
@@ -336,7 +337,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                     this.props.dropAction, this.props.addDocTab, this.titleStyleProvider, this.props.ScreenToLocalTransform, this.props.isContentActive,
                     this.props.panelWidth, this.props.renderDepth, this.props.treeViewHideHeaderFields,
                     [...this.props.renderedIds, doc[Id]], this.props.onCheckedClick, this.props.onChildClick, this.props.skipFields, false, this.props.whenChildContentsActiveChanged,
-                    this.props.dontRegisterView, emptyFunction, emptyFunction);
+                    this.props.dontRegisterView, emptyFunction, emptyFunction, this.childContextMenuItems());
             } else {
                 contentElement = <EditableView key="editableView"
                     contents={contents !== undefined ? Field.toString(contents as Field) : "null"}
@@ -418,7 +419,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                         StrCast(this.doc.childDropAction, this.props.dropAction) as dropActionType, this.props.addDocTab, this.titleStyleProvider, this.props.ScreenToLocalTransform,
                         this.props.isContentActive, this.props.panelWidth, this.props.renderDepth, this.props.treeViewHideHeaderFields,
                         [...this.props.renderedIds, this.doc[Id]], this.props.onCheckedClick, this.props.onChildClick, this.props.skipFields, false, this.props.whenChildContentsActiveChanged,
-                        this.props.dontRegisterView, emptyFunction, emptyFunction)}
+                        this.props.dontRegisterView, emptyFunction, emptyFunction, this.childContextMenuItems())}
             </ul >;
         } else if (this.treeViewExpandedView === "fields") {
             return <ul key={this.doc[Id] + this.doc.title}>
@@ -507,11 +508,19 @@ export class TreeView extends React.Component<TreeViewProps> {
     }
     contextMenuItems = () => {
         const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: "any" })!, label: "New Folder" };
-        return this.doc.isFolder ? [makeFolder] :
+        const openAlias = { script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, label: "Open Alias" };
+        const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, label: "Focus or Open" };
+        return [...this.props.contextMenuItems.filter(mi => !mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result), ... (this.doc.isFolder ? [makeFolder] :
             Doc.IsSystem(this.doc) ? [] :
                 this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc) ?
-                    [{ script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, label: "Open Alias" }, makeFolder] :
-                    [{ script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, label: "Open Alias" }, { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, label: "Focus or Open" }];
+                    [openAlias, makeFolder] :
+                    this.doc.viewType === CollectionViewType.Docking ? [] :
+                        [openAlias, focusDoc])];
+    }
+    childContextMenuItems = () => {
+        const customScripts = Cast(this.doc.childContextMenuScripts, listSpec(ScriptField), []);
+        const customFilters = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []);
+        return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], label }));
     }
     onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick));
 
@@ -817,7 +826,8 @@ export class TreeView extends React.Component<TreeViewProps> {
         whenChildContentsActiveChanged: (isActive: boolean) => void,
         dontRegisterView: boolean | undefined,
         observerHeight: (ref: any) => void,
-        unobserveHeight: (ref: any) => void
+        unobserveHeight: (ref: any) => void,
+        contextMenuItems: ({ script: ScriptField, filter: ScriptField, label: string }[])
     ) {
         const viewSpecScript = Cast(conainerCollection.viewSpecScript, ScriptField);
         if (viewSpecScript) {
@@ -882,6 +892,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 parentTreeView={parentTreeView}
                 observeHeight={observerHeight}
                 unobserveHeight={unobserveHeight}
+                contextMenuItems={contextMenuItems}
             />;
         });
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 745d58656..a29f2f662 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -664,7 +664,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         if (!cm || (e as any)?.nativeEvent?.SchemaHandled) return;
 
         const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
-        Cast(this.props.Document.contextMenuLabels, listSpec("string"), []).forEach((label, i) =>
+        StrListCast(this.Document.contextMenuLabels).forEach((label, i) =>
             cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: "sticky-note" }));
         this.props.contextMenuItems?.().forEach(item =>
             item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: "sticky-note" }));
-- 
cgit v1.2.3-70-g09d2

