From 8910a2649a1b2edea9843df1b780937ad0e69fb5 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 12 Oct 2019 13:09:53 -0400
Subject: color picker as document in sidebar now.

---
 src/client/views/nodes/ColorBox.tsx | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
 create mode 100644 src/client/views/nodes/ColorBox.tsx

(limited to 'src/client/views/nodes/ColorBox.tsx')

diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
new file mode 100644
index 000000000..4aff770f9
--- /dev/null
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -0,0 +1,16 @@
+import React = require("react");
+import { observer } from "mobx-react";
+import { SketchPicker } from 'react-color';
+import { FieldView, FieldViewProps } from './FieldView';
+import "./ColorBox.scss";
+import { InkingControl } from "../InkingControl";
+
+@observer
+export class ColorBox extends React.Component<FieldViewProps> {
+    public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(ColorBox, fieldKey); }
+    render() {
+        return <div className="colorBox-container" >
+            <SketchPicker color={InkingControl.Instance.selectedColor} onChange={InkingControl.Instance.switchColor} />
+        </div>;
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 26e215b0cddbb4c14cfd8eb7a720a373e797c615 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 15 Oct 2019 00:23:00 -0400
Subject: cleaned up current_user_utils.  cleaned up some interactions with
 colorBox, textbox height

---
 src/client/documents/Documents.ts                  |  19 +-
 src/client/views/DocComponent.tsx                  |  34 ++-
 src/client/views/InkingControl.tsx                 |   8 +
 src/client/views/MainView.tsx                      |  16 +-
 src/client/views/nodes/ColorBox.scss               |   7 +-
 src/client/views/nodes/ColorBox.tsx                |  12 +-
 src/client/views/nodes/FormattedTextBox.tsx        |  13 +-
 src/client/views/nodes/PDFBox.tsx                  |   3 +-
 .../authentication/models/current_user_utils.ts    | 295 ++++++++++-----------
 9 files changed, 229 insertions(+), 178 deletions(-)

(limited to 'src/client/views/nodes/ColorBox.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 1f89d2993..13cc7815c 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -40,7 +40,7 @@ import { ButtonBox } from "../views/nodes/ButtonBox";
 import { FontIconBox } from "../views/nodes/FontIconBox";
 import { SchemaHeaderField, RandomPastel } from "../../new_fields/SchemaHeaderField";
 import { PresBox } from "../views/nodes/PresBox";
-import { ComputedField } from "../../new_fields/ScriptField";
+import { ComputedField, ScriptField } from "../../new_fields/ScriptField";
 import { ProxyField } from "../../new_fields/Proxy";
 import { DocumentType } from "./DocumentTypes";
 import { LinkFollowBox } from "../views/linking/LinkFollowBox";
@@ -65,7 +65,10 @@ export interface DocumentOptions {
     page?: number;
     scale?: number;
     fitWidth?: boolean;
+    forceActive?: boolean;
+    preventTreeViewOpen?: boolean; // ignores the treeViewOpen Doc flag which allows a treeViewItem's expande/collapse state to be independent of other views of the same document in the tree view
     layout?: string | Doc;
+    hideHeadings?: boolean; // whether stacking view column headings should be hidden
     isTemplate?: boolean;
     templates?: List<string>;
     viewType?: number;
@@ -84,11 +87,21 @@ export interface DocumentOptions {
     documentText?: string;
     borderRounding?: string;
     boxShadow?: string;
+    sectionFilter?: string; // field key used to determine headings for sections in stacking and masonry views
     schemaColumns?: List<SchemaHeaderField>;
     dockingConfig?: string;
     autoHeight?: boolean;
+    removeDropProperties?: List<string>; // list of properties that should be removed from a document when it is dropped.  e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document
     dbDoc?: Doc;
+    onClick?: ScriptField;
+    onDragStart?: ScriptField; //script to execute at start of drag operation --  e.g., when a "creator" button is dragged this script generates a different document to drop
     icon?: string;
+    gridGap?: number; // gap between items in masonry view
+    xMargin?: number; // gap between left edge of document and start of masonry/stacking layouts
+    yMargin?: number; // gap between top edge of dcoument and start of masonry/stacking layouts
+    panel?: Doc; // panel to display in 'targetContainer' as the result of a button onClick script
+    targetContainer?: Doc; // document whose proto will be set to 'panel' as the result of a onClick click script
+    convertToButtons?: boolean; // whether documents dropped onto a collection should be converted to buttons that will construct the dropped document
     // [key: string]: Opt<Field>;
 }
 
@@ -457,6 +470,10 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Freeform }, id);
         }
 
+        export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Linear }, id);
+        }
+
         export function SchemaDocument(schemaColumns: SchemaHeaderField[], documents: Array<Doc>, options: DocumentOptions) {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List(schemaColumns), ...options, viewType: CollectionViewType.Schema });
         }
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 2c5992259..b05966bb5 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,10 +1,17 @@
 import * as React from 'react';
 import { Doc } from '../../new_fields/Doc';
 import { computed, action } from 'mobx';
-import { Cast } from '../../new_fields/Types';
+import { Cast, BoolCast } from '../../new_fields/Types';
 import { listSpec } from '../../new_fields/Schema';
+import { InkingControl } from './InkingControl';
+import { InkTool } from '../../new_fields/InkField';
 
-export function DocComponent<P extends { Document: Doc }, T>(schemaCtor: (doc: Doc) => T) {
+
+///  DocComponents returns a generic base class for React views of document fields that are not interactive
+interface DocComponentProps {
+    Document: Doc;
+}
+export function DocComponent<P extends DocComponentProps, T>(schemaCtor: (doc: Doc) => T) {
     class Component extends React.Component<P> {
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
         @computed
@@ -15,6 +22,27 @@ export function DocComponent<P extends { Document: Doc }, T>(schemaCtor: (doc: D
     return Component;
 }
 
+
+///  DocStaticProps return a base class for React views of document fields that are interactive only when selected (e.g. ColorBox)
+interface DocStaticProps {
+    Document: Doc;
+    isSelected: () => boolean;
+    renderDepth: number;
+}
+export function DocStaticComponent<P extends DocStaticProps, T>(schemaCtor: (doc: Doc) => T) {
+    class Component extends React.Component<P> {
+        //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
+        @computed
+        get Document(): T {
+            return schemaCtor(this.props.Document);
+        }
+        active = () => (this.props.Document.forceActive || this.props.isSelected() || this.props.renderDepth === 0) && !InkingControl.Instance.selectedTool;
+    }
+    return Component;
+}
+
+
+///  DocAnnotatbleComponent return a base class for React views of document fields that are annotatable *and* interactive when selected (e.g., pdf, image)
 interface DocAnnotatableProps {
     Document: Doc;
     DataDoc?: Doc;
@@ -57,7 +85,7 @@ export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schema
             return Doc.AddDocToList(this.extensionDoc, this.props.fieldExt, doc);
         }
         whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive);
-        active = () => this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0;
+        active = () => (InkingControl.Instance.selectedTool === InkTool.None) && (BoolCast(this.props.Document.forceActive) || this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0);
     }
     return Component;
 }
\ No newline at end of file
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 38734a03d..d42d8d2d9 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -48,6 +48,14 @@ export class InkingControl extends React.Component {
             let selected = SelectionManager.SelectedDocuments();
             let oldColors = selected.map(view => {
                 let targetDoc = view.props.Document.layout instanceof Doc ? view.props.Document.layout : view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document);
+                let sel = window.getSelection();
+                if (StrCast(targetDoc.layout).indexOf("FormattedTextBox") !== -1 && (!sel || sel.toString() !== "")) {
+                    targetDoc.color = this._selectedColor;
+                    return {
+                        target: targetDoc,
+                        previous: StrCast(targetDoc.color)
+                    };
+                }
                 let oldColor = StrCast(targetDoc.backgroundColor);
                 let matchedColor = this._selectedColor;
                 const cvd = view.props.ContainingCollectionDoc;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index ddea3e223..701f094e2 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -343,12 +343,12 @@ export class MainView extends React.Component {
         if (!(sidebarContent instanceof Doc)) {
             return (null);
         }
-        let libraryButtonDoc = Cast(CurrentUserUtils.UserDocument.libraryButtons, Doc) as Doc;
-        libraryButtonDoc.columnWidth = this.flyoutWidth / 3 - 30;
+        let sidebarButtonsDoc = Cast(CurrentUserUtils.UserDocument.sidebarButtons, Doc) as Doc;
+        sidebarButtonsDoc.columnWidth = this.flyoutWidth / 3 - 30;
         return <div className="mainView-flyoutContainer">
             <div className="mainView-tabButtons" style={{ height: `${this._buttonBarHeight}px` }}>
                 <DocumentView
-                    Document={libraryButtonDoc}
+                    Document={sidebarButtonsDoc}
                     DataDoc={undefined}
                     addDocument={undefined}
                     addDocTab={this.addDocTabFunc}
@@ -443,18 +443,20 @@ export class MainView extends React.Component {
     }
 
     addButtonDoc = (doc: Doc) => {
-        Doc.AddDocToList(CurrentUserUtils.UserDocument, "docButtons", doc);
+        Doc.AddDocToList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data", doc);
         return true;
     }
     remButtonDoc = (doc: Doc) => {
-        Doc.RemoveDocFromList(CurrentUserUtils.UserDocument, "docButtons", doc);
+        Doc.RemoveDocFromList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data", doc);
         return true;
     }
     @computed get docButtons() {
         return <div className="mainView-docButtons" style={{ left: (this._flyoutTranslate ? this.flyoutWidth : 0) + 20 }} >
             <MainViewNotifs />
-            <CollectionLinearView Document={CurrentUserUtils.UserDocument} DataDoc={undefined}
-                fieldKey={"docButtons"}
+            <CollectionLinearView 
+                Document={CurrentUserUtils.UserDocument.expandingButtons as Doc} 
+                DataDoc={undefined}
+                fieldKey={"data"}
                 fieldExt={""}
                 showHiddenControls={true}
                 select={emptyFunction}
diff --git a/src/client/views/nodes/ColorBox.scss b/src/client/views/nodes/ColorBox.scss
index 8df617fca..32a7891c6 100644
--- a/src/client/views/nodes/ColorBox.scss
+++ b/src/client/views/nodes/ColorBox.scss
@@ -1,10 +1,13 @@
-.colorBox-container {
+.colorBox-container, .colorBox-container-interactive {
      width:100%;
      height:100%;
      position: relative;
-     pointer-events:all;
+     pointer-events: none;
      
     .sketch-picker {
         margin:auto;
     }
+}
+.colorBox-container-interactive {
+    pointer-events:all;
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index 4aff770f9..d280258ae 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -4,13 +4,19 @@ import { SketchPicker } from 'react-color';
 import { FieldView, FieldViewProps } from './FieldView';
 import "./ColorBox.scss";
 import { InkingControl } from "../InkingControl";
+import { DocStaticComponent } from "../DocComponent";
+import { documentSchema } from "./DocumentView";
+import { makeInterface } from "../../../new_fields/Schema";
+
+type ColorDocument = makeInterface<[typeof documentSchema]>;
+const ColorDocument = makeInterface(documentSchema);
 
 @observer
-export class ColorBox extends React.Component<FieldViewProps> {
+export class ColorBox extends DocStaticComponent<FieldViewProps, ColorDocument>(ColorDocument) {
     public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(ColorBox, fieldKey); }
     render() {
-        return <div className="colorBox-container" >
+        return <div className={`colorBox-container${this.active() ? "-interactive" : ""}`} onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}>
             <SketchPicker color={InkingControl.Instance.selectedColor} onChange={InkingControl.Instance.switchColor} />
         </div>;
     }
-}
\ No newline at end of file
+} 
\ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index b05d0046c..181f37d36 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -43,7 +43,6 @@ import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './Format
 import React = require("react");
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ContextMenu } from '../ContextMenu';
-import { TextShadowProperty } from 'csstype';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -490,7 +489,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         );
 
         this._heightReactionDisposer = reaction(
-            () => this.props.Document[WidthSym](),
+            () => [this.props.Document[WidthSym](), this.props.Document.autoHeight],
             () => this.tryUpdateHeight()
         );
 
@@ -984,13 +983,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     @action
     tryUpdateHeight() {
-        const ChromeHeight = this.props.ChromeHeight;
-        let sh = this._ref.current ? this._ref.current.scrollHeight : 0;
-        if (!this.props.Document.isAnimating && this.props.Document.autoHeight && sh !== 0 && getComputedStyle(this._ref.current!.parentElement!).top === "0px") {
+        let scrollHeight = this._ref.current ? this._ref.current.scrollHeight : 0;
+        if (!this.props.Document.isAnimating && this.props.Document.autoHeight && scrollHeight !== 0 &&
+            getComputedStyle(this._ref.current!.parentElement!).top === "0px") {  // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
             let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0);
             let dh = NumCast(this.props.Document.height, 0);
-            this.props.Document.height = Math.max(10, (nh ? dh / nh * sh : sh) + (ChromeHeight ? ChromeHeight() : 0));
-            this.dataDoc.nativeHeight = nh ? sh : undefined;
+            this.props.Document.height = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
+            this.dataDoc.nativeHeight = nh ? scrollHeight : undefined;
         }
     }
 
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 63b412a23..19797400f 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -115,7 +115,6 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
     private newScriptChange = (e: React.ChangeEvent<HTMLInputElement>) => this._scriptValue = e.currentTarget.value;
 
     whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive);
-    active = () => this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0;
     setPdfViewer = (pdfViewer: PDFViewer) => { this._pdfViewer = pdfViewer; };
     searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => this._searchString = e.currentTarget.value;
 
@@ -205,7 +204,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
     _initialScale: number | undefined;  // the initial scale of the PDF when first rendered which determines whether the document will be live on startup or not.  Getting bigger after startup won't make it automatically be live....
     render() {
         const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
-        let classname = "pdfBox-cont" + (InkingControl.Instance.selectedTool || !this.active ? "" : "-interactive");
+        let classname = "pdfBox-cont" + (this.active() ? "-interactive" : "");
         let noPdf = !(pdfUrl instanceof PdfField) || !this._pdf;
         if (this._initialScale === undefined) this._initialScale = this.props.ScreenToLocalTransform().Scale;
         if (this.props.isSelected() || this.props.Document.scrollY !== undefined) this._everActive = true;
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 73cac879e..8af6bbfd5 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -1,20 +1,17 @@
-import { action, computed, observable, runInAction, reaction } from "mobx";
+import { action, computed, observable, reaction, runInAction } from "mobx";
 import * as rp from 'request-promise';
 import { DocServer } from "../../../client/DocServer";
 import { Docs } from "../../../client/documents/Documents";
 import { Attribute, AttributeGroup, Catalog, Schema } from "../../../client/northstar/model/idea/idea";
 import { ArrayUtil } from "../../../client/northstar/utils/ArrayUtil";
-import { CollectionViewType } from "../../../client/views/collections/CollectionBaseView";
-import { CollectionView } from "../../../client/views/collections/CollectionView";
+import { UndoManager } from "../../../client/util/UndoManager";
 import { Doc, DocListCast } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
-import { Cast, StrCast, PromiseValue } from "../../../new_fields/Types";
+import { ScriptField } from "../../../new_fields/ScriptField";
+import { Cast, PromiseValue } from "../../../new_fields/Types";
 import { Utils } from "../../../Utils";
 import { RouteStore } from "../../RouteStore";
-import { ScriptField } from "../../../new_fields/ScriptField";
-import { ButtonBox } from "../../../client/views/nodes/ButtonBox";
-import { UndoManager } from "../../../client/util/UndoManager";
 
 export class CurrentUserUtils {
     private static curr_id: string;
@@ -22,176 +19,172 @@ export class CurrentUserUtils {
     private static mainDocId: string | undefined;
 
     public static get id() { return this.curr_id; }
-    @computed public static get UserDocument() { return Doc.UserDoc(); }
     public static get MainDocId() { return this.mainDocId; }
     public static set MainDocId(id: string | undefined) { this.mainDocId = id; }
+    @computed public static get UserDocument() { return Doc.UserDoc(); }
 
     @observable public static GuestTarget: Doc | undefined;
     @observable public static GuestWorkspace: Doc | undefined;
 
     private static createUserDocument(id: string): Doc {
         let doc = new Doc(id, true);
-        doc.viewType = CollectionViewType.Tree;
-        doc.layout = CollectionView.LayoutString();
         doc.title = Doc.CurrentUserEmail;
-        doc.data = new List<Doc>();
-        doc.gridGap = 5;
-        doc.xMargin = 5;
-        doc.yMargin = 5;
-        doc.height = 42;
-        doc.boxShadow = "0 0";
-        doc.convertToButtons = true; // for CollectionLinearView used as the docButton layout 
-        doc.optionalRightCollection = Docs.Create.StackingDocument([], { title: "New mobile uploads" });
         return this.updateUserDocument(doc);// this should be the last 
     }
 
-    static updateUserDocument(doc: Doc) {
+    // a default set of note types .. not being used yet...
+    static setupNoteTypes(doc: Doc) {
+        let notes = [
+            Docs.Create.TextDocument({ title: "Note", backgroundColor: "yellow", isTemplate: true }),
+            Docs.Create.TextDocument({ title: "Idea", backgroundColor: "pink", isTemplate: true }),
+            Docs.Create.TextDocument({ title: "Topic", backgroundColor: "lightBlue", isTemplate: true }),
+            Docs.Create.TextDocument({ title: "Person", backgroundColor: "lightGreen", isTemplate: true })
+        ];
+        doc.noteTypes = Docs.Create.TreeDocument(notes, { title: "Note Types", height: 75 });
+    }
 
-        if (doc.undoBtn === undefined) {
-            doc.undoBtn = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, title: "Collection", icon: "undo-alt" });
-            (doc.undoBtn as Doc).onClick = ScriptField.MakeScript('undo()');
-            Doc.AddDocToList(doc, "docButtons", doc.undoBtn as Doc);
-        }
-        if (doc.redoBtn === undefined) {
-            doc.redoBtn = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, title: "Collection", icon: "redo-alt" });
-            (doc.redoBtn as Doc).onClick = ScriptField.MakeScript('redo()');
-            Doc.AddDocToList(doc, "docButtons", doc.redoBtn as Doc);
-        }
+    // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools
+    static setupCreatorButtons() {
+        let docProtoData = [
+            { title: "collection", icon: "folder", script: 'Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" })' },
+            { title: "web page", icon: "globe-asia", script: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })' },
+            { title: "image", icon: "cat", script: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { width: 200, title: "an image of a cat" })' },
+            { title: "button", icon: "bolt", script: 'Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" })' },
+            { title: "presentation", icon: "tv", script: 'Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List<Doc>(), { width: 200, height: 500, title: "a presentation trail" })' },
+            { title: "import folder", icon: "cloud-upload-alt", script: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })' },
+        ];
+        return docProtoData.map(data => Docs.Create.FontIconDocument({
+            nativeWidth: 100, nativeHeight: 100, width: 100, height: 100,
+            title: data.title, icon: data.icon, onDragStart: ScriptField.MakeFunction(data.script)
+        }));
+    }
+
+    // setup the Creator button which will display the creator panel.  This panel will include the drag creators and the color picker.  when clicked, this panel will be displayed in the target container (ie, sidebarContainer)  
+    static setupCreatePanel(sidebarContainer: Doc) {
+        // setup a masonry view of all he creators
+        const dragCreators = Docs.Create.MasonryDocument(CurrentUserUtils.setupCreatorButtons(), {
+            width: 500, autoHeight: true, columnWidth: 35, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons"
+        });
+        // setup a color picker
+        const color = Docs.Create.ColorDocument({
+            title: "color picker", width: 400, removeDropProperties: new List<string>(["dropAction", "forceActive"])
+        });
+        color.dropAction = "alias";  // these must be set on the view document so they can't be part of the creator above.
+        color.forceActive = true;
+
+        return Docs.Create.ButtonDocument({
+            width: 35, height: 35, borderRounding: "50%", boxShadow: "2px 2px 1px", title: "Create", targetContainer: sidebarContainer,
+            panel: Docs.Create.StackingDocument([dragCreators, color], {
+                width: 500, height: 800, chromeStatus: "disabled", title: "creator stack"
+            }),
+            onClick: ScriptField.MakeScript("this.targetContainer.proto = this.panel")
+        });
+    }
+
+    // setup the Library button which will display the library panel.  This panel includes a collection of workspaces, documents, and recently closed views
+    static setupLibraryPanel(sidebarContainer: Doc, doc: Doc) {
         // setup workspaces library item
-        if (doc.workspaces === undefined) {
-            const workspaces = Docs.Create.TreeDocument([], { title: "WORKSPACES", height: 100 });
-            workspaces.boxShadow = "0 0";
-            doc.workspaces = workspaces;
-        }
-        PromiseValue(Cast(doc.workspaces, Doc)).then(workspaces => {
-            if (workspaces) {
-                workspaces.backgroundColor = "#eeeeee";
-                workspaces.preventTreeViewOpen = true;
-                workspaces.forceActive = true;
-                workspaces.lockedPosition = true;
-                if (StrCast(workspaces.title) === "Workspaces") {
-                    workspaces.title = "WORKSPACES";
-                }
-            }
+        doc.workspaces = Docs.Create.TreeDocument([], {
+            title: "WORKSPACES", height: 100, forceActive: true, boxShadow: "0 0", lockedPosition: true, backgroundColor: "#eeeeee"
         });
 
-        // setup notes list
-        if (doc.noteTypes === undefined) {
-            let notes = [Docs.Create.TextDocument({ title: "Note", backgroundColor: "yellow", isTemplate: true }),
-            Docs.Create.TextDocument({ title: "Idea", backgroundColor: "pink", isTemplate: true }),
-            Docs.Create.TextDocument({ title: "Topic", backgroundColor: "lightBlue", isTemplate: true }),
-            Docs.Create.TextDocument({ title: "Person", backgroundColor: "lightGreen", isTemplate: true })];
-            const noteTypes = Docs.Create.TreeDocument(notes, { title: "Note Types", height: 75 });
-            doc.noteTypes = noteTypes;
-        }
-        PromiseValue(Cast(doc.noteTypes, Doc)).then(noteTypes => noteTypes && PromiseValue(noteTypes.data).then(DocListCast));
+        doc.documents = Docs.Create.TreeDocument([], {
+            title: "DOCUMENTS", gridGap: 5, xMargin: 5, yMargin: 5, height: 42, width: 100, boxShadow: "0 0", backgroundColor: "#eeeeee", preventTreeViewOpen: true, forceActive: true, lockedPosition: true
+        });
 
         // setup Recently Closed library item
-        if (doc.recentlyClosed === undefined) {
-            const recentlyClosed = Docs.Create.TreeDocument([], { title: "Recently Closed".toUpperCase(), height: 75 });
-            recentlyClosed.boxShadow = "0 0";
-            doc.recentlyClosed = recentlyClosed;
-        }
-        PromiseValue(Cast(doc.recentlyClosed, Doc)).then(recent => {
-            if (recent) {
-                recent.backgroundColor = "#eeeeee";
-                recent.preventTreeViewOpen = true;
-                recent.forceActive = true;
-                recent.lockedPosition = true;
-                if (StrCast(recent.title) === "Recently Closed") {
-                    recent.title = "RECENTLY CLOSED";
-                }
-            }
+        doc.recentlyClosed = Docs.Create.TreeDocument([], {
+            title: "Recently Closed".toUpperCase(), height: 75, boxShadow: "0 0", preventTreeViewOpen: true, forceActive: true, lockedPosition: true, backgroundColor: "#eeeeee"
         });
 
+        return Docs.Create.ButtonDocument({
+            width: 50, height: 35, borderRounding: "50%", boxShadow: "2px 2px 1px", title: "Library",
+            panel: Docs.Create.TreeDocument([doc.workspaces as Doc, doc, doc.recentlyClosed as Doc], {
+                title: "Library", xMargin: 5, yMargin: 5, gridGap: 5, forceActive: true, dropAction: "alias", lockedPosition: true
+            }),
+            targetContainer: sidebarContainer,
+            onClick: ScriptField.MakeScript("this.targetContainer.proto = this.panel")
+        });
+    }
 
-        if (doc.curPresentation === undefined) {
-            const curPresentation = Docs.Create.PresDocument(new List<Doc>(), { title: "Presentation" });
-            curPresentation.boxShadow = "0 0";
-            doc.curPresentation = curPresentation;
-        }
+    // setup the Search button which will display the search panel.  
+    static setupSearchPanel(sidebarContainer: Doc) {
+        return Docs.Create.ButtonDocument({
+            width: 50, height: 35, borderRounding: "50%", boxShadow: "2px 2px 1px", title: "Search",
+            panel: Docs.Create.QueryDocument({
+                title: "search stack", ignoreClick: true
+            }),
+            targetContainer: sidebarContainer,
+            onClick: ScriptField.MakeScript("this.targetContainer.proto = this.panel")
+        });
+    }
 
-        if (doc.Library === undefined) {
-            let Search = Docs.Create.ButtonDocument({ width: 50, height: 35, borderRounding: "50%", boxShadow: "2px 2px 1px", title: "Search" });
-            let Library = Docs.Create.ButtonDocument({ width: 50, height: 35, borderRounding: "50%", boxShadow: "2px 2px 1px", title: "Library" });
-            let Create = Docs.Create.ButtonDocument({ width: 35, height: 35, borderRounding: "50%", boxShadow: "2px 2px 1px", title: "Create" });
-            if (doc.sidebarContainer === undefined) {
-                doc.sidebarContainer = new Doc();
-                (doc.sidebarContainer as Doc).chromeStatus = "disabled";
-            }
+    // setup the list of sidebar mode buttons which determine what is displayed in the sidebar
+    static setupSidebarButtons(doc: Doc) {
+        doc.sidebarContainer = new Doc();
+        (doc.sidebarContainer as Doc).chromeStatus = "disabled";
 
-            const library = Docs.Create.TreeDocument([doc.workspaces as Doc, doc, doc.recentlyClosed as Doc], { title: "Library" });
-            library.forceActive = true;
-            library.lockedPosition = true;
-            library.gridGap = 5;
-            library.xMargin = 5;
-            library.yMargin = 5;
-            library.dropAction = "alias";
-            Library.targetContainer = doc.sidebarContainer;
-            Library.library = library;
-            Library.onClick = ScriptField.MakeScript("this.targetContainer.proto = this.library");
+        doc.CreateBtn = this.setupCreatePanel(doc.sidebarContainer as Doc);
+        doc.LibraryBtn = this.setupLibraryPanel(doc.sidebarContainer as Doc, doc);
+        doc.SearchBtn = this.setupSearchPanel(doc.sidebarContainer as Doc);
 
-            const searchBox = Docs.Create.QueryDocument({ title: "search stack" });
-            searchBox.ignoreClick = true;
-            Search.searchBox = searchBox;
-            Search.targetContainer = doc.sidebarContainer;
-            Search.onClick = ScriptField.MakeScript("this.targetContainer.proto = this.searchBox");
+        // Finally, setup the list of buttons to display in the sidebar
+        doc.sidebarButtons = Docs.Create.StackingDocument([doc.SearchBtn as Doc, doc.LibraryBtn as Doc, doc.CreateBtn as Doc], {
+            width: 500, height: 80, boxShadow: "0 0", sectionFilter: "title", hideHeadings: true, ignoreClick: true,
+            backgroundColor: "lightgrey", chromeStatus: "disabled", title: "library stack"
+        });
+    }
 
-            let createCollection = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, title: "Collection", icon: "folder" });
-            createCollection.onDragStart = ScriptField.MakeFunction('Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" })');
-            let createWebPage = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, title: "Web Page", icon: "globe-asia" });
-            createWebPage.onDragStart = ScriptField.MakeFunction('Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })');
-            let createCatImage = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, title: "Image", icon: "cat" });
-            createCatImage.onDragStart = ScriptField.MakeFunction('Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { width: 200, title: "an image of a cat" })');
-            let createButton = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, title: "Button", icon: "bolt" });
-            createButton.onDragStart = ScriptField.MakeFunction('Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" })');
-            let createPresentation = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, title: "Presentation", icon: "tv" });
-            createPresentation.onDragStart = ScriptField.MakeFunction('Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List<Doc>(), { width: 200, height: 500, title: "a presentation trail" })');
-            let createFolderImport = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, title: "Import Folder", icon: "cloud-upload-alt" });
-            createFolderImport.onDragStart = ScriptField.MakeFunction('Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })');
-            const dragCreators = Docs.Create.MasonryDocument([createCollection, createWebPage, createCatImage, createButton, createPresentation, createFolderImport], { width: 500, autoHeight: true, columnWidth: 35, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons" });
-            const color = Docs.Create.ColorDocument({ title: "color picker", width: 400 });
-            color.dropAction = "alias";
-            color.ignoreClick = true;
-            color.removeDropProperties = new List<string>(["dropAction", "ignoreClick"]);
-            const creators = Docs.Create.StackingDocument([dragCreators, color], { width: 500, height: 800, chromeStatus: "disabled", title: "creator stack" });
-            Create.targetContainer = doc.sidebarContainer;
-            Create.creators = creators;
-            Create.onClick = ScriptField.MakeScript("this.targetContainer.proto = this.creators");
+    /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window
+    static setupExpandingButtons(doc: Doc) {
+        doc.undoBtn = Docs.Create.FontIconDocument(
+            { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, onClick: ScriptField.MakeScript("undo()"), title: "undo button", icon: "undo-alt" });
+        doc.redoBtn = Docs.Create.FontIconDocument(
+            { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, onClick: ScriptField.MakeScript("redo()"), title: "redo button", icon: "redo-alt" });
 
-            const libraryButtons = Docs.Create.StackingDocument([Search, Library, Create], { width: 500, height: 80, chromeStatus: "disabled", title: "library stack" });
-            libraryButtons.sectionFilter = "title";
-            libraryButtons.boxShadow = "0 0";
-            libraryButtons.ignoreClick = true;
-            libraryButtons.hideHeadings = true;
-            libraryButtons.backgroundColor = "lightgrey";
+        doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc], {
+            title: "expanding buttons", gridGap: 5, xMargin: 5, yMargin: 5, height: 42, width: 100, boxShadow: "0 0",
+            backgroundColor: "#eeeeee", preventTreeViewOpen: true, forceActive: true, lockedPosition: true, convertToButtons: true
+        });
+    }
 
-            doc.libraryButtons = libraryButtons;
-            doc.Library = Library;
-            doc.Create = Create;
-            doc.Search = Search;
-        }
-        PromiseValue(Cast(doc.libraryButtons, Doc)).then(libraryButtons => { });
-        PromiseValue(Cast(doc.Library, Doc)).then(library => library && library.library && library.targetContainer && (library.onClick as ScriptField).script.run({ this: library }));
-        PromiseValue(Cast(doc.Create, Doc)).then(async create => create && create.creators && create.targetContainer);
-        PromiseValue(Cast(doc.Search, Doc)).then(async search => search && search.searchBox && search.targetContainer);
+    // sets up the default set of documents to be shown in the Overlay layer
+    static setupOverlays(doc: Doc) {
+        doc.overlays = Docs.Create.FreeformDocument([], { title: "Overlays", backgroundColor: "#aca3a6" });
+        doc.linkFollowBox = Docs.Create.LinkFollowBoxDocument({ x: 250, y: 20, width: 500, height: 370, title: "Link Follower" });
+        Doc.AddDocToList(doc.overlays as Doc, "data", doc.linkFollowBox as Doc);
+    }
 
-        if (doc.overlays === undefined) {
-            const overlays = Docs.Create.FreeformDocument([], { title: "Overlays" });
-            Doc.GetProto(overlays).backgroundColor = "#aca3a6";
-            doc.overlays = overlays;
-        }
+    // the initial presentation Doc to use
+    static setupDefaultPresentation(doc: Doc) {
+        doc.curPresentation = Docs.Create.PresDocument(new List<Doc>(), { title: "Presentation", boxShadow: "0 0" });
+    }
 
-        if (doc.linkFollowBox === undefined) {
-            PromiseValue(Cast(doc.overlays, Doc)).then(overlays => overlays && Doc.AddDocToList(overlays, "data", doc.linkFollowBox = Docs.Create.LinkFollowBoxDocument({ x: 250, y: 20, width: 500, height: 370, title: "Link Follower" })));
-        }
+    static setupMobileUploads(doc: Doc) {
+        doc.optionalRightCollection = Docs.Create.StackingDocument([], { title: "New mobile uploads" });
+    }
+
+    static updateUserDocument(doc: Doc) {
+        (doc.optionalRightCollection === undefined) && CurrentUserUtils.setupMobileUploads(doc);
+        (doc.noteTypes === undefined) && CurrentUserUtils.setupNoteTypes(doc);
+        (doc.overlays === undefined) && CurrentUserUtils.setupOverlays(doc);
+        (doc.expandingButtons === undefined) && CurrentUserUtils.setupExpandingButtons(doc);
+        (doc.curPresentation === undefined) && CurrentUserUtils.setupDefaultPresentation(doc);
+        (doc.sidebarButtons === undefined) && CurrentUserUtils.setupSidebarButtons(doc);
+
+        // this is equivalent to using PrefetchProxies to make sure all the sidebarButtons and noteType internal Doc's have been retrieved.
+        PromiseValue(Cast(doc.noteTypes, Doc)).then(noteTypes => noteTypes && PromiseValue(noteTypes.data).then(DocListCast));
+        PromiseValue(Cast(doc.sidebarButtons, Doc)).then(stackingDoc => {
+            stackingDoc && PromiseValue(Cast(stackingDoc.data, listSpec(Doc))).then(sidebarButtons => {
+                sidebarButtons && sidebarButtons.map((sidebarBtn, i) => {
+                    sidebarBtn && PromiseValue(Cast(sidebarBtn, Doc)).then(async btn => {
+                        btn && btn.panel && btn.targetContainer && i === 1 && (btn.onClick as ScriptField).script.run({ this: btn });
+                    });
+                });
+            });
+        });
 
-        doc.title = "DOCUMENTS";
-        doc.backgroundColor = "#eeeeee";
-        doc.width = 100;
-        doc.preventTreeViewOpen = true;
-        doc.forceActive = true;
-        doc.lockedPosition = true;
+        // setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet
         doc.undoBtn && reaction(() => UndoManager.undoStack.slice(), () => (doc.undoBtn as Doc).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true });
         doc.redoBtn && reaction(() => UndoManager.redoStack.slice(), () => (doc.redoBtn as Doc).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true });
 
@@ -215,12 +208,8 @@ export class CurrentUserUtils {
         await rp.get(Utils.prepend(RouteStore.getUserDocumentId)).then(id => {
             if (id && id !== "guest") {
                 return DocServer.GetRefField(id).then(async field => {
-                    if (field instanceof Doc) {
-                        await this.updateUserDocument(field);
-                        runInAction(() => Doc.SetUserDoc(field));
-                    } else {
-                        runInAction(() => Doc.SetUserDoc(this.createUserDocument(id)));
-                    }
+                    let userDoc = field instanceof Doc ? await this.updateUserDocument(field) : this.createUserDocument(id);
+                    runInAction(() => Doc.SetUserDoc(userDoc));
                 });
             } else {
                 throw new Error("There should be a user id! Why does Dash think there isn't one?");
-- 
cgit v1.2.3-70-g09d2


From 33811c112c7e479813908ba10f72813954a3e289 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 15 Oct 2019 16:25:10 -0400
Subject: working version of inking buttons

---
 src/client/documents/Documents.ts                  |  2 ++
 src/client/util/DragManager.ts                     |  4 +--
 src/client/util/SelectionManager.ts                | 11 --------
 src/client/views/CollectionLinearView.tsx          |  4 +--
 src/client/views/InkingControl.tsx                 |  6 ++--
 src/client/views/MainView.tsx                      |  6 ++--
 src/client/views/nodes/ColorBox.tsx                | 33 ++++++++++++++++++++--
 src/client/views/nodes/DocumentView.tsx            |  7 +++--
 src/client/views/nodes/FontIconBox.tsx             |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx        | 16 ++++++-----
 src/new_fields/Doc.ts                              |  4 +++
 .../authentication/models/current_user_utils.ts    | 31 +++++++++++---------
 12 files changed, 78 insertions(+), 48 deletions(-)

(limited to 'src/client/views/nodes/ColorBox.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 6df172f46..f4fce34ac 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -93,6 +93,8 @@ export interface DocumentOptions {
     autoHeight?: boolean;
     removeDropProperties?: List<string>; // list of properties that should be removed from a document when it is dropped.  e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document
     dbDoc?: Doc;
+    unchecked?: ScriptField; // returns whether a check box is unchecked
+    activePen?: Doc; // which pen document is currently active (used as the radio button state for the 'unhecked' pen tool scripts)
     onClick?: ScriptField;
     onDragStart?: ScriptField; //script to execute at start of drag operation --  e.g., when a "creator" button is dragged this script generates a different document to drop
     icon?: string;
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 169f2edec..92666c03c 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -234,9 +234,9 @@ export namespace DragManager {
 
     export let StartDragFunctions: (() => void)[] = [];
 
-    export async function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
+    export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
         runInAction(() => StartDragFunctions.map(func => func()));
-        await dragData.draggedDocuments.map(d => d.dragFactory);
+        dragData.draggedDocuments.map(d => d.dragFactory); // does this help?  trying to make sure the dragFactory Doc is loaded
         StartDrag(eles, dragData, downX, downY, options, options && options.finishDrag ? options.finishDrag :
             (dropData: { [id: string]: any }) => {
                 (dropData.droppedDocuments =
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index df1b46b33..2d717ca57 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -45,17 +45,6 @@ export namespace SelectionManager {
     }
 
     const manager = new Manager();
-    reaction(() => manager.SelectedDocuments, sel => {
-        let targetColor = "#FFFFFF";
-        if (sel.length > 0) {
-            let firstView = sel[0];
-            let doc = firstView.props.Document;
-            let targetDoc = doc.isTemplate ? doc : Doc.GetProto(doc);
-            let stored = StrCast(targetDoc.backgroundColor);
-            stored.length > 0 && (targetColor = stored);
-        }
-        InkingControl.Instance.updateSelectedColor(targetColor);
-    }, { fireImmediately: true });
 
     export function DeselectDoc(docView: DocumentView): void {
         manager.DeselectDoc(docView);
diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx
index 9d1dd7749..eb3c768d0 100644
--- a/src/client/views/CollectionLinearView.tsx
+++ b/src/client/views/CollectionLinearView.tsx
@@ -48,7 +48,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
     drop = action((e: Event, de: DragManager.DropEvent) => {
         (de.data as DragManager.DocumentDragData).draggedDocuments.map((doc, i) => {
             let dbox = doc;
-            if (!doc.onDragStart && this.props.Document.convertToButtons && doc.viewType !== CollectionViewType.Linear) {
+            if (!doc.onDragStart && !doc.onClick && this.props.Document.convertToButtons && doc.viewType !== CollectionViewType.Linear) {
                 dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: "bolt" });
                 dbox.dragFactory = doc;
                 dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined;
@@ -90,7 +90,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                         return <div className={`collectionLinearView-docBtn` + (pair.layout.onClick || pair.layout.onDragStart ? "-scalable" : "")} key={StrCast(pair.layout.title)} ref={dref}
                             style={{
                                 width: nested ? pair.layout[WidthSym]() : this.dimension(),
-                                height: nested ? pair.layout[HeightSym]() : this.dimension(),
+                                height: nested && pair.layout.isExpanded ? pair.layout[HeightSym]() : this.dimension(),
                                 transform: nested ? undefined : `translate(${deltaSize / 2}px, ${deltaSize / 2}px)`
                             }}  >
                             <DocumentView
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 41dec9f83..149a4fc8a 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -117,9 +117,9 @@ export class InkingControl {
     }
 
 }
-Scripting.addGlobal(function activatePen() { return InkingControl.Instance.switchTool(InkTool.Pen); });
-Scripting.addGlobal(function activateBrush() { return InkingControl.Instance.switchTool(InkTool.Highlighter); });
-Scripting.addGlobal(function activateEraser() { return InkingControl.Instance.switchTool(InkTool.Eraser); });
+Scripting.addGlobal(function activatePen(pen: any, width: any, color: any) { InkingControl.Instance.switchTool(pen ? InkTool.Pen : InkTool.None); InkingControl.Instance.switchWidth(width); InkingControl.Instance.updateSelectedColor(color); });
+Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any) { InkingControl.Instance.switchTool(pen ? InkTool.Highlighter : InkTool.None); InkingControl.Instance.switchWidth(width); InkingControl.Instance.updateSelectedColor(color); });
+Scripting.addGlobal(function activateEraser(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Eraser : InkTool.None); });
 Scripting.addGlobal(function deactivateInk() { return InkingControl.Instance.switchTool(InkTool.None); });
 Scripting.addGlobal(function setInkWidth(width: any) { return InkingControl.Instance.switchWidth(width); });
 Scripting.addGlobal(function setInkColor(color: any) { return InkingControl.Instance.updateSelectedColor(color); });
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 4e06763a4..8035916a2 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -466,12 +466,12 @@ export class MainView extends React.Component {
     }
     _docBtnRef = React.createRef<HTMLDivElement>();
     @computed get docButtons() {
-        console.log("stuff = " + this.flyoutWidthFunc() + " " + this.getContentsHeight());
         if (CurrentUserUtils.UserDocument.expandingButtons instanceof Doc) {
-            return <div className="mainView-docButtons" ref={this._docBtnRef} style={{ left: (this._flyoutTranslate ? this.flyoutWidth : 0) + 20, height: "42px" }} >
+            return <div className="mainView-docButtons" ref={this._docBtnRef}
+                style={{ left: (this._flyoutTranslate ? this.flyoutWidth : 0) + 20, height: !CurrentUserUtils.UserDocument.expandingButtons.isExpanded ? "42px" : undefined }} >
                 <MainViewNotifs />
                 <CollectionLinearView
-                    Document={CurrentUserUtils.UserDocument.expandingButtons as Doc}
+                    Document={CurrentUserUtils.UserDocument.expandingButtons}
                     DataDoc={undefined}
                     fieldKey={"data"}
                     fieldExt={""}
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index d280258ae..87c91c121 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -7,6 +7,9 @@ import { InkingControl } from "../InkingControl";
 import { DocStaticComponent } from "../DocComponent";
 import { documentSchema } from "./DocumentView";
 import { makeInterface } from "../../../new_fields/Schema";
+import { trace, reaction, observable, action, IReactionDisposer } from "mobx";
+import { SelectionManager } from "../../util/SelectionManager";
+import { StrCast } from "../../../new_fields/Types";
 
 type ColorDocument = makeInterface<[typeof documentSchema]>;
 const ColorDocument = makeInterface(documentSchema);
@@ -14,9 +17,35 @@ const ColorDocument = makeInterface(documentSchema);
 @observer
 export class ColorBox extends DocStaticComponent<FieldViewProps, ColorDocument>(ColorDocument) {
     public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(ColorBox, fieldKey); }
+    _selectedDisposer: IReactionDisposer | undefined;
+    componentDidMount() {
+        this._selectedDisposer = reaction(() => SelectionManager.SelectedDocuments(),
+            action(() => this._startupColor = SelectionManager.SelectedDocuments().length ? StrCast(SelectionManager.SelectedDocuments()[0].Document.backgroundColor, "black") : "black"),
+            { fireImmediately: true });
+
+        // compare to this reaction that used to be in Selection Manager
+        // reaction(() => manager.SelectedDocuments, sel => {
+        //     let targetColor = "#FFFFFF";
+        //     if (sel.length > 0) {
+        //         let firstView = sel[0];
+        //         let doc = firstView.props.Document;
+        //         let targetDoc = doc.isTemplate ? doc : Doc.GetProto(doc);
+        //         let stored = StrCast(targetDoc.backgroundColor);
+        //         stored.length > 0 && (targetColor = stored);
+        //     }
+        //     InkingControl.Instance.updateSelectedColor(targetColor);
+        // }, { fireImmediately: true });
+    }
+    componentWillUnmount() {
+        this._selectedDisposer && this._selectedDisposer();
+    }
+
+    @observable _startupColor = "black";
+
     render() {
-        return <div className={`colorBox-container${this.active() ? "-interactive" : ""}`} onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}>
-            <SketchPicker color={InkingControl.Instance.selectedColor} onChange={InkingControl.Instance.switchColor} />
+        return <div className={`colorBox-container${this.active() ? "-interactive" : ""}`}
+            onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}>
+            <SketchPicker color={this._startupColor} onChange={InkingControl.Instance.switchColor} />
         </div>;
     }
 } 
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c0e5185c1..6f99c541f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -107,7 +107,7 @@ export const documentSchema = createSchema({
     removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped
     onClick: ScriptField,       // script to run when document is clicked (can be overriden by an onClick prop)
     onDragStart: ScriptField,   // script to run when document is dragged (without being selected).  the script should return the Doc to be dropped.
-    dragFactory: Doc,           // the document that serves as the "template" for the onDragStart script
+    dragFactory: Doc,           // the document that serves as the "template" for the onDragStart script.  ie, to drag out copies of the dragFactory document.
     ignoreAspect: "boolean",    // whether aspect ratio should be ignored when laying out or manipulating the document
     autoHeight: "boolean",      // whether the height of the document should be computed automatically based on its contents
     isTemplate: "boolean",      // whether this document acts as a template layout for describing how other documents should be displayed
@@ -676,6 +676,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
         const highlightColors = ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"];
         const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid", "solid"];
+        let highlighting = fullDegree && this.props.Document.type !== DocumentType.FONTICON && this.props.Document.viewType !== CollectionViewType.Linear
         return (
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
@@ -683,8 +684,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     transition: this.props.Document.isAnimating !== undefined ? ".5s linear" : StrCast(this.Document.transition),
                     pointerEvents: this.Document.isBackground && !this.isSelected() ? "none" : "all",
                     color: StrCast(this.Document.color),
-                    outline: fullDegree && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px",
-                    border: fullDegree && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined,
+                    outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px",
+                    border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined,
                     background: this.props.Document.type === DocumentType.FONTICON || this.props.Document.viewType === CollectionViewType.Linear ? undefined : backgroundColor,
                     width: animwidth,
                     height: animheight,
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index aa442cd92..efe47d8a8 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -18,7 +18,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
 
     render() {
         return <button className="fontIconBox-outerDiv" style={{ background: StrCast(this.props.Document.backgroundColor) }}>
-            <FontAwesomeIcon className="fontIconBox-icon" icon={this.Document.icon as any} size="sm" color="white" />
+            <FontAwesomeIcon className="fontIconBox-icon" icon={this.Document.icon as any} size="sm" opacity={this.props.Document.unchecked ? "0.5" : "1"} />
         </button>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 181f37d36..1bdff3ec7 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -927,16 +927,18 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     onMouseUp = (e: React.MouseEvent): void => {
         e.stopPropagation();
 
-        // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there are nested prosemirrors.  We only want the lowest level prosemirror to be invoked.
-        if ((this._editorView as any).mouseDown) {
-            let originalUpHandler = (this._editorView as any).mouseDown.up;
-            (this._editorView as any).root.removeEventListener("mouseup", originalUpHandler);
-            (this._editorView as any).mouseDown.up = (e: MouseEvent) => {
+        let view = this._editorView as any;
+        // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there 
+        // are nested prosemirrors.  We only want the lowest level prosemirror to be invoked.
+        if (view.mouseDown) {
+            let originalUpHandler = view.mouseDown.up;
+            view.root.removeEventListener("mouseup", originalUpHandler);
+            view.mouseDown.up = (e: MouseEvent) => {
                 !(e as any).formattedHandled && originalUpHandler(e);
-                e.stopPropagation();
+                // e.stopPropagation();
                 (e as any).formattedHandled = true;
             };
-            (this._editorView as any).root.addEventListener("mouseup", (this._editorView as any).mouseDown.up);
+            view.root.addEventListener("mouseup", view.mouseDown.up);
         }
     }
 
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 4bed113e3..276596fb8 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -525,6 +525,7 @@ export namespace Doc {
     export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string): Doc {
         const copy = new Doc(copyProtoId, true);
         Object.keys(doc).forEach(key => {
+            let cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
             const field = ProxyField.WithoutProxy(() => doc[key]);
             if (key === "proto" && copyProto) {
                 if (doc[key] instanceof Doc) {
@@ -533,6 +534,8 @@ export namespace Doc {
             } else {
                 if (field instanceof RefField) {
                     copy[key] = field;
+                } else if (cfield instanceof ComputedField) {
+                    copy[key] = ComputedField.MakeFunction(cfield.script.originalScript);
                 } else if (field instanceof ObjectField) {
                     copy[key] = ObjectField.MakeCopy(field);
                 } else if (field instanceof Promise) {
@@ -735,5 +738,6 @@ Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return Doc.Make
 Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy(field); });
 Scripting.addGlobal(function aliasDocs(field: any) { return new List<Doc>(field.map((d: any) => Doc.MakeAlias(d))); });
 Scripting.addGlobal(function docList(field: any) { return DocListCast(field); });
+Scripting.addGlobal(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2) });
 Scripting.addGlobal(function undo() { return UndoManager.Undo(); });
 Scripting.addGlobal(function redo() { return UndoManager.Redo(); });
\ No newline at end of file
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index c4b91dadd..3c4a46ed8 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -8,7 +8,7 @@ import { UndoManager } from "../../../client/util/UndoManager";
 import { Doc, DocListCast } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
-import { ScriptField } from "../../../new_fields/ScriptField";
+import { ScriptField, ComputedField } from "../../../new_fields/ScriptField";
 import { Cast, PromiseValue } from "../../../new_fields/Types";
 import { Utils } from "../../../Utils";
 import { RouteStore } from "../../RouteStore";
@@ -45,29 +45,32 @@ export class CurrentUserUtils {
     }
 
     // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools
-    static setupCreatorButtons() {
-        let docProtoData: { title: string, icon: string, drag?: string, click?: string }[] = [
+    static setupCreatorButtons(doc: Doc) {
+        doc.activePen = doc;
+        let docProtoData: { title: string, icon: string, drag?: string, click?: string, unchecked?: string, activePen?: Doc, backgroundColor?: string }[] = [
             { title: "collection", icon: "folder", drag: 'Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" })' },
             { title: "web page", icon: "globe-asia", drag: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })' },
             { title: "image", icon: "cat", drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { width: 200, title: "an image of a cat" })' },
             { title: "button", icon: "bolt", drag: 'Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" })' },
             { title: "presentation", icon: "tv", drag: 'Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List<Doc>(), { width: 200, height: 500, title: "a presentation trail" })' },
             { title: "import folder", icon: "cloud-upload-alt", drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })' },
-            { title: "pen", icon: "pen-nib", click: 'activatePen(); setInkWidth(2);' },
-            { title: "highlighter", icon: "pen", click: 'activateBrush(); setInkWidth(20);' },
-            { title: "eraser", icon: "eraser", click: 'activateEraser();' },
-            { title: "none", icon: "pause", click: 'deactivateInk();' },
+            { title: "pen", icon: "pen-nib", click: 'activatePen(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", unchecked: `!sameDocs(this.activePen.pen,  this)`, activePen: doc },
+            { title: "highlighter", icon: "pen", click: 'activateBrush(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc },
+            { title: "eraser", icon: "eraser", click: 'activateEraser(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc },
+            { title: "none", icon: "pause", click: 'deactivateInk();this.activePen.pen = this;', unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc },
         ];
         return docProtoData.map(data => Docs.Create.FontIconDocument({
-            nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: data.click ? "alias" : undefined,
-            title: data.title, icon: data.icon, onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined
+            nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: data.click ? "copy" : undefined, title: data.title, icon: data.icon,
+            onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined,
+            unchecked: data.unchecked ? ComputedField.MakeFunction(data.unchecked) : undefined, activePen: data.activePen,
+            backgroundColor: data.backgroundColor
         }));
     }
 
     // setup the Creator button which will display the creator panel.  This panel will include the drag creators and the color picker.  when clicked, this panel will be displayed in the target container (ie, sidebarContainer)  
-    static setupCreatePanel(sidebarContainer: Doc) {
+    static setupCreatePanel(sidebarContainer: Doc, doc: Doc) {
         // setup a masonry view of all he creators
-        const dragCreators = Docs.Create.MasonryDocument(CurrentUserUtils.setupCreatorButtons(), {
+        const dragCreators = Docs.Create.MasonryDocument(CurrentUserUtils.setupCreatorButtons(doc), {
             width: 500, autoHeight: true, columnWidth: 35, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons"
         });
         // setup a color picker
@@ -129,7 +132,7 @@ export class CurrentUserUtils {
         doc.sidebarContainer = new Doc();
         (doc.sidebarContainer as Doc).chromeStatus = "disabled";
 
-        doc.CreateBtn = this.setupCreatePanel(doc.sidebarContainer as Doc);
+        doc.CreateBtn = this.setupCreatePanel(doc.sidebarContainer as Doc, doc);
         doc.LibraryBtn = this.setupLibraryPanel(doc.sidebarContainer as Doc, doc);
         doc.SearchBtn = this.setupSearchPanel(doc.sidebarContainer as Doc);
 
@@ -143,9 +146,9 @@ export class CurrentUserUtils {
     /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window
     static setupExpandingButtons(doc: Doc) {
         doc.undoBtn = Docs.Create.FontIconDocument(
-            { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, onClick: ScriptField.MakeScript("undo()"), title: "undo button", icon: "undo-alt" });
+            { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: "alias", onClick: ScriptField.MakeScript("undo()"), title: "undo button", icon: "undo-alt" });
         doc.redoBtn = Docs.Create.FontIconDocument(
-            { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, onClick: ScriptField.MakeScript("redo()"), title: "redo button", icon: "redo-alt" });
+            { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: "alias", onClick: ScriptField.MakeScript("redo()"), title: "redo button", icon: "redo-alt" });
 
         doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc], {
             title: "expanding buttons", gridGap: 5, xMargin: 5, yMargin: 5, height: 42, width: 100, boxShadow: "0 0",
-- 
cgit v1.2.3-70-g09d2


From e4e6026dfb819dd141597e7a2dfab6c566f61fac Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 15 Oct 2019 23:37:36 -0400
Subject: fixes to feedback of color buttons

---
 src/Utils.ts                              | 10 +++++-----
 src/client/views/CollectionLinearView.tsx |  6 +++---
 src/client/views/GlobalKeyHandler.ts      |  3 +++
 src/client/views/InkingControl.tsx        |  2 +-
 src/client/views/nodes/ColorBox.tsx       |  8 ++++++++
 src/client/views/nodes/DocumentView.tsx   |  2 +-
 src/client/views/nodes/FontIconBox.tsx    | 25 ++++++++++++++++++++++---
 src/new_fields/Doc.ts                     |  2 +-
 8 files changed, 44 insertions(+), 14 deletions(-)

(limited to 'src/client/views/nodes/ColorBox.tsx')

diff --git a/src/Utils.ts b/src/Utils.ts
index 9a2f01f80..6889936b8 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -62,14 +62,14 @@ export namespace Utils {
     }
 
     export function fromRGBAstr(rgba: string) {
-        let rm = rgba.match(/rgb[a]?\(([0-9]+)/);
+        let rm = rgba.match(/rgb[a]?\(([ 0-9]+)/);
         let r = rm ? Number(rm[1]) : 0;
-        let gm = rgba.match(/rgb[a]?\([0-9]+,([0-9]+)/);
+        let gm = rgba.match(/rgb[a]?\([ 0-9]+,([ 0-9]+)/);
         let g = gm ? Number(gm[1]) : 0;
-        let bm = rgba.match(/rgb[a]?\([0-9]+,[0-9]+,([0-9]+)/);
+        let bm = rgba.match(/rgb[a]?\([ 0-9]+,[ 0-9]+,([ 0-9]+)/);
         let b = bm ? Number(bm[1]) : 0;
-        let am = rgba.match(/rgba?\([0-9]+,[0-9]+,[0-9]+,([0-9]+)/);
-        let a = am ? Number(am[1]) : 0;
+        let am = rgba.match(/rgba?\([ 0-9]+,[ 0-9]+,[ 0-9]+,([ .0-9]+)/);
+        let a = am ? Number(am[1]) : 1;
         return { r: r, g: g, b: b, a: a };
     }
 
diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx
index eb3c768d0..af3b194ea 100644
--- a/src/client/views/CollectionLinearView.tsx
+++ b/src/client/views/CollectionLinearView.tsx
@@ -25,6 +25,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
     @observable public addMenuToggle = React.createRef<HTMLInputElement>();
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _heightDisposer?: IReactionDisposer;
+    private _spacing = 20;
 
     componentWillUnmount() {
         this._dropDisposer && this._dropDisposer();
@@ -69,8 +70,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
         if (!ele.current) return Transform.Identity();
         let { scale, translateX, translateY } = Utils.GetScreenTransform(ele.current);
         return new Transform(-translateX, -translateY, 1 / scale);
-    };
-    _spacing = 20;
+    }
     render() {
         let guid = Utils.GenerateGuid();
         return <div className="collectionLinearView-outer">
@@ -118,7 +118,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                                 zoomToScale={emptyFunction}
                                 getScale={returnOne}>
                             </DocumentView>
-                        </div>
+                        </div>;
                     })}
                     {/* <li key="undoTest"><button className="add-button round-button" title="Click if undo isn't working" onClick={() => UndoManager.TraceOpenBatches()}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li> */}
 
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index f31a29bdc..9ca9fc163 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -10,6 +10,8 @@ import SharingManager from "../util/SharingManager";
 import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
 import { Cast, PromiseValue } from "../../new_fields/Types";
 import { ScriptField } from "../../new_fields/ScriptField";
+import { InkingControl } from "./InkingControl";
+import { InkTool } from "../../new_fields/InkField";
 
 const modifiers = ["control", "meta", "shift", "alt"];
 type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
@@ -64,6 +66,7 @@ export default class KeyManager {
         switch (keyname) {
             case "escape":
                 let main = MainView.Instance;
+                InkingControl.Instance.switchTool(InkTool.None);
                 if (main.isPointerDown) {
                     DragManager.AbortDrag();
                 } else {
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index b51264932..51fc7ca8f 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -25,7 +25,7 @@ export class InkingControl {
 
     switchTool = action((tool: InkTool): void => {
         this._selectedTool = tool;
-    })
+    });
     decimalToHexString(number: number) {
         if (number < 0) {
             number = 0xFFFFFFFF + number + 1;
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index 87c91c121..e4fef0922 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -10,6 +10,8 @@ import { makeInterface } from "../../../new_fields/Schema";
 import { trace, reaction, observable, action, IReactionDisposer } from "mobx";
 import { SelectionManager } from "../../util/SelectionManager";
 import { StrCast } from "../../../new_fields/Types";
+import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
+import { Doc } from "../../../new_fields/Doc";
 
 type ColorDocument = makeInterface<[typeof documentSchema]>;
 const ColorDocument = makeInterface(documentSchema);
@@ -18,10 +20,15 @@ const ColorDocument = makeInterface(documentSchema);
 export class ColorBox extends DocStaticComponent<FieldViewProps, ColorDocument>(ColorDocument) {
     public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(ColorBox, fieldKey); }
     _selectedDisposer: IReactionDisposer | undefined;
+    _penDisposer: IReactionDisposer | undefined;
     componentDidMount() {
         this._selectedDisposer = reaction(() => SelectionManager.SelectedDocuments(),
             action(() => this._startupColor = SelectionManager.SelectedDocuments().length ? StrCast(SelectionManager.SelectedDocuments()[0].Document.backgroundColor, "black") : "black"),
             { fireImmediately: true });
+        this._penDisposer = reaction(() => CurrentUserUtils.UserDocument.activePen instanceof Doc && CurrentUserUtils.UserDocument.activePen.pen,
+            action(() => this._startupColor = CurrentUserUtils.UserDocument.activePen instanceof Doc && CurrentUserUtils.UserDocument.activePen.pen instanceof Doc ?
+                StrCast(CurrentUserUtils.UserDocument.activePen.pen.backgroundColor, "black") : "black"),
+            { fireImmediately: true });
 
         // compare to this reaction that used to be in Selection Manager
         // reaction(() => manager.SelectedDocuments, sel => {
@@ -37,6 +44,7 @@ export class ColorBox extends DocStaticComponent<FieldViewProps, ColorDocument>(
         // }, { fireImmediately: true });
     }
     componentWillUnmount() {
+        this._penDisposer && this._penDisposer();
         this._selectedDisposer && this._selectedDisposer();
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6f99c541f..8a320d39b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -676,7 +676,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
         const highlightColors = ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"];
         const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid", "solid"];
-        let highlighting = fullDegree && this.props.Document.type !== DocumentType.FONTICON && this.props.Document.viewType !== CollectionViewType.Linear
+        let highlighting = fullDegree && this.props.Document.type !== DocumentType.FONTICON && this.props.Document.viewType !== CollectionViewType.Linear;
         return (
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index efe47d8a8..1c2ed33eb 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -6,6 +6,8 @@ import { DocComponent } from '../DocComponent';
 import './FontIconBox.scss';
 import { FieldView, FieldViewProps } from './FieldView';
 import { StrCast } from '../../../new_fields/Types';
+import { Utils } from "../../../Utils";
+import { runInAction, observable, reaction, IReactionDisposer } from 'mobx';
 const FontIconSchema = createSchema({
     icon: "string"
 });
@@ -15,10 +17,27 @@ const FontIconDocument = makeInterface(FontIconSchema);
 @observer
 export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(FontIconDocument) {
     public static LayoutString() { return FieldView.LayoutString(FontIconBox); }
-
+    @observable _foregroundColor = "white";
+    _ref: React.RefObject<HTMLButtonElement> = React.createRef();
+    _backgroundReaction: IReactionDisposer | undefined;
+    componentDidMount() {
+        this._backgroundReaction = reaction(() => this.props.Document.backgroundColor,
+            () => {
+                if (this._ref && this._ref.current) {
+                    let col = Utils.fromRGBAstr(getComputedStyle(this._ref.current).backgroundColor!);
+                    let colsum = (col.r + col.g + col.b);
+                    if (colsum / col.a > 600 || col.a < 0.25) runInAction(() => this._foregroundColor = "black");
+                    else if (colsum / col.a <= 600 || col.a >= .25) runInAction(() => this._foregroundColor = "white");
+                }
+            }, { fireImmediately: true });
+    }
+    componentWillUnmount() {
+        this._backgroundReaction && this._backgroundReaction();
+    }
     render() {
-        return <button className="fontIconBox-outerDiv" style={{ background: StrCast(this.props.Document.backgroundColor) }}>
-            <FontAwesomeIcon className="fontIconBox-icon" icon={this.Document.icon as any} size="sm" opacity={this.props.Document.unchecked ? "0.5" : "1"} />
+        return <button className="fontIconBox-outerDiv" ref={this._ref}
+            style={{ background: StrCast(this.props.Document.backgroundColor), boxShadow: this.props.Document.unchecked ? undefined : `4px 4px 12px black` }}>
+            <FontAwesomeIcon className="fontIconBox-icon" icon={this.Document.icon as any} color={this._foregroundColor} size="sm" />
         </button>;
     }
 }
\ No newline at end of file
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 276596fb8..9e0944c69 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -738,6 +738,6 @@ Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return Doc.Make
 Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy(field); });
 Scripting.addGlobal(function aliasDocs(field: any) { return new List<Doc>(field.map((d: any) => Doc.MakeAlias(d))); });
 Scripting.addGlobal(function docList(field: any) { return DocListCast(field); });
-Scripting.addGlobal(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2) });
+Scripting.addGlobal(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); });
 Scripting.addGlobal(function undo() { return UndoManager.Undo(); });
 Scripting.addGlobal(function redo() { return UndoManager.Redo(); });
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From caebaeca6993ff48609ab16dcc390d4d0458b03a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 16 Oct 2019 23:50:27 -0400
Subject: fixed applying templates to collections.  fixed templates containing
 collections

---
 src/client/documents/Documents.ts                  |  2 +-
 src/client/views/CollectionLinearView.tsx          | 35 ++++++++++++++--------
 src/client/views/InkingCanvas.tsx                  |  3 +-
 src/client/views/InkingControl.tsx                 |  2 +-
 src/client/views/collections/CollectionSubView.tsx |  2 +-
 .../views/collections/CollectionViewChromes.tsx    |  4 +--
 src/client/views/nodes/ColorBox.tsx                |  7 ++---
 src/client/views/nodes/FormattedTextBox.tsx        |  4 +--
 src/new_fields/Doc.ts                              | 14 +++++++--
 .../authentication/models/current_user_utils.ts    | 14 +++------
 10 files changed, 51 insertions(+), 36 deletions(-)

(limited to 'src/client/views/nodes/ColorBox.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2ffbc8394..62175cbe3 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -69,7 +69,7 @@ export interface DocumentOptions {
     preventTreeViewOpen?: boolean; // ignores the treeViewOpen Doc flag which allows a treeViewItem's expande/collapse state to be independent of other views of the same document in the tree view
     layout?: string | Doc;
     hideHeadings?: boolean; // whether stacking view column headings should be hidden
-    isTemplate?: boolean;
+    isTemplateField?: boolean;
     isTemplateDoc?: boolean;
     templates?: List<string>;
     viewType?: number;
diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx
index 3e2ab1459..04e131135 100644
--- a/src/client/views/CollectionLinearView.tsx
+++ b/src/client/views/CollectionLinearView.tsx
@@ -47,24 +47,35 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
         }
     }
 
+    makeTemplate = (doc: Doc): boolean => {
+        let layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc;
+        let layout = StrCast(layoutDoc.layout).match(/fieldKey={"[^"]*"}/)![0];
+        let fieldKey = layout.replace('fieldKey={"', "").replace(/"}$/, "");
+        let docs = DocListCast(layoutDoc[fieldKey]);
+        let any = false;
+        docs.map(d => {
+            if (!StrCast(d.title).startsWith("-")) {
+                any = true;
+                return Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc));
+            }
+            if (d.type === DocumentType.COL) return this.makeTemplate(d);
+            return false;
+        });
+        return any;
+    }
+
     drop = action((e: Event, de: DragManager.DropEvent) => {
         (de.data as DragManager.DocumentDragData).draggedDocuments.map((doc, i) => {
             let dbox = doc;
             if (!doc.onDragStart && !doc.onClick && this.props.Document.convertToButtons && doc.viewType !== CollectionViewType.Linear) {
-                let template = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc;
-                if (template.type === DocumentType.COL) {
-                    let layout = StrCast(template.layout).match(/fieldKey={"[^"]*"}/)![0];
-                    let fieldKey = layout.replace('fieldKey={"', "").replace(/"}$/, "");
-                    let docs = DocListCast(template[fieldKey]);
-                    docs.map(d => {
-                        Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(template));
-                    });
-                    template.isTemplateDoc = true;
+                let layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc;
+                if (layoutDoc.type === DocumentType.COL) {
+                    layoutDoc.isTemplateDoc = this.makeTemplate(layoutDoc);
                 } else {
-                    template.isTemplateDoc = (template.type === DocumentType.TEXT || template.layout instanceof Doc) && de.data instanceof DragManager.DocumentDragData && !de.data.userDropAction;
+                    layoutDoc.isTemplateDoc = (layoutDoc.type === DocumentType.TEXT || layoutDoc.layout instanceof Doc) && de.data instanceof DragManager.DocumentDragData && !de.data.userDropAction;
                 }
-                dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: template.isTemplateDoc ? "font" : "bolt" });
-                dbox.dragFactory = template;
+                dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: layoutDoc.isTemplateDoc ? "font" : "bolt" });
+                dbox.dragFactory = layoutDoc;
                 dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined;
                 dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)');
             } else if (doc.viewType === CollectionViewType.Linear) {
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 9ab320eab..920ebaedd 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -181,9 +181,10 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
 
     render() {
         let svgCanvasStyle = InkingControl.Instance.selectedTool !== InkTool.None && !this.props.Document.isBackground ? "canSelect" : "noSelect";
+        let cursor = svgCanvasStyle === "canSelect" ? (InkingControl.Instance.selectedTool === InkTool.Eraser ? "pointer" : "default") : undefined;
         return (
             <div className="inkingCanvas">
-                <div className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} />
+                <div className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} style={{ cursor: cursor }} />
                 {this.props.children()}
                 {this.drawnPaths}
             </div >
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index bc5249acd..b8f3c2875 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -94,7 +94,7 @@ export class InkingControl {
                 redo: () => oldColors.forEach(pair => pair.target.backgroundColor = captured)
             });
         } else {
-            CurrentUserUtils.UserDocument.activePen instanceof Doc && CurrentUserUtils.UserDocument.activePen.pen instanceof Doc && (CurrentUserUtils.UserDocument.activePen.pen.backgroundColor = this._selectedColor);
+            CurrentUserUtils.ActivePen && (CurrentUserUtils.ActivePen.backgroundColor = this._selectedColor);
         }
     });
     @action
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 46fbb5910..1f8c0b18f 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -61,7 +61,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                     if (args[1] instanceof Doc) {
                         this.childDocs.map(async doc => !Doc.AreProtosEqual(args[1] as Doc, (await doc).layout as Doc) && Doc.ApplyTemplateTo(args[1] as Doc, (await doc)));
                     } else {
-                        this.childDocs.map(async doc => doc.layout = undefined);
+                        this.childDocs.filter(d => !d.isTemplateField).map(async doc => doc.layout = undefined);
                     }
                 });
 
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 3a66c05f4..a5b7f0181 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -40,9 +40,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     //(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
 
     _templateCommand = {
-        title: "set template", script: "this.target.childLayout = this.source ? this.source[0] : undefined", params: ["target", "source"],
+        title: "set template", script: "setChildLayout(this.target, this.source && this.source.length ? this.source[0]:undefined)", params: ["target", "source"],
         initialize: emptyFunction,
-        immediate: (draggedDocs: Doc[]) => this.props.CollectionView.props.Document.childLayout = draggedDocs.length ? draggedDocs[0] : undefined
+        immediate: (draggedDocs: Doc[]) => Doc.setChildLayout(this.props.CollectionView.props.Document, draggedDocs.length ? draggedDocs[0] : undefined)
     };
     _contentCommand = {
         // title: "set content", script: "getProto(this.target).data = aliasDocs(this.source.map(async p => await p));", params: ["target", "source"],  // bcz: doesn't look like we can do async stuff in scripting...
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index e4fef0922..30554ea36 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -25,9 +25,8 @@ export class ColorBox extends DocStaticComponent<FieldViewProps, ColorDocument>(
         this._selectedDisposer = reaction(() => SelectionManager.SelectedDocuments(),
             action(() => this._startupColor = SelectionManager.SelectedDocuments().length ? StrCast(SelectionManager.SelectedDocuments()[0].Document.backgroundColor, "black") : "black"),
             { fireImmediately: true });
-        this._penDisposer = reaction(() => CurrentUserUtils.UserDocument.activePen instanceof Doc && CurrentUserUtils.UserDocument.activePen.pen,
-            action(() => this._startupColor = CurrentUserUtils.UserDocument.activePen instanceof Doc && CurrentUserUtils.UserDocument.activePen.pen instanceof Doc ?
-                StrCast(CurrentUserUtils.UserDocument.activePen.pen.backgroundColor, "black") : "black"),
+        this._penDisposer = reaction(() => CurrentUserUtils.ActivePen,
+            action(() => this._startupColor = CurrentUserUtils.ActivePen ? StrCast(CurrentUserUtils.ActivePen.backgroundColor, "black") : "black"),
             { fireImmediately: true });
 
         // compare to this reaction that used to be in Selection Manager
@@ -36,7 +35,7 @@ export class ColorBox extends DocStaticComponent<FieldViewProps, ColorDocument>(
         //     if (sel.length > 0) {
         //         let firstView = sel[0];
         //         let doc = firstView.props.Document;
-        //         let targetDoc = doc.isTemplate ? doc : Doc.GetProto(doc);
+        //         let targetDoc = doc.isTemplateField ? doc : Doc.GetProto(doc);
         //         let stored = StrCast(targetDoc.backgroundColor);
         //         stored.length > 0 && (targetColor = stored);
         //     }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index ea82b1161..045eee3f7 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -143,7 +143,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
 
-    @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? Doc.GetProto(this.props.DataDoc) : Doc.GetProto(this.props.Document); }
+    @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? Doc.GetProto(this.props.DataDoc) : Doc.GetProto(this.props.Document); }
 
     // the document containing the view layout information - will be the Document itself unless the Document has
     // a layout field.  In that case, all layout information comes from there unless overriden by Document
@@ -265,7 +265,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 }
                 // apply as template when dragging with Meta
             } else if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.mods === "MetaKey") {
-                draggedDoc.isTemplate = true;
+                draggedDoc.isTemplateDoc = true;
                 let newLayout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc;
                 if (typeof (draggedDoc.layout) === "string") {
                     newLayout = Doc.MakeDelegate(draggedDoc);
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 51fcb818a..04f310785 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -599,7 +599,7 @@ export namespace Doc {
         return target;
     }
 
-    export function MakeMetadataFieldTemplate(fieldTemplate: Doc, templateDataDoc: Doc, suppressTitle: boolean = false) {
+    export function MakeMetadataFieldTemplate(fieldTemplate: Doc, templateDataDoc: Doc, suppressTitle: boolean = false): boolean {
         // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??)
         let metadataFieldName = StrCast(fieldTemplate.title).replace(/^-/, "");
         let backgroundLayout = StrCast(fieldTemplate.backgroundLayout);
@@ -613,7 +613,7 @@ export namespace Doc {
 
         fieldTemplate.templateField = metadataFieldName;
         fieldTemplate.title = metadataFieldName;
-        fieldTemplate.isTemplate = true;
+        fieldTemplate.isTemplateField = true;
         fieldTemplate.backgroundLayout = backgroundLayout;
         /* move certain layout properties from the original data doc to the template layout to avoid
            inheriting them from the template's data doc which may also define these fields for its own use.
@@ -636,6 +636,7 @@ export namespace Doc {
             if (fieldTemplate.backgroundColor !== templateDataDoc.defaultBackgroundColor) fieldTemplate.defaultBackgroundColor = fieldTemplate.backgroundColor;
             fieldTemplate.proto = templateDataDoc;
         }), 0);
+        return true;
     }
 
     export function overlapping(doc: Doc, doc2: Doc, clusterDistance: number) {
@@ -735,9 +736,18 @@ export namespace Doc {
     export function UnBrushAllDocs() {
         manager.BrushedDoc.clear();
     }
+
+    export function setChildLayout(target: Doc, source?: Doc) {
+        target.childLayout = source && source.isTemplateDoc ? source : source &&
+            source.dragFactory instanceof Doc && source.dragFactory.isTemplateDoc ? source.dragFactory :
+            source && source.layout instanceof Doc && source.layout.isTemplateDoc ? source.layout : undefined;
+    }
 }
+
+
 Scripting.addGlobal(function renameAlias(doc: any, n: any) { return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, "") + `(${n})`; });
 Scripting.addGlobal(function getProto(doc: any) { return Doc.GetProto(doc); });
+Scripting.addGlobal(function setChildLayout(target: any, source: any) { Doc.setChildLayout(target, source); });
 Scripting.addGlobal(function getAlias(doc: any) { return Doc.MakeAlias(doc); });
 Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); });
 Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy(field); });
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 5ce707011..e6f202685 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -23,16 +23,11 @@ export class CurrentUserUtils {
     public static get MainDocId() { return this.mainDocId; }
     public static set MainDocId(id: string | undefined) { this.mainDocId = id; }
     @computed public static get UserDocument() { return Doc.UserDoc(); }
+    @computed public static get ActivePen() { return Doc.UserDoc().activePen instanceof Doc && (Doc.UserDoc().activePen as Doc).pen as Doc; }
 
     @observable public static GuestTarget: Doc | undefined;
     @observable public static GuestWorkspace: Doc | undefined;
 
-    private static createUserDocument(id: string): Doc {
-        let doc = new Doc(id, true);
-        doc.title = Doc.CurrentUserEmail;
-        return this.updateUserDocument(doc);// this should be the last 
-    }
-
     // a default set of note types .. not being used yet...
     static setupNoteTypes(doc: Doc) {
         return [
@@ -174,6 +169,7 @@ export class CurrentUserUtils {
     }
 
     static updateUserDocument(doc: Doc) {
+        doc.title = Doc.CurrentUserEmail;
         new InkingControl();
         (doc.optionalRightCollection === undefined) && CurrentUserUtils.setupMobileUploads(doc);
         (doc.overlays === undefined) && CurrentUserUtils.setupOverlays(doc);
@@ -216,10 +212,8 @@ export class CurrentUserUtils {
         Doc.CurrentUserEmail = email;
         await rp.get(Utils.prepend(RouteStore.getUserDocumentId)).then(id => {
             if (id && id !== "guest") {
-                return DocServer.GetRefField(id).then(async field => {
-                    let userDoc = field instanceof Doc ? await this.updateUserDocument(field) : this.createUserDocument(id);
-                    runInAction(() => Doc.SetUserDoc(userDoc));
-                });
+                return DocServer.GetRefField(id).then(async field =>
+                    Doc.SetUserDoc(await this.updateUserDocument(field instanceof Doc ? field : new Doc(id, true))));
             } else {
                 throw new Error("There should be a user id! Why does Dash think there isn't one?");
             }
-- 
cgit v1.2.3-70-g09d2

