From e7372931b9d28c141aaec9552041b5644c2f415a Mon Sep 17 00:00:00 2001
From: geireann <60007097+geireann@users.noreply.github.com>
Date: Fri, 26 Jun 2020 03:37:08 +0800
Subject: Merge branch 'master' into mobile_revision_direct

And changes to UI of record and image upload
---
 src/client/views/MainView.tsx | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 97953452d..4e2c067ac 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -5,7 +5,7 @@ import {
     faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
     faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
     faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote,
-    faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faQuoteLeft
+    faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faQuoteLeft, faSortAmountDown
 } from '@fortawesome/free-solid-svg-icons';
 import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -55,6 +55,9 @@ import { TimelineMenu } from './animationtimeline/TimelineMenu';
 import { SnappingManager } from '../util/SnappingManager';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { DocumentManager } from '../util/DocumentManager';
+import { DocumentLinksButton } from './nodes/DocumentLinksButton';
+import { LinkMenu } from './linking/LinkMenu';
+import { LinkDocPreview } from './nodes/LinkDocPreview';
 
 @observer
 export class MainView extends React.Component {
@@ -135,7 +138,7 @@ export class MainView extends React.Component {
             faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
             faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
             faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
-            faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faQuoteLeft);
+            faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faQuoteLeft, faSortAmountDown);
         this.initEventListeners();
         this.initAuthenticationRouters();
     }
@@ -578,6 +581,10 @@ export class MainView extends React.Component {
                 {this.mainContent}
             </GestureOverlay>
             <PreviewCursor />
+            {DocumentLinksButton.EditLink ? <LinkMenu location={DocumentLinksButton.EditLinkLoc} docView={DocumentLinksButton.EditLink} addDocTab={DocumentLinksButton.EditLink.props.addDocTab} changeFlyout={emptyFunction} /> : (null)}
+            {LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} backgroundColor={this.defaultBackgroundColors}
+                linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href}
+                addDocTab={LinkDocPreview.LinkInfo.addDocTab} /> : (null)}
             <ContextMenu />
             <RadialMenu />
             <PDFMenu />
-- 
cgit v1.2.3-70-g09d2


From a4543fdc90f8c19e4a032294c2f7d16a3152662f Mon Sep 17 00:00:00 2001
From: anika-ahluwalia <anika.ahluwalia@gmail.com>
Date: Tue, 30 Jun 2020 13:33:04 -0500
Subject: working with linked text

---
 package-lock.json                                  |  10 ++
 package.json                                       |   1 +
 src/client/views/MainView.tsx                      |  56 +++++-----
 src/client/views/linking/LinkMenu.tsx              |   3 +-
 .../formattedText/FormattedTextBoxComment.tsx      | 120 ++++++++++++++++-----
 5 files changed, 133 insertions(+), 57 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index 53b2fc009..9118cd4b0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -853,6 +853,16 @@
                 "@types/prosemirror-view": "*"
             }
         },
+        "@types/prosemirror-dev-tools": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/@types/prosemirror-dev-tools/-/prosemirror-dev-tools-2.1.0.tgz",
+            "integrity": "sha512-OhnSaC4yrrEMLPRUkEWcHAIPVqgKlLkE4kISqL3cHeAYxASouSPvPMLqhBIbWkGwaozy43DjjVC1OXkxTo+y5Q==",
+            "dev": true,
+            "requires": {
+                "@types/prosemirror-state": "*",
+                "@types/prosemirror-view": "*"
+            }
+        },
         "@types/prosemirror-history": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/@types/prosemirror-history/-/prosemirror-history-1.0.1.tgz",
diff --git a/package.json b/package.json
index 8f67704cb..765002d37 100644
--- a/package.json
+++ b/package.json
@@ -52,6 +52,7 @@
         "@types/passport-local": "^1.0.33",
         "@types/pdfjs-dist": "^2.1.4",
         "@types/prosemirror-commands": "^1.0.1",
+        "@types/prosemirror-dev-tools": "^2.1.0",
         "@types/prosemirror-history": "^1.0.1",
         "@types/prosemirror-inputrules": "^1.0.2",
         "@types/prosemirror-keymap": "^1.0.2",
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index cea664543..daf26cfd7 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -65,7 +65,7 @@ export class MainView extends React.Component {
     public static Instance: MainView;
     private _buttonBarHeight = 36;
     private _flyoutSizeOnDown = 0;
-    private _urlState: HistoryUtil.DocUrl;
+    private static _urlState: HistoryUtil.DocUrl;
     private _docBtnRef = React.createRef<HTMLDivElement>();
     private _mainViewRef = React.createRef<HTMLDivElement>();
 
@@ -73,12 +73,12 @@ export class MainView extends React.Component {
     @observable private _panelHeight: number = 0;
     @observable private _flyoutTranslate: boolean = true;
     @observable public flyoutWidth: number = 250;
-    private get darkScheme() { return BoolCast(Cast(this.userDoc?.activeWorkspace, Doc, null)?.darkScheme); }
+    private get darkScheme() { return BoolCast(Cast(MainView.userDoc?.activeWorkspace, Doc, null)?.darkScheme); }
 
-    @computed private get userDoc() { return Doc.UserDoc(); }
-    @computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeWorkspace, Doc)) : CurrentUserUtils.GuestWorkspace; }
+    @computed private static get userDoc() { return Doc.UserDoc(); }
+    @computed private get mainContainer() { return MainView.userDoc ? FieldValue(Cast(MainView.userDoc.activeWorkspace, Doc)) : CurrentUserUtils.GuestWorkspace; }
     @computed public get mainFreeform(): Opt<Doc> { return (docs => (docs && docs.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); }
-    @computed public get sidebarButtonsDoc() { return Cast(this.userDoc["tabs-buttons"], Doc) as Doc; }
+    @computed public get sidebarButtonsDoc() { return Cast(MainView.userDoc["tabs-buttons"], Doc) as Doc; }
 
     public isPointerDown = false;
 
@@ -119,7 +119,7 @@ export class MainView extends React.Component {
     constructor(props: Readonly<{}>) {
         super(props);
         MainView.Instance = this;
-        this._urlState = HistoryUtil.parseUrl(window.location) || {} as any;
+        MainView._urlState = HistoryUtil.parseUrl(window.location) || {} as any;
         // causes errors to be generated when modifying an observable outside of an action
         configure({ enforceActions: "observed" });
         if (window.location.pathname !== "/home") {
@@ -128,7 +128,7 @@ export class MainView extends React.Component {
                 const type = pathname[0];
                 if (type === "doc") {
                     CurrentUserUtils.MainDocId = pathname[1];
-                    if (!this.userDoc) {
+                    if (!MainView.userDoc) {
                         runInAction(() => this.flyoutWidth = 0);
                         DocServer.GetRefField(CurrentUserUtils.MainDocId).then(action((field: Opt<Field>) =>
                             field instanceof Doc && (CurrentUserUtils.GuestTarget = field)));
@@ -172,14 +172,14 @@ export class MainView extends React.Component {
     initAuthenticationRouters = async () => {
         // Load the user's active workspace, or create a new one if initial session after signup
         const received = CurrentUserUtils.MainDocId;
-        if (received && !this.userDoc) {
+        if (received && !MainView.userDoc) {
             reaction(
                 () => CurrentUserUtils.GuestTarget,
                 target => target && this.createNewWorkspace(),
                 { fireImmediately: true }
             );
         } else {
-            if (received && this._urlState.sharing) {
+            if (received && MainView._urlState.sharing) {
                 reaction(() => CollectionDockingView.Instance && CollectionDockingView.Instance.initialized,
                     initialized => initialized && received && DocServer.GetRefField(received).then(docField => {
                         if (docField instanceof Doc && docField._viewType !== CollectionViewType.Docking) {
@@ -188,9 +188,9 @@ export class MainView extends React.Component {
                     }),
                 );
             }
-            const doc = this.userDoc && await Cast(this.userDoc.activeWorkspace, Doc);
+            const doc = MainView.userDoc && await Cast(MainView.userDoc.activeWorkspace, Doc);
             if (doc) {
-                this.openWorkspace(doc);
+                MainView.openWorkspace(doc);
             } else {
                 this.createNewWorkspace();
             }
@@ -199,7 +199,7 @@ export class MainView extends React.Component {
 
     @action
     createNewWorkspace = async (id?: string) => {
-        const workspaces = Cast(this.userDoc.myWorkspaces, Doc) as Doc;
+        const workspaces = Cast(MainView.userDoc.myWorkspaces, Doc) as Doc;
         const workspaceCount = DocListCast(workspaces.data).length + 1;
         const freeformOptions: DocumentOptions = {
             x: 0,
@@ -219,19 +219,19 @@ export class MainView extends React.Component {
 
         Doc.AddDocToList(workspaces, "data", workspaceDoc);
         // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
-        setTimeout(() => this.openWorkspace(workspaceDoc), 0);
+        setTimeout(() => MainView.openWorkspace(workspaceDoc), 0);
     }
 
     @action
-    openWorkspace = (doc: Doc, fromHistory = false) => {
+    static openWorkspace = (doc: Doc, fromHistory = false) => {
         CurrentUserUtils.MainDocId = doc[Id];
 
         if (doc) {  // this has the side-effect of setting the main container since we're assigning the active/guest workspace
             !("presentationView" in doc) && (doc.presentationView = new List<Doc>([Docs.Create.TreeDocument([], { title: "Presentation" })]));
-            this.userDoc ? (this.userDoc.activeWorkspace = doc) : (CurrentUserUtils.GuestWorkspace = doc);
+            MainView.userDoc ? (MainView.userDoc.activeWorkspace = doc) : (CurrentUserUtils.GuestWorkspace = doc);
         }
-        const state = this._urlState;
-        if (state.sharing === true && !this.userDoc) {
+        const state = MainView._urlState;
+        if (state.sharing === true && !MainView.userDoc) {
             DocServer.Control.makeReadOnly();
         } else {
             fromHistory || HistoryUtil.pushState({
@@ -257,7 +257,7 @@ export class MainView extends React.Component {
         }
         // if there is a pending doc, and it has new data, show it (syip: we use a timeout to prevent collection docking view from being uninitialized)
         setTimeout(async () => {
-            const col = this.userDoc && await Cast(this.userDoc.rightSidebarCollection, Doc);
+            const col = MainView.userDoc && await Cast(MainView.userDoc.rightSidebarCollection, Doc);
             col && Cast(col.data, listSpec(Doc)) && runInAction(() => MainViewNotifs.NotifsCol = col);
         }, 100);
         return true;
@@ -305,7 +305,7 @@ export class MainView extends React.Component {
             DataDoc={undefined}
             LibraryPath={emptyPath}
             addDocument={undefined}
-            addDocTab={this.addDocTabFunc}
+            addDocTab={MainView.addDocTabFunc}
             pinToPres={emptyFunction}
             rootSelected={returnTrue}
             onClick={undefined}
@@ -378,15 +378,15 @@ export class MainView extends React.Component {
         document.removeEventListener("pointerup", this.onPointerUp);
     }
     flyoutWidthFunc = () => this.flyoutWidth;
-    addDocTabFunc = (doc: Doc, where: string, libraryPath?: Doc[]): boolean => {
+    static addDocTabFunc = (doc: Doc, where: string, libraryPath?: Doc[]): boolean => {
         return where === "close" ? CollectionDockingView.CloseRightSplit(doc) :
-            doc.dockingConfig ? this.openWorkspace(doc) :
+            doc.dockingConfig ? MainView.openWorkspace(doc) :
                 CollectionDockingView.AddRightSplit(doc, libraryPath);
     }
     mainContainerXf = () => new Transform(0, -this._buttonBarHeight, 1);
 
     @computed get flyout() {
-        const sidebarContent = this.userDoc?.["tabs-panelContainer"];
+        const sidebarContent = MainView.userDoc?.["tabs-panelContainer"];
         if (!(sidebarContent instanceof Doc)) {
             return (null);
         }
@@ -398,7 +398,7 @@ export class MainView extends React.Component {
                     LibraryPath={emptyPath}
                     addDocument={undefined}
                     rootSelected={returnTrue}
-                    addDocTab={this.addDocTabFunc}
+                    addDocTab={MainView.addDocTabFunc}
                     pinToPres={emptyFunction}
                     removeDocument={undefined}
                     onClick={undefined}
@@ -424,7 +424,7 @@ export class MainView extends React.Component {
                     DataDoc={undefined}
                     LibraryPath={emptyPath}
                     addDocument={undefined}
-                    addDocTab={this.addDocTabFunc}
+                    addDocTab={MainView.addDocTabFunc}
                     pinToPres={emptyFunction}
                     NativeHeight={returnZero}
                     NativeWidth={returnZero}
@@ -458,8 +458,8 @@ export class MainView extends React.Component {
     }
 
     @computed get mainContent() {
-        const sidebar = this.userDoc?.["tabs-panelContainer"];
-        return !this.userDoc || !(sidebar instanceof Doc) ? (null) : (
+        const sidebar = MainView.userDoc?.["tabs-panelContainer"];
+        return !MainView.userDoc || !(sidebar instanceof Doc) ? (null) : (
             <div className="mainView-mainContent" style={{
                 color: this.darkScheme ? "rgb(205,205,205)" : "black",
                 height: RichTextMenu.Instance?.Pinned ? `calc(100% - ${ANTIMODEMENU_HEIGHT})` : "100%"
@@ -530,7 +530,7 @@ export class MainView extends React.Component {
                     moveDocument={this.moveButtonDoc}
                     CollectionView={undefined}
                     addDocument={this.addButtonDoc}
-                    addDocTab={this.addDocTabFunc}
+                    addDocTab={MainView.addDocTabFunc}
                     pinToPres={emptyFunction}
                     removeDocument={this.remButtonDoc}
                     onClick={undefined}
@@ -627,5 +627,5 @@ Scripting.addGlobal(function copyWorkspace() {
     const workspaces = Cast(Doc.UserDoc().myWorkspaces, Doc, null);
     Doc.AddDocToList(workspaces, "data", copiedWorkspace);
     // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
-    setTimeout(() => MainView.Instance.openWorkspace(copiedWorkspace), 0);
+    setTimeout(() => MainView.openWorkspace(copiedWorkspace), 0);
 });
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 6b24f96b1..9fd2b98b1 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -74,7 +74,8 @@ export class LinkMenu extends React.Component<Props> {
     render() {
         const sourceDoc = this.props.docView.props.Document;
         const groups: Map<string, Doc[]> = LinkManager.Instance.getRelatedGroupedLinks(sourceDoc);
-        return <div className="linkMenu-list" ref={(r) => this._linkMenuRef = r} style={{ left: this.props.location[0], top: this.props.location[1] }}>
+        return <div className="linkMenu-list"
+            ref={(r) => this._linkMenuRef = r} style={{ left: this.props.location[0], top: this.props.location[1] }}>
             {!this._editingLink ?
                 this.renderAllGroups(groups) :
                 <LinkEditor sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)} />
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 90f2c0aa6..a4153a40c 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -16,6 +16,13 @@ import React = require("react");
 import { Docs } from "../../../documents/Documents";
 import wiki from "wikijs";
 import { DocumentType } from "../../../documents/DocumentTypes";
+import { computed, action, observable } from "mobx";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { LinkManager } from "../../../util/LinkManager";
+import { LinkDocPreview } from "../LinkDocPreview";
+import { DocumentLinksButton } from "../DocumentLinksButton";
+import { ContextMenu } from "../../ContextMenu";
+import { MainView } from "../../MainView";
 
 export let formattedTextBoxCommentPlugin = new Plugin({
     view(editorView) { return new FormattedTextBoxComment(editorView); }
@@ -62,6 +69,10 @@ export class FormattedTextBoxComment {
     static mark: Mark;
     static textBox: FormattedTextBox | undefined;
     static linkDoc: Doc | undefined;
+
+    static targetDoc: Doc | undefined;
+    static _editRef = React.createRef<HTMLDivElement>();
+
     constructor(view: any) {
         if (!FormattedTextBoxComment.tooltip) {
             const root = document.getElementById("root");
@@ -75,8 +86,8 @@ export class FormattedTextBoxComment {
             FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
             FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip";
             FormattedTextBoxComment.tooltip.style.pointerEvents = "all";
-            FormattedTextBoxComment.tooltip.style.maxWidth = "350px";
-            FormattedTextBoxComment.tooltip.style.maxHeight = "250px";
+            FormattedTextBoxComment.tooltip.style.maxWidth = "225px";
+            FormattedTextBoxComment.tooltip.style.maxHeight = "225px";
             FormattedTextBoxComment.tooltip.style.width = "100%";
             FormattedTextBoxComment.tooltip.style.height = "100%";
             FormattedTextBoxComment.tooltip.style.overflow = "hidden";
@@ -106,6 +117,31 @@ export class FormattedTextBoxComment {
         }
     }
 
+    @action
+    public static deleteLink = (): void => {
+        FormattedTextBoxComment.linkDoc ? LinkManager.Instance.deleteLink(FormattedTextBoxComment.linkDoc) : null;
+        //this.props.showLinks();
+        LinkDocPreview.LinkInfo = undefined;
+        DocumentLinksButton.EditLink = undefined;
+    }
+
+    @action
+    public static onContextMenu = (e: React.MouseEvent) => {
+        DocumentLinksButton.EditLink = undefined;
+        LinkDocPreview.LinkInfo = undefined;
+        e.preventDefault();
+        ContextMenu.Instance.addItem({ description: "Follow Default Link", event: () => FormattedTextBoxComment.followDefault(), icon: "arrow-right" });
+        ContextMenu.Instance.displayMenu(e.clientX, e.clientY);
+    }
+
+    @action.bound
+    public static async followDefault() {
+        DocumentLinksButton.EditLink = undefined;
+        LinkDocPreview.LinkInfo = undefined;
+        FormattedTextBoxComment.targetDoc ? DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, FormattedTextBoxComment.targetDoc,
+            doc => MainView.addDocTabFunc(doc, "onRight"), false) : null;
+    }
+
     public static Hide() {
         FormattedTextBoxComment.textBox = undefined;
         FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
@@ -119,6 +155,22 @@ export class FormattedTextBoxComment {
         FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "");
     }
 
+    @action
+    onClick = (e: PointerEvent) => {
+        if (FormattedTextBoxComment._editRef && !FormattedTextBoxComment._editRef.current?.contains(e.target as Node)) {
+            FormattedTextBoxComment.linkDoc = undefined;
+            FormattedTextBoxComment.targetDoc = undefined;
+        }
+    }
+    @action
+    componentDidMount() {
+        document.addEventListener("pointerdown", this.onClick);
+    }
+
+    componentWillUnmount() {
+        document.removeEventListener("pointerdown", this.onClick);
+    }
+
     static showCommentbox(set: string, view: EditorView, nbef: number) {
         const state = view.state;
         if (set !== "none") {
@@ -157,6 +209,7 @@ export class FormattedTextBoxComment {
         (FormattedTextBoxComment.tooltipText as any).href = "";
         FormattedTextBoxComment.tooltipText.style.whiteSpace = "";
         FormattedTextBoxComment.tooltipText.style.overflow = "";
+
         // this section checks to see if the insertion point is over text entered by a different user.  If so, it sets ths comment text to indicate the user and the modification date
         if (state.selection.$from) {
             nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
@@ -209,33 +262,44 @@ export class FormattedTextBoxComment {
                                 target._scrollY = NumCast(anchor?.y);
                             }
                             if (target?.author) {
+
+                                const docPreview = <div style={{ backgroundColor: "white" }}> {target.title}
+                                    <div className="wrapper" style={{ float: "right" }}>
+                                        <div title="Delete link" className="button" style={{ display: "inline" }} ref={this._editRef} onPointerDown={this.deleteLink}>
+                                            <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div>
+                                        <div title="Follow link" className="button" style={{ display: "inline" }} onClick={this.followDefault} onContextMenu={this.onContextMenu}>
+                                            <FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" />
+                                        </div>
+                                    </div>
+                                    <ContentFittingDocumentView
+                                        Document={target}
+                                        LibraryPath={emptyPath}
+                                        fitToBox={true}
+                                        moveDocument={returnFalse}
+                                        rootSelected={returnFalse}
+                                        ScreenToLocalTransform={Transform.Identity}
+                                        parentActive={returnFalse}
+                                        addDocument={returnFalse}
+                                        removeDocument={returnFalse}
+                                        addDocTab={returnFalse}
+                                        pinToPres={returnFalse}
+                                        dontRegisterView={true}
+                                        docFilters={returnEmptyFilter}
+                                        ContainingCollectionDoc={undefined}
+                                        ContainingCollectionView={undefined}
+                                        renderDepth={0}
+                                        PanelWidth={() => Math.min(350, NumCast(target._width, 350))}
+                                        PanelHeight={() => Math.min(250, NumCast(target._height, 250))}
+                                        focus={emptyFunction}
+                                        whenActiveChanged={returnFalse}
+                                        bringToFront={returnFalse}
+                                        ContentScaling={returnOne}
+                                        NativeWidth={returnZero}
+                                        NativeHeight={returnZero}
+                                    />
+                                </div>;
                                 FormattedTextBoxComment.showCommentbox("", view, nbef);
-                                ReactDOM.render(<ContentFittingDocumentView
-                                    Document={target}
-                                    LibraryPath={emptyPath}
-                                    fitToBox={true}
-                                    moveDocument={returnFalse}
-                                    rootSelected={returnFalse}
-                                    ScreenToLocalTransform={Transform.Identity}
-                                    parentActive={returnFalse}
-                                    addDocument={returnFalse}
-                                    removeDocument={returnFalse}
-                                    addDocTab={returnFalse}
-                                    pinToPres={returnFalse}
-                                    dontRegisterView={true}
-                                    docFilters={returnEmptyFilter}
-                                    ContainingCollectionDoc={undefined}
-                                    ContainingCollectionView={undefined}
-                                    renderDepth={0}
-                                    PanelWidth={() => Math.min(350, NumCast(target._width, 350))}
-                                    PanelHeight={() => Math.min(250, NumCast(target._height, 250))}
-                                    focus={emptyFunction}
-                                    whenActiveChanged={returnFalse}
-                                    bringToFront={returnFalse}
-                                    ContentScaling={returnOne}
-                                    NativeWidth={returnZero}
-                                    NativeHeight={returnZero}
-                                />, FormattedTextBoxComment.tooltipText);
+                                ReactDOM.render(docPreview, FormattedTextBoxComment.tooltipText);
                                 FormattedTextBoxComment.tooltip.style.width = NumCast(target._width) ? `${NumCast(target._width)}` : "100%";
                                 FormattedTextBoxComment.tooltip.style.height = NumCast(target._height) ? `${NumCast(target._height)}` : "100%";
                             }
-- 
cgit v1.2.3-70-g09d2


From c51056c7da718a7c0f98f54953f2d7fc287ecf9d Mon Sep 17 00:00:00 2001
From: anika-ahluwalia <anika.ahluwalia@gmail.com>
Date: Wed, 1 Jul 2020 11:33:52 -0500
Subject: mainview work

---
 src/client/views/MainView.tsx | 22 ++++++++++++----------
 1 file changed, 12 insertions(+), 10 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index da76bed11..9328198d0 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -190,7 +190,7 @@ export class MainView extends React.Component {
             }
             const doc = MainView.userDoc && await Cast(MainView.userDoc.activeWorkspace, Doc);
             if (doc) {
-                MainView.openWorkspace(doc);
+                this.openWorkspace(doc);
             } else {
                 this.createNewWorkspace();
             }
@@ -219,11 +219,11 @@ export class MainView extends React.Component {
 
         Doc.AddDocToList(workspaces, "data", workspaceDoc);
         // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
-        setTimeout(() => MainView.openWorkspace(workspaceDoc), 0);
+        setTimeout(() => this.openWorkspace(workspaceDoc), 0);
     }
 
     @action
-    static openWorkspace = (doc: Doc, fromHistory = false) => {
+    openWorkspace = (doc: Doc, fromHistory = false) => {
         CurrentUserUtils.MainDocId = doc[Id];
 
         if (doc) {  // this has the side-effect of setting the main container since we're assigning the active/guest workspace
@@ -305,7 +305,7 @@ export class MainView extends React.Component {
             DataDoc={undefined}
             LibraryPath={emptyPath}
             addDocument={undefined}
-            addDocTab={MainView.addDocTabFunc}
+            addDocTab={this.addDocTabFunc}
             pinToPres={emptyFunction}
             rootSelected={returnTrue}
             onClick={undefined}
@@ -378,9 +378,9 @@ export class MainView extends React.Component {
         document.removeEventListener("pointerup", this.onPointerUp);
     }
     flyoutWidthFunc = () => this.flyoutWidth;
-    static addDocTabFunc = (doc: Doc, where: string, libraryPath?: Doc[]): boolean => {
+    addDocTabFunc = (doc: Doc, where: string, libraryPath?: Doc[]): boolean => {
         return where === "close" ? CollectionDockingView.CloseRightSplit(doc) :
-            doc.dockingConfig ? MainView.openWorkspace(doc) :
+            doc.dockingConfig ? this.openWorkspace(doc) :
                 CollectionDockingView.AddRightSplit(doc, libraryPath);
     }
     sidebarScreenToLocal = () => new Transform(0, RichTextMenu.Instance.Pinned ? -35 : 0, 1);
@@ -399,7 +399,7 @@ export class MainView extends React.Component {
                     LibraryPath={emptyPath}
                     addDocument={undefined}
                     rootSelected={returnTrue}
-                    addDocTab={MainView.addDocTabFunc}
+                    addDocTab={this.addDocTabFunc}
                     pinToPres={emptyFunction}
                     removeDocument={undefined}
                     onClick={undefined}
@@ -425,7 +425,7 @@ export class MainView extends React.Component {
                     DataDoc={undefined}
                     LibraryPath={emptyPath}
                     addDocument={undefined}
-                    addDocTab={MainView.addDocTabFunc}
+                    addDocTab={this.addDocTabFunc}
                     pinToPres={emptyFunction}
                     NativeHeight={returnZero}
                     NativeWidth={returnZero}
@@ -531,7 +531,7 @@ export class MainView extends React.Component {
                     moveDocument={this.moveButtonDoc}
                     CollectionView={undefined}
                     addDocument={this.addButtonDoc}
-                    addDocTab={MainView.addDocTabFunc}
+                    addDocTab={this.addDocTabFunc}
                     pinToPres={emptyFunction}
                     removeDocument={this.remButtonDoc}
                     onClick={undefined}
@@ -628,5 +628,7 @@ Scripting.addGlobal(function copyWorkspace() {
     const workspaces = Cast(Doc.UserDoc().myWorkspaces, Doc, null);
     Doc.AddDocToList(workspaces, "data", copiedWorkspace);
     // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
-    setTimeout(() => MainView.openWorkspace(copiedWorkspace), 0);
+
+
+    // setTimeout(() => .openWorkspace(copiedWorkspace), 0);
 });
-- 
cgit v1.2.3-70-g09d2


From 581ba904dc9bd02f6e2d81f42c45f38f6ad26cab Mon Sep 17 00:00:00 2001
From: anika-ahluwalia <anika.ahluwalia@gmail.com>
Date: Wed, 1 Jul 2020 13:14:02 -0500
Subject: textbox comment changes

---
 src/client/views/MainView.tsx                      | 40 +++++----
 .../formattedText/FormattedTextBoxComment.scss     |  4 +-
 .../formattedText/FormattedTextBoxComment.tsx      | 97 +++++++++-------------
 3 files changed, 62 insertions(+), 79 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 9328198d0..200486279 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -65,7 +65,7 @@ export class MainView extends React.Component {
     public static Instance: MainView;
     private _buttonBarHeight = 36;
     private _flyoutSizeOnDown = 0;
-    private static _urlState: HistoryUtil.DocUrl;
+    private _urlState: HistoryUtil.DocUrl;
     private _docBtnRef = React.createRef<HTMLDivElement>();
     private _mainViewRef = React.createRef<HTMLDivElement>();
 
@@ -73,12 +73,12 @@ export class MainView extends React.Component {
     @observable private _panelHeight: number = 0;
     @observable private _flyoutTranslate: boolean = true;
     @observable public flyoutWidth: number = 250;
-    private get darkScheme() { return BoolCast(Cast(MainView.userDoc?.activeWorkspace, Doc, null)?.darkScheme); }
+    private get darkScheme() { return BoolCast(Cast(this.userDoc?.activeWorkspace, Doc, null)?.darkScheme); }
 
-    @computed private static get userDoc() { return Doc.UserDoc(); }
-    @computed private get mainContainer() { return MainView.userDoc ? FieldValue(Cast(MainView.userDoc.activeWorkspace, Doc)) : CurrentUserUtils.GuestWorkspace; }
+    @computed private get userDoc() { return Doc.UserDoc(); }
+    @computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeWorkspace, Doc)) : CurrentUserUtils.GuestWorkspace; }
     @computed public get mainFreeform(): Opt<Doc> { return (docs => (docs && docs.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); }
-    @computed public get sidebarButtonsDoc() { return Cast(MainView.userDoc["tabs-buttons"], Doc) as Doc; }
+    @computed public get sidebarButtonsDoc() { return Cast(this.userDoc["tabs-buttons"], Doc) as Doc; }
 
     public isPointerDown = false;
 
@@ -119,7 +119,7 @@ export class MainView extends React.Component {
     constructor(props: Readonly<{}>) {
         super(props);
         MainView.Instance = this;
-        MainView._urlState = HistoryUtil.parseUrl(window.location) || {} as any;
+        this._urlState = HistoryUtil.parseUrl(window.location) || {} as any;
         // causes errors to be generated when modifying an observable outside of an action
         configure({ enforceActions: "observed" });
         if (window.location.pathname !== "/home") {
@@ -128,7 +128,7 @@ export class MainView extends React.Component {
                 const type = pathname[0];
                 if (type === "doc") {
                     CurrentUserUtils.MainDocId = pathname[1];
-                    if (!MainView.userDoc) {
+                    if (!this.userDoc) {
                         runInAction(() => this.flyoutWidth = 0);
                         DocServer.GetRefField(CurrentUserUtils.MainDocId).then(action((field: Opt<Field>) =>
                             field instanceof Doc && (CurrentUserUtils.GuestTarget = field)));
@@ -172,14 +172,14 @@ export class MainView extends React.Component {
     initAuthenticationRouters = async () => {
         // Load the user's active workspace, or create a new one if initial session after signup
         const received = CurrentUserUtils.MainDocId;
-        if (received && !MainView.userDoc) {
+        if (received && !this.userDoc) {
             reaction(
                 () => CurrentUserUtils.GuestTarget,
                 target => target && this.createNewWorkspace(),
                 { fireImmediately: true }
             );
         } else {
-            if (received && MainView._urlState.sharing) {
+            if (received && this._urlState.sharing) {
                 reaction(() => CollectionDockingView.Instance && CollectionDockingView.Instance.initialized,
                     initialized => initialized && received && DocServer.GetRefField(received).then(docField => {
                         if (docField instanceof Doc && docField._viewType !== CollectionViewType.Docking) {
@@ -188,7 +188,7 @@ export class MainView extends React.Component {
                     }),
                 );
             }
-            const doc = MainView.userDoc && await Cast(MainView.userDoc.activeWorkspace, Doc);
+            const doc = this.userDoc && await Cast(this.userDoc.activeWorkspace, Doc);
             if (doc) {
                 this.openWorkspace(doc);
             } else {
@@ -199,7 +199,7 @@ export class MainView extends React.Component {
 
     @action
     createNewWorkspace = async (id?: string) => {
-        const workspaces = Cast(MainView.userDoc.myWorkspaces, Doc) as Doc;
+        const workspaces = Cast(this.userDoc.myWorkspaces, Doc) as Doc;
         const workspaceCount = DocListCast(workspaces.data).length + 1;
         const freeformOptions: DocumentOptions = {
             x: 0,
@@ -228,10 +228,10 @@ export class MainView extends React.Component {
 
         if (doc) {  // this has the side-effect of setting the main container since we're assigning the active/guest workspace
             !("presentationView" in doc) && (doc.presentationView = new List<Doc>([Docs.Create.TreeDocument([], { title: "Presentation" })]));
-            MainView.userDoc ? (MainView.userDoc.activeWorkspace = doc) : (CurrentUserUtils.GuestWorkspace = doc);
+            this.userDoc ? (this.userDoc.activeWorkspace = doc) : (CurrentUserUtils.GuestWorkspace = doc);
         }
-        const state = MainView._urlState;
-        if (state.sharing === true && !MainView.userDoc) {
+        const state = this._urlState;
+        if (state.sharing === true && !this.userDoc) {
             DocServer.Control.makeReadOnly();
         } else {
             fromHistory || HistoryUtil.pushState({
@@ -257,7 +257,7 @@ export class MainView extends React.Component {
         }
         // if there is a pending doc, and it has new data, show it (syip: we use a timeout to prevent collection docking view from being uninitialized)
         setTimeout(async () => {
-            const col = MainView.userDoc && await Cast(MainView.userDoc.rightSidebarCollection, Doc);
+            const col = this.userDoc && await Cast(this.userDoc.rightSidebarCollection, Doc);
             col && Cast(col.data, listSpec(Doc)) && runInAction(() => MainViewNotifs.NotifsCol = col);
         }, 100);
         return true;
@@ -387,7 +387,7 @@ export class MainView extends React.Component {
     mainContainerXf = () => this.sidebarScreenToLocal().translate(0, -this._buttonBarHeight);
 
     @computed get flyout() {
-        const sidebarContent = MainView.userDoc?.["tabs-panelContainer"];
+        const sidebarContent = this.userDoc?.["tabs-panelContainer"];
         if (!(sidebarContent instanceof Doc)) {
             return (null);
         }
@@ -459,8 +459,8 @@ export class MainView extends React.Component {
     }
 
     @computed get mainContent() {
-        const sidebar = MainView.userDoc?.["tabs-panelContainer"];
-        return !MainView.userDoc || !(sidebar instanceof Doc) ? (null) : (
+        const sidebar = this.userDoc?.["tabs-panelContainer"];
+        return !this.userDoc || !(sidebar instanceof Doc) ? (null) : (
             <div className="mainView-mainContent" style={{
                 color: this.darkScheme ? "rgb(205,205,205)" : "black",
                 height: RichTextMenu.Instance?.Pinned ? `calc(100% - ${ANTIMODEMENU_HEIGHT})` : "100%"
@@ -628,7 +628,5 @@ Scripting.addGlobal(function copyWorkspace() {
     const workspaces = Cast(Doc.UserDoc().myWorkspaces, Doc, null);
     Doc.AddDocToList(workspaces, "data", copiedWorkspace);
     // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
-
-
-    // setTimeout(() => .openWorkspace(copiedWorkspace), 0);
+    setTimeout(() => MainView.Instance.openWorkspace(copiedWorkspace), 0);
 });
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
index d83583db1..adef7f81f 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
@@ -43,7 +43,7 @@
     right: 0;
     transform: translateY(-50%);
 
-    .button {
+    .FormattedTextBox-button {
         width: 20px;
         height: 20px;
         margin: 0;
@@ -57,7 +57,7 @@
         text-align: center;
         position: relative;
 
-        .fa-icon {
+        .FormattedTextBox-fa-icon {
             position: absolute;
             top: 50%;
             left: 50%;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 15cd68c26..c600e4861 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -18,9 +18,9 @@ import wiki from "wikijs";
 import { DocumentType } from "../../../documents/DocumentTypes";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action } from "mobx";
-// import { LinkManager } from "../../../util/LinkManager";
-// import { LinkDocPreview } from "../LinkDocPreview";
-// import { DocumentLinksButton } from "../DocumentLinksButton";
+import { LinkManager } from "../../../util/LinkManager";
+import { LinkDocPreview } from "../LinkDocPreview";
+import { DocumentLinksButton } from "../DocumentLinksButton";
 
 export let formattedTextBoxCommentPlugin = new Plugin({
     view(editorView) { return new FormattedTextBoxComment(editorView); }
@@ -97,23 +97,23 @@ export class FormattedTextBoxComment {
                 if (FormattedTextBoxComment.linkDoc && !keep && textBox) {
                     if (FormattedTextBoxComment.linkDoc.author) {
 
-                        // if (FormattedTextBoxComment._deleteRef && FormattedTextBoxComment._deleteRef.contains(e.target as any)) {
-                        //     FormattedTextBoxComment.deleteLink();
-                        // } else if (FormattedTextBoxComment._followRef && FormattedTextBoxComment._followRef.contains(e.target as any)) {
-                        //     if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
-                        //         textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
-                        //     } else {
-                        //         DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
-                        //             (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
-                        //     }
-                        // } else {
-                        if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
-                            textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
+                        if (FormattedTextBoxComment._deleteRef && FormattedTextBoxComment._deleteRef.contains(e.target as any)) {
+                            this.deleteLink();
+                        } else if (FormattedTextBoxComment._followRef && FormattedTextBoxComment._followRef.contains(e.target as any)) {
+                            if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
+                                textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
+                            } else {
+                                DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
+                                    (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+                            }
                         } else {
-                            DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
-                                (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+                            if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
+                                textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
+                            } else {
+                                DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
+                                    (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+                            }
                         }
-                        //}
 
                     }
                 } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) {
@@ -125,16 +125,16 @@ export class FormattedTextBoxComment {
                 e.preventDefault();
             };
             root && root.appendChild(FormattedTextBoxComment.tooltip);
-        };
+        }
     }
 
     @action
-    public static deleteLink = (): void => {
-        console.log(FormattedTextBoxComment.linkDoc);
-        // FormattedTextBoxComment.linkDoc ? LinkManager.Instance.deleteLink(FormattedTextBoxComment.linkDoc) : null;
-        // //this.props.showLinks();
-        // LinkDocPreview.LinkInfo = undefined;
-        // DocumentLinksButton.EditLink = undefined;
+    deleteLink = () => {
+        FormattedTextBoxComment.linkDoc ? LinkManager.Instance.deleteLink(FormattedTextBoxComment.linkDoc) : null;
+        LinkDocPreview.LinkInfo = undefined;
+        DocumentLinksButton.EditLink = undefined;
+        //FormattedTextBoxComment.tooltipText = undefined;
+        FormattedTextBoxComment.Hide();
     }
 
     public static Hide() {
@@ -241,12 +241,22 @@ export class FormattedTextBoxComment {
                             }
                             if (target?.author) {
                                 FormattedTextBoxComment.showCommentbox("", view, nbef);
-                                const docPreview = <div style={{ backgroundColor: "white" }}> {target.title}
+                                const docPreview = <div style={{ backgroundColor: "white", border: "7px solid white" }}>
+                                    {target.title}
                                     <div className="wrapper" style={{ float: "right" }}>
-                                        <div title="Delete link" className="FormattedTextBoxComment-button" style={{ display: "inline" }} ref={(r) => this._deleteRef = r}>
-                                            <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div>
-                                        <div title="Follow link" className="FormattedTextBoxComment-button" style={{ display: "inline" }} ref={(r) => this._followRef = r}>
-                                            <FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" />
+                                        <div title="Delete link" className="FormattedTextBoxComment-button" style={{
+                                            display: "inline",
+                                            paddingLeft: "5px",
+                                            paddingRight: "5px"
+                                        }} ref={(r) => this._deleteRef = r}>
+                                            <FontAwesomeIcon className="FormattedTextBox-fa-icon" icon="trash" size="sm"
+                                            /></div>
+                                        <div title="Follow link" className="FormattedTextBoxComment-button" style={{
+                                            display: "inline",
+                                            paddingRight: "5px"
+                                        }} ref={(r) => this._followRef = r}>
+                                            <FontAwesomeIcon className="FormattedTextBox-fa-icon" icon="arrow-right"
+                                                size="sm" />
                                         </div>
                                     </div>
                                     <ContentFittingDocumentView
@@ -278,32 +288,7 @@ export class FormattedTextBoxComment {
                                 </div>;
                                 FormattedTextBoxComment.showCommentbox("", view, nbef);
 
-                                ReactDOM.render(<ContentFittingDocumentView
-                                    Document={target}
-                                    LibraryPath={emptyPath}
-                                    fitToBox={true}
-                                    moveDocument={returnFalse}
-                                    rootSelected={returnFalse}
-                                    ScreenToLocalTransform={Transform.Identity}
-                                    parentActive={returnFalse}
-                                    addDocument={returnFalse}
-                                    removeDocument={returnFalse}
-                                    addDocTab={returnFalse}
-                                    pinToPres={returnFalse}
-                                    dontRegisterView={true}
-                                    docFilters={returnEmptyFilter}
-                                    ContainingCollectionDoc={undefined}
-                                    ContainingCollectionView={undefined}
-                                    renderDepth={0}
-                                    PanelWidth={() => Math.min(350, NumCast(target._width, 350))}
-                                    PanelHeight={() => Math.min(250, NumCast(target._height, 250))}
-                                    focus={emptyFunction}
-                                    whenActiveChanged={returnFalse}
-                                    bringToFront={returnFalse}
-                                    ContentScaling={returnOne}
-                                    NativeWidth={returnZero}
-                                    NativeHeight={returnZero}
-                                />, FormattedTextBoxComment.tooltipText);
+                                ReactDOM.render(docPreview, FormattedTextBoxComment.tooltipText);
 
                                 FormattedTextBoxComment.tooltip.style.width = NumCast(target._width) ? `${NumCast(target._width)}` : "100%";
                                 FormattedTextBoxComment.tooltip.style.height = NumCast(target._height) ? `${NumCast(target._height)}` : "100%";
-- 
cgit v1.2.3-70-g09d2


From 24ac6997048932c4f2717651e992f744e6a5276a Mon Sep 17 00:00:00 2001
From: anika-ahluwalia <anika.ahluwalia@gmail.com>
Date: Wed, 1 Jul 2020 17:42:38 -0500
Subject: started clicking mode popup

---
 src/client/views/MainView.tsx                      |  4 +--
 .../views/collections/CollectionLinearView.scss    | 42 +++++++++++++++-------
 .../views/collections/CollectionLinearView.tsx     | 25 ++++++++++++-
 src/client/views/linking/LinkMenu.tsx              |  6 ++--
 src/client/views/linking/LinkMenuItem.tsx          |  3 +-
 src/client/views/nodes/DocumentLinksButton.tsx     | 31 +++++++++++++---
 src/client/views/nodes/DocumentView.tsx            |  1 +
 .../views/nodes/formattedText/FormattedTextBox.tsx | 36 +++++++++++++++++++
 8 files changed, 124 insertions(+), 24 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f6db1af66..662232810 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -4,7 +4,7 @@ import {
     faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
     faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
     faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
-    faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote,
+    faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTimesCircle,
     faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight
 } from '@fortawesome/free-solid-svg-icons';
 import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
@@ -138,7 +138,7 @@ export class MainView extends React.Component {
         }
 
         library.add(faTasks, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
-            faTerminal, faToggleOn, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard, fileSolid,
+            faTerminal, faToggleOn, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faTimesCircle, faWindowMaximize, faAddressCard, fileSolid,
             faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
             faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
             faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
diff --git a/src/client/views/collections/CollectionLinearView.scss b/src/client/views/collections/CollectionLinearView.scss
index 123a27deb..90c96e08b 100644
--- a/src/client/views/collections/CollectionLinearView.scss
+++ b/src/client/views/collections/CollectionLinearView.scss
@@ -1,12 +1,27 @@
 @import "../globalCssVariables";
 @import "../_nodeModuleOverrides";
 
-.collectionLinearView-outer{
+.collectionLinearView-outer {
     overflow: hidden;
-    height:100%;
+    height: 100%;
+
     .collectionLinearView {
-        display:flex;
+        display: flex;
         height: 100%;
+
+        >span {
+            margin-top: "auto";
+            margin-bottom: "auto";
+            background: $dark-color;
+            color: $light-color;
+            display: inline-block;
+            border-radius: 18px;
+            margin-top: auto;
+            margin-bottom: auto;
+            margin-right: 3px;
+            cursor: pointer;
+        }
+
         >label {
             margin-top: "auto";
             margin-bottom: "auto";
@@ -17,15 +32,15 @@
             font-size: 12.5px;
             width: 18px;
             height: 18px;
-            margin-top:auto;
-            margin-bottom:auto;
+            margin-top: auto;
+            margin-bottom: auto;
             margin-right: 3px;
             cursor: pointer;
             transition: transform 0.2s;
         }
 
         label p {
-            padding-left:5px;
+            padding-left: 5px;
         }
 
         label:hover {
@@ -36,13 +51,14 @@
         >input {
             display: none;
         }
+
         >input:not(:checked)~.collectionLinearView-content {
             display: none;
         }
 
         >input:checked~label {
             transform: rotate(45deg);
-            transition: transform 0.5s;
+transition: transform 0.5s;
             cursor: pointer;
         }
 
@@ -52,12 +68,14 @@
             position: relative;
             margin-top: auto;
 
-            .collectionLinearView-docBtn, .collectionLinearView-docBtn-scalable {
-                position:relative;
-                margin:auto;
+            .collectionLinearView-docBtn,
+            .collectionLinearView-docBtn-scalable {
+                position: relative;
+                margin: auto;
                 margin-left: 3px;
                 transform-origin: center 80%;
             }
+
             .collectionLinearView-docBtn-scalable:hover {
                 transform: scale(1.15);
             }
@@ -68,10 +86,10 @@
                 border-radius: 18px;
                 font-size: 15px;
             }
-            
+
             .collectionLinearView-round-button:hover {
                 transform: scale(1.15);
             }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index f38eeaf41..29deb906a 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -13,6 +13,8 @@ import { CollectionSubView } from './CollectionSubView';
 import { DocumentView } from '../nodes/DocumentView';
 import { documentSchema } from '../../../fields/documentSchemas';
 import { Id } from '../../../fields/FieldSymbols';
+import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 
 
 type LinearDocument = makeInterface<[typeof documentSchema,]>;
@@ -75,6 +77,11 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
         return new Transform(-translateX, -translateY, 1);
     }
 
+    @action
+    exitLongLinks = () => {
+        DocumentLinksButton.StartLink = undefined
+    }
+
     render() {
         const guid = Utils.GenerateGuid();
         const flexDir: any = StrCast(this.Document.flexDirection);
@@ -82,7 +89,12 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
         const color = StrCast(this.props.Document.color, "white");
         return <div className="collectionLinearView-outer">
             <div className="collectionLinearView" ref={this.createDashEventsTarget} >
-                <label htmlFor={`${guid}`} title="Close Menu" style={{ background: backgroundColor === color ? "black" : backgroundColor }}
+                <label htmlFor={`${guid}`} title="Close Menu" style={{
+                    background: backgroundColor === color ? "black" : backgroundColor,
+                    // width: "18px", height: "18px", fontSize: "12.5px",
+                    // transition: this.props.Document.linearViewIsExpanded ? "transform 0.2s" : "transform 0.5s",
+                    // transform: this.props.Document.linearViewIsExpanded ? "" : "rotate(45deg)"
+                }}
                     onPointerDown={e => e.stopPropagation()} >
                     <p>+</p>
                 </label>
@@ -130,6 +142,17 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                         </div>;
                     })}
                 </div>
+                {DocumentLinksButton.StartLink ? <span style={{
+                    background: backgroundColor === color ? "black" : backgroundColor,
+                }}
+                    onPointerDown={e => e.stopPropagation()} >
+                    <span style={{ fontSize: "7px", display: "inline-block" }}>
+                        Creating Link From: {DocumentLinksButton.StartLink.title} </span>
+
+                    <FontAwesomeIcon icon="times-circle" size="lg" style={{ color: "red" }}
+                        onClick={this.exitLongLinks} />
+
+                </span> : null}
             </div>
         </div>;
     }
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 2a9bbed39..c36e7b67c 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -32,9 +32,9 @@ export class LinkMenu extends React.Component<Props> {
             DocumentLinksButton.EditLink = undefined;
         }
 
-        // if (this._linkMenuRef && !this._linkMenuRef.contains(e.target)) {
-        //     DocumentLinksButton.EditLink = undefined;
-        // }
+        if (this._linkMenuRef && !this._linkMenuRef.contains(e.target as any)) {
+            DocumentLinksButton.EditLink = undefined;
+        }
     }
     @action
     componentDidMount() {
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index d5d31d1ba..54dca261d 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -137,6 +137,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
 
     @action.bound
     async followDefault() {
+        console.log("FOLLOWWW");
         DocumentLinksButton.EditLink = undefined;
         LinkDocPreview.LinkInfo = undefined;
         DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false);
@@ -174,7 +175,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                             {/* <div title="Edit link" className="button" ref={this._editRef} onPointerDown={this.onEdit}><FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div> */}
                             <div title="Delete link" className="button" ref={this._editRef} onPointerDown={this.deleteLink}>
                                 <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div>
-                            <div title="Follow link" className="button" onClick={this.followDefault} onContextMenu={this.onContextMenu}>
+                            <div title="Follow link" className="button" onClick={e => this.followDefault()} onContextMenu={this.onContextMenu}>
                                 <FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" />
                             </div>
                         </div>
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index c625c4cd6..d04cdd34c 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -24,6 +24,8 @@ interface DocumentLinksButtonProps {
 export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
     private _linkButton = React.createRef<HTMLDivElement>();
 
+    @observable public static StartLink: DocumentView | undefined;
+
     @action
     onLinkButtonMoved = (e: PointerEvent) => {
         if (this._linkButton.current !== null) {
@@ -51,11 +53,9 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
         return false;
     }
 
-    @observable static StartLink: DocumentView | undefined;
     onLinkButtonDown = (e: React.PointerEvent): void => {
-
         setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => {
-            if (doubleTap) {
+            if (doubleTap && this.props.InMenu) {
                 DocumentLinksButton.StartLink = this.props.View;
             } else if (!!!this.props.InMenu) {
                 DocumentLinksButton.EditLink = this.props.View;
@@ -63,6 +63,17 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             }
         }));
     }
+
+    @action
+    onLinkClick = (e: React.MouseEvent): void => {
+        if (this.props.InMenu) {
+            DocumentLinksButton.StartLink = this.props.View;
+        } else if (!!!this.props.InMenu) {
+            DocumentLinksButton.EditLink = this.props.View;
+            DocumentLinksButton.EditLinkLoc = [e.clientX + 10, e.clientY];
+        }
+    }
+
     completeLink = (e: React.PointerEvent): void => {
         setupMoveUpEvents(this, e, returnFalse, emptyFunction, action((e, doubleTap) => {
             if (doubleTap) {
@@ -76,6 +87,15 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
         }));
     }
 
+    finishLinkClick = () => {
+        if (DocumentLinksButton.StartLink === this.props.View) {
+            DocumentLinksButton.StartLink = undefined;
+        } else {
+            DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
+                DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
+        }
+    }
+
     @observable
     public static EditLink: DocumentView | undefined;
     public static EditLinkLoc: number[] = [0, 0];
@@ -86,7 +106,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
         return (!links.length || links[0].hidden) && !this.props.AlwaysOn ? (null) :
             <div title="Drag(create link) Tap(view links)" ref={this._linkButton} style={{ minWidth: 20, minHeight: 20, position: "absolute", left: this.props.Offset?.[0] }}>
                 <div className={"documentLinksButton"} style={{ backgroundColor: DocumentLinksButton.StartLink ? "transparent" : "" }}
-                    onPointerDown={this.onLinkButtonDown}
+                    onPointerDown={this.onLinkButtonDown} onClick={this.onLinkClick}
                 // onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
                 // onPointerEnter={action(e => links.length && (LinkDocPreview.LinkInfo = {
                 //     addDocTab: this.props.View.props.addDocTab,
@@ -97,7 +117,8 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                 >
                     {links.length && !!!this.props.InMenu ? links.length : <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />}
                 </div>
-                {DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View ? <div className={"documentLinksButton-endLink"} onPointerDown={this.completeLink} /> : (null)}
+                {DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View ? <div className={"documentLinksButton-endLink"}
+                    onPointerDown={this.completeLink} onClick={this.finishLinkClick} /> : (null)}
                 {DocumentLinksButton.StartLink === this.props.View ? <div className={"documentLinksButton-startLink"} /> : (null)}
             </div>;
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 09eeaee36..6820b2047 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -116,6 +116,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
     private holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
+    public get title() { return this.props.Document.title }
     public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
     public get ContentDiv() { return this._mainCont.current; }
     get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2a79e2f13..dc7d8c7ca 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -58,6 +58,7 @@ import { FieldView, FieldViewProps } from "../FieldView";
 import "./FormattedTextBox.scss";
 import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment';
 import React = require("react");
+import { DocumentManager } from '../../../util/DocumentManager';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -1047,6 +1048,40 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         }
     }
 
+    @action
+    onDoubleClick = (e: React.MouseEvent): void => {
+
+        this.doLinkOnDeselect();
+        FormattedTextBox._downEvent = true;
+        FormattedTextBoxComment.textBox = this;
+        if (this.props.onClick && e.button === 0 && !this.props.isSelected(false)) {
+            e.preventDefault();
+        }
+        if (e.button === 0 && this.props.isSelected(true) && !e.altKey && !e.ctrlKey && !e.metaKey) {
+            if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar
+                e.stopPropagation();  // if the text box is selected, then it consumes all down events
+            }
+        }
+        if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
+            e.preventDefault();
+        }
+        FormattedTextBoxComment.Hide();
+        if (FormattedTextBoxComment.linkDoc) {
+            if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
+                this.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
+            } else {
+                DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, this.props.Document,
+                    (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+            }
+        }
+
+        (e.nativeEvent as any).formattedHandled = true;
+
+        if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) {
+            e.stopPropagation();
+        }
+    }
+
     @action
     onFocused = (e: React.FocusEvent): void => {
         console.log("FOUCSS")
@@ -1317,6 +1352,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                             }
                         }
                     })}
+                    onDoubleClick={this.onDoubleClick}
                 >
                     <div className={`formattedTextBox-outer`} ref={this._scrollRef}
                         style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: !this.props.isSelected() ? "none" : undefined }}
-- 
cgit v1.2.3-70-g09d2


From 96001ff5b299326de8742a6540acd5017d0f751a Mon Sep 17 00:00:00 2001
From: anika-ahluwalia <anika.ahluwalia@gmail.com>
Date: Thu, 2 Jul 2020 10:40:17 -0500
Subject: started link created popup, tried hihglighting source

---
 package-lock.json                                  | 331 ++++++++++++++++++++-
 package.json                                       |   2 +
 src/client/documents/Documents.ts                  |   9 +-
 src/client/views/GlobalKeyHandler.ts               |   8 +
 src/client/views/MainView.tsx                      |  17 ++
 .../views/collections/CollectionLinearView.scss    |   2 +-
 .../views/collections/CollectionLinearView.tsx     |  17 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |   9 +
 src/client/views/nodes/DocumentView.tsx            |   7 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   4 +-
 src/fields/Doc.ts                                  |   2 +
 11 files changed, 398 insertions(+), 10 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index d42e286e2..ad181758c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -372,6 +372,101 @@
             "resolved": "https://registry.npmjs.org/@log4js-node/log4js-api/-/log4js-api-1.0.2.tgz",
             "integrity": "sha512-6SJfx949YEWooh/CUPpJ+F491y4BYJmknz4hUN1+RHvKoUEynKbRmhnwbk/VLmh4OthLLDNCyWXfbh4DG1cTXA=="
         },
+        "@material-ui/core": {
+            "version": "4.11.0",
+            "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.11.0.tgz",
+            "integrity": "sha512-bYo9uIub8wGhZySHqLQ833zi4ZML+XCBE1XwJ8EuUVSpTWWG57Pm+YugQToJNFsEyiKFhPh8DPD0bgupz8n01g==",
+            "requires": {
+                "@babel/runtime": "^7.4.4",
+                "@material-ui/styles": "^4.10.0",
+                "@material-ui/system": "^4.9.14",
+                "@material-ui/types": "^5.1.0",
+                "@material-ui/utils": "^4.10.2",
+                "@types/react-transition-group": "^4.2.0",
+                "clsx": "^1.0.4",
+                "hoist-non-react-statics": "^3.3.2",
+                "popper.js": "1.16.1-lts",
+                "prop-types": "^15.7.2",
+                "react-is": "^16.8.0",
+                "react-transition-group": "^4.4.0"
+            },
+            "dependencies": {
+                "dom-helpers": {
+                    "version": "5.1.4",
+                    "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.4.tgz",
+                    "integrity": "sha512-TjMyeVUvNEnOnhzs6uAn9Ya47GmMo3qq7m+Lr/3ON0Rs5kHvb8I+SQYjLUSYn7qhEm0QjW0yrBkvz9yOrwwz1A==",
+                    "requires": {
+                        "@babel/runtime": "^7.8.7",
+                        "csstype": "^2.6.7"
+                    }
+                },
+                "popper.js": {
+                    "version": "1.16.1-lts",
+                    "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz",
+                    "integrity": "sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA=="
+                },
+                "react-transition-group": {
+                    "version": "4.4.1",
+                    "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz",
+                    "integrity": "sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==",
+                    "requires": {
+                        "@babel/runtime": "^7.5.5",
+                        "dom-helpers": "^5.0.1",
+                        "loose-envify": "^1.4.0",
+                        "prop-types": "^15.6.2"
+                    }
+                }
+            }
+        },
+        "@material-ui/styles": {
+            "version": "4.10.0",
+            "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.10.0.tgz",
+            "integrity": "sha512-XPwiVTpd3rlnbfrgtEJ1eJJdFCXZkHxy8TrdieaTvwxNYj42VnnCyFzxYeNW9Lhj4V1oD8YtQ6S5Gie7bZDf7Q==",
+            "requires": {
+                "@babel/runtime": "^7.4.4",
+                "@emotion/hash": "^0.8.0",
+                "@material-ui/types": "^5.1.0",
+                "@material-ui/utils": "^4.9.6",
+                "clsx": "^1.0.4",
+                "csstype": "^2.5.2",
+                "hoist-non-react-statics": "^3.3.2",
+                "jss": "^10.0.3",
+                "jss-plugin-camel-case": "^10.0.3",
+                "jss-plugin-default-unit": "^10.0.3",
+                "jss-plugin-global": "^10.0.3",
+                "jss-plugin-nested": "^10.0.3",
+                "jss-plugin-props-sort": "^10.0.3",
+                "jss-plugin-rule-value-function": "^10.0.3",
+                "jss-plugin-vendor-prefixer": "^10.0.3",
+                "prop-types": "^15.7.2"
+            }
+        },
+        "@material-ui/system": {
+            "version": "4.9.14",
+            "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.9.14.tgz",
+            "integrity": "sha512-oQbaqfSnNlEkXEziDcJDDIy8pbvwUmZXWNqlmIwDqr/ZdCK8FuV3f4nxikUh7hvClKV2gnQ9djh5CZFTHkZj3w==",
+            "requires": {
+                "@babel/runtime": "^7.4.4",
+                "@material-ui/utils": "^4.9.6",
+                "csstype": "^2.5.2",
+                "prop-types": "^15.7.2"
+            }
+        },
+        "@material-ui/types": {
+            "version": "5.1.0",
+            "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz",
+            "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A=="
+        },
+        "@material-ui/utils": {
+            "version": "4.10.2",
+            "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.10.2.tgz",
+            "integrity": "sha512-eg29v74P7W5r6a4tWWDAAfZldXIzfyO1am2fIsC39hdUUHm/33k6pGOKPbgDjg/U/4ifmgAePy/1OjkKN6rFRw==",
+            "requires": {
+                "@babel/runtime": "^7.4.4",
+                "prop-types": "^15.7.2",
+                "react-is": "^16.8.0"
+            }
+        },
         "@rkusa/linebreak": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/@rkusa/linebreak/-/linebreak-1.0.0.tgz",
@@ -1075,7 +1170,6 @@
             "version": "4.4.0",
             "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.0.tgz",
             "integrity": "sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w==",
-            "dev": true,
             "requires": {
                 "@types/react": "*"
             }
@@ -2478,6 +2572,11 @@
             "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.5.0.tgz",
             "integrity": "sha512-Z93QoXvodoVslA+PWNdk23Hze4RBYIkpb5h8I2HY2Tu2h7A0LpAgLcyrhrSUyo2/Oxm2l1fRZPs1e5hnxnliXA=="
         },
+        "bowser": {
+            "version": "1.9.4",
+            "resolved": "https://registry.npmjs.org/bowser/-/bowser-1.9.4.tgz",
+            "integrity": "sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ=="
+        },
         "boxen": {
             "version": "1.3.0",
             "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz",
@@ -2878,6 +2977,11 @@
                 "type-detect": "^4.0.5"
             }
         },
+        "chain-function": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/chain-function/-/chain-function-1.0.1.tgz",
+            "integrity": "sha512-SxltgMwL9uCko5/ZCLiyG2B7R9fY4pDZUw7hJ4MhirdjBLosoDqkWABi3XMucddHdLiFJMb7PD2MZifZriuMTg=="
+        },
         "chalk": {
             "version": "2.4.2",
             "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -2888,6 +2992,11 @@
                 "supports-color": "^5.3.0"
             }
         },
+        "change-emitter": {
+            "version": "0.1.6",
+            "resolved": "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz",
+            "integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU="
+        },
         "character-parser": {
             "version": "2.2.0",
             "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz",
@@ -3614,6 +3723,11 @@
                 "shallow-clone": "^3.0.0"
             }
         },
+        "clsx": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz",
+            "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA=="
+        },
         "code-point-at": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
@@ -4263,6 +4377,15 @@
             "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz",
             "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4="
         },
+        "css-in-js-utils": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz",
+            "integrity": "sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA==",
+            "requires": {
+                "hyphenate-style-name": "^1.0.2",
+                "isobject": "^3.0.1"
+            }
+        },
         "css-loader": {
             "version": "2.1.1",
             "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-2.1.1.tgz",
@@ -4317,6 +4440,15 @@
                 }
             }
         },
+        "css-vendor": {
+            "version": "2.0.8",
+            "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz",
+            "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==",
+            "requires": {
+                "@babel/runtime": "^7.8.3",
+                "is-in-browser": "^1.0.2"
+            }
+        },
         "css-what": {
             "version": "2.1.3",
             "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz",
@@ -7032,6 +7164,11 @@
                 }
             }
         },
+        "hyphenate-style-name": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz",
+            "integrity": "sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ=="
+        },
         "i": {
             "version": "0.3.6",
             "resolved": "https://registry.npmjs.org/i/-/i-0.3.6.tgz",
@@ -7247,6 +7384,15 @@
             "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
             "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
         },
+        "inline-style-prefixer": {
+            "version": "3.0.8",
+            "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-3.0.8.tgz",
+            "integrity": "sha1-hVG45bTVcyROZqNLBPfTIHaitTQ=",
+            "requires": {
+                "bowser": "^1.7.3",
+                "css-in-js-utils": "^2.0.0"
+            }
+        },
         "inspect-function": {
             "version": "0.2.2",
             "resolved": "https://registry.npmjs.org/inspect-function/-/inspect-function-0.2.2.tgz",
@@ -7555,6 +7701,11 @@
                 "is-extglob": "^2.1.1"
             }
         },
+        "is-in-browser": {
+            "version": "1.1.3",
+            "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz",
+            "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU="
+        },
         "is-installed-globally": {
             "version": "0.1.0",
             "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz",
@@ -7922,6 +8073,84 @@
                 "verror": "1.10.0"
             }
         },
+        "jss": {
+            "version": "10.3.0",
+            "resolved": "https://registry.npmjs.org/jss/-/jss-10.3.0.tgz",
+            "integrity": "sha512-B5sTRW9B6uHaUVzSo9YiMEOEp3UX8lWevU0Fsv+xtRnsShmgCfIYX44bTH8bPJe6LQKqEXku3ulKuHLbxBS97Q==",
+            "requires": {
+                "@babel/runtime": "^7.3.1",
+                "csstype": "^2.6.5",
+                "is-in-browser": "^1.1.3",
+                "tiny-warning": "^1.0.2"
+            }
+        },
+        "jss-plugin-camel-case": {
+            "version": "10.3.0",
+            "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.3.0.tgz",
+            "integrity": "sha512-tadWRi/SLWqLK3EUZEdDNJL71F3ST93Zrl9JYMjV0QDqKPAl0Liue81q7m/nFUpnSTXczbKDy4wq8rI8o7WFqA==",
+            "requires": {
+                "@babel/runtime": "^7.3.1",
+                "hyphenate-style-name": "^1.0.3",
+                "jss": "^10.3.0"
+            }
+        },
+        "jss-plugin-default-unit": {
+            "version": "10.3.0",
+            "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.3.0.tgz",
+            "integrity": "sha512-tT5KkIXAsZOSS9WDSe8m8lEHIjoEOj4Pr0WrG0WZZsMXZ1mVLFCSsD2jdWarQWDaRNyMj/I4d7czRRObhOxSuw==",
+            "requires": {
+                "@babel/runtime": "^7.3.1",
+                "jss": "^10.3.0"
+            }
+        },
+        "jss-plugin-global": {
+            "version": "10.3.0",
+            "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.3.0.tgz",
+            "integrity": "sha512-etYTG/y3qIR/vxZnKY+J3wXwObyBDNhBiB3l/EW9/pE3WHE//BZdK8LFvQcrCO48sZW1Z6paHo6klxUPP7WbzA==",
+            "requires": {
+                "@babel/runtime": "^7.3.1",
+                "jss": "^10.3.0"
+            }
+        },
+        "jss-plugin-nested": {
+            "version": "10.3.0",
+            "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.3.0.tgz",
+            "integrity": "sha512-qWiEkoXNEkkZ+FZrWmUGpf+zBsnEOmKXhkjNX85/ZfWhH9dfGxUCKuJFuOWFM+rjQfxV4csfesq4hY0jk8Qt0w==",
+            "requires": {
+                "@babel/runtime": "^7.3.1",
+                "jss": "^10.3.0",
+                "tiny-warning": "^1.0.2"
+            }
+        },
+        "jss-plugin-props-sort": {
+            "version": "10.3.0",
+            "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.3.0.tgz",
+            "integrity": "sha512-boetORqL/lfd7BWeFD3K+IyPqyIC+l3CRrdZr+NPq7Noqp+xyg/0MR7QisgzpxCEulk+j2CRcEUoZsvgPC4nTg==",
+            "requires": {
+                "@babel/runtime": "^7.3.1",
+                "jss": "^10.3.0"
+            }
+        },
+        "jss-plugin-rule-value-function": {
+            "version": "10.3.0",
+            "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.3.0.tgz",
+            "integrity": "sha512-7WiMrKIHH3rwxTuJki9+7nY11r1UXqaUZRhHvqTD4/ZE+SVhvtD5Tx21ivNxotwUSleucA/8boX+NF21oXzr5Q==",
+            "requires": {
+                "@babel/runtime": "^7.3.1",
+                "jss": "^10.3.0",
+                "tiny-warning": "^1.0.2"
+            }
+        },
+        "jss-plugin-vendor-prefixer": {
+            "version": "10.3.0",
+            "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.3.0.tgz",
+            "integrity": "sha512-sZQbrcZyP5V0ADjCLwUA1spVWoaZvM7XZ+2fSeieZFBj31cRsnV7X70FFDerMHeiHAXKWzYek+67nMDjhrZAVQ==",
+            "requires": {
+                "@babel/runtime": "^7.3.1",
+                "css-vendor": "^2.0.8",
+                "jss": "^10.3.0"
+            }
+        },
         "jstransformer": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz",
@@ -7955,6 +8184,11 @@
             "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz",
             "integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw=="
         },
+        "keycode": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz",
+            "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ="
+        },
         "keygrip": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz",
@@ -8201,6 +8435,11 @@
             "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
             "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4="
         },
+        "lodash.merge": {
+            "version": "4.6.2",
+            "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+            "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
+        },
         "lodash.padend": {
             "version": "4.6.1",
             "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz",
@@ -8212,6 +8451,11 @@
             "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=",
             "dev": true
         },
+        "lodash.throttle": {
+            "version": "4.1.1",
+            "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
+            "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
+        },
         "lodash.union": {
             "version": "4.6.0",
             "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
@@ -8371,6 +8615,38 @@
             "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
             "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg=="
         },
+        "material-ui": {
+            "version": "0.20.2",
+            "resolved": "https://registry.npmjs.org/material-ui/-/material-ui-0.20.2.tgz",
+            "integrity": "sha512-VeqgQkdvtK193w+FFvXDEwlVxI4rWk83eWbpYLeOIHDPWr3rbB9B075JRnJt/8IsI2X8q5Aia5W3+7m4KkleDg==",
+            "requires": {
+                "babel-runtime": "^6.23.0",
+                "inline-style-prefixer": "^3.0.8",
+                "keycode": "^2.1.8",
+                "lodash.merge": "^4.6.0",
+                "lodash.throttle": "^4.1.1",
+                "prop-types": "^15.5.7",
+                "react-event-listener": "^0.6.2",
+                "react-transition-group": "^1.2.1",
+                "recompose": "^0.26.0",
+                "simple-assign": "^0.1.0",
+                "warning": "^3.0.0"
+            },
+            "dependencies": {
+                "react-transition-group": {
+                    "version": "1.2.1",
+                    "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-1.2.1.tgz",
+                    "integrity": "sha512-CWaL3laCmgAFdxdKbhhps+c0HRGF4c+hdM4H23+FI1QBNUyx/AMeIJGWorehPNSaKnQNOAxL7PQmqMu78CDj3Q==",
+                    "requires": {
+                        "chain-function": "^1.0.0",
+                        "dom-helpers": "^3.2.0",
+                        "loose-envify": "^1.3.1",
+                        "prop-types": "^15.5.6",
+                        "warning": "^3.0.0"
+                    }
+                }
+            }
+        },
         "md5.js": {
             "version": "1.3.5",
             "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@@ -13888,6 +14164,26 @@
                 "prop-types": "^15.6.0"
             }
         },
+        "react-event-listener": {
+            "version": "0.6.6",
+            "resolved": "https://registry.npmjs.org/react-event-listener/-/react-event-listener-0.6.6.tgz",
+            "integrity": "sha512-+hCNqfy7o9wvO6UgjqFmBzARJS7qrNoda0VqzvOuioEpoEXKutiKuv92dSz6kP7rYLmyHPyYNLesi5t/aH1gfw==",
+            "requires": {
+                "@babel/runtime": "^7.2.0",
+                "prop-types": "^15.6.0",
+                "warning": "^4.0.1"
+            },
+            "dependencies": {
+                "warning": {
+                    "version": "4.0.3",
+                    "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
+                    "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+                    "requires": {
+                        "loose-envify": "^1.0.0"
+                    }
+                }
+            }
+        },
         "react-grid-layout": {
             "version": "0.18.3",
             "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-0.18.3.tgz",
@@ -14224,6 +14520,24 @@
                 "resolve": "^1.1.6"
             }
         },
+        "recompose": {
+            "version": "0.26.0",
+            "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.26.0.tgz",
+            "integrity": "sha512-KwOu6ztO0mN5vy3+zDcc45lgnaUoaQse/a5yLVqtzTK13czSWnFGmXbQVmnoMgDkI5POd1EwIKSbjU1V7xdZog==",
+            "requires": {
+                "change-emitter": "^0.1.2",
+                "fbjs": "^0.8.1",
+                "hoist-non-react-statics": "^2.3.1",
+                "symbol-observable": "^1.0.4"
+            },
+            "dependencies": {
+                "hoist-non-react-statics": {
+                    "version": "2.5.5",
+                    "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
+                    "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
+                }
+            }
+        },
         "redent": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
@@ -14988,6 +15302,11 @@
             "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
             "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
         },
+        "simple-assign": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/simple-assign/-/simple-assign-0.1.0.tgz",
+            "integrity": "sha1-F/0wZqXz13OPUDIbsPFMooHMS6o="
+        },
         "simple-concat": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz",
@@ -15827,6 +16146,11 @@
                 "has-flag": "^3.0.0"
             }
         },
+        "symbol-observable": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
+            "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
+        },
         "symbol-tree": {
             "version": "3.2.4",
             "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
@@ -16122,6 +16446,11 @@
             "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
             "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="
         },
+        "tiny-warning": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
+            "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
+        },
         "tinycolor2": {
             "version": "1.4.1",
             "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz",
diff --git a/package.json b/package.json
index 5e7598376..75dc817f5 100644
--- a/package.json
+++ b/package.json
@@ -121,6 +121,7 @@
         "@hig/flyout": "^1.2.1",
         "@hig/theme-context": "^2.1.3",
         "@hig/theme-data": "^2.16.1",
+        "@material-ui/core": "^4.11.0",
         "@types/google-maps": "^3.2.2",
         "@types/webscopeio__react-textarea-autocomplete": "^4.6.1",
         "@webscopeio/react-textarea-autocomplete": "^4.7.0",
@@ -172,6 +173,7 @@
         "jsonschema": "^1.2.5",
         "libxmljs": "^0.19.7",
         "lodash": "^4.17.15",
+        "material-ui": "^0.20.2",
         "mobile-detect": "^1.4.4",
         "mobx": "^5.15.3",
         "mobx-react": "^5.3.5",
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 85da621e0..2e0323ede 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,4 +1,4 @@
-import { runInAction } from "mobx";
+import { runInAction, action } from "mobx";
 import { extname, basename } from "path";
 import { DateField } from "../../fields/DateField";
 import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../fields/Doc";
@@ -50,6 +50,7 @@ import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
 import { DocumentType } from "./DocumentTypes";
 import { Networking } from "../Network";
 import { Upload } from "../../server/SharedMediaTypes";
+import { MainView } from "../views/MainView";
 const path = require('path');
 
 export interface DocumentOptions {
@@ -866,6 +867,7 @@ export namespace DocUtils {
     export function MakeLinkToActiveAudio(doc: Doc) {
         DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "audio timeline"));
     }
+
     export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", id?: string) {
         const sv = DocumentManager.Instance.getDocumentView(source.doc);
         if (sv && sv.props.ContainingCollectionDoc === target.doc) return;
@@ -877,6 +879,11 @@ export namespace DocUtils {
 
         Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(self)");
         Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(self)");
+
+        runInAction(() => { MainView.linkCreated = true; });
+        console.log("link created");
+        runInAction(() => { setTimeout("MainView.changeLinkCreated()", 2000); });
+
         return linkDoc;
     }
 
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index c85849adb..369d6f4ae 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -79,7 +79,15 @@ export default class KeyManager {
                 // MarqueeView.DragMarquee = !MarqueeView.DragMarquee; // bcz: this needs a better disclosure UI
                 break;
             case "escape":
+                // if (DocumentLinksButton.StartLink) {
+                //     if (DocumentLinksButton.StartLink.Document) {
+                //         action((e: React.PointerEvent<HTMLDivElement>) => {
+                //             Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc);
+                //         });
+                //     }
+                // }
                 DocumentLinksButton.StartLink = undefined;
+
                 const main = MainView.Instance;
                 Doc.SetSelectedTool(InkTool.None);
                 var doDeselect = true;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 662232810..4898e114a 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -59,6 +59,7 @@ import { DocumentManager } from '../util/DocumentManager';
 import { DocumentLinksButton } from './nodes/DocumentLinksButton';
 import { LinkMenu } from './linking/LinkMenu';
 import { LinkDocPreview } from './nodes/LinkDocPreview';
+import { Fade } from '@material-ui/core';
 
 @observer
 export class MainView extends React.Component {
@@ -82,6 +83,8 @@ export class MainView extends React.Component {
 
     public isPointerDown = false;
 
+    @observable public static linkCreated: boolean = false;
+
 
     componentDidMount() {
         DocServer.setPlaygroundFields(["dataTransition", "_viewTransition", "_panX", "_panY", "_viewScale", "_viewType"]); // can play with these fields on someone else's
@@ -362,6 +365,11 @@ export class MainView extends React.Component {
         }
     }
 
+    @action
+    public static changeLinkCreated = () => {
+        MainView.linkCreated = !MainView.linkCreated;
+    }
+
     @action
     onPointerMove = (e: PointerEvent) => {
         this.flyoutWidth = Math.max(e.clientX, 0);
@@ -606,6 +614,15 @@ export class MainView extends React.Component {
                 {this.mainContent}
             </GestureOverlay>
             <PreviewCursor />
+            {MainView.linkCreated ? <div className="mainView-linkCreated" style={{
+                border: "2px solid red", top: screenTop / 2,
+                left: screenLeft / 2, width: "50px", height: "20px", zIndex: 10000,
+                fontSize: "12px"
+            }}>
+                {/* <Fade in={MainView.linkCreated}> */}
+                link created!
+                {/* </Fade> */}
+            </div> : null}
             {DocumentLinksButton.EditLink ? <LinkMenu location={DocumentLinksButton.EditLinkLoc} docView={DocumentLinksButton.EditLink} addDocTab={DocumentLinksButton.EditLink.props.addDocTab} changeFlyout={emptyFunction} /> : (null)}
             {LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} backgroundColor={this.defaultBackgroundColors}
                 linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href}
diff --git a/src/client/views/collections/CollectionLinearView.scss b/src/client/views/collections/CollectionLinearView.scss
index 647ce1b01..134d62d4f 100644
--- a/src/client/views/collections/CollectionLinearView.scss
+++ b/src/client/views/collections/CollectionLinearView.scss
@@ -10,6 +10,7 @@
         height: 100%;
 
         >span {
+
             margin-top: 8px;
             margin-left: 4px;
             //margin-bottom: 2px;
@@ -18,7 +19,6 @@
             display: inline-block;
             border-radius: 18px;
             margin-right: 6px;
-            padding: "4px";
             cursor: pointer;
         }
 
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index df8e2c58e..a180991d0 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -79,7 +79,14 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
 
     @action
     exitLongLinks = () => {
-        DocumentLinksButton.StartLink = undefined
+        if (DocumentLinksButton.StartLink) {
+            if (DocumentLinksButton.StartLink.Document) {
+                action((e: React.PointerEvent<HTMLDivElement>) => {
+                    Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc);
+                });
+            }
+        }
+        DocumentLinksButton.StartLink = undefined;
     }
 
     render() {
@@ -146,8 +153,12 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                     background: backgroundColor === color ? "black" : backgroundColor,
                 }}
                     onPointerDown={e => e.stopPropagation()} >
-                    <span style={{ fontSize: "12.5px", display: "inline", whiteSpace: "nowrap" }}>
-                        Creating Link From: {DocumentLinksButton.StartLink.title} </span>
+                    <span style={{
+                        fontSize: "12.5px", display: "inline", whiteSpace: "nowrap",
+                        paddingLeft: "8px", paddingRight: "8px", paddingTop: "4px",
+                        paddingBottom: "10px"
+                    }}>
+                        Creating link from: {DocumentLinksButton.StartLink.title} </span>
 
                     <FontAwesomeIcon icon="times-circle" size="lg" style={{ color: "red" }}
                         onClick={this.exitLongLinks} />
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index d04cdd34c..27fbbc7fc 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -10,6 +10,7 @@ import React = require("react");
 import { DocUtils } from "../../documents/Documents";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { LinkDocPreview } from "./LinkDocPreview";
+import { MainView } from "../MainView";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -56,6 +57,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
     onLinkButtonDown = (e: React.PointerEvent): void => {
         setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => {
             if (doubleTap && this.props.InMenu) {
+                //action(() => Doc.BrushDoc(this.props.View.Document));
                 DocumentLinksButton.StartLink = this.props.View;
             } else if (!!!this.props.InMenu) {
                 DocumentLinksButton.EditLink = this.props.View;
@@ -68,6 +70,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
     onLinkClick = (e: React.MouseEvent): void => {
         if (this.props.InMenu) {
             DocumentLinksButton.StartLink = this.props.View;
+            //action(() => Doc.BrushDoc(this.props.View.Document));
         } else if (!!!this.props.InMenu) {
             DocumentLinksButton.EditLink = this.props.View;
             DocumentLinksButton.EditLinkLoc = [e.clientX + 10, e.clientY];
@@ -79,6 +82,9 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             if (doubleTap) {
                 if (DocumentLinksButton.StartLink === this.props.View) {
                     DocumentLinksButton.StartLink = undefined;
+                    // action((e: React.PointerEvent<HTMLDivElement>) => {
+                    //     Doc.UnBrushDoc(this.props.View.Document);
+                    // });
                 } else {
                     DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
                         DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
@@ -90,6 +96,9 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
     finishLinkClick = () => {
         if (DocumentLinksButton.StartLink === this.props.View) {
             DocumentLinksButton.StartLink = undefined;
+            // action((e: React.PointerEvent<HTMLDivElement>) => {
+            //     Doc.UnBrushDoc(this.props.View.Document);
+            // });
         } else {
             DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
                 DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6820b2047..0d7029576 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -116,7 +116,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
     private holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
-    public get title() { return this.props.Document.title }
+    public get title() { return this.props.Document.title; }
     public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
     public get ContentDiv() { return this._mainCont.current; }
     get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
@@ -1191,7 +1191,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             id={this.props.Document[Id]}
             ref={this._mainCont} onKeyDown={this.onKeyDown}
             onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
-            onPointerEnter={action(() => Doc.BrushDoc(this.props.Document))}
+            onPointerEnter={action(() => { Doc.BrushDoc(this.props.Document); })}
             onPointerLeave={action((e: React.PointerEvent<HTMLDivElement>) => {
                 let entered = false;
                 const target = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y);
@@ -1200,7 +1200,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                         entered = true;
                     }
                 }
+                // if (this.props.Document !== DocumentLinksButton.StartLink?.Document) {
                 !entered && Doc.UnBrushDoc(this.props.Document);
+                //}
+
             })}
             style={{
                 transformOrigin: this._animateScalingTo ? "center center" : undefined,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index dc7d8c7ca..276c79453 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1084,7 +1084,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     @action
     onFocused = (e: React.FocusEvent): void => {
-        console.log("FOUCSS")
+        console.log("FOUCSS");
         FormattedTextBox.FocusedBox = this;
         this.tryUpdateHeight();
 
@@ -1216,7 +1216,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
     public static HadSelection: boolean = false;
     onBlur = (e: any) => {
-        console.log("BLURRR")
+        console.log("BLURRR");
         FormattedTextBox.HadSelection = window.getSelection()?.toString() !== "";
         //DictationManager.Controls.stop(false);
         this.endUndoTypingBatch();
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index e4d11dd4d..8a7b91e49 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -832,12 +832,14 @@ export namespace Doc {
         })(doc);
     }
     export function BrushDoc(doc: Doc) {
+        console.log("brushed");
         if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return doc;
         brushManager.BrushedDoc.set(doc, true);
         brushManager.BrushedDoc.set(Doc.GetProto(doc), true);
         return doc;
     }
     export function UnBrushDoc(doc: Doc) {
+        console.log("unbrushed");
         if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return doc;
         brushManager.BrushedDoc.delete(doc);
         brushManager.BrushedDoc.delete(Doc.GetProto(doc));
-- 
cgit v1.2.3-70-g09d2


From 37392f606d29547bed46ffcd0f0190ad38f51148 Mon Sep 17 00:00:00 2001
From: anika-ahluwalia <anika.ahluwalia@gmail.com>
Date: Fri, 3 Jul 2020 01:06:18 -0500
Subject: link created popup

---
 src/client/documents/Documents.ts                  |  8 ++++----
 src/client/views/MainView.tsx                      | 23 +++++++++++++---------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  5 +++++
 src/client/views/linking/LinkMenu.scss             |  9 ++++-----
 src/client/views/linking/LinkMenu.tsx              |  4 ++++
 src/client/views/linking/LinkMenuItem.tsx          | 11 ++++++++---
 src/client/views/nodes/DocumentLinksButton.tsx     | 14 +++++++++++--
 src/fields/Doc.ts                                  |  4 ++--
 8 files changed, 53 insertions(+), 25 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2e0323ede..0f434d616 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -868,6 +868,10 @@ export namespace DocUtils {
         DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "audio timeline"));
     }
 
+    function stopLinkCreated() {
+        MainView.linkCreated = false;
+    }
+
     export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", id?: string) {
         const sv = DocumentManager.Instance.getDocumentView(source.doc);
         if (sv && sv.props.ContainingCollectionDoc === target.doc) return;
@@ -880,10 +884,6 @@ export namespace DocUtils {
         Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(self)");
         Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(self)");
 
-        runInAction(() => { MainView.linkCreated = true; });
-        console.log("link created");
-        runInAction(() => { setTimeout("MainView.changeLinkCreated()", 2000); });
-
         return linkDoc;
     }
 
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 4898e114a..617cc19b1 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -84,6 +84,8 @@ export class MainView extends React.Component {
     public isPointerDown = false;
 
     @observable public static linkCreated: boolean = false;
+    @observable public static popupX: number = 600;
+    @observable public static popupY: number = 250;
 
 
     componentDidMount() {
@@ -614,15 +616,18 @@ export class MainView extends React.Component {
                 {this.mainContent}
             </GestureOverlay>
             <PreviewCursor />
-            {MainView.linkCreated ? <div className="mainView-linkCreated" style={{
-                border: "2px solid red", top: screenTop / 2,
-                left: screenLeft / 2, width: "50px", height: "20px", zIndex: 10000,
-                fontSize: "12px"
-            }}>
-                {/* <Fade in={MainView.linkCreated}> */}
-                link created!
-                {/* </Fade> */}
-            </div> : null}
+
+            <Fade in={MainView.linkCreated}>
+                <div style={{
+                    border: "1px solid rgb(111, 144, 175)", top: MainView.popupX ? MainView.popupX : 600
+                    , position: "absolute",
+                    left: MainView.popupY ? MainView.popupY : 250
+                    , width: "auto", height: "auto", zIndex: 10000,
+                    fontSize: "13px", whiteSpace: "nowrap", backgroundColor: "rgb(170, 205, 238)",
+                    padding: "5px", //fontWeight: "bold"
+                }}>Link Created</div>
+            </Fade>
+
             {DocumentLinksButton.EditLink ? <LinkMenu location={DocumentLinksButton.EditLinkLoc} docView={DocumentLinksButton.EditLink} addDocTab={DocumentLinksButton.EditLink.props.addDocTab} changeFlyout={emptyFunction} /> : (null)}
             {LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} backgroundColor={this.defaultBackgroundColors}
                 linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 546a4307c..39bfd8239 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -48,6 +48,7 @@ import { SnappingManager } from "../../../util/SnappingManager";
 import { InkingStroke, ActiveArrowStart, ActiveArrowEnd, ActiveInkColor, ActiveFillColor, ActiveInkWidth, ActiveInkBezierApprox, ActiveDash } from "../../InkingStroke";
 import { DocumentType } from "../../../documents/DocumentTypes";
 import { DocumentLinksButton } from "../../nodes/DocumentLinksButton";
+import { MainView } from "../../MainView";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -248,6 +249,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         } else {
             const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
             this.props.addDocument(source);
+            MainView.popupX = xp;
+            MainView.popupY = yp;
+            runInAction(() => { MainView.linkCreated = true; });
+            runInAction(() => { setTimeout(function () { runInAction(() => MainView.linkCreated = false); }, 2500); });
             linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation"); // TODODO this is where in text links get passed
             e.stopPropagation();
             return true;
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index c372e7098..6468ccd3d 100644
--- a/src/client/views/linking/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
@@ -15,7 +15,7 @@
 }
 
 .linkMenu-group {
-    border-bottom: 0.5px solid lightgray; 
+    border-bottom: 0.5px solid lightgray;
     padding: 5px 0;
 
 
@@ -30,9 +30,11 @@
             p {
                 background-color: lightgray;
             }
+
             p.expand-one {
                 width: calc(100% - 26px);
             }
+
             .linkEditor-tableButton {
                 display: block;
             }
@@ -50,7 +52,4 @@
             display: none;
         }
     }
-}
-
-
-
+}
\ No newline at end of file
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index c36e7b67c..c672511ac 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -10,6 +10,7 @@ import { LinkMenuGroup } from "./LinkMenuGroup";
 import { faTrash } from '@fortawesome/free-solid-svg-icons';
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { DocumentLinksButton } from "../nodes/DocumentLinksButton";
+import { LinkDocPreview } from "../nodes/LinkDocPreview";
 
 library.add(faTrash);
 
@@ -28,6 +29,9 @@ export class LinkMenu extends React.Component<Props> {
 
     @action
     onClick = (e: PointerEvent) => {
+
+        LinkDocPreview.LinkInfo = undefined;
+
         if (this._linkMenuRef?.contains(e.target as any)) {
             DocumentLinksButton.EditLink = undefined;
         }
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 54dca261d..b11a173f3 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -97,6 +97,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
         return (<div className="link-metadata">{mdRows}</div>);
     }
 
+    @action
     onLinkButtonDown = (e: React.PointerEvent): void => {
         this._downX = e.clientX;
         this._downY = e.clientY;
@@ -106,6 +107,10 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
         document.addEventListener("pointermove", this.onLinkButtonMoved);
         document.removeEventListener("pointerup", this.onLinkButtonUp);
         document.addEventListener("pointerup", this.onLinkButtonUp);
+
+        //if (this._editRef && this._editRef.current?.contains(e.target as any)) {
+        LinkDocPreview.LinkInfo = undefined;
+        //}
     }
 
     onLinkButtonUp = (e: PointerEvent): void => {
@@ -168,14 +173,14 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                         }))}
                         onPointerDown={this.onLinkButtonDown}>
                         <p >{StrCast(this.props.destinationDoc.title)}</p>
-                        <div className="linkMenu-item-buttons">
+                        <div className="linkMenu-item-buttons" ref={this._editRef} >
                             {canExpand ? <div title="Show more" className="button" onPointerDown={e => this.toggleShowMore(e)}>
                                 <FontAwesomeIcon className="fa-icon" icon={this._showMore ? "chevron-up" : "chevron-down"} size="sm" /></div> : <></>}
 
                             {/* <div title="Edit link" className="button" ref={this._editRef} onPointerDown={this.onEdit}><FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div> */}
-                            <div title="Delete link" className="button" ref={this._editRef} onPointerDown={this.deleteLink}>
+                            <div title="Delete link" className="button" onPointerDown={this.deleteLink}>
                                 <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div>
-                            <div title="Follow link" className="button" onClick={e => this.followDefault()} onContextMenu={this.onContextMenu}>
+                            <div title="Follow link" className="button" onPointerDown={this.followDefault} onContextMenu={this.onContextMenu}>
                                 <FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" />
                             </div>
                         </div>
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index d67a01d8b..95c2988da 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -77,6 +77,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
         }
     }
 
+    @action
     completeLink = (e: React.PointerEvent): void => {
         setupMoveUpEvents(this, e, returnFalse, emptyFunction, action((e, doubleTap) => {
             if (doubleTap) {
@@ -88,12 +89,17 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                 } else {
                     DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
                         DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
+                    runInAction(() => { MainView.linkCreated = true; });
+                    runInAction(() => { setTimeout(function () { runInAction(() => MainView.linkCreated = false); }, 2500); });
+                    MainView.popupX = e.screenX;
+                    MainView.popupY = e.screenY;
                 }
             }
         }));
     }
 
-    finishLinkClick = () => {
+    @action
+    finishLinkClick = (e: React.MouseEvent) => {
         if (DocumentLinksButton.StartLink === this.props.View) {
             DocumentLinksButton.StartLink = undefined;
             // action((e: React.PointerEvent<HTMLDivElement>) => {
@@ -102,6 +108,10 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
         } else {
             DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
                 DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
+            runInAction(() => { MainView.linkCreated = true; });
+            runInAction(() => { setTimeout(function () { runInAction(() => MainView.linkCreated = false); }, 2500); });
+            MainView.popupX = e.screenX;
+            MainView.popupY = e.screenY;
         }
     }
 
@@ -131,7 +141,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                 </div>
                 {DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View ? <div className={"documentLinksButton-endLink"}
                     style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px" }}
-                    onPointerDown={this.completeLink} onClick={this.finishLinkClick} /> : (null)}
+                    onPointerDown={this.completeLink} onClick={e => this.finishLinkClick(e)} /> : (null)}
                 {DocumentLinksButton.StartLink === this.props.View ? <div className={"documentLinksButton-startLink"}
                     style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px" }} /> : (null)}
             </div>;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index b4bb556c7..7aa1d528d 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -832,14 +832,14 @@ export namespace Doc {
         })(doc);
     }
     export function BrushDoc(doc: Doc) {
-        console.log("brushed");
+
         if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return doc;
         brushManager.BrushedDoc.set(doc, true);
         brushManager.BrushedDoc.set(Doc.GetProto(doc), true);
         return doc;
     }
     export function UnBrushDoc(doc: Doc) {
-        console.log("unbrushed");
+
         if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return doc;
         brushManager.BrushedDoc.delete(doc);
         brushManager.BrushedDoc.delete(Doc.GetProto(doc));
-- 
cgit v1.2.3-70-g09d2


From 7c93a65535a278dd1381b27fbd4c3869eed19527 Mon Sep 17 00:00:00 2001
From: yunahi <60233430+yunahi@users.noreply.github.com>
Date: Fri, 3 Jul 2020 20:22:52 +0900
Subject: pin and icon

---
 src/client/views/AntimodeMenu.tsx                  |   7 +-
 src/client/views/GestureOverlay.tsx                |  10 +-
 src/client/views/MainView.tsx                      |  11 +-
 .../collectionFreeForm/InkOptionsMenu.scss         |   5 +
 .../collectionFreeForm/InkOptionsMenu.tsx          | 267 ++++++++++++++-------
 5 files changed, 202 insertions(+), 98 deletions(-)

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

diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index 3e4d20fea..d83ac434c 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -137,13 +137,18 @@ export default abstract class AntimodeMenu extends React.Component {
     protected getElement(buttons: JSX.Element[]) {
         return (
             <div className="antimodeMenu-cont" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu}
-                style={{ left: this._left, top: this._top, opacity: this._opacity, transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay }}>
+                style={{
+                    left: this._left, top: this._top, opacity: this._opacity, transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay,
+                    position: this.Pinned ? "unset" : undefined
+                }}>
                 <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: "20px" }} />
                 {buttons}
             </div>
         );
     }
 
+
+
     protected getElementWithRows(rows: JSX.Element[], numRows: number, hasDragger: boolean = true) {
         return (
             <div className="antimodeMenu-cont with-rows" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu}
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 85695bbac..bfd659207 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -25,6 +25,7 @@ import HorizontalPalette from "./Palette";
 import { Touchable } from "./Touchable";
 import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu";
 import HeightLabel from "./collections/collectionMulticolumn/MultirowHeightLabel";
+import InkOptionsMenu from "./collections/collectionFreeForm/InkOptionsMenu";
 
 @observer
 export default class GestureOverlay extends Touchable {
@@ -684,12 +685,17 @@ export default class GestureOverlay extends Touchable {
         } else {
             this._points = [];
         }
+        //get out of ink mode after each stroke=
+        console.log("now");
+        Doc.SetSelectedTool(InkTool.None);
+        InkOptionsMenu.Instance._selected = InkOptionsMenu.Instance._shapesNum;
         SetActiveArrowStart("none");
         GestureOverlay.Instance.SavedArrowStart = ActiveArrowStart();
         SetActiveArrowEnd("none");
         GestureOverlay.Instance.SavedArrowEnd = ActiveArrowEnd();
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
+
     }
 
     makePolygon = (shape: string, gesture: boolean) => {
@@ -885,7 +891,9 @@ export default class GestureOverlay extends Touchable {
 
     render() {
         return (
-            <div className="gestureOverlay-cont" onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}>
+
+            <div className="gestureOverlay-cont" style={{ position: "relative" }}
+                onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}>
                 {this.showMobileInkOverlay ? <MobileInkOverlay /> : <></>}
                 {this.elements}
 
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 8f5a31b6c..afdff4ed8 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -453,10 +453,12 @@ export class MainView extends React.Component {
 
     @computed get mainContent() {
         const sidebar = this.userDoc?.["tabs-panelContainer"];
+        console.log(InkOptionsMenu.Instance?.Pinned);
         return !this.userDoc || !(sidebar instanceof Doc) ? (null) : (
             <div className="mainView-mainContent" style={{
                 color: this.darkScheme ? "rgb(205,205,205)" : "black",
-                height: RichTextMenu.Instance?.Pinned ? `calc(100% - ${ANTIMODEMENU_HEIGHT})` : "100%"
+                height: (RichTextMenu.Instance?.Pinned || InkOptionsMenu.Instance?.Pinned) ? `calc(100% - ${ANTIMODEMENU_HEIGHT})` : "100%"
+
             }} >
                 <div style={{ display: "contents", flexDirection: "row", position: "relative" }}>
                     <div className="mainView-flyoutContainer" onPointerLeave={this.pointerLeaveDragger} style={{ width: this.flyoutWidth }}>
@@ -593,7 +595,10 @@ export class MainView extends React.Component {
             <SettingsManager />
             <GoogleAuthenticationManager />
             <DocumentDecorations />
-            <GestureOverlay>
+            <InkOptionsMenu />
+
+            <GestureOverlay >
+
                 <RichTextMenu key="rich" />
                 {this.mainContent}
             </GestureOverlay>
@@ -606,7 +611,7 @@ export class MainView extends React.Component {
             <RadialMenu />
             <PDFMenu />
             <MarqueeOptionsMenu />
-            <InkOptionsMenu />
+
             <OverlayView />
             <TimelineMenu />
             {this.snapLines}
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
index a9fab4c1e..7329f4fc1 100644
--- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
+++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
@@ -26,6 +26,11 @@
     /* Make the buttons appear below each other */
 }
 
+.btn-draw {
+    display: inline;
+    /* Make the buttons appear below each other */
+}
+
 .btn2-group {
     display: block;
     background: #323232;
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
index f1032adaa..7de70b8cd 100644
--- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
@@ -17,6 +17,7 @@ import { DocumentType } from "../../../documents/DocumentTypes";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
 import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faArrowsAlt, faHighlighter, faLink, faPaintRoller, faSleigh, faBars, faFillDrip, faBrush, faPenNib, faShapes, faArrowLeft, faEllipsisH, faBezierCurve, } from "@fortawesome/free-solid-svg-icons";
+import { Cast, StrCast, BoolCast } from "../../../../fields/Types";
 
 library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faArrowsAlt, faHighlighter, faLink, faPaintRoller, faBars, faFillDrip, faBrush, faPenNib, faShapes, faArrowLeft, faEllipsisH, faBezierCurve);
 
@@ -28,36 +29,67 @@ export default class InkOptionsMenu extends AntimodeMenu {
     private _width = ["1", "5", "10", "100"];
     // private _buttons = ["circle", "triangle", "rectangle", "arrow", "line"];
     // private _icons = ["O", "∆", "ロ", "➜", "-"];
-    private _buttons = ["circle", "triangle", "rectangle", "line", "noRec", "",];
-    private _icons = ["O", "∆", "ロ", "⎯", "✖︎", " "];
+    // private _buttons = ["circle", "triangle", "rectangle", "line", "noRec", "",];
+    // private _icons = ["O", "∆", "ロ", "⎯⎯⎯", "✖︎", " "];
     //arrowStart and arrowEnd must match and defs must exist in Inking Stroke
-    private _arrowStart = ["arrowHead", "arrowHead", "dot", "dot", "none"];
-    private _arrowEnd = ["none", "arrowEnd", "none", "dot", "none"];
-    private _arrowIcons = ["→", "↔︎", "•", "••", " "];
+    // private _arrowStart = ["arrowHead", "arrowHead", "dot", "dot", "none"];
+    // private _arrowEnd = ["none", "arrowEnd", "none", "dot", "none"];
+    // private _arrowIcons = ["→", "↔︎", "•", "••", " "];
+    private _draw = ["⎯", "→", "↔︎", "∿", "↝", "↭", "ロ", "O", "∆"];
+    private _head = ["none", "arrowHead", "arrowHead", "none", "arrowHead", "arrowHead", "none", "none", "none"];
+    private _end = ["none", "none", "arrowEnd", "none", "none", "arrowEnd", "none", "none", "none"];
+    private _shape = ["", "", "", "", "", "", "rectangle", "circle", "triangle"];
+
+    @observable _shapesNum = this._shape.length;
+    @observable _selected = this._shapesNum;
+
+    @observable private collapsed: boolean = false;
 
     @observable _colorBtn = false;
     @observable _widthBtn = false;
     @observable _fillBtn = false;
-    @observable _arrowBtn = false;
-    @observable _dashBtn = false;
-    @observable _shapeBtn = false;
+    // @observable _arrowBtn = false;
+    // @observable _dashBtn = false;
+    // @observable _shapeBtn = false;
 
     constructor(props: Readonly<{}>) {
         super(props);
         InkOptionsMenu.Instance = this;
         this._canFade = false; // don't let the inking menu fade away
+        this.Pinned = BoolCast(Doc.UserDoc()["inkOptionsMenu-pinned"]);
+
     }
 
-    getColors = () => {
-        return this._palette;
+    @action
+    toggleMenuPin = (e: React.MouseEvent) => {
+        Doc.UserDoc()["inkOptionsMenu-pinned"] = this.Pinned = !this.Pinned;
+        if (!this.Pinned) {
+            // this.fadeOut(true);
+        }
     }
 
     @action
-    changeArrow = (arrowStart: string, arrowEnd: string) => {
-        SetActiveArrowStart(arrowStart);
-        SetActiveArrowEnd(arrowEnd);
+    protected toggleCollapse = (e: React.MouseEvent) => {
+        this.collapsed = !this.collapsed;
+        setTimeout(() => {
+            const x = Math.min(this._left, window.innerWidth - InkOptionsMenu.Instance.width);
+            InkOptionsMenu.Instance.jumpTo(x, this._top, true);
+        }, 0);
+    }
+
+
+
+
+    getColors = () => {
+        return this._palette;
     }
 
+    // @action
+    // changeArrow = (arrowStart: string, arrowEnd: string) => {
+    //     SetActiveArrowStart(arrowStart);
+    //     SetActiveArrowEnd(arrowEnd);
+    // }
+
     @action
     changeColor = (color: string, type: string) => {
         const col: ColorState = {
@@ -116,40 +148,77 @@ export default class InkOptionsMenu extends AntimodeMenu {
         this.editProperties(ActiveDash(), "dash");
     }
 
-    @computed get arrowPicker() {
-        var currIcon;
-        for (var i = 0; i < this._arrowStart.length; i++) {
-            if (this._arrowStart[i] === ActiveArrowStart() && this._arrowEnd[i] === ActiveArrowEnd()) {
-                currIcon = this._arrowIcons[i];
-                if (this._arrowIcons[i] === " ") {
-                    currIcon = "➤";
-                }
-            }
-        }
-        var arrowPicker = <button
-            className="antimodeMenu-button"
-            key="arrow"
-            onPointerDown={action(e => this._arrowBtn = !this._arrowBtn)}
-            style={{ backgroundColor: this._arrowBtn ? "121212" : "" }}>
-            {currIcon}
-        </button>;
-        if (this._arrowBtn) {
-            arrowPicker = <div className="btn2-group" key="arrows">
-                {arrowPicker}
-                {this._arrowStart.map((arrowStart, i) => {
-                    return <button
-                        className="antimodeMenu-button"
-                        key={arrowStart}
-                        onPointerDown={action(() => { SetActiveArrowStart(arrowStart); SetActiveArrowEnd(this._arrowEnd[i]); this.editProperties(arrowStart, "arrowStart"), this.editProperties(this._arrowEnd[i], "arrowEnd"); this._arrowBtn = false; })}
-                        style={{ backgroundColor: this._arrowBtn ? "121212" : "" }}>
-                        {this._arrowIcons[i]}
-                    </button>;
-                })}
-            </div>;
-        }
-        return arrowPicker;
+    @computed get drawButtons() {
+        const drawButtons = <div className="btn-draw" key="draw">
+            {this._draw.map((icon, i) => {
+                return <button
+                    className="antimodeMenu-button"
+                    key={icon}
+                    onPointerDown={action(() => {
+                        console.log(this._selected);
+
+                        if (this._selected !== i) {
+                            this._selected = i;
+                            Doc.SetSelectedTool(InkTool.Pen);
+                            SetActiveArrowStart(this._head[i]);
+                            SetActiveArrowEnd(this._end[i]);
+                            SetActiveBezierApprox("300");
+
+                            //  this.editProperties(this._head[i], "arrowStart"), this.editProperties(this._end[i], "arrowEnd"); 
+                            GestureOverlay.Instance.InkShape = this._shape[i];
+                        } else {
+                            this._selected = this._shapesNum;
+                            Doc.SetSelectedTool(InkTool.None);
+                            SetActiveArrowStart("none");
+                            SetActiveArrowEnd("none");
+                            GestureOverlay.Instance.InkShape = "";
+                            SetActiveBezierApprox("0");
+
+                        }
+                        console.log(this._selected);
+
+                    })}
+                    style={{ backgroundColor: i === this._selected ? "121212" : "", fontSize: "20" }}>
+                    {this._draw[i]}
+                </button>;
+            })}</div>;
+        return drawButtons;
     }
 
+    // @computed get arrowPicker() {
+    //     var currIcon;
+    //     for (var i = 0; i < this._arrowStart.length; i++) {
+    //         if (this._arrowStart[i] === ActiveArrowStart() && this._arrowEnd[i] === ActiveArrowEnd()) {
+    //             currIcon = this._arrowIcons[i];
+    //             if (this._arrowIcons[i] === " ") {
+    //                 currIcon = "➤";
+    //             }
+    //         }
+    //     }
+    //     var arrowPicker = <button
+    //         className="antimodeMenu-button"
+    //         key="arrow"
+    //         onPointerDown={action(e => this._arrowBtn = !this._arrowBtn)}
+    //         style={{ backgroundColor: this._arrowBtn ? "121212" : "" }}>
+    //         {currIcon}
+    //     </button>;
+    //     if (this._arrowBtn) {
+    //         arrowPicker = <div className="btn2-group" key="arrows">
+    //             {arrowPicker}
+    //             {this._arrowStart.map((arrowStart, i) => {
+    //                 return <button
+    //                     className="antimodeMenu-button"
+    //                     key={arrowStart}
+    //                     onPointerDown={action(() => { SetActiveArrowStart(arrowStart); SetActiveArrowEnd(this._arrowEnd[i]); this.editProperties(arrowStart, "arrowStart"), this.editProperties(this._arrowEnd[i], "arrowEnd"); this._arrowBtn = false; })}
+    //                     style={{ backgroundColor: this._arrowBtn ? "121212" : "" }}>
+    //                     {this._arrowIcons[i]}
+    //                 </button>;
+    //             })}
+    //         </div>;
+    //     }
+    //     return arrowPicker;
+    // }
+
     @computed get widthPicker() {
         var widthPicker = <button
             className="antimodeMenu-button"
@@ -234,48 +303,48 @@ export default class InkOptionsMenu extends AntimodeMenu {
         return fillPicker;
     }
 
-    @computed get shapePicker() {
-        var currIcon;
-        if (GestureOverlay.Instance.InkShape === "") {
-            currIcon = <FontAwesomeIcon icon="shapes" size="lg" />;
-        } else {
-            for (var i = 0; i < this._icons.length; i++) {
-                if (GestureOverlay.Instance.InkShape === this._buttons[i]) {
-                    currIcon = this._icons[i];
-                }
-            }
-        }
-        var shapePicker = <button
-            className="antimodeMenu-button"
-            key="shape"
-            onPointerDown={action(e => this._shapeBtn = !this._shapeBtn)}
-            style={{ backgroundColor: this._shapeBtn ? "121212" : "" }}>
-            {currIcon}
-        </button>;
-        if (this._shapeBtn) {
-            shapePicker = <div className="btn2-group" key="shape">
-                {shapePicker}
-                {this._buttons.map((btn, i) => {
-                    var ttl = btn;
-                    if (btn === "") {
-                        ttl = "no shape";
-                    }
-                    if (btn === "noRec") {
-                        ttl = "disable shape recognition";
-                    }
-                    return <button
-                        className="antimodeMenu-button"
-                        title={`Draw ${btn}`}
-                        key={ttl}
-                        onPointerDown={action((e) => { GestureOverlay.Instance.InkShape = btn; this._shapeBtn = false; })}
-                        style={{ backgroundColor: this._shapeBtn ? "121212" : "" }}>
-                        {this._icons[i]}
-                    </button>;
-                })}
-            </div>;
-        }
-        return shapePicker;
-    }
+    // @computed get shapePicker() {
+    //     var currIcon;
+    //     if (GestureOverlay.Instance.InkShape === "") {
+    //         currIcon = <FontAwesomeIcon icon="shapes" size="lg" />;
+    //     } else {
+    //         for (var i = 0; i < this._icons.length; i++) {
+    //             if (GestureOverlay.Instance.InkShape === this._buttons[i]) {
+    //                 currIcon = this._icons[i];
+    //             }
+    //         }
+    //     }
+    //     var shapePicker = <button
+    //         className="antimodeMenu-button"
+    //         key="shape"
+    //         onPointerDown={action(e => this._shapeBtn = !this._shapeBtn)}
+    //         style={{ backgroundColor: this._shapeBtn ? "121212" : "" }}>
+    //         {currIcon}
+    //     </button>;
+    //     if (this._shapeBtn) {
+    //         shapePicker = <div className="btn2-group" key="shape">
+    //             {shapePicker}
+    //             {this._buttons.map((btn, i) => {
+    //                 var ttl = btn;
+    //                 if (btn === "") {
+    //                     ttl = "no shape";
+    //                 }
+    //                 if (btn === "noRec") {
+    //                     ttl = "disable shape recognition";
+    //                 }
+    //                 return <button
+    //                     className="antimodeMenu-button"
+    //                     title={`Draw ${btn}`}
+    //                     key={ttl}
+    //                     onPointerDown={action((e) => { GestureOverlay.Instance.InkShape = btn; this._shapeBtn = false; })}
+    //                     style={{ backgroundColor: this._shapeBtn ? "121212" : "", fontSize: "20" }}>
+    //                     {this._icons[i]}
+    //                 </button>;
+    //             })}
+    //         </div>;
+    //     }
+    //     return shapePicker;
+    // }
 
     @computed get bezierButton() {
         return <button
@@ -306,23 +375,35 @@ export default class InkOptionsMenu extends AntimodeMenu {
             // <button className="antimodeMenu-button" title="Drag" key="drag" onPointerDown={e => this.dragStart(e)}>
             //     <FontAwesomeIcon icon="arrows-alt" size="lg" />
             // </button>,
-            this.shapePicker,
-            this.bezierButton,
+            // this.shapePicker,
+            // this.bezierButton,
             this.widthPicker,
             this.colorPicker,
             this.fillPicker,
-            this.arrowPicker,
-            this.dashButton,
+            this.drawButtons,
+            // this.arrowPicker,
+            // this.dashButton,
+            <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this.collapsed ? "none" : undefined }}>
+                <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
+            </button>
+
         ];
+
+        // return this.getElement(buttons);
         return this.getElement(buttons);
     }
 }
 Scripting.addGlobal(function activatePen(penBtn: any) {
     if (penBtn) {
-        Doc.SetSelectedTool(InkTool.Pen);
+        //no longer changes to inkmode
+        // Doc.SetSelectedTool(InkTool.Pen);
         InkOptionsMenu.Instance.jumpTo(300, 300);
+        InkOptionsMenu.Instance.Pinned = true;
+
     } else {
-        Doc.SetSelectedTool(InkTool.None);
+        // Doc.SetSelectedTool(InkTool.None);
+        InkOptionsMenu.Instance.Pinned = false;
         InkOptionsMenu.Instance.fadeOut(true);
+
     }
 });
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From c146d9a6165dcfa9ebd98868dfb741a26ede5a9f Mon Sep 17 00:00:00 2001
From: anika-ahluwalia <anika.ahluwalia@gmail.com>
Date: Fri, 3 Jul 2020 12:09:48 -0500
Subject: finished link created popups

---
 src/client/views/MainView.tsx                             | 15 +++++++++------
 .../collectionFreeForm/CollectionFreeFormView.tsx         |  4 ----
 src/client/views/nodes/DocumentLinksButton.tsx            |  6 +++---
 src/client/views/nodes/DocumentView.tsx                   | 12 ++++++++++++
 4 files changed, 24 insertions(+), 13 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index acbeb65cc..3d47d09c8 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -619,12 +619,15 @@ export class MainView extends React.Component {
 
             <Fade in={MainView.linkCreated}>
                 <div style={{
-                    border: "1px solid rgb(111, 144, 175)", top: MainView.popupX ? MainView.popupX : 600
-                    , position: "absolute",
-                    left: MainView.popupY ? MainView.popupY : 250
-                    , width: "auto", height: "auto", zIndex: 10000,
-                    fontSize: "13px", whiteSpace: "nowrap", backgroundColor: "rgb(170, 205, 238)",
-                    padding: "5px", //fontWeight: "bold"
+                    border: "1px solid rgb(100, 100, 100)",
+                    left: MainView.popupX ? MainView.popupX : 600,
+                    position: "absolute",
+                    top: MainView.popupY ? MainView.popupY : 250, width: "auto",
+                    height: "auto", zIndex: 10000, borderRadius: "13px",
+                    fontSize: "13px", whiteSpace: "nowrap",
+                    color: "rgb(100, 100, 100)", backgroundColor: "rgba(250, 250, 250, 0.85)",
+                    paddingTop: "6.5px", paddingBottom: "6.5px", fontWeight: "bold",
+                    paddingLeft: "9px", paddingRight: "9px"
                 }}>Link Created</div>
             </Fade>
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 25f724449..7f6d1d506 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -249,10 +249,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         } else {
             const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
             this.props.addDocument(source);
-            MainView.popupX = xp;
-            MainView.popupY = yp;
-            runInAction(() => { MainView.linkCreated = true; });
-            runInAction(() => { setTimeout(function () { runInAction(() => MainView.linkCreated = false); }, 2500); });
             linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation"); // TODODO this is where in text links get passed
             e.stopPropagation();
             return true;
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 7faae68bf..6a6624aa0 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -109,9 +109,9 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
                 DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
             runInAction(() => { MainView.linkCreated = true; });
-            runInAction(() => { setTimeout(function () { runInAction(() => MainView.linkCreated = false); }, 2500); });
-            MainView.popupX = e.screenX;
-            MainView.popupY = e.screenY;
+            runInAction(() => { setTimeout(function () { runInAction(() => MainView.linkCreated = false); }, 2000); });
+            MainView.popupX = e.screenX - 25;
+            MainView.popupY = e.screenY - 140;
         }
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 66af29286..3a9e4fcf0 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -42,6 +42,7 @@ import { RadialMenu } from './RadialMenu';
 import React = require("react");
 import { DocumentLinksButton } from './DocumentLinksButton';
 import { MobileInterface } from '../../../mobile/MobileInterface';
+import { MainView } from '../MainView';
 
 library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight,
     fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale,
@@ -641,12 +642,23 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             e.stopPropagation();
             de.complete.annoDragData.linkedToDoc = true;
 
+            runInAction(() => MainView.popupX = de.x);
+            runInAction(() => MainView.popupY = de.y);
+            runInAction(() => { MainView.linkCreated = true; });
+            runInAction(() => { setTimeout(function () { runInAction(() => MainView.linkCreated = false); }, 2500); });
+
             DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document }, "link");
         }
         if (de.complete.linkDragData) {
             e.stopPropagation();
             // const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true);
             // const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView);
+            runInAction(() => MainView.popupX = de.x);
+            runInAction(() => MainView.popupY = de.y);
+
+            runInAction(() => { MainView.linkCreated = true; });
+            runInAction(() => { setTimeout(function () { runInAction(() => MainView.linkCreated = false); }, 2000); });
+
             de.complete.linkDragData.linkSourceDocument !== this.props.Document &&
                 (de.complete.linkDragData.linkDocument = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument },
                     { doc: this.props.Document }, `link`)); // TODODO this is where in text links get passed
-- 
cgit v1.2.3-70-g09d2


From ed74b8b6d6c65a24803c4e0e68aa192a750d60fa Mon Sep 17 00:00:00 2001
From: anika-ahluwalia <anika.ahluwalia@gmail.com>
Date: Fri, 3 Jul 2020 18:47:48 -0500
Subject: restructure

---
 src/client/documents/Documents.ts                  |  5 ----
 src/client/views/MainView.tsx                      | 27 ++---------------
 .../views/collections/CollectionLinearView.scss    | 34 ++++++++++++++++++----
 .../views/collections/CollectionLinearView.tsx     | 32 ++++----------------
 src/client/views/linking/LinkMenuItem.scss         | 34 ++++++++++++----------
 src/client/views/nodes/DocumentLinksButton.tsx     | 24 +++++++++------
 src/client/views/nodes/DocumentView.tsx            | 22 ++++++++------
 src/client/views/nodes/LinkCreatedBox.scss         | 21 +++++++++++++
 src/client/views/nodes/LinkCreatedBox.tsx          | 31 ++++++++++++++++++++
 9 files changed, 134 insertions(+), 96 deletions(-)
 create mode 100644 src/client/views/nodes/LinkCreatedBox.scss
 create mode 100644 src/client/views/nodes/LinkCreatedBox.tsx

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 0f434d616..f81c25bab 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -50,7 +50,6 @@ import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
 import { DocumentType } from "./DocumentTypes";
 import { Networking } from "../Network";
 import { Upload } from "../../server/SharedMediaTypes";
-import { MainView } from "../views/MainView";
 const path = require('path');
 
 export interface DocumentOptions {
@@ -868,10 +867,6 @@ export namespace DocUtils {
         DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "audio timeline"));
     }
 
-    function stopLinkCreated() {
-        MainView.linkCreated = false;
-    }
-
     export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", id?: string) {
         const sv = DocumentManager.Instance.getDocumentView(source.doc);
         if (sv && sv.props.ContainingCollectionDoc === target.doc) return;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 3d47d09c8..15f818d1f 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -60,6 +60,7 @@ import { DocumentLinksButton } from './nodes/DocumentLinksButton';
 import { LinkMenu } from './linking/LinkMenu';
 import { LinkDocPreview } from './nodes/LinkDocPreview';
 import { Fade } from '@material-ui/core';
+import { LinkCreatedBox } from './nodes/LinkCreatedBox';
 
 @observer
 export class MainView extends React.Component {
@@ -83,11 +84,6 @@ export class MainView extends React.Component {
 
     public isPointerDown = false;
 
-    @observable public static linkCreated: boolean = false;
-    @observable public static popupX: number = 600;
-    @observable public static popupY: number = 250;
-
-
     componentDidMount() {
         DocServer.setPlaygroundFields(["dataTransition", "_viewTransition", "_panX", "_panY", "_viewScale", "_viewType"]); // can play with these fields on someone else's
 
@@ -367,11 +363,6 @@ export class MainView extends React.Component {
         }
     }
 
-    @action
-    public static changeLinkCreated = () => {
-        MainView.linkCreated = !MainView.linkCreated;
-    }
-
     @action
     onPointerMove = (e: PointerEvent) => {
         this.flyoutWidth = Math.max(e.clientX, 0);
@@ -616,21 +607,7 @@ export class MainView extends React.Component {
                 {this.mainContent}
             </GestureOverlay>
             <PreviewCursor />
-
-            <Fade in={MainView.linkCreated}>
-                <div style={{
-                    border: "1px solid rgb(100, 100, 100)",
-                    left: MainView.popupX ? MainView.popupX : 600,
-                    position: "absolute",
-                    top: MainView.popupY ? MainView.popupY : 250, width: "auto",
-                    height: "auto", zIndex: 10000, borderRadius: "13px",
-                    fontSize: "13px", whiteSpace: "nowrap",
-                    color: "rgb(100, 100, 100)", backgroundColor: "rgba(250, 250, 250, 0.85)",
-                    paddingTop: "6.5px", paddingBottom: "6.5px", fontWeight: "bold",
-                    paddingLeft: "9px", paddingRight: "9px"
-                }}>Link Created</div>
-            </Fade>
-
+            <LinkCreatedBox />
             {DocumentLinksButton.EditLink ? <LinkMenu location={DocumentLinksButton.EditLinkLoc} docView={DocumentLinksButton.EditLink} addDocTab={DocumentLinksButton.EditLink.props.addDocTab} changeFlyout={emptyFunction} /> : (null)}
             {LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} backgroundColor={this.defaultBackgroundColors}
                 linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href}
diff --git a/src/client/views/collections/CollectionLinearView.scss b/src/client/views/collections/CollectionLinearView.scss
index b3fc5f0e7..5ada79a28 100644
--- a/src/client/views/collections/CollectionLinearView.scss
+++ b/src/client/views/collections/CollectionLinearView.scss
@@ -10,18 +10,42 @@
         height: 100%;
 
         >span {
-            //margin-top: 8px;
-            //margin-left: 4px;
-            //margin-bottom: 2px;
             background: $dark-color;
             color: $light-color;
-            //display: inline-block;
             border-radius: 18px;
             margin-right: 6px;
-            //height: 100%;
             cursor: pointer;
         }
 
+        .bottomPopup-background {
+            padding-right: 14px;
+            height: 35;
+            transform: translate3d(6px, 5px, 0px);
+            padding-top: 6.5px;
+            padding-bottom: 7px;
+            padding-left: 5px;
+        }
+
+        .bottomPopup-text {
+            display: inline;
+            white-space: nowrap;
+            padding-left: 8px;
+            padding-right: 4px;
+            vertical-align: middle;
+            font-size: 12.5px;
+        }
+
+        .bottomPopup-exit {
+            display: inline;
+            white-space: nowrap;
+            padding-left: 8px;
+            padding-right: 8px;
+            vertical-align: middle;
+            background-color: lightgrey;
+            border-radius: 5.5px;
+            color: black;
+        }
+
         >label {
             margin-top: "auto";
             margin-bottom: "auto";
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 7635dac24..7cbe5c19d 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -149,36 +149,14 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                         </div>;
                     })}
                 </div>
-                {DocumentLinksButton.StartLink ? <span style={{
-                    background: backgroundColor === color ? "black" : backgroundColor,
-                    paddingRight: "14px",
-                    height: "35",
-                    transform: "translate3d(6px, 5px, 0px)",
-                    paddingTop: "6.5px",
-                    paddingBottom: "7px",
-                    paddingLeft: "5px"
+                {DocumentLinksButton.StartLink ? <span className="bottomPopup-background" style={{
+                    background: backgroundColor === color ? "black" : backgroundColor
                 }}
                     onPointerDown={e => e.stopPropagation()} >
-                    <span style={{
-                        display: "inline",
-                        whiteSpace: "nowrap",
-                        paddingLeft: "8px",
-                        paddingRight: "4px",
-                        verticalAlign: "middle",
-                        fontSize: "12.5px"
-                    }}>
+                    <span className="bottomPopup-text" >
                         Creating link from: {DocumentLinksButton.StartLink.title} </span>
-                    <span onClick={this.exitLongLinks}
-                        style={{
-                            display: "inline",
-                            whiteSpace: "nowrap",
-                            paddingLeft: "8px",
-                            paddingRight: "8px",
-                            verticalAlign: "middle",
-                            backgroundColor: "lightgrey",
-                            borderRadius: "5.5px",
-                            color: "black"
-                        }}>Exit</span>
+                    <span className="bottomPopup-exit" onClick={this.exitLongLinks}
+                    >Exit</span>
 
                     {/* <FontAwesomeIcon icon="times-circle" size="lg" style={{ color: "red" }}
                         onClick={this.exitLongLinks} /> */}
diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss
index c5becae7d..e3ce69cd7 100644
--- a/src/client/views/linking/LinkMenuItem.scss
+++ b/src/client/views/linking/LinkMenuItem.scss
@@ -31,21 +31,23 @@
         font-size: 10.5px;
     }
 
-    // &:hover {
-    //     .linkMenu-item-buttons {
-    //         display: flex;
-    //     }
-    //     .linkMenu-item-content {
-    //         &.expand-two p {
-    //             width: calc(100% - 52px);
-    //             background-color: lightgray;
-    //         }
-    //         &.expand-three p {
-    //             width: calc(100% - 84px);
-    //             background-color: lightgray;
-    //         }
-    //     }
-    // }
+    &:hover {
+        .linkMenu-item-buttons {
+            display: flex;
+        }
+
+        .linkMenu-item-content {
+            &.expand-two p {
+                width: calc(100% - 52px);
+                background-color: lightgray;
+            }
+
+            &.expand-three p {
+                width: calc(100% - 84px);
+                background-color: lightgray;
+            }
+        }
+    }
 }
 
 .linkMenu-item-buttons {
@@ -53,7 +55,7 @@
     top: 50%;
     right: 0;
     transform: translateY(-50%);
-    display: flex;
+    display: none;
 
     .button {
         width: 20px;
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 6a6624aa0..bfd860f65 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -10,7 +10,7 @@ import React = require("react");
 import { DocUtils } from "../../documents/Documents";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { LinkDocPreview } from "./LinkDocPreview";
-import { MainView } from "../MainView";
+import { LinkCreatedBox } from "./LinkCreatedBox";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -89,10 +89,13 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                 } else {
                     DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
                         DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
-                    runInAction(() => { MainView.linkCreated = true; });
-                    runInAction(() => { setTimeout(function () { runInAction(() => MainView.linkCreated = false); }, 2500); });
-                    MainView.popupX = e.screenX;
-                    MainView.popupY = e.screenY;
+
+                    runInAction(() => {
+                        LinkCreatedBox.popupX = e.screenX;
+                        LinkCreatedBox.popupY = e.screenY - 120;
+                        LinkCreatedBox.linkCreated = true;
+                        setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+                    });
                 }
             }
         }));
@@ -108,10 +111,13 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
         } else {
             DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
                 DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
-            runInAction(() => { MainView.linkCreated = true; });
-            runInAction(() => { setTimeout(function () { runInAction(() => MainView.linkCreated = false); }, 2000); });
-            MainView.popupX = e.screenX - 25;
-            MainView.popupY = e.screenY - 140;
+
+            runInAction(() => {
+                LinkCreatedBox.popupX = e.screenX;
+                LinkCreatedBox.popupY = e.screenY - 120;
+                LinkCreatedBox.linkCreated = true;
+                setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+            });
         }
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3a9e4fcf0..b38db9a1e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -42,7 +42,7 @@ import { RadialMenu } from './RadialMenu';
 import React = require("react");
 import { DocumentLinksButton } from './DocumentLinksButton';
 import { MobileInterface } from '../../../mobile/MobileInterface';
-import { MainView } from '../MainView';
+import { LinkCreatedBox } from './LinkCreatedBox';
 
 library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight,
     fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale,
@@ -642,10 +642,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             e.stopPropagation();
             de.complete.annoDragData.linkedToDoc = true;
 
-            runInAction(() => MainView.popupX = de.x);
-            runInAction(() => MainView.popupY = de.y);
-            runInAction(() => { MainView.linkCreated = true; });
-            runInAction(() => { setTimeout(function () { runInAction(() => MainView.linkCreated = false); }, 2500); });
+            runInAction(() => {
+                LinkCreatedBox.popupX = de.x;
+                LinkCreatedBox.popupY = de.y;
+                LinkCreatedBox.linkCreated = true;
+                setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+            });
 
             DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document }, "link");
         }
@@ -653,11 +655,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             e.stopPropagation();
             // const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true);
             // const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView);
-            runInAction(() => MainView.popupX = de.x);
-            runInAction(() => MainView.popupY = de.y);
 
-            runInAction(() => { MainView.linkCreated = true; });
-            runInAction(() => { setTimeout(function () { runInAction(() => MainView.linkCreated = false); }, 2000); });
+            runInAction(() => {
+                LinkCreatedBox.popupX = de.x;
+                LinkCreatedBox.popupY = de.y;
+                LinkCreatedBox.linkCreated = true;
+                setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+            });
 
             de.complete.linkDragData.linkSourceDocument !== this.props.Document &&
                 (de.complete.linkDragData.linkDocument = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument },
diff --git a/src/client/views/nodes/LinkCreatedBox.scss b/src/client/views/nodes/LinkCreatedBox.scss
new file mode 100644
index 000000000..3cbd38b55
--- /dev/null
+++ b/src/client/views/nodes/LinkCreatedBox.scss
@@ -0,0 +1,21 @@
+.linkCreatedBox-fade {
+    border: 1px solid rgb(100, 100, 100);
+
+
+    width: auto;
+    position: absolute;
+
+    height: auto;
+    z-index: 10000;
+    border-radius: 13px;
+    font-size: 13px;
+    white-space: nowrap;
+
+    color: rgb(100, 100, 100);
+    background-color: rgba(250, 250, 250, 0.85);
+    padding-top: 6.5px;
+    padding-bottom: 6.5px;
+    font-weight: bold;
+    padding-left: 9px;
+    padding-right: 9px;
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkCreatedBox.tsx b/src/client/views/nodes/LinkCreatedBox.tsx
new file mode 100644
index 000000000..d157d3fca
--- /dev/null
+++ b/src/client/views/nodes/LinkCreatedBox.tsx
@@ -0,0 +1,31 @@
+import React = require("react");
+import { observer } from "mobx-react";
+import { documentSchema } from "../../../fields/documentSchemas";
+import { makeInterface } from "../../../fields/Schema";
+import "./LinkCreatedBox.scss";
+import { observable, action } from "mobx";
+import { Fade } from "@material-ui/core";
+
+
+@observer
+export class LinkCreatedBox extends React.Component<{}> {
+
+    @observable public static linkCreated: boolean = false;
+    @observable public static popupX: number = 600;
+    @observable public static popupY: number = 250;
+
+    @action
+    public static changeLinkCreated = () => {
+        LinkCreatedBox.linkCreated = !LinkCreatedBox.linkCreated;
+    }
+
+    render() {
+        return <Fade in={LinkCreatedBox.linkCreated}>
+            <div className="linkCreatedBox-fade"
+                style={{
+                    left: LinkCreatedBox.popupX ? LinkCreatedBox.popupX : 600,
+                    top: LinkCreatedBox.popupY ? LinkCreatedBox.popupY : 250,
+                }}>Link Created</div>
+        </Fade>;
+    }
+} 
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 8264ac5484c0c7b5d47cb78ce60f5d6568e736d9 Mon Sep 17 00:00:00 2001
From: anika-ahluwalia <anika.ahluwalia@gmail.com>
Date: Sat, 4 Jul 2020 17:25:10 -0500
Subject: redesigned editing menu, added link descriptions and popup on created

---
 src/client/documents/Documents.ts                  |  9 +--
 src/client/util/LinkManager.ts                     |  6 +-
 src/client/views/EditableView.tsx                  |  6 +-
 src/client/views/GestureOverlay.tsx                |  2 +-
 src/client/views/MainView.tsx                      |  2 +
 src/client/views/RecommendationsBox.tsx            |  2 +-
 .../views/collections/CollectionTreeView.tsx       |  2 +-
 src/client/views/collections/CollectionView.tsx    |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 +-
 src/client/views/linking/LinkEditor.scss           | 48 ++++++++++++---
 src/client/views/linking/LinkEditor.tsx            | 59 +++++++++++++++---
 src/client/views/linking/LinkMenu.scss             | 13 +++-
 src/client/views/linking/LinkMenu.tsx              | 15 +++--
 src/client/views/linking/LinkMenuGroup.tsx         |  6 +-
 src/client/views/linking/LinkMenuItem.scss         | 48 ++++++++++++---
 src/client/views/linking/LinkMenuItem.tsx          | 23 ++++---
 src/client/views/nodes/DocumentLinksButton.tsx     | 52 ++++++++++------
 src/client/views/nodes/DocumentView.tsx            | 42 +++++++++----
 src/client/views/nodes/LinkCreatedBox.tsx          |  8 +--
 src/client/views/nodes/LinkDescriptionPopup.scss   | 62 +++++++++++++++++++
 src/client/views/nodes/LinkDescriptionPopup.tsx    | 70 ++++++++++++++++++++++
 22 files changed, 393 insertions(+), 88 deletions(-)
 create mode 100644 src/client/views/nodes/LinkDescriptionPopup.scss
 create mode 100644 src/client/views/nodes/LinkDescriptionPopup.tsx

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index f81c25bab..11b80aef9 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -94,6 +94,7 @@ export interface DocumentOptions {
     label?: string; // short form of title for use as an icon label
     style?: string;
     page?: number;
+    description?: string; // added for links
     _viewScale?: number;
     isDisplayPanel?: boolean; // whether the panel functions as GoldenLayout "stack" used to display documents
     forceActive?: boolean;
@@ -259,7 +260,7 @@ export namespace Docs {
             }],
             [DocumentType.LINK, {
                 layout: { view: LinkBox, dataField: defaultDataKey },
-                options: { _height: 150 }
+                options: { _height: 150, description: "" }
             }],
             [DocumentType.LINKDB, {
                 data: new List<Doc>(),
@@ -864,15 +865,15 @@ export namespace DocUtils {
     export let ActiveRecordings: Doc[] = [];
 
     export function MakeLinkToActiveAudio(doc: Doc) {
-        DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "audio timeline"));
+        DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "", "audio timeline"));
     }
 
-    export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", id?: string) {
+    export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string) {
         const sv = DocumentManager.Instance.getDocumentView(source.doc);
         if (sv && sv.props.ContainingCollectionDoc === target.doc) return;
         if (target.doc === Doc.UserDoc()) return undefined;
 
-        const linkDoc = Docs.Create.LinkDocument(source, target, { linkRelationship, layoutKey: "layout_linkView" }, id);
+        const linkDoc = Docs.Create.LinkDocument(source, target, { linkRelationship, layoutKey: "layout_linkView", description }, id);
         linkDoc.layout_linkView = Cast(Cast(Doc.UserDoc()["template-button-link"], Doc, null).dragFactory, Doc, null);
         Doc.GetProto(linkDoc).title = ComputedField.MakeFunction('self.anchor1?.title +" (" + (self.linkRelationship||"to") +") "  + self.anchor2?.title');
 
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 749fabfcc..6da581f35 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,4 +1,4 @@
-import { Doc, DocListCast } from "../../fields/Doc";
+import { Doc, DocListCast, Opt } from "../../fields/Doc";
 import { List } from "../../fields/List";
 import { listSpec } from "../../fields/Schema";
 import { Cast, StrCast } from "../../fields/Types";
@@ -23,6 +23,10 @@ import { Scripting } from "./Scripting";
 export class LinkManager {
 
     private static _instance: LinkManager;
+
+
+    public static currentLink: Opt<Doc>;
+
     public static get Instance(): LinkManager {
         return this._instance || (this._instance = new this());
     }
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 628db366f..25a87ab56 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -194,7 +194,11 @@ export class EditableView extends React.Component<EditableProps> {
                     ref={this._ref}
                     style={{ display: this.props.display, minHeight: "20px", height: `${this.props.height ? this.props.height : "auto"}`, maxHeight: `${this.props.maxHeight}` }}
                     onClick={this.onClick} placeholder={this.props.placeholder}>
-                    <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize, color: this.props.contents ? "black" : "grey" }}>{this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span>
+                    <span style={{
+                        fontStyle: this.props.fontStyle, fontSize: this.props.fontSize,
+                        color: this.props.contents ? this.props.color ? this.props.color : "black" : "grey"
+                    }}>
+                        {this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span>
                 </div>
             );
         }
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 487467b2b..cdc468066 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -551,7 +551,7 @@ export default class GestureOverlay extends Touchable {
             else if (this._d1 !== doc && !LinkManager.Instance.doesLinkExist(this._d1, doc)) {
                 // we don't want to create a link between ink strokes (doing so makes drawing a t very hard)
                 if (this._d1.type !== "ink" && doc.type !== "ink") {
-                    DocUtils.MakeLink({ doc: this._d1 }, { doc: doc }, "gestural link");
+                    DocUtils.MakeLink({ doc: this._d1 }, { doc: doc }, "gestural link", "");
                     actionPerformed = true;
                 }
             }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 15f818d1f..68ea51456 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -61,6 +61,7 @@ import { LinkMenu } from './linking/LinkMenu';
 import { LinkDocPreview } from './nodes/LinkDocPreview';
 import { Fade } from '@material-ui/core';
 import { LinkCreatedBox } from './nodes/LinkCreatedBox';
+import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
 
 @observer
 export class MainView extends React.Component {
@@ -608,6 +609,7 @@ export class MainView extends React.Component {
             </GestureOverlay>
             <PreviewCursor />
             <LinkCreatedBox />
+            {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
             {DocumentLinksButton.EditLink ? <LinkMenu location={DocumentLinksButton.EditLinkLoc} docView={DocumentLinksButton.EditLink} addDocTab={DocumentLinksButton.EditLink.props.addDocTab} changeFlyout={emptyFunction} /> : (null)}
             {LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} backgroundColor={this.defaultBackgroundColors}
                 linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href}
diff --git a/src/client/views/RecommendationsBox.tsx b/src/client/views/RecommendationsBox.tsx
index cdde32c21..196151e32 100644
--- a/src/client/views/RecommendationsBox.tsx
+++ b/src/client/views/RecommendationsBox.tsx
@@ -169,7 +169,7 @@ export class RecommendationsBox extends React.Component<FieldViewProps> {
                             <div style={{ marginRight: 50 }} onClick={() => DocumentManager.Instance.jumpToDocument(doc, false)}>
                                 <FontAwesomeIcon className="documentdecorations-icon" icon={"bullseye"} size="sm" />
                             </div>
-                            <div style={{ marginRight: 50 }} onClick={() => DocUtils.MakeLink({ doc: this.props.Document.sourceDoc as Doc }, { doc: doc }, "Recommender", undefined)}>
+                            <div style={{ marginRight: 50 }} onClick={() => DocUtils.MakeLink({ doc: this.props.Document.sourceDoc as Doc }, { doc: doc }, "Recommender", "", undefined)}>
                                 <FontAwesomeIcon className="documentdecorations-icon" icon={"link"} size="sm" />
                             </div>
                         </div>
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 620b977fa..26c41f524 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -207,7 +207,7 @@ class TreeView extends React.Component<TreeViewProps> {
         if (complete.linkDragData) {
             const sourceDoc = complete.linkDragData.linkSourceDocument;
             const destDoc = this.doc;
-            DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, "tree link");
+            DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, "tree link", "");
             e.stopPropagation();
         }
         const docDragData = complete.docDragData;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 26abd2529..df21d6a28 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -147,7 +147,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
                             _width: 15, _height: 15, _xPadding: 0, isLinkButton: true, displayTimecode: Cast(doc.displayTimecode, "number", null)
                         });
                         Doc.AddDocToList(context, Doc.LayoutFieldKey(context) + "-annotations", pushpin);
-                        const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin");
+                        const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin", "");
                         const first = DocListCast(pushpin.links).find(d => d instanceof Doc);
                         first && (first.hidden = true);
                         pushpinLink && (Doc.GetProto(pushpinLink).isPushpin = true);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b81e400b3..9bf425db2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -246,7 +246,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         } else {
             const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
             this.props.addDocument(source);
-            linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation"); // TODODO this is where in text links get passed
+            linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation", ""); // TODODO this is where in text links get passed
             e.stopPropagation();
             return true;
         }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 97ed74c10..b47236bea 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -493,7 +493,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         summary._backgroundColor = "#e2ad32";
         portal.layoutKey = "layout_portal";
         portal.title = "document collection";
-        DocUtils.MakeLink({ doc: summary }, { doc: portal }, "summarizing");
+        DocUtils.MakeLink({ doc: summary }, { doc: portal }, "summarizing", "");
 
         this.props.addLiveTextDocument(summary);
         MarqueeOptionsMenu.Instance.fadeOut(true);
diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss
index b47c8976e..14b2106d5 100644
--- a/src/client/views/linking/LinkEditor.scss
+++ b/src/client/views/linking/LinkEditor.scss
@@ -7,34 +7,66 @@
     user-select: none;
 }
 
-.linkEditor-back {
+.linkEditor-button-back {
     margin-bottom: 6px;
+    border-radius: 10px;
+    width: 18px;
+    height: 18px;
+    padding: 0;
 }
 
 .linkEditor-info {
-    border-bottom: 0.5px solid $light-color-secondary; 
-    padding-bottom: 6px;
-    margin-bottom: 6px;
+    //border-bottom: 0.5px solid $light-color-secondary;
+    padding-bottom: 4px;
+    padding-top: 5px;
+    padding-left: 5px;
+    //margin-bottom: 6px;
     display: flex;
     justify-content: space-between;
 
     .linkEditor-linkedTo {
         width: calc(100% - 26px);
+        padding-left: 5px;
+        padding-right: 5px
+    }
+}
+
+.linkEditor-description {
+    padding-left: 6.5px;
+    padding-right: 6.5px;
+    padding-bottom: 3.5px;
+
+    .linkEditor-description-text {
+        text-decoration-color: grey;
+    }
+
+    .linkEditor-description-input {
+        border: 1px solid rgb(114, 162, 179);
+        border-radius: 4px;
+        background-color: lightblue;
+        padding-left: 2px;
+        padding-right: 2px;
+        color: grey;
+        text-decoration-color: grey;
     }
 }
 
-.linkEditor-button, .linkEditor-addbutton {
+
+.linkEditor-button,
+.linkEditor-addbutton {
     width: 18px;
     height: 18px;
     padding: 0;
     // font-size: 12px;
     border-radius: 10px;
 
+
     &:disabled {
         background-color: gray;
     }
 }
-.linkEditor-addbutton{
+
+.linkEditor-addbutton {
     margin-left: 0px;
 }
 
@@ -44,7 +76,7 @@
 }
 
 .linkEditor-group {
-    background-color: $light-color-secondary; 
+    background-color: $light-color-secondary;
     padding: 6px;
     margin: 3px 0;
     border-radius: 3px;
@@ -56,7 +88,7 @@
 
     .linkEditor-group-row-label {
         margin-right: 6px;
-        display:inline-block;
+        display: inline-block;
     }
 
     .linkEditor-metadata-row {
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index 13b9a2459..4a6d9f2d3 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -1,14 +1,17 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faArrowLeft, faCog, faEllipsisV, faExchangeAlt, faPlus, faTable, faTimes, faTrash } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable } from "mobx";
+import { action, observable, computed } from "mobx";
 import { observer } from "mobx-react";
-import { Doc } from "../../../fields/Doc";
+import { Doc, Opt } from "../../../fields/Doc";
 import { StrCast } from "../../../fields/Types";
 import { Utils } from "../../../Utils";
 import { LinkManager } from "../../util/LinkManager";
 import './LinkEditor.scss';
 import React = require("react");
+import { DocumentView } from "../nodes/DocumentView";
+import { DocumentLinksButton } from "../nodes/DocumentLinksButton";
+import { EditableView } from "../EditableView";
 
 library.add(faArrowLeft, faEllipsisV, faTable, faTrash, faCog, faExchangeAlt, faTimes, faPlus);
 
@@ -281,27 +284,69 @@ interface LinkEditorProps {
 @observer
 export class LinkEditor extends React.Component<LinkEditorProps> {
 
+
+    @observable description = StrCast(LinkManager.currentLink?.description);
+
+    //@observable description = this.props.linkDoc.description ? StrCast(this.props.linkDoc.description) : "DESCRIPTION";
+
     @action
     deleteLink = (): void => {
         LinkManager.Instance.deleteLink(this.props.linkDoc);
         this.props.showLinks();
     }
 
+    @action
+    setDescripValue = (value: string) => {
+        if (LinkManager.currentLink) {
+            LinkManager.currentLink.description = value;
+            return true;
+        }
+    }
+
+    @computed
+    get editDescription() {
+        return <div className="linkEditor-description">
+            <div className="linkEditor-description-label">
+                Link Description:</div>
+            <div className="linkEditor-description-input">
+                <EditableView
+                    GetValue={() => StrCast(LinkManager.currentLink?.description)}
+                    SetValue={value => { this.setDescripValue(value); return false; }}
+                    contents={LinkManager.currentLink?.description}
+                    placeholder={"(optional) enter link description"}
+                    color={"rgb(88, 88, 88)"}
+                ></EditableView></div></div>;
+    }
+
+    @computed
+    get followingDropdown() {
+        return "choose follow behavior";
+    }
+
     render() {
         const destination = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
 
         const groups = [this.props.linkDoc].map(groupDoc => {
-            return <LinkGroupEditor key={"gred-" + StrCast(groupDoc.linkRelationship)} linkDoc={this.props.linkDoc} sourceDoc={this.props.sourceDoc} groupDoc={groupDoc} />;
+            return <LinkGroupEditor key={"gred-" + StrCast(groupDoc.linkRelationship)} linkDoc={this.props.linkDoc}
+                sourceDoc={this.props.sourceDoc} groupDoc={groupDoc} />;
         });
 
         return !destination ? (null) : (
             <div className="linkEditor">
-                {this.props.hideback ? (null) : <button className="linkEditor-back" onPointerDown={() => this.props.showLinks()}><FontAwesomeIcon icon="arrow-left" size="sm" /></button>}
                 <div className="linkEditor-info">
-                    <p className="linkEditor-linkedTo">editing link to: <b>{destination.proto?.title ?? destination.title ?? "untitled"}</b></p>
-                    <button className="linkEditor-button" onPointerDown={() => this.deleteLink()} title="Delete link"><FontAwesomeIcon icon="trash" size="sm" /></button>
+                    {this.props.hideback ? (null) : <button className="linkEditor-button-back"
+                        onPointerDown={this.props.showLinks}>
+                        <FontAwesomeIcon icon="arrow-left" size="sm" /></button>}
+                    <p className="linkEditor-linkedTo">editing link to: <b>{
+                        destination.proto?.title ?? destination.title ?? "untitled"}</b></p>
+                    <button className="linkEditor-button" onPointerDown={() => this.deleteLink()} title="Delete link">
+                        <FontAwesomeIcon icon="trash" size="sm" /></button>
                 </div>
-                {groups.length > 0 ? groups : <div className="linkEditor-group">There are currently no relationships associated with this link.</div>}
+
+                <div>{this.editDescription}</div>
+                <div>{this.followingDropdown}</div>
+
+                {/* {groups.length > 0 ? groups : <div className="linkEditor-group">There are currently no relationships associated with this link.</div>} */}
             </div>
 
         );
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index 6468ccd3d..f827f25c2 100644
--- a/src/client/views/linking/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
@@ -11,12 +11,17 @@
     position: absolute;
     z-index: 10;
     background: $link-color;
-    min-width: 150px
+    min-width: 150px;
+    border-radius: 5px;
+    padding-top: 6.5px;
+    padding-bottom: 6.5px;
+    padding-left: 6.5px;
+    padding-right: 2px;
 }
 
 .linkMenu-group {
-    border-bottom: 0.5px solid lightgray;
-    padding: 5px 0;
+    //border-bottom: 0.5px solid lightgray;
+    //@extend: 5px 0;
 
 
     &:last-child {
@@ -26,9 +31,11 @@
     .linkMenu-group-name {
         display: flex;
 
+
         &:hover {
             p {
                 background-color: lightgray;
+
             }
 
             p.expand-one {
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index c672511ac..8721b9f3d 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -11,6 +11,7 @@ import { faTrash } from '@fortawesome/free-solid-svg-icons';
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { DocumentLinksButton } from "../nodes/DocumentLinksButton";
 import { LinkDocPreview } from "../nodes/LinkDocPreview";
+import { isUndefined } from "util";
 
 library.add(faTrash);
 
@@ -26,18 +27,19 @@ export class LinkMenu extends React.Component<Props> {
 
     @observable private _editingLink?: Doc;
     @observable private _linkMenuRef: Opt<HTMLDivElement | null>;
+    private _editorRef = React.createRef<HTMLDivElement>();
 
     @action
     onClick = (e: PointerEvent) => {
 
         LinkDocPreview.LinkInfo = undefined;
 
-        if (this._linkMenuRef?.contains(e.target as any)) {
-            DocumentLinksButton.EditLink = undefined;
-        }
 
-        if (this._linkMenuRef && !this._linkMenuRef.contains(e.target as any)) {
-            DocumentLinksButton.EditLink = undefined;
+        if (this._linkMenuRef && !!!this._linkMenuRef.contains(e.target as any)) {
+            if (this._editorRef && !!!this._editorRef.current?.contains(e.target as any)) {
+                console.log("outside click");
+                DocumentLinksButton.EditLink = undefined;
+            }
         }
     }
     @action
@@ -82,7 +84,8 @@ export class LinkMenu extends React.Component<Props> {
             ref={(r) => this._linkMenuRef = r} style={{ left: this.props.location[0], top: this.props.location[1] }}>
             {!this._editingLink ?
                 this.renderAllGroups(groups) :
-                <LinkEditor sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)} />
+                <LinkEditor sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink}
+                    showLinks={action(() => this._editingLink = undefined)} />
             }
         </div>;
     }
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 7892d381b..ec17776e3 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -26,6 +26,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
 
     private _drag = React.createRef<HTMLDivElement>();
     private _table = React.createRef<HTMLDivElement>();
+    private _menuRef = React.createRef<HTMLDivElement>();
 
     onLinkButtonDown = (e: React.PointerEvent): void => {
         e.stopPropagation();
@@ -74,12 +75,13 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
                     linkDoc={linkDoc}
                     sourceDoc={this.props.sourceDoc}
                     destinationDoc={destination}
-                    showEditor={this.props.showEditor} />;
+                    showEditor={this.props.showEditor}
+                    menuRef={this._menuRef} />;
             }
         });
 
         return (
-            <div className="linkMenu-group">
+            <div className="linkMenu-group" ref={this._menuRef}>
                 {/* <div className="linkMenu-group-name">
                     <p ref={this._drag} onPointerDown={this.onLinkButtonDown}
                         className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"} > {this.props.groupType}:</p>
diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss
index e3ce69cd7..a71b2dbba 100644
--- a/src/client/views/linking/LinkMenuItem.scss
+++ b/src/client/views/linking/LinkMenuItem.scss
@@ -4,19 +4,38 @@
     // border-top: 0.5px solid $main-accent; 
     position: relative;
     display: flex;
-    font-size: 12px;
 
 
     .linkMenu-name {
         position: relative;
 
-        p {
-            padding: 4px 6px;
-            line-height: 12px;
-            border-radius: 5px;
-            overflow-wrap: break-word;
-            user-select: none;
+        .linkMenu-text {
+
+            padding: 4px 2px;
+
+            .linkMenu-destination-title {
+                text-decoration: none;
+                color: rgb(46, 82, 160);
+                font-size: 14px;
+                padding-bottom: 2px;
+            }
+
+            .linkMenu-description {
+                text-decoration: none;
+                font-style: italic;
+                color: rgb(95, 97, 102);
+                font-size: 10px;
+            }
+
+            p {
+                //padding: 4px 2px;
+                line-height: 12px;
+                border-radius: 5px;
+                overflow-wrap: break-word;
+                user-select: none;
+            }
         }
+
     }
 
     .linkMenu-item-content {
@@ -32,19 +51,30 @@
     }
 
     &:hover {
+
         .linkMenu-item-buttons {
             display: flex;
         }
 
         .linkMenu-item-content {
+
+            .linkMenu-destination-title {
+                text-decoration: underline;
+                color: rgb(15, 57, 148);
+            }
+
             &.expand-two p {
                 width: calc(100% - 52px);
-                background-color: lightgray;
+                //text-decoration: underline;
+                //color: rgb(15, 57, 148);
+                //background-color: lightgray;
             }
 
             &.expand-three p {
                 width: calc(100% - 84px);
-                background-color: lightgray;
+                //text-decoration: underline;
+                //color: rgb(15, 57, 148);
+                //background-color: lightgray;
             }
         }
     }
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 04cd83ee0..891c6d263 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -26,6 +26,7 @@ interface LinkMenuItemProps {
     destinationDoc: Doc;
     showEditor: (linkDoc: Doc) => void;
     addDocTab: (document: Doc, where: string) => boolean;
+    menuRef: React.Ref<HTMLDivElement>;
 }
 
 // drag links and drop link targets (aliasing them if needed)
@@ -77,6 +78,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
     @action toggleShowMore(e: React.PointerEvent) { e.stopPropagation(); this._showMore = !this._showMore; }
 
     onEdit = (e: React.PointerEvent): void => {
+        LinkManager.currentLink = this.props.linkDoc;
         setupMoveUpEvents(this, e, this.editMoved, emptyFunction, () => this.props.showEditor(this.props.linkDoc));
     }
 
@@ -110,7 +112,8 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
         document.removeEventListener("pointerup", this.onLinkButtonUp);
         document.addEventListener("pointerup", this.onLinkButtonUp);
 
-        if (this._buttonRef && this._buttonRef.current?.contains(e.target as any)) {
+        if (this._buttonRef && !!!this._buttonRef.current?.contains(e.target as any)) {
+            console.log("outside click");
             LinkDocPreview.LinkInfo = undefined;
         }
     }
@@ -174,18 +177,24 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                             Location: [e.clientX, e.clientY + 20]
                         }))}
                         onPointerDown={this.onLinkButtonDown}>
-                        <p >{StrCast(this.props.destinationDoc.title)}</p>
+
+                        <div className="linkMenu-text">
+                            <p className="linkMenu-destination-title"
+                                onPointerDown={this.followDefault}>
+                                {StrCast(this.props.destinationDoc.title)}</p>
+                            {this.props.linkDoc.description !== "" ? <p className="linkMenu-description">
+                                {StrCast(this.props.linkDoc.description)}</p> : null} </div>
+
                         <div className="linkMenu-item-buttons" ref={this._buttonRef} >
                             {canExpand ? <div title="Show more" className="button" onPointerDown={e => this.toggleShowMore(e)}>
                                 <FontAwesomeIcon className="fa-icon" icon={this._showMore ? "chevron-up" : "chevron-down"} size="sm" /></div> : <></>}
 
-                            {/* <div title="Edit link" className="button" ref={this._editRef} onPointerDown={this.onEdit}>
-                                <FontAwesomeIcon className="fa-icon" icon="pencil-alt" size="sm" /></div> */}
+                            <div title="Edit link" className="button" ref={this._editRef} onPointerDown={this.onEdit}>
+                                <FontAwesomeIcon className="fa-icon" icon="pencil-alt" size="sm" /></div>
                             <div title="Delete link" className="button" onPointerDown={this.deleteLink}>
                                 <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div>
-                            <div title="Follow link" className="button" onPointerDown={this.followDefault} onContextMenu={this.onContextMenu}>
-                                <FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" />
-                            </div>
+                            {/* <div title="Follow link" className="button" onPointerDown={this.followDefault} onContextMenu={this.onContextMenu}>
+                                <FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" /></div> */}
                         </div>
                     </div>
                     {this._showMore ? this.renderMetadata() : <></>}
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index bfd860f65..5d1a68af5 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -11,6 +11,8 @@ import { DocUtils } from "../../documents/Documents";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { LinkDocPreview } from "./LinkDocPreview";
 import { LinkCreatedBox } from "./LinkCreatedBox";
+import { LinkDescriptionPopup } from "./LinkDescriptionPopup";
+import { LinkManager } from "../../util/LinkManager";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -54,6 +56,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
         return false;
     }
 
+
     onLinkButtonDown = (e: React.PointerEvent): void => {
         setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => {
             if (doubleTap && this.props.InMenu) {
@@ -87,15 +90,22 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                     //     Doc.UnBrushDoc(this.props.View.Document);
                     // });
                 } else {
-                    DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
-                        DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
-
-                    runInAction(() => {
-                        LinkCreatedBox.popupX = e.screenX;
-                        LinkCreatedBox.popupY = e.screenY - 120;
-                        LinkCreatedBox.linkCreated = true;
-                        setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
-                    });
+
+                    if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View) {
+                        const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
+                        LinkManager.currentLink = linkDoc;
+                        runInAction(() => {
+                            LinkCreatedBox.popupX = e.screenX;
+                            LinkCreatedBox.popupY = e.screenY - 33;
+                            LinkCreatedBox.linkCreated = true;
+
+                            LinkDescriptionPopup.popupX = e.screenX;
+                            LinkDescriptionPopup.popupY = e.screenY;
+                            LinkDescriptionPopup.descriptionPopup = true;
+
+                            setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+                        });
+                    }
                 }
             }
         }));
@@ -109,15 +119,21 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             //     Doc.UnBrushDoc(this.props.View.Document);
             // });
         } else {
-            DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
-                DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
-
-            runInAction(() => {
-                LinkCreatedBox.popupX = e.screenX;
-                LinkCreatedBox.popupY = e.screenY - 120;
-                LinkCreatedBox.linkCreated = true;
-                setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
-            });
+            if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View) {
+                const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
+                LinkManager.currentLink = linkDoc;
+                runInAction(() => {
+                    LinkCreatedBox.popupX = e.screenX;
+                    LinkCreatedBox.popupY = e.screenY - 33;
+                    LinkCreatedBox.linkCreated = true;
+
+                    LinkDescriptionPopup.popupX = e.screenX;
+                    LinkDescriptionPopup.popupY = e.screenY;
+                    LinkDescriptionPopup.descriptionPopup = true;
+
+                    setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+                });
+            }
         }
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b38db9a1e..9a4d9ac53 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -43,6 +43,8 @@ import React = require("react");
 import { DocumentLinksButton } from './DocumentLinksButton';
 import { MobileInterface } from '../../../mobile/MobileInterface';
 import { LinkCreatedBox } from './LinkCreatedBox';
+import { LinkDescriptionPopup } from './LinkDescriptionPopup';
+import { LinkManager } from '../../util/LinkManager';
 
 library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight,
     fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale,
@@ -642,30 +644,46 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             e.stopPropagation();
             de.complete.annoDragData.linkedToDoc = true;
 
+            const linkDoc = DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document }, "link");
+            LinkManager.currentLink = linkDoc;
+
             runInAction(() => {
                 LinkCreatedBox.popupX = de.x;
-                LinkCreatedBox.popupY = de.y;
+                LinkCreatedBox.popupY = de.y - 33;
                 LinkCreatedBox.linkCreated = true;
+
+                LinkDescriptionPopup.popupX = de.x;
+                LinkDescriptionPopup.popupY = de.y;
+                LinkDescriptionPopup.descriptionPopup = true;
+
                 setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
             });
-
-            DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document }, "link");
         }
         if (de.complete.linkDragData) {
             e.stopPropagation();
             // const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true);
             // const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView);
 
-            runInAction(() => {
-                LinkCreatedBox.popupX = de.x;
-                LinkCreatedBox.popupY = de.y;
-                LinkCreatedBox.linkCreated = true;
-                setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
-            });
+            if (de.complete.linkDragData.linkSourceDocument !== this.props.Document) {
+                const linkDoc = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument },
+                    { doc: this.props.Document }, `link`);
+                LinkManager.currentLink = linkDoc;
+
+                de.complete.linkDragData.linkSourceDocument !== this.props.Document &&
+                    (de.complete.linkDragData.linkDocument = linkDoc); // TODODO this is where in text links get passed
+                runInAction(() => {
+                    LinkCreatedBox.popupX = de.x;
+                    LinkCreatedBox.popupY = de.y - 33;
+                    LinkCreatedBox.linkCreated = true;
+
+                    LinkDescriptionPopup.popupX = de.x;
+                    LinkDescriptionPopup.popupY = de.y;
+                    LinkDescriptionPopup.descriptionPopup = true;
+
+                    setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+                });
+            }
 
-            de.complete.linkDragData.linkSourceDocument !== this.props.Document &&
-                (de.complete.linkDragData.linkDocument = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument },
-                    { doc: this.props.Document }, `link`)); // TODODO this is where in text links get passed
         }
     }
 
diff --git a/src/client/views/nodes/LinkCreatedBox.tsx b/src/client/views/nodes/LinkCreatedBox.tsx
index d157d3fca..648ae23c8 100644
--- a/src/client/views/nodes/LinkCreatedBox.tsx
+++ b/src/client/views/nodes/LinkCreatedBox.tsx
@@ -11,8 +11,8 @@ import { Fade } from "@material-ui/core";
 export class LinkCreatedBox extends React.Component<{}> {
 
     @observable public static linkCreated: boolean = false;
-    @observable public static popupX: number = 600;
-    @observable public static popupY: number = 250;
+    @observable public static popupX: number = 500;
+    @observable public static popupY: number = 150;
 
     @action
     public static changeLinkCreated = () => {
@@ -23,8 +23,8 @@ export class LinkCreatedBox extends React.Component<{}> {
         return <Fade in={LinkCreatedBox.linkCreated}>
             <div className="linkCreatedBox-fade"
                 style={{
-                    left: LinkCreatedBox.popupX ? LinkCreatedBox.popupX : 600,
-                    top: LinkCreatedBox.popupY ? LinkCreatedBox.popupY : 250,
+                    left: LinkCreatedBox.popupX ? LinkCreatedBox.popupX : 500,
+                    top: LinkCreatedBox.popupY ? LinkCreatedBox.popupY : 150,
                 }}>Link Created</div>
         </Fade>;
     }
diff --git a/src/client/views/nodes/LinkDescriptionPopup.scss b/src/client/views/nodes/LinkDescriptionPopup.scss
new file mode 100644
index 000000000..474bd919b
--- /dev/null
+++ b/src/client/views/nodes/LinkDescriptionPopup.scss
@@ -0,0 +1,62 @@
+.linkDescriptionPopup {
+
+    display: flex;
+
+    border: 1px solid rgb(100, 100, 100);
+
+    width: auto;
+    position: absolute;
+
+    height: auto;
+    z-index: 10000;
+    border-radius: 10px;
+    font-size: 12px;
+    //white-space: nowrap;
+
+    background-color: rgba(250, 250, 250, 0.95);
+    padding-top: 9px;
+    padding-bottom: 9px;
+    padding-left: 9px;
+    padding-right: 9px;
+
+    .linkDescriptionPopup-input {
+        float: left;
+        color: rgb(100, 100, 100);
+    }
+
+    .linkDescriptionPopup-btn {
+
+        float: right;
+
+
+        .linkDescriptionPopup-btn-dismiss {
+            background-color: white;
+            color: black;
+            display: inline;
+            right: 0;
+            border-radius: 10px;
+            border: 1px solid black;
+            padding: 3px;
+            font-size: 9px;
+            text-align: center;
+            position: relative;
+            transform: translateY(5px);
+        }
+
+        .linkDescriptionPopup-btn-add {
+            background-color: black;
+            color: white;
+            display: inline;
+            right: 0;
+            border-radius: 10px;
+            border: 1px solid black;
+            padding: 3px;
+            font-size: 9px;
+            text-align: center;
+            position: relative;
+            transform: translateY(5px);
+        }
+    }
+
+
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
new file mode 100644
index 000000000..078a738e7
--- /dev/null
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -0,0 +1,70 @@
+import React = require("react");
+import { observer } from "mobx-react";
+import "./LinkDescriptionPopup.scss";
+import { observable, action } from "mobx";
+import { EditableView } from "../EditableView";
+import { LinkManager } from "../../util/LinkManager";
+
+
+@observer
+export class LinkDescriptionPopup extends React.Component<{}> {
+
+    @observable public static descriptionPopup: boolean = false;
+    @observable public static popupX: number = 700;
+    @observable public static popupY: number = 350;
+    @observable description: string = "";
+    @observable popupRef = React.createRef<HTMLDivElement>();
+
+    @action
+    descriptionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
+        this.description = e.currentTarget.value;
+    }
+
+    @action
+    setDescription = () => {
+        if (LinkManager.currentLink) {
+            LinkManager.currentLink.description = this.description;
+        }
+        LinkDescriptionPopup.descriptionPopup = false;
+    }
+
+    @action
+    onDismiss = () => {
+        LinkDescriptionPopup.descriptionPopup = false;
+    }
+
+    @action
+    onClick = (e: PointerEvent) => {
+        if (this.popupRef && !!!this.popupRef.current?.contains(e.target as any)) {
+            LinkDescriptionPopup.descriptionPopup = false;
+        }
+    }
+
+    @action
+    componentDidMount() {
+        document.addEventListener("pointerdown", this.onClick);
+    }
+
+    componentWillUnmount() {
+        document.removeEventListener("pointerdown", this.onClick);
+    }
+
+    render() {
+        return <div className="linkDescriptionPopup" ref={this.popupRef}
+            style={{
+                left: LinkDescriptionPopup.popupX ? LinkDescriptionPopup.popupX : 700,
+                top: LinkDescriptionPopup.popupY ? LinkDescriptionPopup.popupY : 350,
+            }}>
+            <input className="linkDescriptionPopup-input"
+                placeholder={"(optional) enter link label..."}
+                onChange={(e) => this.descriptionChanged(e)}>
+            </input>
+            <div className="linkDescriptionPopup-btn">
+                <div className="linkDescriptionPopup-btn-dismiss"
+                    onPointerDown={this.onDismiss}> Dismiss </div>
+                <div className="linkDescriptionPopup-btn-add"
+                    onPointerDown={this.setDescription}> Add </div>
+            </div>
+        </div>;
+    }
+} 
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 0438137cd435c47ce334b15a4ad00cbd70d80662 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 7 Jul 2020 00:23:24 -0400
Subject: made docFilters apply to links.  added hypothesis api token input

---
 .../apis/HypothesisAuthenticationManager.scss      |  26 ++++
 .../apis/HypothesisAuthenticationManager.tsx       | 158 +++++++++++++++++++++
 src/client/documents/Documents.ts                  |  41 ++++++
 src/client/util/CurrentUserUtils.ts                |   1 +
 src/client/views/GlobalKeyHandler.ts               |   2 +
 src/client/views/MainView.tsx                      |   2 +
 src/client/views/collections/CollectionSubView.tsx |  43 +-----
 src/client/views/nodes/DocumentView.tsx            |   2 +-
 src/server/ApiManagers/HypothesisManager.ts        |  44 ++++++
 src/server/ApiManagers/UploadManager.ts            |   3 +-
 src/server/apis/google/GoogleApiServerUtils.ts     |   3 +-
 src/server/index.ts                                |   3 +-
 12 files changed, 285 insertions(+), 43 deletions(-)
 create mode 100644 src/client/apis/HypothesisAuthenticationManager.scss
 create mode 100644 src/client/apis/HypothesisAuthenticationManager.tsx
 create mode 100644 src/server/ApiManagers/HypothesisManager.ts

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

diff --git a/src/client/apis/HypothesisAuthenticationManager.scss b/src/client/apis/HypothesisAuthenticationManager.scss
new file mode 100644
index 000000000..bd30dd94f
--- /dev/null
+++ b/src/client/apis/HypothesisAuthenticationManager.scss
@@ -0,0 +1,26 @@
+.authorize-container {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+
+    .paste-target {
+        padding: 5px;
+        width: 100%;
+    }
+
+    .avatar {
+        border-radius: 50%;
+    }
+
+    .welcome {
+        font-style: italic;
+        margin-top: 15px;
+    }
+
+    .disconnect {
+        font-size: 10px;
+        margin-top: 20px;
+        color: red;
+        cursor: grab;
+    }
+}
\ No newline at end of file
diff --git a/src/client/apis/HypothesisAuthenticationManager.tsx b/src/client/apis/HypothesisAuthenticationManager.tsx
new file mode 100644
index 000000000..cffb87227
--- /dev/null
+++ b/src/client/apis/HypothesisAuthenticationManager.tsx
@@ -0,0 +1,158 @@
+import { observable, action, reaction, runInAction, IReactionDisposer } from "mobx";
+import { observer } from "mobx-react";
+import * as React from "react";
+import MainViewModal from "../views/MainViewModal";
+import { Opt } from "../../fields/Doc";
+import { Networking } from "../Network";
+import "./HypothesisAuthenticationManager.scss";
+import { Scripting } from "../util/Scripting";
+
+const prompt = "Paste authorization code here...";
+
+@observer
+export default class HypothesisAuthenticationManager extends React.Component<{}> {
+    public static Instance: HypothesisAuthenticationManager;
+    private authenticationLink: Opt<string> = undefined;
+    @observable private openState = false;
+    @observable private authenticationCode: Opt<string> = undefined;
+    @observable private showPasteTargetState = false;
+    @observable private success: Opt<boolean> = undefined;
+    @observable private displayLauncher = true;
+    @observable private credentials: string;
+    private disposer: Opt<IReactionDisposer>;
+
+    private set isOpen(value: boolean) {
+        runInAction(() => this.openState = value);
+    }
+
+    private set shouldShowPasteTarget(value: boolean) {
+        runInAction(() => this.showPasteTargetState = value);
+    }
+
+    public cancel() {
+        this.openState && this.resetState(0, 0);
+    }
+
+    public fetchAccessToken = async (displayIfFound = false) => {
+        let response: any = await Networking.FetchFromServer("/readHypothesisAccessToken");
+        // if this is an authentication url, activate the UI to register the new access token
+        if (!response) { // new RegExp(AuthenticationUrl).test(response)) {
+            this.isOpen = true;
+            this.authenticationLink = response;
+            return new Promise<string>(async resolve => {
+                this.disposer?.();
+                this.disposer = reaction(
+                    () => this.authenticationCode,
+                    async authenticationCode => {
+                        if (authenticationCode) {
+                            this.disposer?.();
+                            Networking.PostToServer("/writeHypothesisAccessToken", { authenticationCode });
+                            runInAction(() => {
+                                this.success = true;
+                                this.credentials = response;
+                            });
+                            this.resetState();
+                            resolve(authenticationCode);
+                        }
+                    }
+                );
+            });
+        }
+
+        if (displayIfFound) {
+            runInAction(() => {
+                this.success = true;
+                this.credentials = response;
+            });
+            this.resetState(-1, -1);
+            this.isOpen = true;
+        }
+        return response.access_token;
+    }
+
+    resetState = action((visibleForMS: number = 3000, fadesOutInMS: number = 500) => {
+        if (!visibleForMS && !fadesOutInMS) {
+            runInAction(() => {
+                this.isOpen = false;
+                this.success = undefined;
+                this.displayLauncher = true;
+                this.credentials = "";
+                this.shouldShowPasteTarget = false;
+                this.authenticationCode = undefined;
+            });
+            return;
+        }
+        this.authenticationCode = undefined;
+        this.displayLauncher = false;
+        this.shouldShowPasteTarget = false;
+        if (visibleForMS > 0 && fadesOutInMS > 0) {
+            setTimeout(action(() => {
+                this.isOpen = false;
+                setTimeout(action(() => {
+                    this.success = undefined;
+                    this.displayLauncher = true;
+                    this.credentials = "";
+                }), fadesOutInMS);
+            }), visibleForMS);
+        }
+    });
+
+    constructor(props: {}) {
+        super(props);
+        HypothesisAuthenticationManager.Instance = this;
+    }
+
+    private get renderPrompt() {
+        return (
+            <div className={'authorize-container'}>
+
+                {this.displayLauncher ? <button
+                    className={"dispatch"}
+                    onClick={() => {
+                        this.shouldShowPasteTarget = true;
+                    }}
+                    style={{ marginBottom: this.showPasteTargetState ? 15 : 0 }}
+                >Authorize a Hypothesis account...</button> : (null)}
+                {this.showPasteTargetState ? <input
+                    className={'paste-target'}
+                    onChange={action(e => this.authenticationCode = e.currentTarget.value)}
+                    placeholder={prompt}
+                /> : (null)}
+                {this.credentials ?
+                    <>
+                        <span
+                            className={'welcome'}
+                        >Welcome to Dash, {this.credentials}
+                        </span>
+                        <div
+                            className={'disconnect'}
+                            onClick={async () => {
+                                await Networking.FetchFromServer("/revokeHypothesisAccessToken");
+                                this.resetState(0, 0);
+                            }}
+                        >Disconnect Account</div>
+                    </> : (null)}
+            </div>
+        );
+    }
+
+    private get dialogueBoxStyle() {
+        const borderColor = this.success === undefined ? "black" : this.success ? "green" : "red";
+        return { borderColor, transition: "0.2s borderColor ease" };
+    }
+
+    render() {
+        return (
+            <MainViewModal
+                isDisplayed={this.openState}
+                interactive={true}
+                contents={this.renderPrompt}
+                overlayDisplayedOpacity={0.9}
+                dialogueBoxStyle={this.dialogueBoxStyle}
+            />
+        );
+    }
+
+}
+
+Scripting.addGlobal("HypothesisAuthenticationManager", HypothesisAuthenticationManager);
\ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index f81c25bab..3355c0091 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -829,6 +829,47 @@ export namespace Docs {
 }
 
 export namespace DocUtils {
+    export function FilterDocs(docs: Doc[], docFilters: string[], docRangeFilters: string[], viewSpecScript?: ScriptField) {
+        const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
+
+        const filterFacets: { [key: string]: { [value: string]: string } } = {};  // maps each filter key to an object with value=>modifier fields
+        for (let i = 0; i < docFilters.length; i += 3) {
+            const [key, value, modifiers] = docFilters.slice(i, i + 3);
+            if (!filterFacets[key]) {
+                filterFacets[key] = {};
+            }
+            filterFacets[key][value] = modifiers;
+        }
+
+        const filteredDocs = docFilters.length ? childDocs.filter(d => {
+            for (const facetKey of Object.keys(filterFacets)) {
+                const facet = filterFacets[facetKey];
+                const satisfiesFacet = Object.keys(facet).some(value => {
+                    if (facet[value] === "match") {
+                        return d[facetKey] === undefined || Field.toString(d[facetKey] as Field).includes(value);
+                    }
+                    return (facet[value] === "x") !== Doc.matchFieldValue(d, facetKey, value);
+                });
+                if (!satisfiesFacet) {
+                    return false;
+                }
+            }
+            return true;
+        }) : childDocs;
+        const rangeFilteredDocs = filteredDocs.filter(d => {
+            for (let i = 0; i < docRangeFilters.length; i += 3) {
+                const key = docRangeFilters[i];
+                const min = Number(docRangeFilters[i + 1]);
+                const max = Number(docRangeFilters[i + 2]);
+                const val = Cast(d[key], "number", null);
+                if (val !== undefined && (val < min || val > max)) {
+                    return false;
+                }
+            }
+            return true;
+        });
+        return rangeFilteredDocs;
+    }
 
     export function Publish(promoteDoc: Doc, targetID: string, addDoc: any, remDoc: any) {
         targetID = targetID.replace(/^-/, "").replace(/\([0-9]*\)$/, "");
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 4276e04e4..9f04aab04 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -424,6 +424,7 @@ export class CurrentUserUtils {
             { title: "Drag a document previewer", label: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.emptyDocHolder as Doc },
             { title: "Toggle a Calculator REPL", label: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' },
             { title: "Connect a Google Account", label: "Google Account", icon: "external-link-alt", click: 'GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' },
+            { title: "Connect a Hypothesis Account", label: "Hypothesis Account", icon: "houzz", click: 'HypothesisAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' },
         ];
 
     }
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 34f666f62..a3a023164 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -7,6 +7,7 @@ import { List } from "../../fields/List";
 import { ScriptField } from "../../fields/ScriptField";
 import { Cast, PromiseValue } from "../../fields/Types";
 import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager";
+import HypothesisAuthenticationManager from "../apis/HypothesisAuthenticationManager";
 import { DocServer } from "../DocServer";
 import { DocumentType } from "../documents/DocumentTypes";
 import { DictationManager } from "../util/DictationManager";
@@ -104,6 +105,7 @@ export default class KeyManager {
                 DictationManager.Controls.stop();
                 // RecommendationsBox.Instance.closeMenu();
                 GoogleAuthenticationManager.Instance.cancel();
+                HypothesisAuthenticationManager.Instance.cancel();
                 SharingManager.Instance.close();
                 break;
             case "delete":
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 15f818d1f..eba9bb344 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -61,6 +61,7 @@ import { LinkMenu } from './linking/LinkMenu';
 import { LinkDocPreview } from './nodes/LinkDocPreview';
 import { Fade } from '@material-ui/core';
 import { LinkCreatedBox } from './nodes/LinkCreatedBox';
+import HypothesisAuthenticationManager from '../apis/HypothesisAuthenticationManager';
 
 @observer
 export class MainView extends React.Component {
@@ -601,6 +602,7 @@ export class MainView extends React.Component {
             <SettingsManager />
             <GroupManager />
             <GoogleAuthenticationManager />
+            <HypothesisAuthenticationManager />
             <DocumentDecorations />
             <GestureOverlay>
                 <RichTextMenu key="rich" />
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index ed8535ecb..ce6872695 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -106,16 +106,6 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                 [...this.props.docFilters(), ...Cast(this.props.Document._docFilters, listSpec("string"), [])];
         }
         @computed get childDocs() {
-            const docFilters = this.docFilters();
-            const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
-            const filterFacets: { [key: string]: { [value: string]: string } } = {};  // maps each filter key to an object with value=>modifier fields
-            for (let i = 0; i < docFilters.length; i += 3) {
-                const [key, value, modifiers] = docFilters.slice(i, i + 3);
-                if (!filterFacets[key]) {
-                    filterFacets[key] = {};
-                }
-                filterFacets[key][value] = modifiers;
-            }
 
             let rawdocs: (Doc | Promise<Doc>)[] = [];
             if (this.dataField instanceof Doc) { // if collection data is just a document, then promote it to a singleton list;
@@ -128,38 +118,13 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                 const rootDoc = Cast(this.props.Document.rootDocument, Doc, null);
                 rawdocs = rootDoc && !this.props.annotationsKey ? [Doc.GetProto(rootDoc)] : [];
             }
+
             const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc);
+            const docFilters = this.docFilters();
             const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
-            const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
+            const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
 
-            const filteredDocs = docFilters.length && !this.props.dontRegisterView ? childDocs.filter(d => {
-                for (const facetKey of Object.keys(filterFacets)) {
-                    const facet = filterFacets[facetKey];
-                    const satisfiesFacet = Object.keys(facet).some(value => {
-                        if (facet[value] === "match") {
-                            return d[facetKey] === undefined || Field.toString(d[facetKey] as Field).includes(value);
-                        }
-                        return (facet[value] === "x") !== Doc.matchFieldValue(d, facetKey, value);
-                    });
-                    if (!satisfiesFacet) {
-                        return false;
-                    }
-                }
-                return true;
-            }) : childDocs;
-            const rangeFilteredDocs = filteredDocs.filter(d => {
-                for (let i = 0; i < docRangeFilters.length; i += 3) {
-                    const key = docRangeFilters[i];
-                    const min = Number(docRangeFilters[i + 1]);
-                    const max = Number(docRangeFilters[i + 2]);
-                    const val = Cast(d[key], "number", null);
-                    if (val !== undefined && (val < min || val > max)) {
-                        return false;
-                    }
-                }
-                return true;
-            });
-            return rangeFilteredDocs;
+            return this.props.Document.dontRegisterView ? docs : DocUtils.FilterDocs(docs, docFilters, docRangeFilters, viewSpecScript);
         }
 
         @action
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b38db9a1e..3311bfa33 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1094,7 +1094,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         return (this.props.treeViewDoc && this.props.LayoutTemplateString) || // render nothing for: tree view anchor dots
             this.layoutDoc.presBox ||  // presentationbox nodes
             this.props.dontRegisterView ? (null) : // view that are not registered
-            DocListCast(this.Document.links).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
+            DocUtils.FilterDocs(DocListCast(this.Document.links), this.props.docFilters(), []).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
                 <DocumentView {...this.props} key={i + 1}
                     Document={d}
                     ContainingCollectionView={this.props.ContainingCollectionView}
diff --git a/src/server/ApiManagers/HypothesisManager.ts b/src/server/ApiManagers/HypothesisManager.ts
new file mode 100644
index 000000000..33badbc42
--- /dev/null
+++ b/src/server/ApiManagers/HypothesisManager.ts
@@ -0,0 +1,44 @@
+import ApiManager, { Registration } from "./ApiManager";
+import { Method, _permission_denied } from "../RouteManager";
+import { GoogleApiServerUtils } from "../apis/google/GoogleApiServerUtils";
+import { Database } from "../database";
+import { writeFile, readFile, readFileSync, existsSync } from "fs";
+import { serverPathToFile, Directory } from "./UploadManager";
+
+export default class HypothesisManager extends ApiManager {
+
+    protected initialize(register: Registration): void {
+
+        register({
+            method: Method.GET,
+            subscription: "/readHypothesisAccessToken",
+            secureHandler: async ({ user, res }) => {
+                if (existsSync(serverPathToFile(Directory.hypothesis, user.id))) {
+                    const read = readFileSync(serverPathToFile(Directory.hypothesis, user.id), "base64") || "";
+                    console.log("READ = " + read);
+                    res.send(read);
+                } else res.send("");
+            }
+        });
+
+        register({
+            method: Method.POST,
+            subscription: "/writeHypothesisAccessToken",
+            secureHandler: async ({ user, req, res }) => {
+                const write = req.body.authenticationCode;
+                console.log("WRITE = " + write);
+                res.send(await writeFile(serverPathToFile(Directory.hypothesis, user.id), write, "base64", () => { }));
+            }
+        });
+
+        register({
+            method: Method.GET,
+            subscription: "/revokeHypothesisAccessToken",
+            secureHandler: async ({ user, res }) => {
+                await Database.Auxiliary.GoogleAccessToken.Revoke("dash-hyp-" + user.id);
+                res.send();
+            }
+        });
+
+    }
+}
\ No newline at end of file
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index fe39b84e6..55ceab9fb 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -24,7 +24,8 @@ export enum Directory {
     pdfs = "pdfs",
     text = "text",
     pdf_thumbnails = "pdf_thumbnails",
-    audio = "audio"
+    audio = "audio",
+    hypothesis = "hypothesis"
 }
 
 export function serverPathToFile(directory: Directory, filename: string) {
diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts
index 20f96f432..b0157a85f 100644
--- a/src/server/apis/google/GoogleApiServerUtils.ts
+++ b/src/server/apis/google/GoogleApiServerUtils.ts
@@ -39,7 +39,8 @@ export namespace GoogleApiServerUtils {
      */
     export enum Service {
         Documents = "Documents",
-        Slides = "Slides"
+        Slides = "Slides",
+        Hypothesis = "Hypothesis"
     }
 
     /**
diff --git a/src/server/index.ts b/src/server/index.ts
index 083173bb5..9af4b00bc 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -18,10 +18,10 @@ import PDFManager from "./ApiManagers/PDFManager";
 import UploadManager from "./ApiManagers/UploadManager";
 import { log_execution } from "./ActionUtilities";
 import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager";
+import HypothesisManager from "./ApiManagers/HypothesisManager";
 import GooglePhotosManager from "./ApiManagers/GooglePhotosManager";
 import { Logger } from "./ProcessFactory";
 import { yellow } from "colors";
-import { DashSessionAgent } from "./DashSession/DashSessionAgent";
 import SessionManager from "./ApiManagers/SessionManager";
 import { AppliedSessionAgent } from "./DashSession/Session/agents/applied_session_agent";
 
@@ -72,6 +72,7 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
         new DeleteManager(),
         new UtilManager(),
         new GeneralGoogleManager(),
+        new HypothesisManager(),
         new GooglePhotosManager(),
     ];
 
-- 
cgit v1.2.3-70-g09d2


From 92aaf350e7b1baebd681eaeafa2a11c104f0708a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 7 Jul 2020 10:32:14 -0400
Subject: fixed hypothesis icon

---
 src/client/util/CurrentUserUtils.ts | 2 +-
 src/client/views/MainView.tsx       | 6 ++++--
 2 files changed, 5 insertions(+), 3 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 9f04aab04..8099228c6 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -424,7 +424,7 @@ export class CurrentUserUtils {
             { title: "Drag a document previewer", label: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.emptyDocHolder as Doc },
             { title: "Toggle a Calculator REPL", label: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' },
             { title: "Connect a Google Account", label: "Google Account", icon: "external-link-alt", click: 'GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' },
-            { title: "Connect a Hypothesis Account", label: "Hypothesis Account", icon: "houzz", click: 'HypothesisAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' },
+            { title: "Connect a Hypothesis Account", label: "Hypothesis Account", icon: "heading", click: 'HypothesisAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' },
         ];
 
     }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 584ed2f1c..81195b550 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -5,7 +5,8 @@ import {
     faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
     faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
     faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTimesCircle,
-    faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight
+    faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
+    faHeading
 } from '@fortawesome/free-solid-svg-icons';
 import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -145,7 +146,8 @@ export class MainView extends React.Component {
             faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
             faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
             faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
-            faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight);
+            faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
+            faHeading);
         this.initEventListeners();
         this.initAuthenticationRouters();
     }
-- 
cgit v1.2.3-70-g09d2


From 065a29e87c771b5cd9301b0468fb036e070cad17 Mon Sep 17 00:00:00 2001
From: yunahi <60233430+yunahi@users.noreply.github.com>
Date: Fri, 10 Jul 2020 05:08:53 +0900
Subject: added format shape

---
 src/client/documents/Documents.ts                  |   3 +-
 src/client/util/InteractionUtils.tsx               |   8 +
 src/client/util/SelectionManager.ts                |   5 +
 src/client/views/AntimodeMenu.tsx                  |  18 +
 src/client/views/ContextMenu.tsx                   |   1 +
 src/client/views/DocumentDecorations.tsx           |   2 +
 src/client/views/GestureOverlay.tsx                |  17 +-
 src/client/views/InkingStroke.tsx                  |  15 +-
 src/client/views/MainView.tsx                      |  13 +-
 .../collectionFreeForm/FormatShapePane.scss        |  53 ++
 .../collectionFreeForm/FormatShapePane.tsx         | 996 +++++++++++++++++++++
 .../collectionFreeForm/InkOptionsMenu.scss         |   2 +-
 .../collectionFreeForm/InkOptionsMenu.tsx          |  40 +-
 13 files changed, 1152 insertions(+), 21 deletions(-)
 create mode 100644 src/client/views/collections/collectionFreeForm/FormatShapePane.scss
 create mode 100644 src/client/views/collections/collectionFreeForm/FormatShapePane.tsx

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index b70971c2d..eb8e04317 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -653,7 +653,7 @@ export namespace Docs {
             I.type = DocumentType.INK;
             I.layout = InkingStroke.LayoutString("data");
             I.color = color;
-            I.strokeWidth = strokeWidth;
+            I.strokeWidth = Number(strokeWidth);
             I.strokeBezier = strokeBezier;
             I.fillColor = fillColor;
             I.arrowStart = arrowStart;
@@ -667,6 +667,7 @@ export namespace Docs {
             I._width = options._width;
             I._height = options._height;
             I.author = Doc.CurrentUserEmail;
+            I.rotation = 0;
             I.data = new InkField(points);
             return I;
             // return I;
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index edeb461e0..2883e2056 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -3,6 +3,7 @@ import * as beziercurve from 'bezier-curve';
 import * as fitCurve from 'fit-curve';
 import "./InteractionUtils.scss";
 import { Utils } from "../../Utils";
+import InkOptionsMenu from "../views/collections/collectionFreeForm/InkOptionsMenu";
 
 export namespace InteractionUtils {
     export const MOUSETYPE = "mouse";
@@ -94,6 +95,13 @@ export namespace InteractionUtils {
     export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number,
         color: string, width: number, strokeWidth: number, bezier: string, fill: string, arrowStart: string, arrowEnd: string,
         dash: string, scalex: number, scaley: number, shape: string, pevents: string, drawHalo: boolean, nodefs: boolean) {
+
+        // if (InkOptionsMenu.Instance.Pinned) {
+        //     for (var i = 0; i < points.length; i++) {
+        //         points[i].Y -= 35;
+        //     }
+        // }
+
         let pts: { X: number; Y: number; }[] = [];
         if (shape) { //if any of the shape are true
             pts = makePolygon(shape, points);
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 024532f90..aea2c542b 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -5,6 +5,7 @@ import { computedFn } from "mobx-utils";
 import { List } from "../../fields/List";
 import { Scripting } from "./Scripting";
 import { DocumentManager } from "./DocumentManager";
+import FormatShapePane from "../views/collections/collectionFreeForm/FormatShapePane";
 
 export namespace SelectionManager {
 
@@ -14,6 +15,8 @@ export namespace SelectionManager {
         SelectedDocuments: ObservableMap<DocumentView, boolean> = new ObservableMap();
         @action
         SelectDoc(docView: DocumentView, ctrlPressed: boolean): void {
+            console.log("select"
+            );
             // if doc is not in SelectedDocuments, add it
             if (!manager.SelectedDocuments.get(docView)) {
                 if (!ctrlPressed) {
@@ -29,6 +32,8 @@ export namespace SelectionManager {
                 manager.SelectedDocuments.set(docView, true);
             }
             Doc.UserDoc().activeSelection = new List(SelectionManager.SelectedDocuments().map(dv => dv.props.Document));
+            FormatShapePane.Instance.selected();
+
         }
         @action
         DeselectDoc(docView: DocumentView): void {
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index d83ac434c..cb6de1785 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -1,6 +1,7 @@
 import React = require("react");
 import { observable, action } from "mobx";
 import "./AntimodeMenu.scss";
+import { inherits } from "util";
 
 /**
  * This is an abstract class that serves as the base for a PDF-style or Marquee-style
@@ -147,6 +148,23 @@ export default abstract class AntimodeMenu extends React.Component {
         );
     }
 
+    protected getElementVert(buttons: JSX.Element[]) {
+        return (
+            <div className="antimodeMenu-cont" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu}
+                style={{
+                    left: this.Pinned ? undefined : this._left,
+                    top: this.Pinned ? 0 : this._top,
+                    right: this.Pinned ? 0 : undefined,
+                    height: "inherit",
+                    width: 200,
+                    opacity: this._opacity, transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay,
+                    position: this.Pinned ? "absolute" : undefined
+                }}>
+                {buttons}
+            </div>
+        );
+    }
+
 
 
     protected getElementWithRows(rows: JSX.Element[], numRows: number, hasDragger: boolean = true) {
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 941d7b44a..1a6b7ab90 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -224,6 +224,7 @@ export class ContextMenu extends React.Component {
     }
 
     render() {
+        console.log("context");
         if (!this._display) {
             return null;
         }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 4fda10926..8edd4c4a9 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -293,6 +293,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
             const doc = Document(element.rootDoc);
             if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
+                doc.rotation = Number(doc.rotation) + Number(angle);
+                console.log(doc.rotation);
                 const ink = Cast(doc.data, InkField)?.inkData;
                 if (ink) {
 
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index bfd659207..26a06090b 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -633,7 +633,8 @@ export default class GestureOverlay extends Touchable {
                 this.makePolygon(this.InkShape, false);
                 this.dispatchGesture(GestureUtils.Gestures.Stroke);
                 this._points = [];
-                if (this.InkShape !== "noRec") {
+                if (InkOptionsMenu.Instance._double === "") {
+
                     this.InkShape = "";
                 }
             }
@@ -687,12 +688,14 @@ export default class GestureOverlay extends Touchable {
         }
         //get out of ink mode after each stroke=
         console.log("now");
-        Doc.SetSelectedTool(InkTool.None);
-        InkOptionsMenu.Instance._selected = InkOptionsMenu.Instance._shapesNum;
-        SetActiveArrowStart("none");
-        GestureOverlay.Instance.SavedArrowStart = ActiveArrowStart();
-        SetActiveArrowEnd("none");
-        GestureOverlay.Instance.SavedArrowEnd = ActiveArrowEnd();
+        if (InkOptionsMenu.Instance._double === "") {
+            Doc.SetSelectedTool(InkTool.None);
+            InkOptionsMenu.Instance._selected = InkOptionsMenu.Instance._shapesNum;
+            SetActiveArrowStart("none");
+            GestureOverlay.Instance.SavedArrowStart = ActiveArrowStart();
+            SetActiveArrowEnd("none");
+            GestureOverlay.Instance.SavedArrowEnd = ActiveArrowEnd();
+        }
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
 
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index c32e76cec..4a7ec40be 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -15,6 +15,8 @@ import { FieldView, FieldViewProps } from "./nodes/FieldView";
 import React = require("react");
 import { Scripting } from "../util/Scripting";
 import { Doc } from "../../fields/Doc";
+import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane";
+import { action } from "mobx";
 
 library.add(faPaintBrush);
 
@@ -38,10 +40,19 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         this.props.Document.isInkMask = true;
     }
 
+    @action
+    private formatShape = () => {
+        console.log("Clicked");
+        FormatShapePane.Instance.Pinned = true;
+        FormatShapePane.Instance.selected();
+
+    }
+
     render() {
         TraceMobx();
         const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
-        const strokeWidth = Number(StrCast(this.layoutDoc.strokeWidth, ActiveInkWidth()));
+        // const strokeWidth = Number(StrCast(this.layoutDoc.strokeWidth, ActiveInkWidth()));
+        const strokeWidth = Number(this.layoutDoc.strokeWidth);
         const xs = data.map(p => p.X);
         const ys = data.map(p => p.Y);
         const left = Math.min(...xs) - strokeWidth / 2;
@@ -74,6 +85,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                 onContextMenu={() => {
                     ContextMenu.Instance.addItem({ description: "Analyze Stroke", event: this.analyzeStrokes, icon: "paint-brush" });
                     ContextMenu.Instance.addItem({ description: "Make Mask", event: this.makeMask, icon: "paint-brush" });
+                    ContextMenu.Instance.addItem({ description: "Format Shape", event: this.formatShape, icon: "paint-brush" });
+
                 }}
             ><defs>
                 </defs>
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index afdff4ed8..3eed44654 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -58,7 +58,7 @@ import { DocumentManager } from '../util/DocumentManager';
 import { DocumentLinksButton } from './nodes/DocumentLinksButton';
 import { LinkMenu } from './linking/LinkMenu';
 import { LinkDocPreview } from './nodes/LinkDocPreview';
-
+import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane";
 @observer
 export class MainView extends React.Component {
     public static Instance: MainView;
@@ -453,12 +453,13 @@ export class MainView extends React.Component {
 
     @computed get mainContent() {
         const sidebar = this.userDoc?.["tabs-panelContainer"];
-        console.log(InkOptionsMenu.Instance?.Pinned);
+        console.log('${ ANTIMODEMENU_HEIGHT }');
         return !this.userDoc || !(sidebar instanceof Doc) ? (null) : (
             <div className="mainView-mainContent" style={{
                 color: this.darkScheme ? "rgb(205,205,205)" : "black",
-                height: (RichTextMenu.Instance?.Pinned || InkOptionsMenu.Instance?.Pinned) ? `calc(100% - ${ANTIMODEMENU_HEIGHT})` : "100%"
-
+                //change to times 2 for both pinned
+                height: (RichTextMenu.Instance?.Pinned || InkOptionsMenu.Instance?.Pinned) ? (RichTextMenu.Instance?.Pinned && InkOptionsMenu.Instance?.Pinned) ? `calc(100% - 2*${ANTIMODEMENU_HEIGHT})` : `calc(100% - ${ANTIMODEMENU_HEIGHT})` : "100%",
+                width: (FormatShapePane.Instance?.Pinned) ? `calc(100% - 200px)` : "100%"
             }} >
                 <div style={{ display: "contents", flexDirection: "row", position: "relative" }}>
                     <div className="mainView-flyoutContainer" onPointerLeave={this.pointerLeaveDragger} style={{ width: this.flyoutWidth }}>
@@ -597,7 +598,10 @@ export class MainView extends React.Component {
             <DocumentDecorations />
             <InkOptionsMenu />
 
+
             <GestureOverlay >
+                <FormatShapePane />
+
 
                 <RichTextMenu key="rich" />
                 {this.mainContent}
@@ -608,6 +612,7 @@ export class MainView extends React.Component {
                 linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href}
                 addDocTab={LinkDocPreview.LinkInfo.addDocTab} /> : (null)}
             <ContextMenu />
+            <FormatShapePane />
             <RadialMenu />
             <PDFMenu />
             <MarqueeOptionsMenu />
diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.scss b/src/client/views/collections/collectionFreeForm/FormatShapePane.scss
new file mode 100644
index 000000000..5789c6c75
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.scss
@@ -0,0 +1,53 @@
+.antimodeMenu-button {
+    width: 200px;
+
+    .color-previewI {
+        width: 100%;
+        height: 40%;
+    }
+
+    .color-previewII {
+        width: 100%;
+        height: 100%;
+    }
+
+
+}
+
+.sketch-picker {
+    background: #323232;
+
+    .flexbox-fit {
+        background: #323232;
+    }
+}
+
+.btn-group {
+    display: grid;
+    grid-template-columns: auto auto auto auto;
+    /* Make the buttons appear below each other */
+}
+
+.btn-group-palette {
+    display: block;
+    /* Make the buttons appear below each other */
+}
+
+.btn-draw {
+    display: inline;
+    /* Make the buttons appear below each other */
+}
+
+.btn2-group {
+    display: block;
+    background: #323232;
+    grid-template-columns: auto;
+
+    /* Make the buttons appear below each other */
+    .antimodeMenu-button {
+        background: #323232;
+        display: block;
+
+
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
new file mode 100644
index 000000000..7b47b7ca4
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
@@ -0,0 +1,996 @@
+import React = require("react");
+import AntimodeMenu from "../../AntimodeMenu";
+import { observer } from "mobx-react";
+import { observable, action, computed } from "mobx";
+import "./FormatShapePane.scss";
+import { ActiveInkColor, ActiveInkBezierApprox, ActiveFillColor, ActiveArrowStart, ActiveArrowEnd, SetActiveInkWidth, SetActiveInkColor, SetActiveBezierApprox, SetActiveFillColor, SetActiveArrowStart, SetActiveArrowEnd, ActiveDash, SetActiveDash } from "../../InkingStroke";
+import { Scripting } from "../../../util/Scripting";
+import { InkTool, InkField } from "../../../../fields/InkField";
+import { ColorState } from "react-color";
+import { Utils } from "../../../../Utils";
+import GestureOverlay from "../../GestureOverlay";
+import { Doc } from "../../../../fields/Doc";
+import { SelectionManager } from "../../../util/SelectionManager";
+import { DocumentView } from "../../../views/nodes/DocumentView";
+import { Document } from "../../../../fields/documentSchemas";
+import { DocumentType } from "../../../documents/DocumentTypes";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
+import { faRulerCombined, faFillDrip, faPenNib } from "@fortawesome/free-solid-svg-icons";
+import { Cast, StrCast, BoolCast, NumCast } from "../../../../fields/Types";
+import e = require("express");
+import { ChangeEvent } from "mongodb";
+import { black } from "colors";
+
+
+library.add(faRulerCombined, faFillDrip, faPenNib);
+
+@observer
+export default class FormatShapePane extends AntimodeMenu {
+    static Instance: FormatShapePane;
+
+    private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF"];
+    private _width = ["1", "5", "10", "100"];
+    private _mode = ["fill-drip", "ruler-combined"];
+    private _subMenu = ["fill", "line", "size", "position"];
+    private _headIcon = ["⎯", "←"];
+    private _endIcon = ["⎯", "→"];
+    private _head = ["none", "arrowHead"];
+    private _end = ["none", "arrowEnd"];
+
+    @observable private _subOpen = [false, false, false, false];
+
+    @observable private collapsed: boolean = false;
+    @observable private currMode: string = "fill-drip";
+    @observable _fillBtn = false;
+    @observable _lineBtn = false;
+
+    @observable _selectDoc: DocumentView[] = [];
+
+    @observable _noFill = false;
+    @observable _solidFill = false;
+    @observable _noLine = false;
+    @observable _solidLine = false;
+    @observable _dashLine = false;
+    @observable _lock = false;
+
+    @observable _multiple = false;
+
+    @observable _widthBtn = false;
+
+    @observable _currWidth = "10";
+
+    @observable _single = false;
+    @observable _currFill = "#D0021B";
+    @observable _currColor = "#D0021B";
+
+    @observable _arrowHead = false;
+    @observable _arrowEnd = false;
+
+
+    @observable _currSizeHeight = "10";
+    @observable _currSizeWidth = "10";
+    @observable _currRotation = "10";
+
+    @observable _currPositionHorizontal = "10";
+    @observable _currPositionVertical = "10";
+
+
+    constructor(props: Readonly<{}>) {
+        super(props);
+        FormatShapePane.Instance = this;
+        this._canFade = false; // don't let the inking menu fade away
+        this.Pinned = BoolCast(Doc.UserDoc()["formatShapePane-pinned"]);
+
+    }
+
+    @action
+    toggleMenuPin = (e: React.MouseEvent) => {
+        Doc.UserDoc()["formatShapePane-pinned"] = this.Pinned = !this.Pinned;
+        if (!this.Pinned) {
+            // this.fadeOut(true);
+        }
+    }
+
+    @action
+    protected toggleCollapse = (e: React.MouseEvent) => {
+        this.collapsed = !this.collapsed;
+        setTimeout(() => {
+            const x = Math.min(this._left, window.innerWidth - FormatShapePane.Instance.width);
+            FormatShapePane.Instance.jumpTo(x, this._top, true);
+        }, 0);
+    }
+
+    @action
+    closePane = () => {
+        this.jumpTo(-300, -300);
+        this.Pinned = false;
+
+    }
+
+    @action
+    checkSame = () => {
+        const docs = SelectionManager.SelectedDocuments();
+        const inks: DocumentView[] = [];
+        for (var i = 0; i < docs.length; i++) {
+            if (Document(docs[i].rootDoc).type === DocumentType.INK) {
+                inks.push(docs[i]);
+            }
+        }
+        const firstWidth = Document(inks[0].rootDoc).strokeWidth;
+        const firstColor = Document(inks[0].rootDoc).color;
+        const firstFill = Document(inks[0].rootDoc).fillColor;
+        // const firstBezier
+        const firstRotation = Document(inks[0].rootDoc).rotation;
+        const firstArrowStart = Document(inks[0].rootDoc).arrowStart;
+        const firstArrowEnd = Document(inks[0].rootDoc).arrowEnd;
+        const firstDash = Document(inks[0].rootDoc).dash;
+        const firstWidthSize = Document(inks[0].rootDoc)._width;
+        const firstHeightSize = Document(inks[0].rootDoc)._height;
+        const firstHorizontal = Document(inks[0].rootDoc).x;
+        const firstVertical = Document(inks[0].rootDoc).y;
+        this._noFill = Document(inks[0].rootDoc).fillColor === "none" ? true : false;
+        this._solidFill = Document(inks[0].rootDoc).fillColor === "none" ? false : true;
+        this._noLine = Document(inks[0].rootDoc).color === "none" ? true : false;
+        if (Document(inks[0].rootDoc).color !== "none") {
+            this._solidLine = true;
+            this._dashLine = true;
+            if (Document(inks[0].rootDoc).dash === "0") {
+                this._dashLine = false;
+            } else {
+                this._solidLine = false;
+
+            }
+        }
+        // this._solidLine = (Document(inks[0].rootDoc).color === "none") ? false : Document(inks[0].rootDoc).dash === 0 ? true : false;
+        // this._dashLine = (Document(inks[0].rootDoc).color === "none") ? false : Document(inks[0].rootDoc).dash !== 0 ? true : false;
+        // // this._widthBtn = false;    
+        this._currWidth = String(Document(inks[0].rootDoc).strokeWidth);
+        // this._single = false;
+        this._currFill = String(Document(inks[0].rootDoc).fillColor);
+        this._currColor = String(Document(inks[0].rootDoc).color);
+        this._arrowHead = Document(inks[0].rootDoc).arrowStart === "none" ? false : true;
+        this._arrowEnd = Document(inks[0].rootDoc).arrowEnd === "none" ? false : true;
+        this._currSizeHeight = String(Document(inks[0].rootDoc)._height);
+        this._currSizeWidth = String(Document(inks[0].rootDoc)._width);
+        this._currRotation = String(Document(inks[0].rootDoc).rotation);
+        this._currPositionHorizontal = String(Document(inks[0].rootDoc).x);
+        this._currPositionVertical = String(Document(inks[0].rootDoc).y);
+        for (var i = 0; i < inks.length; i++) {
+            if (Document(inks[i].rootDoc).strokeWidth !== firstWidth) {
+                this._currWidth = "";
+            }
+            if (Document(inks[i].rootDoc).color !== firstColor) {
+                this._noLine = false;
+                this._solidLine = false;
+                this._dashLine = false;
+            }
+            if (Document(inks[i].rootDoc).fillColor !== firstFill) {
+                this._solidFill = false;
+                this._noFill = false;
+            }
+            if (Document(inks[i].rootDoc).arrowStart !== firstArrowStart) {
+                this._arrowHead = false;
+            }
+            if (Document(inks[i].rootDoc).arrowEnd !== firstArrowEnd) {
+                this._arrowEnd = false;
+            }
+            if (Document(inks[i].rootDoc).x !== firstHorizontal) {
+                this._currPositionHorizontal = "";
+            }
+            if (Document(inks[i].rootDoc).y !== firstVertical) {
+                this._currPositionVertical = "";
+            }
+            if (Document(inks[i].rootDoc)._width !== firstWidthSize) {
+                this._currSizeWidth = "";
+            }
+            if (Document(inks[i].rootDoc)._height !== firstHeightSize) {
+                this._currSizeHeight = "";
+            }
+            if (Document(inks[i].rootDoc).rotation !== firstRotation) {
+                this._currRotation = "";
+            }
+        }
+
+
+
+    }
+
+    @action
+    upDownButtons = (dirs: string, field: string) => {
+        SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
+            const doc = Document(element.rootDoc);
+            if (doc.type === DocumentType.INK) {
+                switch (field) {
+                    case "width":
+                        if (doc.strokeWidth) {
+                            doc.strokeWidth = dirs === "up" ? doc.strokeWidth + 1 : doc.strokeWidth - 1;
+                            SetActiveInkWidth(String(doc.strokeWidth));
+
+                        }
+                        break;
+                    case "sizeWidth":
+                        if (doc._width && doc._height) {
+                            const oldWidth = doc._width;
+                            const oldHeight = doc._height;
+
+                            doc._width = dirs === "up" ? doc._width + 10 : doc._width - 10;
+                            if (this._lock) {
+                                doc._height = (doc._width * oldHeight) / oldWidth;
+                            }
+                        }
+                        break;
+                    case "sizeHeight":
+                        if (doc._width && doc._height) {
+                            const oldWidth = doc._width;
+                            const oldHeight = doc._height;
+                            doc._height = dirs === "up" ? doc._height + 10 : doc._height - 10;
+                            if (this._lock) {
+                                doc._width = (doc._height * oldWidth) / oldHeight;
+                            }
+                        }
+                        break;
+                    case "horizontal":
+                        if (doc.x) {
+                            doc.x = dirs === "up" ? doc.x + 10 : doc.x - 10;
+                        }
+                        break;
+                    case "vertical":
+                        if (doc.y) {
+                            doc.y = dirs === "up" ? doc.y + 10 : doc.y - 10;
+                        }
+                    case "rotation":
+
+                        this.rotate(dirs === "up" ? Number(doc.rotation) + Number(0.1) : Number(doc.rotation) - Number(0.1));
+
+                        break;
+                    default:
+                        break;
+                }
+                this.selected();
+
+            }
+        }));
+    }
+
+    @action
+    editProperties = (value: any, field: string) => {
+        SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
+            const doc = Document(element.rootDoc);
+            if (doc.type === DocumentType.INK) {
+                switch (field) {
+                    case "width":
+                        SetActiveInkWidth(value);
+                        doc.strokeWidth = Number(value);
+                        break;
+                    case "color":
+                        doc.color = String(value);
+                        break;
+                    case "fill":
+                        doc.fillColor = String(value);
+                        break;
+                    case "bezier":
+                        // doc.strokeBezier === 300 ? doc.strokeBezier = 0 : doc.strokeBezier = 300;
+                        break;
+                    case "arrowStart":
+                        doc.arrowStart = String(value);
+                        break;
+                    case "arrowEnd":
+                        doc.arrowEnd = String(value);
+                        break;
+                    case "dash":
+                        doc.dash = String(value);
+                        break;
+                    case "widthSize":
+                        if (doc._width && doc._height) {
+                            const oldWidth = doc._width;
+                            const oldHeight = doc._height;
+
+                            doc._width = Number(value);
+                            if (this._lock) {
+                                doc._height = (doc._width * oldHeight) / oldWidth;
+                            }
+                        }
+                        break;
+                    case "heightSize":
+                        if (doc._width && doc._height) {
+                            const oldWidth = doc._width;
+                            const oldHeight = doc._height;
+
+                            doc._height = Number(value);
+                            if (this._lock) {
+                                doc._width = (doc._height * oldWidth) / oldHeight;
+                            }
+                        }
+                        break;
+                    case "horizontal":
+                        doc.x = Number(value);
+                        break;
+                    case "vertical":
+                        doc.y = Number(value);
+
+                        break;
+                    default:
+                        break;
+                }
+            }
+            this.selected();
+        }));
+        this.checkSame();
+
+    }
+
+    @computed get close() {
+        const close = <button
+            className="antimodeMenu-button"
+            key="close"
+            onPointerDown={action(() => { this.closePane(); })}
+            style={{ right: 0, position: "absolute" }}>
+            X
+                </button>;
+        return close;
+    }
+
+    @computed get modes() {
+        const modes = <div className="antimodeMenu-button-tab">
+            {this._mode.map(mode => {
+                return <button
+                    className="antimodeMenu-button"
+                    key={mode}
+                    onPointerDown={action(() => { console.log("mode"); this.currMode = mode; })}
+                    style={{ backgroundColor: this.currMode === mode ? "121212" : "", position: "relative", top: 30 }}>
+                    <FontAwesomeIcon icon={mode as IconProp} size="lg" />
+                </button>;
+            })
+            }</div>;
+        return modes;
+    }
+
+    @action
+    selected = () => {
+        this._selectDoc = SelectionManager.SelectedDocuments();
+        if (this._selectDoc.length === 1 && Document(this._selectDoc[0].rootDoc).type === DocumentType.INK) {
+            this._single = true;
+            const doc = Document(this._selectDoc[0].rootDoc);
+            if (doc.type === DocumentType.INK) {
+                console.log(doc.fillColor);
+                if (doc.fillColor === "none") {
+                    this._noFill = true;
+                    this._solidFill = false;
+                } else {
+                    this._solidFill = true;
+                    this._noFill = false;
+                    this._currFill = String(doc.fillColor);
+                }
+                if (doc.color === "none") {
+                    this._noLine = true;
+                    this._solidLine = false;
+                    this._dashLine = false;
+                } else {
+                    console.log(doc.strokeWidth);
+                    this._currWidth = String(doc.strokeWidth);
+                    this._currColor = String(doc.color);
+                    if (doc.dash === "0") {
+                        this._solidLine = true;
+                        this._noLine = false;
+                        this._dashLine = false;
+                    } else {
+                        this._dashLine = true;
+                        this._noLine = false;
+                        this._solidLine = false;
+                    }
+
+                    this._arrowHead = doc.arrowStart === "none" ? false : true;
+                    this._arrowEnd = doc.arrowEnd === "none" ? false : true;
+
+
+                    this._currPositionHorizontal = String(doc.x);
+                    this._currPositionVertical = String(doc.y);
+                    console.log(doc.rotation);
+                    this._currRotation = String(doc.rotation);
+                    this._currSizeHeight = String(doc._height);
+                    this._currSizeWidth = String(doc._width);
+                }
+
+            }
+        } else {
+            this._noFill = false;
+            this._solidFill = false;
+            this._single = false;
+            this._currFill = "#D0021B";
+            this._noLine = false;
+            this._solidLine = false;
+            this._dashLine = false;
+            this._currColor = "#D0021B";
+            this._arrowHead = false;
+            this._arrowEnd = false;
+            this._currPositionHorizontal = "";
+            this._currPositionVertical = "";
+            this._currRotation = "";
+            this._currSizeHeight = "";
+            this._currSizeWidth = "";
+        }
+        this.checkSame();
+    }
+
+    @action
+    rotate = (degrees: number) => {
+        SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
+            const doc = Document(element.rootDoc);
+            if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
+                // doc.rotation = (Number(doc.rotation) + (Number(angle) * 180 / Math.PI));
+                // console.log(doc.rotation);
+                const angle = Number(degrees) - Number(doc.rotation);
+                doc.rotation = Number(degrees);
+                const ink = Cast(doc.data, InkField)?.inkData;
+                if (ink) {
+                    const xs = ink.map(p => p.X);
+                    const ys = ink.map(p => p.Y);
+                    const left = Math.min(...xs);
+                    const top = Math.min(...ys);
+                    const right = Math.max(...xs);
+                    const bottom = Math.max(...ys);
+                    const _centerPoints: { X: number, Y: number }[] = [];
+                    _centerPoints.push({ X: left, Y: top });
+
+                    const newPoints: { X: number, Y: number }[] = [];
+                    for (var i = 0; i < ink.length; i++) {
+                        const newX = Math.cos(angle) * (ink[i].X - _centerPoints[0].X) - Math.sin(angle) * (ink[i].Y - _centerPoints[0].Y) + _centerPoints[0].X;
+                        const newY = Math.sin(angle) * (ink[i].X - _centerPoints[0].X) + Math.cos(angle) * (ink[i].Y - _centerPoints[0].Y) + _centerPoints[0].Y;
+                        newPoints.push({ X: newX, Y: newY });
+                    }
+                    doc.data = new InkField(newPoints);
+                    const xs2 = newPoints.map(p => p.X);
+                    const ys2 = newPoints.map(p => p.Y);
+                    const left2 = Math.min(...xs2);
+                    const top2 = Math.min(...ys2);
+                    const right2 = Math.max(...xs2);
+                    const bottom2 = Math.max(...ys2);
+
+                    doc._height = (bottom2 - top2) * element.props.ScreenToLocalTransform().Scale;
+                    doc._width = (right2 - left2) * element.props.ScreenToLocalTransform().Scale;
+
+                }
+            }
+        }));
+    }
+
+    @action
+    toggle = (check: string) => {
+        switch (check) {
+            case "noFill":
+                if (!this._noFill) {
+                    this._noFill = true;
+                    this._solidFill = false;
+                }
+                break;
+            case "solidFill":
+                if (!this._solidFill) {
+                    this._solidFill = true;
+                    this._noFill = false;
+                    if (this._currFill === "none") {
+                        this._currFill = "#D0021B";
+                    }
+                }
+                break;
+            case "noLine":
+                if (!this._noLine) {
+                    this._noLine = true;
+                    this._solidLine = false;
+                    this._dashLine = false;
+                }
+                break;
+            case "solidLine":
+                if (!this._solidLine) {
+                    this._solidLine = true;
+                    this._noLine = false;
+                    this._dashLine = false;
+                    if (this._currColor === "none") {
+                        this._currColor = "#D0021B";
+                    }
+                }
+                break;
+            case "dashLine":
+                if (!this._dashLine) {
+                    this._dashLine = true;
+                    this._solidLine = false;
+                    this._noLine = false;
+                    if (this._currColor === "none") {
+                        this._currColor = "#D0021B";
+                    }
+                }
+                break;
+            case "lock":
+                if (this._lock) {
+                    this._lock = false;
+                } else {
+                    this._lock = true;
+                }
+                break;
+            case "arrowHead":
+                this._arrowHead = this._arrowHead ? false : true;
+                break;
+            case "arrowEnd":
+                this._arrowEnd = this._arrowEnd ? false : true;
+                break;
+            default:
+                break;
+        }
+    }
+
+    @computed get subMenu() {
+        const fillCheck = <div style={{ width: "inherit", backgroundColor: "#323232", color: "white", }}>
+            <input id="nofill" style={{ width: "inherit", position: "absolute" }} type="checkbox" checked={this._noFill} onChange={action(() => { this.toggle("noFill"); this.editProperties("none", "fill"); })} />
+            No Fill
+            <br></br>
+            <input id="solidfill" style={{ width: "inherit", position: "absolute" }} type="checkbox" checked={this._solidFill} onChange={action(() => { this.toggle("solidFill"); this.editProperties(this._currFill, "fill"); })} />
+            Solid Fill
+            <br></br>
+            <br></br>
+            {this._solidFill ? "Color" : ""}
+            {this._solidFill ? this.fillButton : ""}
+            {this._fillBtn && this._solidFill ? this.fillPicker : ""}
+
+        </div>;
+        const arrows = <> <input id="arrowHead" style={{ width: "inherit", position: "absolute" }} type="checkbox" checked={this._arrowHead} onChange={action(() => { this.toggle("arrowHead"); this.editProperties(this._arrowHead ? "arrowHead" : "none", "arrowStart"); })} />
+         Arrow Head
+            <br></br>
+
+            <input id="arrowEnd" style={{ width: "inherit", position: "absolute" }} type="checkbox" checked={this._arrowEnd} onChange={action(() => { this.toggle("arrowEnd"); this.editProperties(this._arrowEnd ? "arrowEnd" : "none", "arrowEnd"); })} />
+         Arrow End
+            <br></br></>;
+        const lineCheck = <div style={{ width: "inherit", backgroundColor: "#323232", color: "white", }}>
+            <input id="noLine" style={{ width: "inherit", position: "absolute" }} type="checkbox" checked={this._noLine} onChange={action(() => { this.toggle("noLine"); this.editProperties("none", "color"); })} />
+                No Line
+                <br></br>
+            <input id="solidLine" style={{ width: "inherit", position: "absolute" }} type="checkbox" checked={this._solidLine} onChange={action(() => { this.toggle("solidLine"); this.editProperties(this._currColor, "color"); this.editProperties("0", "dash"); })} />
+                Solid Line
+                <br></br>
+            <input id="dashLine" style={{ width: "inherit", position: "absolute" }} type="checkbox" checked={this._dashLine} onChange={action(() => { this.toggle("dashLine"); this.editProperties(this._currColor, "color"); this.editProperties("2", "dash"); })} />
+                Dash Line
+                <br></br>
+            <br></br>
+            {(this._solidLine || this._dashLine) ? "Color" : ""}
+            {(this._solidLine || this._dashLine) ? this.lineButton : ""}
+            {this._lineBtn && (this._solidLine || this._dashLine) ? this.linePicker : ""}
+            <br></br>
+            {(this._solidLine || this._dashLine) ? "Width" : ""}
+            {(this._solidLine || this._dashLine) ? this.widthInput : ""}
+            <br></br>
+            <br></br>
+            {(this._solidLine || this._dashLine) ? arrows : ""}
+
+        </div>;
+
+        const sizeCheck = <div style={{ width: "inherit", backgroundColor: "#323232", color: "white", }}>
+            Height {this.sizeHeightInput}
+            <br></br>
+            <br></br>
+
+
+            Width {this.sizeWidthInput}
+            <br></br>
+            <br></br>
+
+            <input id="lock" style={{ width: "inherit", position: "absolute", right: 0 }} type="checkbox" checked={this._lock} onChange={action(() => { this.toggle("lock"); })} />
+                Lock Ratio
+            <br></br>
+            <br></br>
+
+
+            Rotation {this.rotationInput}
+            <br></br>
+            <br></br>
+
+
+        </div>;
+        const positionCheck = <div style={{ width: "inherit", backgroundColor: "#323232", color: "white", }}>
+            Horizontal {this.positionHorizontalInput}
+            <br></br>
+            <br></br>
+
+            Vertical {this.positionVerticalInput}
+            <br></br>
+            <br></br>
+
+
+        </div>;
+
+        const subMenu = <div className="antimodeMenu-sub" style={{ position: "absolute", width: "inherit", top: 60 }}>
+            {this._subMenu.map((subMenu, i) => {
+                if (subMenu === "fill" || subMenu === "line") {
+                    return <div style={{ width: "inherit" }}><button
+                        className="antimodeMenu-button"
+                        key={subMenu}
+                        onPointerDown={action(() => { this._subOpen[i] = this._subOpen[i] ? false : true; })}
+                        style={{ backgroundColor: "121212", display: this.currMode === "fill-drip" ? "" : "none", width: "inherit", textAlign: "left" }}>
+                        {this._subOpen[i] ? "▼" : "▶︎"}
+                        {subMenu}
+                    </button>
+                        {this.currMode === "fill-drip" && subMenu === "fill" && this._subOpen[0] ? fillCheck : ""}
+                        {this.currMode === "fill-drip" && subMenu === "line" && this._subOpen[1] ? lineCheck : ""}
+
+                    </div>;
+                }
+                else if (subMenu === "size" || subMenu === "position") {
+                    return <div style={{ width: "inherit" }}><button
+                        className="antimodeMenu-button"
+                        key={subMenu}
+                        onPointerDown={action(() => { this._subOpen[i] = this._subOpen[i] ? false : true; })}
+                        style={{ backgroundColor: "121212", display: this.currMode === "ruler-combined" ? "" : "none", width: "inherit", textAlign: "left" }}>
+                        {this._subOpen[i] ? "▼" : "▶︎"}
+                        {subMenu}
+                    </button>
+                        {this.currMode === "ruler-combined" && subMenu === "size" && this._subOpen[2] ? sizeCheck : ""}
+                        {this.currMode === "ruler-combined" && subMenu === "position" && this._subOpen[3] ? positionCheck : ""}
+
+                    </div>
+                        ;
+
+                }
+            })
+            }</div>;
+        return subMenu;
+    }
+
+    @computed get fillButton() {
+        const fillButton = <><button
+            className="antimodeMenu-button"
+            key="fill"
+            title="fillChanger"
+            onPointerDown={action(e => this._fillBtn = !this._fillBtn)}
+            style={{
+                // backgroundColor: "121212",
+                position: "absolute", right: 80
+            }}>
+            <FontAwesomeIcon icon="fill-drip" size="lg" />
+            <div className="color-previewI" style={{ backgroundColor: this._currFill ?? "121212" }}></div>
+        </button>
+            <br></br>
+            <br></br></>;
+        return fillButton;
+    }
+    @computed get fillPicker() {
+        const fillPicker = <div className="btn-group-palette" key="fill" >
+            {this._palette.map(color => {
+                return <button
+                    className="antimodeMenu-button"
+                    key={color}
+                    onPointerDown={action(() => { this._currFill = color; this.editProperties(color, "fill"); })}
+                    style={{
+                        // backgroundColor: this._fillBtn ? "121212" : "",
+                        zIndex: 1001
+                    }}>
+                    <div className="color-previewII" style={{ backgroundColor: color }}></div>
+                </button>;
+            })}
+
+        </div>;
+        return fillPicker;
+    }
+
+    @computed get lineButton() {
+        const lineButton = <><button
+            className="antimodeMenu-button"
+            key="line"
+            title="lineChanger"
+            onPointerDown={action(e => this._lineBtn = !this._lineBtn)}
+            style={{
+                // backgroundColor: "121212",
+                position: "absolute", right: 80
+            }}>
+            <FontAwesomeIcon icon="pen-nib" size="lg" />
+            <div className="color-previewI" style={{ backgroundColor: this._currColor ?? "121212" }}></div>
+        </button>
+            <br></br>
+            <br></br></>;
+        return lineButton;
+    }
+    @computed get linePicker() {
+        const linePicker = <div className="btn-group-palette" key="line" >
+            {this._palette.map(color => {
+                return <button
+                    className="antimodeMenu-button"
+                    key={color}
+                    onPointerDown={action(() => { this._currColor = color; this.editProperties(color, "color"); })}
+                    style={{
+                        // backgroundColor: this._lineBtn ? "121212" : "",
+                        zIndex: 1001
+                    }}>
+                    <div className="color-previewII" style={{ backgroundColor: color }}></div>
+                </button>;
+            })}
+
+        </div>;
+        return linePicker;
+    }
+    @computed get widthInput() {
+        const widthInput = <>
+            <input
+                style={{ color: "black", width: 80, position: "absolute", right: 20 }}
+                type="text" value={this._currWidth}
+                onChange={e => this.onChange(e.target.value, "width")}
+                autoFocus></input>  <button
+                    className="antiMenu-Buttonup"
+                    key="up"
+                    onPointerDown={action(() => { this.upDownButtons("up", "width"); })}
+                    style={{ position: "absolute", width: 20, height: 10, right: 0, padding: 0 }}>
+                ˄
+            </button>
+            <br />
+            <button
+                className="antiMenu-Buttonup"
+                key="up"
+                onPointerDown={action(() => { this.upDownButtons("down", "width"); })}
+                style={{ position: "absolute", width: 20, height: 10, right: 0, padding: 0, marginTop: -8 }}>
+                ˅
+            </button></>;
+        return widthInput;
+    }
+    @computed get sizeHeightInput() {
+        const sizeHeightInput = <>
+            <input
+                style={{ color: "black", width: 80, position: "absolute", right: 20 }}
+                type="text" value={this._currSizeHeight}
+                onChange={e => this.onChange(e.target.value, "sizeHeight")}
+                autoFocus></input>
+            <button
+                className="antiMenu-Buttonup"
+                key="up"
+                onPointerDown={action(() => { this.upDownButtons("up", "sizeHeight"); })}
+                style={{ position: "absolute", width: 20, height: 10, right: 0, padding: 0 }}>
+                ˄
+            </button>
+            <br />
+            <button
+                className="antiMenu-Buttonup"
+                key="up"
+                onPointerDown={action(() => { this.upDownButtons("down", "sizeHeight"); })}
+                style={{ position: "absolute", width: 20, height: 10, right: 0, padding: 0, marginTop: -8 }}>
+                ˅
+            </button>
+        </>;
+        return sizeHeightInput;
+    }
+
+    @computed get sizeWidthInput() {
+        const sizeWidthInput = <>
+            <input
+                style={{ color: "black", width: 80, position: "absolute", right: 20 }}
+                type="text" value={this._currSizeWidth}
+                onChange={e => this.onChange(e.target.value, "sizeWidth")}
+                autoFocus></input>
+            <button
+                className="antiMenu-Buttonup"
+                key="up"
+                onPointerDown={action(() => { this.upDownButtons("up", "sizeWidth"); })}
+                style={{ position: "absolute", width: 20, height: 10, right: 0, padding: 0 }}>
+                ˄
+            </button>
+            <br></br>
+            <button
+                className="antiMenu-Buttonup"
+                key="up"
+                onPointerDown={action(() => { this.upDownButtons("down", "sizeWidth"); })}
+                style={{ position: "absolute", width: 20, height: 10, right: 0, padding: 0, marginTop: -8 }}>
+                ˅
+            </button></>;
+        return sizeWidthInput;
+    }
+
+    @computed get rotationInput() {
+        const rotationInput =
+            <>
+                <input
+                    style={{ color: "black", width: 80, position: "absolute", right: 20 }}
+                    type="text" value={this._currRotation}
+                    onChange={e => this.onChange(e.target.value, "rotation")}
+                    autoFocus></input>
+                <button
+                    className="antiMenu-Buttonup"
+                    key="up"
+                    onPointerDown={action(() => { this.upDownButtons("up", "rotation"); })}
+                    style={{ position: "absolute", width: 20, height: 10, right: 0, padding: 0 }}>
+                    ˄
+            </button>
+                <br></br>
+                <button
+                    className="antiMenu-Buttonup"
+                    key="up"
+                    onPointerDown={action(() => { this.upDownButtons("down", "rotation"); })}
+                    style={{ position: "absolute", width: 20, height: 10, right: 0, padding: 0, marginTop: -8 }}>
+                    ˅
+            </button></>;
+        return rotationInput;
+    }
+
+    @computed get positionHorizontalInput() {
+        const positionHorizontalInput =
+            <><input
+                style={{ color: "black", width: 80, position: "absolute", right: 20 }}
+                type="text" value={this._currPositionHorizontal}
+                onChange={e => this.onChange(e.target.value, "positionHorizontal")}
+                autoFocus></input>
+                <button
+                    className="antiMenu-Buttonup"
+                    key="up"
+                    onPointerDown={action(() => { this.upDownButtons("up", "horizontal"); })}
+                    style={{ position: "absolute", width: 20, height: 10, right: 0, padding: 0 }}>
+                    ˄
+            </button>
+                <br></br>
+                <button
+                    className="antiMenu-Buttonup"
+                    key="up"
+                    onPointerDown={action(() => { this.upDownButtons("down", "horizontal"); })}
+                    style={{ position: "absolute", width: 20, height: 10, right: 0, padding: 0, marginTop: -8 }}>
+                    ˅
+            </button></>;
+        return positionHorizontalInput;
+    }
+
+    @computed get positionVerticalInput() {
+        const positionVerticalInput =
+            <><input
+                style={{ color: "black", width: 80, position: "absolute", right: 20 }}
+                type="text" value={this._currPositionVertical}
+                onChange={e => this.onChange(e.target.value, "positionVertical")}
+                autoFocus></input>
+                <button
+                    className="antiMenu-Buttonup"
+                    key="up"
+                    onPointerDown={action(() => { this.upDownButtons("down", "vertical"); })}
+                    style={{ position: "absolute", width: 20, height: 10, right: 0, padding: 0 }}>
+                    ˄
+            </button>
+                <br></br>
+                <button
+                    className="antiMenu-Buttonup"
+                    key="up"
+                    onPointerDown={action(() => { this.upDownButtons("up", "vertical"); })}
+                    style={{ position: "absolute", width: 20, height: 10, right: 0, padding: 0, marginTop: -8 }}>
+                    ˅
+            </button></>;
+        return positionVerticalInput;
+    }
+
+
+    @action
+    onChange = (val: string, property: string): void => {
+        if (!Number.isNaN(Number(val)) && Number(val) !== null && val !== " ") {
+
+            // if (val === "") {
+            //     val = "0";
+            // }
+            switch (property) {
+                case "width":
+                    this._currWidth = val;
+                    if (val !== "") {
+                        this.editProperties(this._currWidth, "width");
+                    }
+                    break;
+                case "sizeHeight":
+                    this._currSizeHeight = val;
+                    if (val !== "") {
+                        this.editProperties(this._currSizeHeight, "heightSize");
+                    }
+                    break;
+                case "sizeWidth":
+                    this._currSizeWidth = val;
+                    if (val !== "") {
+
+                        this.editProperties(this._currSizeWidth, "widthSize");
+                    }
+                    break;
+                case "rotation":
+
+                    this._currRotation = val;
+                    if (val !== "") {
+
+                        this.rotate(Number(val));
+                    }
+                    break;
+                case "positionHorizontal":
+                    this._currPositionHorizontal = val; if (val !== "") {
+
+                        this.editProperties(this._currPositionHorizontal, "horizontal");
+                    }
+
+                    break;
+                case "positionVertical":
+                    this._currPositionVertical = val;
+                    if (val !== "") {
+
+                        this.editProperties(this._currPositionVertical, "vertical");
+                    }
+
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+
+
+
+    // @action
+    // changeWidth = (event: React.ChangeEvent<HTMLInputElement>) => {
+    //     console.log(event.target.value);
+    //     this._currWidth = NumCast(event.target.value);
+
+    // }
+
+    @computed get upDown() {
+        const upDown = <div>
+            <button
+                className="antiMenu-Button"
+                key="up"
+                onPointerDown={action(() => { console.log("up"); })}>
+                up
+            </button>
+            <br></br>
+            <button
+                className="antiMenu-Button"
+                key="up"
+                onPointerDown={action(() => { console.log("dpwm"); })}>
+                down
+            </button>
+        </div>;
+        return upDown;
+    }
+
+
+    @computed get widthPicker() {
+        var widthPicker = <button
+            className="antimodeMenu-button"
+            key="width"
+            onPointerDown={action(e => this._widthBtn = !this._widthBtn)}
+            style={{ backgroundColor: this._widthBtn ? "121212" : "" }}>
+            <FontAwesomeIcon icon="bars" size="lg" />
+        </button>;
+        if (this._widthBtn) {
+            widthPicker = <div className="btn2-group" key="width">
+                {widthPicker}
+                {this._width.map(wid => {
+                    return <button
+                        className="antimodeMenu-button"
+                        key={wid}
+                        onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; this.editProperties(wid, "width"); })}
+                        style={{ backgroundColor: this._widthBtn ? "121212" : "" }}>
+                        {wid}
+                    </button>;
+                })}
+            </div>;
+        }
+        return widthPicker;
+    }
+
+
+
+    render() {
+        const buttons = [
+
+            this.close,
+            this.modes,
+            this.subMenu
+
+        ];
+
+        return this.getElementVert(buttons);
+    }
+}
+Scripting.addGlobal(function activatePen2(penBtn: any) {
+    if (penBtn) {
+        //no longer changes to inkmode
+        // Doc.SetSelectedTool(InkTool.Pen);
+        FormatShapePane.Instance.jumpTo(300, 300);
+        FormatShapePane.Instance.Pinned = true;
+
+    } else {
+        // Doc.SetSelectedTool(InkTool.None);
+        FormatShapePane.Instance.Pinned = false;
+        FormatShapePane.Instance.fadeOut(true);
+
+    }
+});
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
index 7329f4fc1..f739fcc8c 100644
--- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
+++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
@@ -32,7 +32,7 @@
 }
 
 .btn2-group {
-    display: block;
+    display: grid;
     background: #323232;
     grid-template-columns: auto;
 
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
index 7de70b8cd..e4f3248d0 100644
--- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
@@ -36,14 +36,15 @@ export default class InkOptionsMenu extends AntimodeMenu {
     // private _arrowEnd = ["none", "arrowEnd", "none", "dot", "none"];
     // private _arrowIcons = ["→", "↔︎", "•", "••", " "];
     private _draw = ["⎯", "→", "↔︎", "∿", "↝", "↭", "ロ", "O", "∆"];
-    private _head = ["none", "arrowHead", "arrowHead", "none", "arrowHead", "arrowHead", "none", "none", "none"];
-    private _end = ["none", "none", "arrowEnd", "none", "none", "arrowEnd", "none", "none", "none"];
+    private _head = ["none", "none", "arrowHead", "none", "none", "arrowHead", "none", "none", "none"];
+    private _end = ["none", "arrowEnd", "arrowEnd", "none", "arrowEnd", "arrowEnd", "none", "none", "none"];
     private _shape = ["", "", "", "", "", "", "rectangle", "circle", "triangle"];
 
     @observable _shapesNum = this._shape.length;
     @observable _selected = this._shapesNum;
 
     @observable private collapsed: boolean = false;
+    @observable _double = "";
 
     @observable _colorBtn = false;
     @observable _widthBtn = false;
@@ -155,7 +156,7 @@ export default class InkOptionsMenu extends AntimodeMenu {
                     className="antimodeMenu-button"
                     key={icon}
                     onPointerDown={action(() => {
-                        console.log(this._selected);
+                        this._double = "";
 
                         if (this._selected !== i) {
                             this._selected = i;
@@ -177,6 +178,31 @@ export default class InkOptionsMenu extends AntimodeMenu {
                         }
                         console.log(this._selected);
 
+                    })}
+                    onDoubleClick={action(() => {
+                        console.log("double");
+                        this._double = this._draw[i];
+                        if (this._selected !== i) {
+                            this._selected = i;
+                            Doc.SetSelectedTool(InkTool.Pen);
+                            SetActiveArrowStart(this._head[i]);
+                            SetActiveArrowEnd(this._end[i]);
+                            SetActiveBezierApprox("300");
+
+                            //  this.editProperties(this._head[i], "arrowStart"), this.editProperties(this._end[i], "arrowEnd"); 
+                            GestureOverlay.Instance.InkShape = this._shape[i];
+                        } else {
+                            this._selected = this._shapesNum;
+                            Doc.SetSelectedTool(InkTool.None);
+                            SetActiveArrowStart("none");
+                            SetActiveArrowEnd("none");
+                            GestureOverlay.Instance.InkShape = "";
+                            SetActiveBezierApprox("0");
+
+                        }
+
+
+
                     })}
                     style={{ backgroundColor: i === this._selected ? "121212" : "", fontSize: "20" }}>
                     {this._draw[i]}
@@ -235,7 +261,7 @@ export default class InkOptionsMenu extends AntimodeMenu {
                         className="antimodeMenu-button"
                         key={wid}
                         onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; this.editProperties(wid, "width"); })}
-                        style={{ backgroundColor: this._widthBtn ? "121212" : "" }}>
+                        style={{ backgroundColor: this._widthBtn ? "121212" : "", zIndex: 1001 }}>
                         {wid}
                     </button>;
                 })}
@@ -265,7 +291,7 @@ export default class InkOptionsMenu extends AntimodeMenu {
                         className="antimodeMenu-button"
                         key={color}
                         onPointerDown={action(() => { this.changeColor(color, "color"); this._colorBtn = false; this.editProperties(color, "color"); })}
-                        style={{ backgroundColor: this._colorBtn ? "121212" : "" }}>
+                        style={{ backgroundColor: this._colorBtn ? "121212" : "", zIndex: 1001 }}>
                         {/* <FontAwesomeIcon icon="pen-nib" size="lg" /> */}
                         <div className="color-previewII" style={{ backgroundColor: color }}></div>
                     </button>;
@@ -286,14 +312,14 @@ export default class InkOptionsMenu extends AntimodeMenu {
             <div className="color-previewI" style={{ backgroundColor: ActiveFillColor() ?? "121212" }}></div>
         </button>;
         if (this._fillBtn) {
-            fillPicker = <div className="btn-group" key="fill">
+            fillPicker = <div className="btn-group" key="fill" >
                 {fillPicker}
                 {this._palette.map(color => {
                     return <button
                         className="antimodeMenu-button"
                         key={color}
                         onPointerDown={action(() => { this.changeColor(color, "fill"); this._fillBtn = false; this.editProperties(color, "fill"); })}
-                        style={{ backgroundColor: this._fillBtn ? "121212" : "" }}>
+                        style={{ backgroundColor: this._fillBtn ? "121212" : "", zIndex: 1001 }}>
                         <div className="color-previewII" style={{ backgroundColor: color }}></div>
                     </button>;
                 })}
-- 
cgit v1.2.3-70-g09d2


From 39836c84bb518b8497925434190309a1e9eafe55 Mon Sep 17 00:00:00 2001
From: yunahi <60233430+yunahi@users.noreply.github.com>
Date: Fri, 10 Jul 2020 05:25:46 +0900
Subject: added format shape

---
 src/client/views/ContextMenu.tsx                                    | 1 -
 src/client/views/InkingStroke.tsx                                   | 1 -
 src/client/views/MainView.tsx                                       | 1 -
 src/client/views/collections/collectionFreeForm/FormatShapePane.tsx | 1 -
 4 files changed, 4 deletions(-)

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

diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 1a6b7ab90..941d7b44a 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -224,7 +224,6 @@ export class ContextMenu extends React.Component {
     }
 
     render() {
-        console.log("context");
         if (!this._display) {
             return null;
         }
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 4a7ec40be..7d9a3c8b3 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -42,7 +42,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
 
     @action
     private formatShape = () => {
-        console.log("Clicked");
         FormatShapePane.Instance.Pinned = true;
         FormatShapePane.Instance.selected();
 
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 3eed44654..30ad621fd 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -453,7 +453,6 @@ export class MainView extends React.Component {
 
     @computed get mainContent() {
         const sidebar = this.userDoc?.["tabs-panelContainer"];
-        console.log('${ ANTIMODEMENU_HEIGHT }');
         return !this.userDoc || !(sidebar instanceof Doc) ? (null) : (
             <div className="mainView-mainContent" style={{
                 color: this.darkScheme ? "rgb(205,205,205)" : "black",
diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
index 37ba805b6..bf2218fdc 100644
--- a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
+++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
@@ -315,7 +315,6 @@ export default class FormatShapePane extends AntimodeMenu {
                     this._solidLine = false;
                     this._dashLine = false;
                 } else {
-                    console.log(doc.strokeWidth);
                     this._currWidth = String(doc.strokeWidth);
                     this._currColor = String(doc.color);
                     if (doc.dash === "0") {
-- 
cgit v1.2.3-70-g09d2


From 6f985b10f71ace2303b669d4e2a1165ff3a29eda Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Fri, 10 Jul 2020 18:24:32 +0530
Subject: added _chromeStatus to playground fields

---
 src/client/util/SettingsManager.tsx             | 2 ++
 src/client/views/MainView.tsx                   | 5 +----
 src/client/views/collections/CollectionView.tsx | 2 +-
 src/fields/util.ts                              | 2 --
 4 files changed, 4 insertions(+), 7 deletions(-)

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

diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index fa2b20095..ecc17eec1 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -11,6 +11,7 @@ import { Networking } from "../Network";
 import { CurrentUserUtils } from "./CurrentUserUtils";
 import { Utils } from "../../Utils";
 import { Doc } from "../../fields/Doc";
+import GroupManager from "./GroupManager";
 
 library.add(fa.faWindowClose);
 
@@ -100,6 +101,7 @@ export default class SettingsManager extends React.Component<{}> {
                         <button onClick={() => window.location.assign(Utils.prepend("/logout"))}>
                             {CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"}
                         </button>
+                        <button onClick={() => GroupManager.Instance.open()}>Manage groups</button>
                     </div>
                     {this.settingsContent === "password" ?
                         <div className="settings-content">
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 81195b550..1bda9e9c2 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -88,7 +88,7 @@ export class MainView extends React.Component {
     public isPointerDown = false;
 
     componentDidMount() {
-        DocServer.setPlaygroundFields(["dataTransition", "_viewTransition", "_panX", "_panY", "_viewScale", "_viewType"]); // can play with these fields on someone else's
+        DocServer.setPlaygroundFields(["dataTransition", "_viewTransition", "_panX", "_panY", "_viewScale", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's
 
         const tag = document.createElement('script');
 
@@ -454,9 +454,6 @@ export class MainView extends React.Component {
                     <button className="mainView-settings" key="settings" onClick={() => SettingsManager.Instance.open()}>
                         <FontAwesomeIcon icon="cog" size="lg" />
                     </button>
-                    <button className="mainView-settings" key="groups" onClick={() => GroupManager.Instance.open()}>
-                        <FontAwesomeIcon icon="columns" size="lg" />
-                    </button>
                 </div>
             </div>
             {this.docButtons}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 34d67905e..47b6dc058 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -136,7 +136,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
         if (added.length) {
             if (effectiveAcl === AclReadonly && !getPlaygroundMode()) {
                 return false;
-            } else if (effectiveAcl === AclAddonly && !getPlaygroundMode()) {
+            } else if (effectiveAcl === AclAddonly) {
                 added.map(doc => Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc));
             } else {
                 added.map(doc => {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 77855887e..ebfc3933a 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -81,12 +81,10 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
         }
         //if (typeof value === "object" && !(value instanceof ObjectField)) debugger;
 
-        // remove below lines to prevent saving changes to server
         if (writeToServer) {
             if (value === undefined) target[Update]({ '$unset': { ["fields." + prop]: "" } });
             else target[Update]({ '$set': { ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) } });
         } else {
-            console.log("not writing to server")
             DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue);
         }
         UndoManager.AddEvent({
-- 
cgit v1.2.3-70-g09d2


From 3439640aad338bb8ff0e12df637e1b4509684001 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 12 Jul 2020 13:05:42 -0400
Subject: fixed stroke alignment with docked menus.  fixed initializing
 InkingStrokes

---
 src/client/documents/Documents.ts   |  14 ++---
 src/client/views/GestureOverlay.tsx | 103 +++++++++++-------------------------
 src/client/views/MainView.tsx       |   8 +--
 src/fields/documentSchemas.ts       |   3 ++
 4 files changed, 40 insertions(+), 88 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 26abd4c3c..72b716492 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -671,12 +671,12 @@ export namespace Docs {
             I.type = DocumentType.INK;
             I.layout = InkingStroke.LayoutString("data");
             I.color = color;
+            I.fillColor = fillColor;
             I.strokeWidth = Number(strokeWidth);
             I.strokeBezier = strokeBezier;
-            I.fillColor = fillColor;
-            I.arrowStart = arrowStart;
-            I.arrowEnd = arrowEnd;
-            I.dash = dash;
+            I.strokeStartMarker = arrowStart;
+            I.strokeEndMarker = arrowEnd;
+            I.strokeDash = dash;
             I.tool = tool;
             I.title = "ink";
             I.x = options.x;
@@ -688,12 +688,6 @@ export namespace Docs {
             I.rotation = 0;
             I.data = new InkField(points);
             return I;
-            // return I;
-            // const doc = InstanceFromProto(Prototypes.get(DocumentType.INK), new InkField(points), options);
-            // doc.color = color;
-            // doc.strokeWidth = strokeWidth;
-            // doc.tool = tool;
-            // return doc;
         }
 
         export function PdfDocument(url: string, options: DocumentOptions = {}) {
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index fc8530811..768057c22 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -7,9 +7,8 @@ import { Cast, FieldValue, NumCast } from "../../fields/Types";
 import MobileInkOverlay from "../../mobile/MobileInkOverlay";
 import { GestureUtils } from "../../pen-gestures/GestureUtils";
 import { MobileInkOverlayContent } from "../../server/Message";
-import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, returnEmptyFilter } from "../../Utils";
+import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, returnEmptyFilter, setupMoveUpEvents } from "../../Utils";
 import { CognitiveServices } from "../cognitive_services/CognitiveServices";
-import { DocServer } from "../DocServer";
 import { DocUtils } from "../documents/Documents";
 import { CurrentUserUtils } from "../util/CurrentUserUtils";
 import { InteractionUtils } from "../util/InteractionUtils";
@@ -23,7 +22,6 @@ import { RadialMenu } from "./nodes/RadialMenu";
 import HorizontalPalette from "./Palette";
 import { Touchable } from "./Touchable";
 import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu";
-import HeightLabel from "./collections/collectionMulticolumn/MultirowHeightLabel";
 import InkOptionsMenu from "./collections/collectionFreeForm/InkOptionsMenu";
 
 @observer
@@ -56,6 +54,7 @@ export default class GestureOverlay extends Touchable {
 
     @observable private showMobileInkOverlay: boolean = false;
 
+    private _overlayRef = React.createRef<HTMLDivElement>();
     private _d1: Doc | undefined;
     private _inkToTextDoc: Doc | undefined;
     private _thumbDoc: Doc | undefined;
@@ -498,39 +497,26 @@ export default class GestureOverlay extends Touchable {
     onPointerDown = (e: React.PointerEvent) => {
         if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) {
             this._points.push({ X: e.clientX, Y: e.clientY });
-            e.stopPropagation();
-            e.preventDefault();
-
-            document.removeEventListener("pointermove", this.onPointerMove);
-            document.removeEventListener("pointerup", this.onPointerUp);
-            document.addEventListener("pointermove", this.onPointerMove);
-            document.addEventListener("pointerup", this.onPointerUp);
+            setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
         }
     }
 
     @action
     onPointerMove = (e: PointerEvent) => {
-        if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) {
-            this._points.push({ X: e.clientX, Y: e.clientY });
-            e.stopPropagation();
-            e.preventDefault();
-
-
-            if (this._points.length > 1) {
-                const B = this.svgBounds;
-                const initialPoint = this._points[0.];
-                const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + this.height;
-                const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER);
-                if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) {
-                    switch (this.Tool) {
-                        case ToolglassTools.RadialMenu:
-                            document.removeEventListener("pointermove", this.onPointerMove);
-                            document.removeEventListener("pointerup", this.onPointerUp);
-                        //this.handle1PointerHoldStart(e);
-                    }
+        this._points.push({ X: e.clientX, Y: e.clientY });
+
+        if (this._points.length > 1) {
+            const B = this.svgBounds;
+            const initialPoint = this._points[0.];
+            const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + this.height;
+            const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER);
+            if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) {
+                switch (this.Tool) {
+                    case ToolglassTools.RadialMenu: return true;
                 }
             }
         }
+        return false;
     }
 
     handleLineGesture = (): boolean => {
@@ -589,8 +575,6 @@ export default class GestureOverlay extends Touchable {
             if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) {
                 switch (this.Tool) {
                     case ToolglassTools.InkToText:
-                        document.removeEventListener("pointermove", this.onPointerMove);
-                        document.removeEventListener("pointerup", this.onPointerUp);
                         this._strokes.push(new Array(...this._points));
                         this._points = [];
                         CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes).then((results) => {
@@ -635,50 +619,27 @@ export default class GestureOverlay extends Touchable {
                 let actionPerformed = false;
                 if (result && result.Score > 0.7) {
                     switch (result.Name) {
-                        case GestureUtils.Gestures.Box:
-                            this.dispatchGesture(GestureUtils.Gestures.Box);
-                            actionPerformed = true;
-                            break;
-                        case GestureUtils.Gestures.StartBracket:
-                            this.dispatchGesture(GestureUtils.Gestures.StartBracket);
-                            actionPerformed = true;
-                            break;
-                        case GestureUtils.Gestures.EndBracket:
-                            this.dispatchGesture("endbracket");
-                            actionPerformed = true;
-                            break;
-                        case GestureUtils.Gestures.Line:
-                            actionPerformed = this.handleLineGesture();
-                            break;
-                        case GestureUtils.Gestures.Triangle:
-                            this.makePolygon("triangle", true);
-                            break;
-                        case GestureUtils.Gestures.Circle:
-                            this.makePolygon("circle", true);
-                            break;
-                        case GestureUtils.Gestures.Rectangle:
-                            this.makePolygon("rectangle", true);
-                            break;
-                        case GestureUtils.Gestures.Scribble:
-                            console.log("scribble");
-                            break;
-                    }
-                    if (actionPerformed) {
-                        this._points = [];
+                        case GestureUtils.Gestures.Box: actionPerformed = this.dispatchGesture(GestureUtils.Gestures.Box); break;
+                        case GestureUtils.Gestures.StartBracket: actionPerformed = this.dispatchGesture(GestureUtils.Gestures.StartBracket); break;
+                        case GestureUtils.Gestures.EndBracket: actionPerformed = this.dispatchGesture("endbracket"); break;
+                        case GestureUtils.Gestures.Line: actionPerformed = this.handleLineGesture(); break;
+                        case GestureUtils.Gestures.Triangle: actionPerformed = this.makePolygon("triangle", true); break;
+                        case GestureUtils.Gestures.Circle: actionPerformed = this.makePolygon("circle", true); break;
+                        case GestureUtils.Gestures.Rectangle: actionPerformed = this.makePolygon("rectangle", true); break;
+                        case GestureUtils.Gestures.Scribble: console.log("scribble"); break;
                     }
                 }
 
                 // if no gesture (or if the gesture was unsuccessful), "dry" the stroke into an ink document
                 if (!actionPerformed) {
                     this.dispatchGesture(GestureUtils.Gestures.Stroke);
-                    this._points = [];
                 }
+                this._points = [];
             }
         } else {
             this._points = [];
         }
         //get out of ink mode after each stroke=
-        console.log("now");
         if (InkOptionsMenu.Instance._double === "") {
             Doc.SetSelectedTool(InkTool.None);
             InkOptionsMenu.Instance._selected = InkOptionsMenu.Instance._shapesNum;
@@ -687,9 +648,6 @@ export default class GestureOverlay extends Touchable {
             SetActiveArrowEnd("none");
             GestureOverlay.Instance.SavedArrowEnd = ActiveArrowEnd();
         }
-        document.removeEventListener("pointermove", this.onPointerMove);
-        document.removeEventListener("pointerup", this.onPointerUp);
-
     }
 
     makePolygon = (shape: string, gesture: boolean) => {
@@ -700,7 +658,7 @@ export default class GestureOverlay extends Touchable {
         var bottom = Math.max(...ys);
         var top = Math.min(...ys);
         if (shape === "noRec") {
-            return;
+            return false;
         }
         if (!gesture) {
             //if shape options is activated in inkOptionMenu
@@ -781,11 +739,12 @@ export default class GestureOverlay extends Touchable {
                 this._points.push({ X: x2, Y: y2 });
             // this._points.push({ X: x1, Y: y1 - 1 });
         }
+        return true;
     }
 
     dispatchGesture = (gesture: "box" | "line" | "startbracket" | "endbracket" | "stroke" | "scribble" | "text", stroke?: InkData, data?: any) => {
         const target = document.elementFromPoint((stroke ?? this._points)[0].X, (stroke ?? this._points)[0].Y);
-        target?.dispatchEvent(
+        return target?.dispatchEvent(
             new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
                 {
                     bubbles: true,
@@ -797,7 +756,7 @@ export default class GestureOverlay extends Touchable {
                     }
                 }
             )
-        );
+        ) || false;
     }
 
     getBounds = (stroke: InkData) => {
@@ -816,10 +775,11 @@ export default class GestureOverlay extends Touchable {
 
     @computed get elements() {
         const width = Number(ActiveInkWidth());
+        const rect = this._overlayRef.current?.getBoundingClientRect();
         const B = this.svgBounds;
         B.left = B.left - width / 2;
         B.right = B.right + width / 2;
-        B.top = B.top - width / 2;
+        B.top = B.top - width / 2 - (rect?.y || 0);
         B.bottom = B.bottom + width / 2;
         B.width += width;
         B.height += width;
@@ -836,7 +796,7 @@ export default class GestureOverlay extends Touchable {
             }),
             this._points.length <= 1 ? (null) : <svg key="svg" width={B.width} height={B.height}
                 style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}>
-                {InteractionUtils.CreatePolyline(this._points, B.left, B.top, ActiveInkColor(), width, width, ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", false, false)}
+                {InteractionUtils.CreatePolyline(this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })), B.left, B.top, ActiveInkColor(), width, width, ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", false, false)}
             </svg>]
         ];
     }
@@ -885,8 +845,7 @@ export default class GestureOverlay extends Touchable {
 
     render() {
         return (
-
-            <div className="gestureOverlay-cont" style={{ position: "relative" }}
+            <div className="gestureOverlay-cont" style={{ position: "relative" }} ref={this._overlayRef}
                 onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}>
                 {this.showMobileInkOverlay ? <MobileInkOverlay /> : <></>}
                 {this.elements}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index ab23a6ee9..b9ee58d5d 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -610,13 +610,9 @@ export class MainView extends React.Component {
             <HypothesisAuthenticationManager />
             <DocumentDecorations />
             <InkOptionsMenu />
-
-
+            <FormatShapePane />
+            <RichTextMenu key="rich" />
             <GestureOverlay >
-                <FormatShapePane />
-
-
-                <RichTextMenu key="rich" />
                 {this.mainContent}
             </GestureOverlay>
             <PreviewCursor />
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 97f62c9d4..c1659d4d5 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -73,6 +73,9 @@ export const documentSchema = createSchema({
     opacity: "number",          // opacity of document
     strokeWidth: "number",
     strokeBezier: "number",
+    strokeStartMarker: "string",
+    strokeEndMarker: "string",
+    strokeDash: "string",
     textTransform: "string",
     treeViewOpen: "boolean",    //  flag denoting whether the documents sub-tree (contents) is visible or hidden
     treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree
-- 
cgit v1.2.3-70-g09d2


From f159aa5378b433111d15d08ffe37d1e342958466 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 12 Jul 2020 13:42:03 -0400
Subject: fixed display of linkMenu

---
 src/client/views/GestureOverlay.scss           |  2 +-
 src/client/views/GestureOverlay.tsx            |  2 +-
 src/client/views/MainView.tsx                  | 10 +++++-----
 src/client/views/linking/LinkMenu.scss         |  2 +-
 src/client/views/linking/LinkMenu.tsx          | 18 ++++++++++--------
 src/client/views/nodes/DocumentLinksButton.tsx |  4 ++--
 6 files changed, 20 insertions(+), 18 deletions(-)

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

diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss
index f61f4a05e..c9d78890e 100644
--- a/src/client/views/GestureOverlay.scss
+++ b/src/client/views/GestureOverlay.scss
@@ -1,7 +1,7 @@
 .gestureOverlay-cont {
     width: 100vw;
     height: 100vh;
-    position: fixed;
+    position: relative;
     top: 0;
     left: 0;
     touch-action: none;
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 768057c22..a48b7b673 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -845,7 +845,7 @@ export default class GestureOverlay extends Touchable {
 
     render() {
         return (
-            <div className="gestureOverlay-cont" style={{ position: "relative" }} ref={this._overlayRef}
+            <div className="gestureOverlay-cont" ref={this._overlayRef}
                 onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}>
                 {this.showMobileInkOverlay ? <MobileInkOverlay /> : <></>}
                 {this.elements}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index b9ee58d5d..92f1dfec0 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -612,16 +612,16 @@ export class MainView extends React.Component {
             <InkOptionsMenu />
             <FormatShapePane />
             <RichTextMenu key="rich" />
-            <GestureOverlay >
-                {this.mainContent}
-            </GestureOverlay>
-            <PreviewCursor />
-            <LinkCreatedBox />
             {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
             {DocumentLinksButton.EditLink ? <LinkMenu location={DocumentLinksButton.EditLinkLoc} docView={DocumentLinksButton.EditLink} addDocTab={DocumentLinksButton.EditLink.props.addDocTab} changeFlyout={emptyFunction} /> : (null)}
             {LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} backgroundColor={this.defaultBackgroundColors}
                 linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href}
                 addDocTab={LinkDocPreview.LinkInfo.addDocTab} /> : (null)}
+            <GestureOverlay >
+                {this.mainContent}
+            </GestureOverlay>
+            <PreviewCursor />
+            <LinkCreatedBox />
             <ContextMenu />
             <FormatShapePane />
             <RadialMenu />
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index 1fa185150..3fb0ff517 100644
--- a/src/client/views/linking/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
@@ -3,7 +3,7 @@
 .linkMenu {
     width: auto;
     height: auto;
-
+    position: absolute;
 
     .linkMenu-list {
 
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 234cd5e07..2d151e9bc 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -44,8 +44,8 @@ export class LinkMenu extends React.Component<Props> {
         LinkDocPreview.LinkInfo = undefined;
 
 
-        if (this._linkMenuRef && !!!this._linkMenuRef.current?.contains(e.target as any)) {
-            if (this._editorRef && !!!this._editorRef.current?.contains(e.target as any)) {
+        if (this._linkMenuRef && !this._linkMenuRef.current?.contains(e.target as any)) {
+            if (this._editorRef && !this._editorRef.current?.contains(e.target as any)) {
                 console.log("outside click");
                 DocumentLinksButton.EditLink = undefined;
             }
@@ -90,14 +90,16 @@ export class LinkMenu extends React.Component<Props> {
         const sourceDoc = this.props.docView.props.Document;
         const groups: Map<string, Doc[]> = LinkManager.Instance.getRelatedGroupedLinks(sourceDoc);
         return <div className="linkMenu" ref={this._linkMenuRef} >
-             {!this._editingLink ? <div className="linkMenu-list" style={{
-                  left: this.props.location[0], top: this.props.location[1] }}>
-              {this.renderAllGroups(groups)} 
-              </div> : <div className="linkMenu-listEditor" style={{
-                  left: this.props.location[0], top: this.props.location[1]}}>
+            {!this._editingLink ? <div className="linkMenu-list" style={{
+                left: this.props.location[0], top: this.props.location[1]
+            }}>
+                {this.renderAllGroups(groups)}
+            </div> : <div className="linkMenu-listEditor" style={{
+                left: this.props.location[0], top: this.props.location[1]
+            }}>
                     <LinkEditor sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink}
                         showLinks={action(() => this._editingLink = undefined)} />
-                </div> 
+                </div>
             }
 
         </div>;
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 1c15b31de..e6e4aa6c2 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -63,7 +63,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             if (doubleTap && this.props.InMenu) {
                 //action(() => Doc.BrushDoc(this.props.View.Document));
                 DocumentLinksButton.StartLink = this.props.View;
-            } else if (!!!this.props.InMenu) {
+            } else if (!this.props.InMenu) {
                 console.log("editing");
                 this.props.View ? console.log("view") : null;
                 DocumentLinksButton.EditLink = this.props.View;
@@ -77,7 +77,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
         if (this.props.InMenu) {
             DocumentLinksButton.StartLink = this.props.View;
             //action(() => Doc.BrushDoc(this.props.View.Document));
-        } else if (!!!this.props.InMenu) {
+        } else if (!this.props.InMenu) {
             DocumentLinksButton.EditLink = this.props.View;
             DocumentLinksButton.EditLinkLoc = [e.clientX + 10, e.clientY];
         }
-- 
cgit v1.2.3-70-g09d2


From c4ec1a6a5baff6574fe8236ad52386b7ea11057f Mon Sep 17 00:00:00 2001
From: anika-ahluwalia <anika.ahluwalia@gmail.com>
Date: Sun, 12 Jul 2020 16:19:50 -0500
Subject: norm's changes

---
 deploy/assets/endLink.png                          | Bin 0 -> 10322 bytes
 deploy/assets/link.png                             | Bin 0 -> 8790 bytes
 deploy/assets/startLink.png                        | Bin 0 -> 10538 bytes
 src/client/views/DocumentButtonBar.tsx             |   5 +-
 src/client/views/MainView.tsx                      |   4 +-
 .../views/collections/CollectionLinearView.tsx     |   2 +-
 src/client/views/nodes/DocumentLinksButton.scss    |  19 ++-
 src/client/views/nodes/DocumentLinksButton.tsx     | 129 +++++++++++++--------
 8 files changed, 101 insertions(+), 58 deletions(-)
 create mode 100644 deploy/assets/endLink.png
 create mode 100644 deploy/assets/link.png
 create mode 100644 deploy/assets/startLink.png

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

diff --git a/deploy/assets/endLink.png b/deploy/assets/endLink.png
new file mode 100644
index 000000000..abadc9550
Binary files /dev/null and b/deploy/assets/endLink.png differ
diff --git a/deploy/assets/link.png b/deploy/assets/link.png
new file mode 100644
index 000000000..2dbcd61ce
Binary files /dev/null and b/deploy/assets/link.png differ
diff --git a/deploy/assets/startLink.png b/deploy/assets/startLink.png
new file mode 100644
index 000000000..8f3825682
Binary files /dev/null and b/deploy/assets/startLink.png differ
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 072648ef0..a3d24b3b6 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -274,7 +274,10 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         const considerPush = isText && this.considerGoogleDocsPush;
         return <div className="documentButtonBar">
             <div className="documentButtonBar-button">
-                <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} />
+                <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={true} />
+            </div>
+            <div className="documentButtonBar-button">
+                <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
             </div>
             <div className="documentButtonBar-button">
                 {this.templateButton}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 92f1dfec0..2ed528836 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -6,7 +6,7 @@ import {
     faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
     faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTimesCircle,
     faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
-    faHeading, faRulerCombined, faFillDrip
+    faHeading, faRulerCombined, faFillDrip, faUnlink
 } from '@fortawesome/free-solid-svg-icons';
 import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -147,7 +147,7 @@ export class MainView extends React.Component {
             faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
             faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
             faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
-            faHeading, faRulerCombined, faFillDrip);
+            faHeading, faRulerCombined, faFillDrip, faUnlink);
         this.initEventListeners();
         this.initAuthenticationRouters();
     }
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 4c68cc949..dd4df20c9 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -186,7 +186,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
 
                     <Tooltip title="Exit link clicking mode" placement="top">
                         <span className="bottomPopup-exit" onClick={this.exitLongLinks}
-                        >Exit</span>
+                        >Clear</span>
                     </Tooltip>
 
                     {/* <FontAwesomeIcon icon="times-circle" size="lg" style={{ color: "red" }}
diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss
index 580e86442..35d99d44b 100644
--- a/src/client/views/nodes/DocumentLinksButton.scss
+++ b/src/client/views/nodes/DocumentLinksButton.scss
@@ -20,18 +20,27 @@
     justify-content: center;
     align-items: center;
 
-    &:hover {
-        background: deepskyblue;
-        transform: scale(1.05);
-        cursor: pointer;
-    }
+    // &:hover {
+    //     background: deepskyblue;
+    //     transform: scale(1.05);
+    //     cursor: pointer;
+    // }
 }
+
 .documentLinksButton {
     background-color: $link-color;
 }
+
 .documentLinksButton-endLink {
     border: red solid 2px;
+
+    &:hover {
+        background: deepskyblue;
+        transform: scale(1.05);
+        cursor: pointer;
+    }
 }
+
 .documentLinksButton-startLink {
     border: red solid 2px;
     background-color: rgba(255, 192, 203, 0.5);
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index e6e4aa6c2..83710cfbf 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -23,6 +23,7 @@ interface DocumentLinksButtonProps {
     Offset?: number[];
     AlwaysOn?: boolean;
     InMenu?: boolean;
+    StartLink?: boolean;
 }
 @observer
 export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
@@ -32,27 +33,30 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
 
     @action @undoBatch
     onLinkButtonMoved = (e: PointerEvent) => {
-        if (this._linkButton.current !== null) {
-            const linkDrag = UndoManager.StartBatch("Drag Link");
-            this.props.View && DragManager.StartLinkDrag(this._linkButton.current, this.props.View.props.Document, e.pageX, e.pageY, {
-                dragComplete: dropEv => {
-                    const linkDoc = dropEv.linkDragData?.linkDocument as Doc; // equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
-                    if (this.props.View && linkDoc) {
-                        !linkDoc.linkRelationship && (Doc.GetProto(linkDoc).linkRelationship = "hyperlink");
-
-                        // we want to allow specific views to handle the link creation in their own way (e.g., rich text makes text hyperlinks)
-                        // the dragged view can regiser a linkDropCallback to be notified that the link was made and to update their data structures
-                        // however, the dropped document isn't so accessible.  What we do is set the newly created link document on the documentView
-                        // The documentView passes a function prop returning this link doc to its descendants who can react to changes to it.
-                        dropEv.linkDragData?.linkDropCallback?.(dropEv.linkDragData);
-                        runInAction(() => this.props.View._link = linkDoc);
-                        setTimeout(action(() => this.props.View._link = undefined), 0);
-                    }
-                    linkDrag?.end();
-                },
-                hideSource: false
-            });
-            return true;
+        if (this.props.InMenu && this.props.StartLink) {
+            if (this._linkButton.current !== null) {
+                const linkDrag = UndoManager.StartBatch("Drag Link");
+                this.props.View && DragManager.StartLinkDrag(this._linkButton.current, this.props.View.props.Document, e.pageX, e.pageY, {
+                    dragComplete: dropEv => {
+                        const linkDoc = dropEv.linkDragData?.linkDocument as Doc; // equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
+                        if (this.props.View && linkDoc) {
+                            !linkDoc.linkRelationship && (Doc.GetProto(linkDoc).linkRelationship = "hyperlink");
+
+                            // we want to allow specific views to handle the link creation in their own way (e.g., rich text makes text hyperlinks)
+                            // the dragged view can regiser a linkDropCallback to be notified that the link was made and to update their data structures
+                            // however, the dropped document isn't so accessible.  What we do is set the newly created link document on the documentView
+                            // The documentView passes a function prop returning this link doc to its descendants who can react to changes to it.
+                            dropEv.linkDragData?.linkDropCallback?.(dropEv.linkDragData);
+                            runInAction(() => this.props.View._link = linkDoc);
+                            setTimeout(action(() => this.props.View._link = undefined), 0);
+                        }
+                        linkDrag?.end();
+                    },
+                    hideSource: false
+                });
+                return true;
+            }
+            return false;
         }
         return false;
     }
@@ -60,7 +64,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
     @undoBatch
     onLinkButtonDown = (e: React.PointerEvent): void => {
         setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => {
-            if (doubleTap && this.props.InMenu) {
+            if (doubleTap && this.props.InMenu && this.props.StartLink) {
                 //action(() => Doc.BrushDoc(this.props.View.Document));
                 DocumentLinksButton.StartLink = this.props.View;
             } else if (!this.props.InMenu) {
@@ -74,7 +78,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
 
     @action @undoBatch
     onLinkClick = (e: React.MouseEvent): void => {
-        if (this.props.InMenu) {
+        if (this.props.InMenu && this.props.StartLink) {
             DocumentLinksButton.StartLink = this.props.View;
             //action(() => Doc.BrushDoc(this.props.View.Document));
         } else if (!this.props.InMenu) {
@@ -86,7 +90,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
     @action @undoBatch
     completeLink = (e: React.PointerEvent): void => {
         setupMoveUpEvents(this, e, returnFalse, emptyFunction, action((e, doubleTap) => {
-            if (doubleTap) {
+            if (doubleTap && this.props.InMenu && !!!this.props.StartLink) {
                 if (DocumentLinksButton.StartLink === this.props.View) {
                     DocumentLinksButton.StartLink = undefined;
                     // action((e: React.PointerEvent<HTMLDivElement>) => {
@@ -128,27 +132,29 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             //     Doc.UnBrushDoc(this.props.View.Document);
             // });
         } else {
-            if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View) {
-                const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
-                LinkManager.currentLink = linkDoc;
-                linkDoc ? linkDoc.hidden = true : null;
-                linkDoc ? linkDoc.linkDisplay = true : null;
-
-                runInAction(() => {
-                    if (linkDoc) {
-                        LinkCreatedBox.popupX = e.screenX;
-                        LinkCreatedBox.popupY = e.screenY - 133;
-                        LinkCreatedBox.linkCreated = true;
-
-                        if (LinkDescriptionPopup.showDescriptions === "ON" || !LinkDescriptionPopup.showDescriptions) {
-                            LinkDescriptionPopup.popupX = e.screenX;
-                            LinkDescriptionPopup.popupY = e.screenY - 100;
-                            LinkDescriptionPopup.descriptionPopup = true;
-                        }
+            if (this.props.InMenu && !!!this.props.StartLink) {
+                if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View) {
+                    const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
+                    LinkManager.currentLink = linkDoc;
+                    linkDoc ? linkDoc.hidden = true : null;
+                    linkDoc ? linkDoc.linkDisplay = true : null;
+
+                    runInAction(() => {
+                        if (linkDoc) {
+                            LinkCreatedBox.popupX = e.screenX;
+                            LinkCreatedBox.popupY = e.screenY - 133;
+                            LinkCreatedBox.linkCreated = true;
+
+                            if (LinkDescriptionPopup.showDescriptions === "ON" || !LinkDescriptionPopup.showDescriptions) {
+                                LinkDescriptionPopup.popupX = e.screenX;
+                                LinkDescriptionPopup.popupY = e.screenY - 100;
+                                LinkDescriptionPopup.descriptionPopup = true;
+                            }
 
-                        setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
-                    }
-                });
+                            setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+                        }
+                    });
+                }
             }
         }
     }
@@ -161,11 +167,29 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
     get linkButton() {
         const links = DocListCast(this.props.View.props.Document.links);
 
-        const title = this.props.InMenu ? "Drag or tap to create links" : DocumentLinksButton.StartLink ? "Tap to finish link" : "Tap to view links";
+        const menuTitle = this.props.StartLink ? "Drag or tap to start link" : "Tap to complete link";
+
+        const title = this.props.InMenu ? menuTitle : "Tap to view links";
+
+
+        const startLink = <img
+            style={{ width: "11px", height: "11px" }}
+            id={"startLink-icon"}
+            src={`/assets/${"startLink.png"}`} />;
+
+        const endLink = <img
+            style={{ width: "14px", height: "9px" }}
+            id={"endLink-icon"}
+            src={`/assets/${"endLink.png"}`} />;
+
+        const link = <img
+            style={{ width: "22px", height: "16px" }}
+            id={"link-icon"}
+            src={`/assets/${"link.png"}`} />;
 
         const linkButton = <div ref={this._linkButton} style={{ minWidth: 20, minHeight: 20, position: "absolute", left: this.props.Offset?.[0] }}>
             <div className={"documentLinksButton"} style={{
-                backgroundColor: DocumentLinksButton.StartLink && !!!this.props.InMenu ? "transparent" : this.props.InMenu ? "black" : "",
+                backgroundColor: this.props.InMenu ? "black" : "",
                 color: this.props.InMenu ? "white" : "black",
                 width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px", fontWeight: "bold"
             }}
@@ -178,12 +202,19 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             //     Location: [e.clientX, e.clientY + 20]
             // }))} 
             >
-                {links.length && !!!this.props.InMenu ? links.length : <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />}
+                {/* {this.props.InMenu ? this.props.StartLink ? <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" /> :
+                    <FontAwesomeIcon className="documentdecorations-icon" icon="unlink" size="sm" /> : links.length} */}
+
+                {/* {this.props.InMenu ? this.props.StartLink ? <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" /> :
+                    link : links.length} */}
+
+                {this.props.InMenu ? this.props.StartLink ? startLink :
+                    endLink : links.length}
             </div>
-            {DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View ? <div className={"documentLinksButton-endLink"}
+            {DocumentLinksButton.StartLink && this.props.InMenu && !!!this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View ? <div className={"documentLinksButton-endLink"}
                 style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px" }}
                 onPointerDown={this.completeLink} onClick={e => this.finishLinkClick(e)} /> : (null)}
-            {DocumentLinksButton.StartLink === this.props.View ? <div className={"documentLinksButton-startLink"}
+            {DocumentLinksButton.StartLink === this.props.View && this.props.InMenu && this.props.StartLink ? <div className={"documentLinksButton-startLink"}
                 style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px" }} /> : (null)}
         </div>;
         return (!links.length) && !this.props.AlwaysOn ? (null) :
-- 
cgit v1.2.3-70-g09d2


From 0dafe85b7d0c826340299a7e6b51190a6eaa7004 Mon Sep 17 00:00:00 2001
From: anika-ahluwalia <anika.ahluwalia@gmail.com>
Date: Mon, 13 Jul 2020 17:56:46 -0500
Subject: fixed preview backgrounds, text size, tooltip size, adding labels,
 conditional end link btn, tooltips size

---
 src/client/views/DocumentButtonBar.tsx             | 106 +++++++++++----------
 src/client/views/DocumentDecorations.tsx           |  12 +--
 src/client/views/MainView.tsx                      |   4 +-
 .../views/collections/CollectionLinearView.tsx     |   9 +-
 src/client/views/linking/LinkEditor.scss           |  24 ++++-
 src/client/views/linking/LinkEditor.tsx            |  27 ++++--
 src/client/views/linking/LinkMenuItem.scss         |   1 +
 src/client/views/linking/LinkMenuItem.tsx          |   6 +-
 src/client/views/nodes/DocumentLinksButton.scss    |  12 +--
 src/client/views/nodes/DocumentLinksButton.tsx     |  24 ++---
 src/client/views/nodes/LinkDocPreview.tsx          |   3 +-
 .../formattedText/FormattedTextBoxComment.tsx      |   4 +-
 12 files changed, 135 insertions(+), 97 deletions(-)

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

diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index a3d24b3b6..c132eb5bb 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -119,7 +119,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         const targetDoc = this.view0?.props.Document;
         const published = targetDoc && Doc.GetProto(targetDoc)[GoogleRef] !== undefined;
         const animation = this.isAnimatingPulse ? "shadow-pulse 1s linear infinite" : "none";
-        return !targetDoc ? (null) : <Tooltip title={`${published ? "Push" : "Publish"} to Google Docs`}>
+        return !targetDoc ? (null) : <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>{`${published ? "Push" : "Publish"} to Google Docs`}</div></React.Fragment>}>
             <div
                 className="documentButtonBar-linker"
                 style={{ animation }}
@@ -138,46 +138,49 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         const targetDoc = this.view0?.props.Document;
         const dataDoc = targetDoc && Doc.GetProto(targetDoc);
         const animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none";
+
+        const title = (() => {
+            switch (this.openHover) {
+                default:
+                case UtilityButtonState.Default: return `${!dataDoc?.unchanged ? "Pull from" : "Fetch"} Google Docs`;
+                case UtilityButtonState.OpenRight: return "Open in Right Split";
+                case UtilityButtonState.OpenExternally: return "Open in new Browser Tab";
+            }
+        })();
+
         return !targetDoc || !dataDoc || !dataDoc[GoogleRef] ? (null) : <Tooltip
-            title={(() => {
-                switch (this.openHover) {
-                    default:
-                    case UtilityButtonState.Default: return `${!dataDoc.unchanged ? "Pull from" : "Fetch"} Google Docs`;
-                    case UtilityButtonState.OpenRight: return "Open in Right Split";
-                    case UtilityButtonState.OpenExternally: return "Open in new Browser Tab";
-                }
-            })()}
-        ><div className="documentButtonBar-linker"
-            style={{ backgroundColor: this.pullColor }}
-            onPointerEnter={action(e => {
-                if (e.altKey) {
-                    this.openHover = UtilityButtonState.OpenExternally;
-                } else if (e.shiftKey) {
-                    this.openHover = UtilityButtonState.OpenRight;
-                }
-            })}
-            onPointerLeave={action(() => this.openHover = UtilityButtonState.Default)}
-            onClick={async e => {
-                const googleDocUrl = `https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`;
-                if (e.shiftKey) {
-                    e.preventDefault();
-                    let googleDoc = await Cast(dataDoc.googleDoc, Doc);
-                    if (!googleDoc) {
-                        const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, isAnnotating: false, UseCors: false };
-                        googleDoc = Docs.Create.WebDocument(googleDocUrl, options);
-                        dataDoc.googleDoc = googleDoc;
+            title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>{title}</div></React.Fragment>}>
+            <div className="documentButtonBar-linker"
+                style={{ backgroundColor: this.pullColor }}
+                onPointerEnter={action(e => {
+                    if (e.altKey) {
+                        this.openHover = UtilityButtonState.OpenExternally;
+                    } else if (e.shiftKey) {
+                        this.openHover = UtilityButtonState.OpenRight;
+                    }
+                })}
+                onPointerLeave={action(() => this.openHover = UtilityButtonState.Default)}
+                onClick={async e => {
+                    const googleDocUrl = `https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`;
+                    if (e.shiftKey) {
+                        e.preventDefault();
+                        let googleDoc = await Cast(dataDoc.googleDoc, Doc);
+                        if (!googleDoc) {
+                            const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, isAnnotating: false, UseCors: false };
+                            googleDoc = Docs.Create.WebDocument(googleDocUrl, options);
+                            dataDoc.googleDoc = googleDoc;
+                        }
+                        CollectionDockingView.AddRightSplit(googleDoc);
+                    } else if (e.altKey) {
+                        e.preventDefault();
+                        window.open(googleDocUrl);
+                    } else {
+                        this.clearPullColor();
+                        DocumentButtonBar.hasPulledHack = false;
+                        targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1;
+                        dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true);
                     }
-                    CollectionDockingView.AddRightSplit(googleDoc);
-                } else if (e.altKey) {
-                    e.preventDefault();
-                    window.open(googleDocUrl);
-                } else {
-                    this.clearPullColor();
-                    DocumentButtonBar.hasPulledHack = false;
-                    targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1;
-                    dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true);
-                }
-            }}>
+                }}>
                 <FontAwesomeIcon className="documentdecorations-icon" size="sm"
                     style={{ WebkitAnimation: animation, MozAnimation: animation }}
                     icon={(() => {
@@ -195,7 +198,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
     get pinButton() {
         const targetDoc = this.view0?.props.Document;
         const isPinned = targetDoc && Doc.isDocPinned(targetDoc);
-        return !targetDoc ? (null) : <Tooltip title={Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}>
+        return !targetDoc ? (null) : <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>{Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}</div></React.Fragment>}>
             <div className="documentButtonBar-linker"
                 style={{ backgroundColor: isPinned ? "black" : "white", color: isPinned ? "white" : "black" }}
                 onClick={e => DockedFrameRenderer.PinDoc(targetDoc, isPinned)}>
@@ -207,14 +210,15 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
     @computed
     get metadataButton() {
         const view0 = this.view0;
-        return !view0 ? (null) : <Tooltip title="Show metadata panel"><div className="documentButtonBar-linkFlyout">
-            <Flyout anchorPoint={anchorPoints.LEFT_TOP}
-                content={<MetadataEntryMenu docs={() => this.props.views().filter(dv => dv).map(dv => dv!.props.Document)} suggestWithFunction />  /* tfs: @bcz This might need to be the data document? */}>
-                <div className={"documentButtonBar-linkButton-" + "empty"} onPointerDown={e => e.stopPropagation()} >
-                    {<FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" />}
-                </div>
-            </Flyout>
-        </div></Tooltip>;
+        return !view0 ? (null) : <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Show metadata panel</div></React.Fragment>}>
+            <div className="documentButtonBar-linkFlyout">
+                <Flyout anchorPoint={anchorPoints.LEFT_TOP}
+                    content={<MetadataEntryMenu docs={() => this.props.views().filter(dv => dv).map(dv => dv!.props.Document)} suggestWithFunction />  /* tfs: @bcz This might need to be the data document? */}>
+                    <div className={"documentButtonBar-linkButton-" + "empty"} onPointerDown={e => e.stopPropagation()} >
+                        {<FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" />}
+                    </div>
+                </Flyout>
+            </div></Tooltip>;
     }
 
     @computed
@@ -255,7 +259,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         Array.from(Object.values(Templates.TemplateList)).map(template =>
             templates.set(template, views.reduce((checked, doc) => checked || doc?.props.Document["_show" + template.Name] ? true : false, false as boolean)));
         return !view0 ? (null) :
-            <Tooltip title="Tap: Customize layout.  Drag: Create alias" >
+            <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Tap: Customize layout.  Drag: Create alias</div></React.Fragment>}>
                 <div className="documentButtonBar-linkFlyout" ref={this._dragRef}>
                     <Flyout anchorPoint={anchorPoints.LEFT_TOP} onOpen={action(() => this._aliasDown = true)} onClose={action(() => this._aliasDown = false)}
                         content={!this._aliasDown ? (null) : <TemplateMenu docViews={views.filter(v => v).map(v => v as DocumentView)} templates={templates} />}>
@@ -276,9 +280,9 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
             <div className="documentButtonBar-button">
                 <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={true} />
             </div>
-            <div className="documentButtonBar-button">
+            {DocumentLinksButton.StartLink ? <div className="documentButtonBar-button">
                 <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
-            </div>
+            </div> : null}
             <div className="documentButtonBar-button">
                 {this.templateButton}
             </div>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index d7324e1a6..2e3c63cf2 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -547,11 +547,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         }
         const minimal = bounds.r - bounds.x < 100 ? true : false;
         const maximizeIcon = minimal ? (
-            <Tooltip title="Show context menu" placement="top">
+            <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Show context menu</div></React.Fragment>} placement="top">
                 <div className="documentDecorations-contextMenu" onPointerDown={this.onSettingsDown}>
                     <FontAwesomeIcon size="lg" icon="cog" />
                 </div></Tooltip>) : (
-                <Tooltip title="Iconify" placement="top">
+                <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Iconify</div></React.Fragment>} placement="top">
                     <div className="documentDecorations-minimizeButton" onClick={this.onCloseClick}>
                         {/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/}
                         <FontAwesomeIcon className="documentdecorations-times" icon={faTimes} size="lg" />
@@ -575,7 +575,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 </div>}
             </> :
             <>
-                {minimal ? (null) : <Tooltip title="Show context menu" placement="top"><div className="documentDecorations-contextMenu" key="menu" onPointerDown={this.onSettingsDown}>
+                {minimal ? (null) : <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Show context menu</div></React.Fragment>} placement="top"><div className="documentDecorations-contextMenu" key="menu" onPointerDown={this.onSettingsDown}>
                     <FontAwesomeIcon size="lg" icon="cog" />
                 </div></Tooltip>}
                 <div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown} >
@@ -614,11 +614,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                     {maximizeIcon}
                     {titleArea}
                     {SelectionManager.SelectedDocuments().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) :
-                        <Tooltip title={`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`} placement="top">
+                        <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>{`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`}</div></React.Fragment>} placement="top">
                             <div className="documentDecorations-iconifyButton" onPointerDown={this.onIconifyDown}>
                                 {"_"}
                             </div></Tooltip>}
-                    <Tooltip title="Open Document in Tab" placement="top"><div className="documentDecorations-closeButton" onPointerDown={this.onMaximizeDown}>
+                    <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Open Document In Tab</div></React.Fragment>} placement="top"><div className="documentDecorations-closeButton" onPointerDown={this.onMaximizeDown}>
                         {SelectionManager.SelectedDocuments().length === 1 ? DocumentDecorations.DocumentIcon(StrCast(seldoc.props.Document.layout, "...")) : "..."}
                     </div></Tooltip>
                     <div id="documentDecorations-rotation" className="documentDecorations-rotation"
@@ -641,7 +641,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                     <div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer"
                         onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
                     {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) :
-                        <Tooltip title="tap to select containing document" placement="top">
+                        <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>tap to select containing document</div></React.Fragment>} placement="top">
                             <div id="documentDecorations-levelSelector" className="documentDecorations-selector"
                                 onPointerDown={this.onSelectorUp} onContextMenu={e => e.preventDefault()}>
                                 <FontAwesomeIcon className="documentdecorations-times" icon={faArrowAltCircleUp} size="lg" />
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 2ed528836..a54973489 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -6,7 +6,7 @@ import {
     faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
     faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTimesCircle,
     faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
-    faHeading, faRulerCombined, faFillDrip, faUnlink
+    faHeading, faRulerCombined, faFillDrip, faUnlink, faHandPaper
 } from '@fortawesome/free-solid-svg-icons';
 import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -147,7 +147,7 @@ export class MainView extends React.Component {
             faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
             faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
             faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
-            faHeading, faRulerCombined, faFillDrip, faUnlink);
+            faHeading, faRulerCombined, faFillDrip, faUnlink, faHandPaper);
         this.initEventListeners();
         this.initAuthenticationRouters();
     }
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index dd4df20c9..319cca70f 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -124,7 +124,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
 
         return <div className="collectionLinearView-outer">
             <div className="collectionLinearView" ref={this.createDashEventsTarget} >
-                <Tooltip title={BoolCast(this.props.Document.linearViewIsExpanded) ? "Close menu" : "Open menu"} placement="top">
+                <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>{BoolCast(this.props.Document.linearViewIsExpanded) ? "Close menu" : "Open menu"}</div></React.Fragment>} placement="top">
                     {menuOpener}
                 </Tooltip>
                 <input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.linearViewIsExpanded)} ref={this.addMenuToggle}
@@ -177,14 +177,15 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                     onPointerDown={e => e.stopPropagation()} >
                     <span className="bottomPopup-text" >
                         Creating link from: {DocumentLinksButton.StartLink.title} </span>
-                    <Tooltip title={LinkDescriptionPopup.showDescriptions ? "Turn off description pop-up" :
-                        "Turn on description pop-up"} placement="top">
+
+                    <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>{LinkDescriptionPopup.showDescriptions ? "Turn off description pop-up" :
+                        "Turn on description pop-up"} </div></React.Fragment>} placement="top">
                         <span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}
                         > Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : "ON"}
                         </span>
                     </Tooltip>
 
-                    <Tooltip title="Exit link clicking mode" placement="top">
+                    <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Exit link clicking mode </div></React.Fragment>} placement="top">
                         <span className="bottomPopup-exit" onClick={this.exitLongLinks}
                         >Clear</span>
                     </Tooltip>
diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss
index 87afc99eb..d26b7920a 100644
--- a/src/client/views/linking/LinkEditor.scss
+++ b/src/client/views/linking/LinkEditor.scss
@@ -13,6 +13,10 @@
     width: 18px;
     height: 18px;
     padding: 0;
+
+    &:hover {
+        cursor: pointer;
+    }
 }
 
 .linkEditor-info {
@@ -28,7 +32,13 @@
     .linkEditor-linkedTo {
         width: calc(100% - 26px);
         padding-left: 5px;
-        padding-right: 5px
+        padding-right: 5px;
+
+        .linkEditor-downArrow {
+            &:hover {
+                cursor: pointer;
+            }
+        }
     }
 }
 
@@ -43,6 +53,10 @@
 
     .button {
         color: black;
+
+        &:hover {
+            cursor: pointer;
+        }
     }
 }
 
@@ -83,6 +97,10 @@
             padding-right: 8px;
             height: 80%;
             color: white;
+
+            &:hover {
+                cursor: pointer;
+            }
         }
     }
 }
@@ -92,6 +110,10 @@
     padding-right: 6.5px;
     padding-bottom: 6px;
 
+    &:hover {
+        cursor: pointer;
+    }
+
     .linkEditor-followingDropdown-label {
         color: black;
     }
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index a26685318..1cc981d42 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -13,6 +13,7 @@ import { DocumentView } from "../nodes/DocumentView";
 import { DocumentLinksButton } from "../nodes/DocumentLinksButton";
 import { EditableView } from "../EditableView";
 import { RefObject } from "react";
+import { Tooltip } from "@material-ui/core";
 
 library.add(faArrowLeft, faEllipsisV, faTable, faTrash, faCog, faExchangeAlt, faTimes, faPlus);
 
@@ -285,15 +286,12 @@ interface LinkEditorProps {
 @observer
 export class LinkEditor extends React.Component<LinkEditorProps> {
 
-
     @observable description = StrCast(LinkManager.currentLink?.description);
     @observable openDropdown: boolean = false;
-
     @observable followBehavior = this.props.linkDoc.follow ? this.props.linkDoc.follow : "Default";
-
     @observable showInfo: boolean = false;
-
     @computed get infoIcon() { if (this.showInfo) { return "chevron-up"; } return "chevron-down"; }
+    @observable private buttonColor: string = "black";
 
 
     //@observable description = this.props.linkDoc.description ? StrCast(this.props.linkDoc.description) : "DESCRIPTION";
@@ -308,6 +306,10 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
     setDescripValue = (value: string) => {
         if (LinkManager.currentLink) {
             LinkManager.currentLink.description = value;
+            this.buttonColor = "rgb(62, 133, 55)";
+            setTimeout(action(() => {
+                this.buttonColor = "black";
+            }), 750);
             return true;
         }
     }
@@ -349,7 +351,8 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
                     ></input>
                 </div>
                 <div className="linkEditor-description-add-button"
-                    onPointerDown={this.onDown}>Add</div>
+                    style={{ backgroundColor: this.buttonColor }}
+                    onPointerDown={this.onDown}>Set</div>
             </div></div>;
     }
 
@@ -413,15 +416,19 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
         return !destination ? (null) : (
             <div className="linkEditor">
                 <div className="linkEditor-info">
-                    <button className="linkEditor-button-back"
-                        style={{ display: this.props.hideback ? "none" : "" }}
-                        onClick={this.props.showLinks}>
-                        <FontAwesomeIcon icon="arrow-left" size="sm" /> </button>
+                    <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Return to link menu</div></React.Fragment>} placement="top">
+                        <button className="linkEditor-button-back"
+                            style={{ display: this.props.hideback ? "none" : "" }}
+                            onClick={this.props.showLinks}>
+                            <FontAwesomeIcon icon="arrow-left" size="sm" /> </button>
+                    </Tooltip>
                     <p className="linkEditor-linkedTo">Editing Link to: <b>{
                         destination.proto?.title ?? destination.title ?? "untitled"}</b></p>
                     {/* <button className="linkEditor-button" onPointerDown={() => this.deleteLink()} title="Delete link">
                         <FontAwesomeIcon icon="trash" size="sm" /></button> */}
-                    <FontAwesomeIcon className="button" icon={this.infoIcon} size="lg" onPointerDown={this.changeInfo} />
+                    <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Show more link information</div></React.Fragment>} placement="top">
+                        <div className="linkEditor-downArrow"><FontAwesomeIcon className="button" icon={this.infoIcon} size="lg" onPointerDown={this.changeInfo} /></div>
+                    </Tooltip>
                 </div>
                 {this.showInfo ? <div className="linkEditor-moreInfo">
                     <div>{this.props.linkDoc.author ? <div> <b>Author:</b> {this.props.linkDoc.author}</div> : null}</div>
diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss
index f70f5a23e..4e13ef8c8 100644
--- a/src/client/views/linking/LinkMenuItem.scss
+++ b/src/client/views/linking/LinkMenuItem.scss
@@ -67,6 +67,7 @@
                         color: rgb(60, 90, 156);
                         //display: inline;
                         text-overflow: break;
+                        cursor: pointer;
                     }
                 }
             }
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index b451f0168..ff9401696 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -253,16 +253,16 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                             {canExpand ? <div title="Show more" className="button" onPointerDown={e => this.toggleShowMore(e)}>
                                 <FontAwesomeIcon className="fa-icon" icon={this._showMore ? "chevron-up" : "chevron-down"} size="sm" /></div> : <></>}
 
-                            <Tooltip title={showTitle}>
+                            <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>{showTitle}</div></React.Fragment>}>
                                 <div className="button" ref={this._editRef} onPointerDown={this.showLink}>
                                     <FontAwesomeIcon className="fa-icon" icon={eyeIcon} size="sm" /></div>
                             </Tooltip>
 
-                            <Tooltip title="Edit Link">
+                            <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Edit Link</div></React.Fragment>}>
                                 <div className="button" ref={this._editRef} onPointerDown={this.onEdit}>
                                     <FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div>
                             </Tooltip>
-                            <Tooltip title="Delete Link">
+                            <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Delete Link</div></React.Fragment>}>
                                 <div className="button" onPointerDown={this.deleteLink}>
                                     <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div>
                             </Tooltip>
diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss
index 35d99d44b..97e714cd5 100644
--- a/src/client/views/nodes/DocumentLinksButton.scss
+++ b/src/client/views/nodes/DocumentLinksButton.scss
@@ -13,18 +13,18 @@
     color: black;
     text-transform: uppercase;
     letter-spacing: 2px;
-    font-size: 75%;
+    font-size: 10px;
     transition: transform 0.2s;
     text-align: center;
     display: flex;
     justify-content: center;
     align-items: center;
 
-    // &:hover {
-    //     background: deepskyblue;
-    //     transform: scale(1.05);
-    //     cursor: pointer;
-    // }
+    &:hover {
+        // background: deepskyblue;
+        // transform: scale(1.05);
+        cursor: pointer;
+    }
 }
 
 .documentLinksButton {
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 83710cfbf..96ff0157e 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -168,8 +168,8 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
         const links = DocListCast(this.props.View.props.Document.links);
 
         const menuTitle = this.props.StartLink ? "Drag or tap to start link" : "Tap to complete link";
-
-        const title = this.props.InMenu ? menuTitle : "Tap to view links";
+        const buttonTitle = "Tap to view links";
+        const title = this.props.InMenu ? menuTitle : buttonTitle;
 
 
         const startLink = <img
@@ -202,14 +202,10 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             //     Location: [e.clientX, e.clientY + 20]
             // }))} 
             >
-                {/* {this.props.InMenu ? this.props.StartLink ? <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" /> :
-                    <FontAwesomeIcon className="documentdecorations-icon" icon="unlink" size="sm" /> : links.length} */}
 
-                {/* {this.props.InMenu ? this.props.StartLink ? <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" /> :
-                    link : links.length} */}
+                {this.props.InMenu ? this.props.StartLink ? <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" /> :
+                    <FontAwesomeIcon className="documentdecorations-icon" icon="hand-paper" size="sm" /> : links.length}
 
-                {this.props.InMenu ? this.props.StartLink ? startLink :
-                    endLink : links.length}
             </div>
             {DocumentLinksButton.StartLink && this.props.InMenu && !!!this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View ? <div className={"documentLinksButton-endLink"}
                 style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px" }}
@@ -217,10 +213,16 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             {DocumentLinksButton.StartLink === this.props.View && this.props.InMenu && this.props.StartLink ? <div className={"documentLinksButton-startLink"}
                 style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px" }} /> : (null)}
         </div>;
+
         return (!links.length) && !this.props.AlwaysOn ? (null) :
-            <Tooltip title={title}>
-                {linkButton}
-            </Tooltip>;
+            this.props.InMenu ?
+                <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>{title}</div></React.Fragment>}>
+                    {linkButton}
+                </Tooltip> : !!!DocumentLinksButton.EditLink ?
+                    <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>{title}</div></React.Fragment>}>
+                        {linkButton}
+                    </Tooltip> :
+                    linkButton;
     }
     render() {
         return this.linkButton;
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 079920f56..1caa82380 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -123,7 +123,8 @@ export class LinkDocPreview extends React.Component<Props> {
                 bringToFront={returnFalse}
                 ContentScaling={returnOne}
                 NativeWidth={returnZero}
-                NativeHeight={returnZero} />;
+                NativeHeight={returnZero}
+                backgroundColor={this.props.backgroundColor} />;
     }
 
     render() {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index fa2548cb5..c98b5eac1 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -270,14 +270,14 @@ export class FormattedTextBoxComment {
                                         </div>
                                         <div className="wrapper" style={{ float: "right" }}>
 
-                                            <Tooltip title="Delete Link" placement="top">
+                                            <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Delete Link</div></React.Fragment>} placement="top">
                                                 <div className="FormattedTextBoxComment-button"
                                                     ref={(r) => this._deleteRef = r}>
                                                     <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="trash" color="white"
                                                         size="sm" /></div>
                                             </Tooltip>
 
-                                            <Tooltip title="Follow Link" placement="top">
+                                            <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Follow Link</div></React.Fragment>} placement="top">
                                                 <div className="FormattedTextBoxComment-button"
                                                     ref={(r) => this._followRef = r}>
                                                     <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="arrow-right" color="white"
-- 
cgit v1.2.3-70-g09d2


From 07d71ea914c5bae3136b6c911c38b3373afcd5a7 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 14 Jul 2020 19:32:56 -0400
Subject: cleaned up InkOptionsMenu

---
 src/client/views/GestureOverlay.tsx                |   5 +-
 src/client/views/MainView.tsx                      |   7 +-
 .../collectionFreeForm/InkOptionsMenu.tsx          | 381 ++++-----------------
 3 files changed, 77 insertions(+), 316 deletions(-)

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

diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index a48b7b673..2f34cbc05 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -608,8 +608,7 @@ export default class GestureOverlay extends Touchable {
                 this.makePolygon(this.InkShape, false);
                 this.dispatchGesture(GestureUtils.Gestures.Stroke);
                 this._points = [];
-                if (InkOptionsMenu.Instance._double === "") {
-
+                if (!InkOptionsMenu.Instance._keepMode) {
                     this.InkShape = "";
                 }
             }
@@ -640,7 +639,7 @@ export default class GestureOverlay extends Touchable {
             this._points = [];
         }
         //get out of ink mode after each stroke=
-        if (InkOptionsMenu.Instance._double === "") {
+        if (!InkOptionsMenu.Instance._keepMode) {
             Doc.SetSelectedTool(InkTool.None);
             InkOptionsMenu.Instance._selected = InkOptionsMenu.Instance._shapesNum;
             SetActiveArrowStart("none");
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 2ed528836..41efe246c 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,4 +1,5 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
+
 import {
     faTasks, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
     faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
@@ -6,7 +7,8 @@ import {
     faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
     faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTimesCircle,
     faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
-    faHeading, faRulerCombined, faFillDrip, faUnlink
+    faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper,
+    faPaintRoller, faBars, faBrush, faShapes, faEllipsisH
 } from '@fortawesome/free-solid-svg-icons';
 import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -147,7 +149,8 @@ export class MainView extends React.Component {
             faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
             faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
             faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
-            faHeading, faRulerCombined, faFillDrip, faUnlink);
+            faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper,
+            faPaintRoller, faBars, faBrush, faShapes, faEllipsisH);
         this.initEventListeners();
         this.initAuthenticationRouters();
     }
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
index 15707ad9e..23e1c194a 100644
--- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
@@ -3,7 +3,7 @@ import AntimodeMenu from "../../AntimodeMenu";
 import { observer } from "mobx-react";
 import { observable, action, computed } from "mobx";
 import "./InkOptionsMenu.scss";
-import { ActiveInkColor, ActiveInkBezierApprox, ActiveFillColor, ActiveArrowStart, ActiveArrowEnd, SetActiveInkWidth, SetActiveInkColor, SetActiveBezierApprox, SetActiveFillColor, SetActiveArrowStart, SetActiveArrowEnd, ActiveDash, SetActiveDash } from "../../InkingStroke";
+import { ActiveInkColor, ActiveFillColor, SetActiveInkWidth, SetActiveInkColor, SetActiveBezierApprox, SetActiveFillColor, SetActiveArrowStart, SetActiveArrowEnd, ActiveDash, SetActiveDash } from "../../InkingStroke";
 import { Scripting } from "../../../util/Scripting";
 import { InkTool } from "../../../../fields/InkField";
 import { ColorState } from "react-color";
@@ -14,30 +14,16 @@ import { SelectionManager } from "../../../util/SelectionManager";
 import { DocumentView } from "../../../views/nodes/DocumentView";
 import { Document } from "../../../../fields/documentSchemas";
 import { DocumentType } from "../../../documents/DocumentTypes";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faArrowsAlt, faHighlighter, faLink, faPaintRoller, faSleigh, faBars, faFillDrip, faBrush, faPenNib, faShapes, faArrowLeft, faEllipsisH, faBezierCurve, } from "@fortawesome/free-solid-svg-icons";
-import { Cast, StrCast, BoolCast } from "../../../../fields/Types";
+import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome";
+import { BoolCast } from "../../../../fields/Types";
 import FormatShapePane from "./FormatShapePane";
 
-library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faArrowsAlt, faHighlighter, faLink, faPaintRoller, faBars, faFillDrip, faBrush, faPenNib, faShapes, faArrowLeft, faEllipsisH, faBezierCurve);
-
-
-
 @observer
 export default class InkOptionsMenu extends AntimodeMenu {
     static Instance: InkOptionsMenu;
 
     private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF", ""];
     private _width = ["1", "5", "10", "100"];
-    // private _buttons = ["circle", "triangle", "rectangle", "arrow", "line"];
-    // private _icons = ["O", "∆", "ロ", "➜", "-"];
-    // private _buttons = ["circle", "triangle", "rectangle", "line", "noRec", "",];
-    // private _icons = ["O", "∆", "ロ", "⎯⎯⎯", "✖︎", " "];
-    //arrowStart and arrowEnd must match and defs must exist in Inking Stroke
-    // private _arrowStart = ["arrowStart", "arrowStart", "dot", "dot", "none"];
-    // private _arrowEnd = ["none", "arrowEnd", "none", "dot", "none"];
-    // private _arrowIcons = ["→", "↔︎", "•", "••", " "];
     private _draw = ["⎯", "→", "↔︎", "∿", "↝", "↭", "ロ", "O", "∆"];
     private _head = ["", "", "arrow", "", "", "arrow", "", "", ""];
     private _end = ["", "arrow", "arrow", "", "arrow", "arrow", "", "", ""];
@@ -46,56 +32,25 @@ export default class InkOptionsMenu extends AntimodeMenu {
     @observable _shapesNum = this._shape.length;
     @observable _selected = this._shapesNum;
 
-    @observable private collapsed: boolean = false;
-    @observable _double = "";
+    @observable _collapsed = false;
+    @observable _keepMode = false;
 
     @observable _colorBtn = false;
     @observable _widthBtn = false;
     @observable _fillBtn = false;
-    // @observable _arrowBtn = false;
-    // @observable _dashBtn = false;
-    // @observable _shapeBtn = false;
-
-
 
     constructor(props: Readonly<{}>) {
         super(props);
         InkOptionsMenu.Instance = this;
         this._canFade = false; // don't let the inking menu fade away
         this.Pinned = BoolCast(Doc.UserDoc()["menuInkOptions-pinned"]);
-
     }
 
     @action
     toggleMenuPin = (e: React.MouseEvent) => {
         Doc.UserDoc()["menuInkOptions-pinned"] = this.Pinned = !this.Pinned;
-        if (!this.Pinned) {
-            // this.fadeOut(true);
-        }
-    }
-
-    @action
-    protected toggleCollapse = (e: React.MouseEvent) => {
-        this.collapsed = !this.collapsed;
-        setTimeout(() => {
-            const x = Math.min(this._left, window.innerWidth - InkOptionsMenu.Instance.width);
-            InkOptionsMenu.Instance.jumpTo(x, this._top, true);
-        }, 0);
-    }
-
-
-
-
-    getColors = () => {
-        return this._palette;
     }
 
-    // @action
-    // changeArrow = (arrowStart: string, arrowEnd: string) => {
-    //     SetActiveArrowStart(arrowStart);
-    //     SetActiveArrowEnd(arrowEnd);
-    // }
-
     @action
     changeColor = (color: string, type: string) => {
         const col: ColorState = {
@@ -115,313 +70,117 @@ export default class InkOptionsMenu extends AntimodeMenu {
             const doc = Document(element.rootDoc);
             if (doc.type === DocumentType.INK) {
                 switch (field) {
-                    case "width":
-                        doc.strokeWidth = Number(value);
-                        break;
-                    case "color":
-                        doc.color = String(value);
-                        break;
-                    case "fill":
-                        doc.fillColor = String(value);
-                        break;
-                    case "bezier":
-                        // doc.strokeBezier === 300 ? doc.strokeBezier = 0 : doc.strokeBezier = 300;
-                        break;
-                    case "dash":
-                        doc.strokeDash = Number(value);
-                    default:
-                        break;
+                    case "width": doc.strokeWidth = Number(value); break;
+                    case "color": doc.color = String(value); break;
+                    case "fill": doc.fillColor = String(value); break;
+                    case "dash": doc.strokeDash = value;
                 }
             }
         }));
     }
 
-
-    @action
-    changeBezier = (e: React.PointerEvent): void => {
-        SetActiveBezierApprox(!ActiveInkBezierApprox() ? "300" : "");
-        this.editProperties(0, "bezier");
-    }
-    @action
-    changeDash = (e: React.PointerEvent): void => {
-        SetActiveDash(ActiveDash() === "0" ? "2" : "0");
-        this.editProperties(ActiveDash(), "strokeDash");
-    }
-
     @computed get drawButtons() {
-        const drawButtons = <div className="btn-draw" key="draw">
-            {this._draw.map((icon, i) => {
-                return <button
-                    className="antimodeMenu-button"
-                    key={icon}
-                    onPointerDown={action(() => {
-                        this._double = "";
-
-                        if (this._selected !== i) {
-                            this._selected = i;
-                            Doc.SetSelectedTool(InkTool.Pen);
-                            SetActiveArrowStart(this._head[i]);
-                            SetActiveArrowEnd(this._end[i]);
-                            SetActiveBezierApprox("300");
-
-                            GestureOverlay.Instance.InkShape = this._shape[i];
-                        } else {
-                            this._selected = this._shapesNum;
-                            Doc.SetSelectedTool(InkTool.None);
-                            SetActiveArrowStart("");
-                            SetActiveArrowEnd("");
-                            GestureOverlay.Instance.InkShape = "";
-                            SetActiveBezierApprox("0");
-
-                        }
-                        console.log(this._selected);
-
-                    })}
-                    onDoubleClick={action(() => {
-                        console.log("double");
-                        this._double = this._draw[i];
-                        if (this._selected !== i) {
-                            this._selected = i;
-                            Doc.SetSelectedTool(InkTool.Pen);
-                            SetActiveArrowStart(this._head[i]);
-                            SetActiveArrowEnd(this._end[i]);
-                            SetActiveBezierApprox("300");
-
-                            GestureOverlay.Instance.InkShape = this._shape[i];
-                        } else {
-                            this._selected = this._shapesNum;
-                            Doc.SetSelectedTool(InkTool.None);
-                            SetActiveArrowStart("");
-                            SetActiveArrowEnd("");
-                            GestureOverlay.Instance.InkShape = "";
-                            SetActiveBezierApprox("0");
-
-                        }
-
-
-
-                    })}
+        const func = action((i: number, keep: boolean) => {
+            this._keepMode = keep;
+            if (this._selected !== i) {
+                this._selected = i;
+                Doc.SetSelectedTool(InkTool.Pen);
+                SetActiveArrowStart(this._head[i]);
+                SetActiveArrowEnd(this._end[i]);
+                SetActiveBezierApprox("300");
+
+                GestureOverlay.Instance.InkShape = this._shape[i];
+            } else {
+                this._selected = this._shapesNum;
+                Doc.SetSelectedTool(InkTool.None);
+                SetActiveArrowStart("");
+                SetActiveArrowEnd("");
+                GestureOverlay.Instance.InkShape = "";
+                SetActiveBezierApprox("0");
+            }
+        });
+        return <div className="btn-draw" key="draw">
+            {this._draw.map((icon, i) =>
+                <button className="antimodeMenu-button" key={icon} onPointerDown={() => func(i, false)} onDoubleClick={() => func(i, true)}
                     style={{ backgroundColor: i === this._selected ? "121212" : "", fontSize: "20" }}>
                     {this._draw[i]}
-                </button>;
-            })}</div>;
-        return drawButtons;
+                </button>)}
+        </div>;
     }
 
-    // @computed get arrowPicker() {
-    //     var currIcon;
-    //     for (var i = 0; i < this._arrowStart.length; i++) {
-    //         if (this._arrowStart[i] === ActiveArrowStart() && this._arrowEnd[i] === ActiveArrowEnd()) {
-    //             currIcon = this._arrowIcons[i];
-    //             if (this._arrowIcons[i] === " ") {
-    //                 currIcon = "➤";
-    //             }
-    //         }
-    //     }
-    //     var arrowPicker = <button
-    //         className="antimodeMenu-button"
-    //         key="arrow"
-    //         onPointerDown={action(e => this._arrowBtn = !this._arrowBtn)}
-    //         style={{ backgroundColor: this._arrowBtn ? "121212" : "" }}>
-    //         {currIcon}
-    //     </button>;
-    //     if (this._arrowBtn) {
-    //         arrowPicker = <div className="btn2-group" key="arrows">
-    //             {arrowPicker}
-    //             {this._arrowStart.map((arrowStart, i) => {
-    //                 return <button
-    //                     className="antimodeMenu-button"
-    //                     key={arrowStart}
-    //                     onPointerDown={action(() => { SetActiveArrowStart(arrowStart); SetActiveArrowEnd(this._arrowEnd[i]); this.editProperties(arrowStart, "arrowStart"), this.editProperties(this._arrowEnd[i], "arrowEnd"); this._arrowBtn = false; })}
-    //                     style={{ backgroundColor: this._arrowBtn ? "121212" : "" }}>
-    //                     {this._arrowIcons[i]}
-    //                 </button>;
-    //             })}
-    //         </div>;
-    //     }
-    //     return arrowPicker;
-    // }
+    toggleButton = (key: string, value: boolean, setter: () => {}, icon: FontAwesomeIconProps["icon"], ele: JSX.Element | null) => {
+        return <button className="antimodeMenu-button" key={key} title={key}
+            onPointerDown={action(e => setter())}
+            style={{ backgroundColor: value ? "121212" : "" }}>
+            <FontAwesomeIcon icon={icon} size="lg" />
+            {ele}
+        </button>;
+    }
 
     @computed get widthPicker() {
-        var widthPicker = <button
-            className="antimodeMenu-button"
-            key="width"
-            onPointerDown={action(e => this._widthBtn = !this._widthBtn)}
-            style={{ backgroundColor: this._widthBtn ? "121212" : "" }}>
-            <FontAwesomeIcon icon="bars" size="lg" />
-        </button>;
-        if (this._widthBtn) {
-            widthPicker = <div className="btn2-group" key="width">
+        var widthPicker = this.toggleButton("stroke width", this._widthBtn, () => this._widthBtn = !this._widthBtn, "bars", null);
+        return !this._widthBtn ? widthPicker :
+            <div className="btn2-group" key="width">
                 {widthPicker}
-                {this._width.map(wid => {
-                    return <button
-                        className="antimodeMenu-button"
-                        key={wid}
+                {this._width.map(wid =>
+                    <button className="antimodeMenu-button" key={wid}
                         onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; this.editProperties(wid, "width"); })}
                         style={{ backgroundColor: this._widthBtn ? "121212" : "", zIndex: 1001 }}>
                         {wid}
-                    </button>;
-                })}
+                    </button>)}
             </div>;
-        }
-        return widthPicker;
     }
 
-
-
     @computed get colorPicker() {
-        var colorPicker = <button
-            className="antimodeMenu-button"
-            key="color"
-            title="colorChanger"
-            onPointerDown={action(e => this._colorBtn = !this._colorBtn)}
-            style={{ backgroundColor: this._colorBtn ? "121212" : "" }}>
-            <FontAwesomeIcon icon="pen-nib" size="lg" />
-            <div className="color-previewI" style={{ backgroundColor: ActiveInkColor() ?? "121212" }}></div>
-
-        </button>;
-        if (this._colorBtn) {
-            colorPicker = <div className="btn-group" key="color">
+        var colorPicker = this.toggleButton("stroke color", this._colorBtn, () => this._colorBtn = !this._colorBtn, "pen-nib",
+            <div className="color-previewI" style={{ backgroundColor: ActiveInkColor() ?? "121212" }} />);
+        return !this._colorBtn ? colorPicker :
+            <div className="btn-group" key="color">
                 {colorPicker}
-                {this._palette.map(color => {
-                    return <button
-                        className="antimodeMenu-button"
-                        key={color}
+                {this._palette.map(color =>
+                    <button className="antimodeMenu-button" key={color}
                         onPointerDown={action(() => { this.changeColor(color, "color"); this._colorBtn = false; this.editProperties(color, "color"); })}
                         style={{ backgroundColor: this._colorBtn ? "121212" : "", zIndex: 1001 }}>
                         {/* <FontAwesomeIcon icon="pen-nib" size="lg" /> */}
-                        <div className="color-previewII" style={{ backgroundColor: color }}></div>
-                    </button>;
-                })}
+                        <div className="color-previewII" style={{ backgroundColor: color }} />
+                    </button>)}
             </div>;
-        }
-        return colorPicker;
-    }
-    @computed get formatPane() {
-        return <button className="antimodeMenu-button" key="format"
-            title="toggle foramatting pane"
-            onPointerDown={action(e => FormatShapePane.Instance.Pinned = !FormatShapePane.Instance.Pinned)}
-            style={{ backgroundColor: this._fillBtn ? "121212" : "" }}>
-            <FontAwesomeIcon icon="chevron-right" size="lg" />
-        </button>;
     }
-
     @computed get fillPicker() {
-        var fillPicker = <button
-            className="antimodeMenu-button"
-            key="fill"
-            title="fillChanger"
-            onPointerDown={action(e => this._fillBtn = !this._fillBtn)}
-            style={{ backgroundColor: this._fillBtn ? "121212" : "" }}>
-            <FontAwesomeIcon icon="fill-drip" size="lg" />
-            <div className="color-previewI" style={{ backgroundColor: ActiveFillColor() ?? "121212" }}></div>
-        </button>;
-        if (this._fillBtn) {
-            fillPicker = <div className="btn-group" key="fill" >
+        var fillPicker = this.toggleButton("shape fill color", this._fillBtn, () => this._fillBtn = !this._fillBtn, "fill-drip",
+            <div className="color-previewI" style={{ backgroundColor: ActiveFillColor() ?? "121212" }} />);
+        return !this._fillBtn ? fillPicker :
+            <div className="btn-group" key="fill" >
                 {fillPicker}
-                {this._palette.map(color => {
-                    return <button
-                        className="antimodeMenu-button"
-                        key={color}
+                {this._palette.map(color =>
+                    <button className="antimodeMenu-button" key={color}
                         onPointerDown={action(() => { this.changeColor(color, "fill"); this._fillBtn = false; this.editProperties(color, "fill"); })}
                         style={{ backgroundColor: this._fillBtn ? "121212" : "", zIndex: 1001 }}>
                         <div className="color-previewII" style={{ backgroundColor: color }}></div>
-                    </button>;
-                })}
+                    </button>)}
 
             </div>;
-        }
-        return fillPicker;
     }
 
-    // @computed get shapePicker() {
-    //     var currIcon;
-    //     if (GestureOverlay.Instance.InkShape === "") {
-    //         currIcon = <FontAwesomeIcon icon="shapes" size="lg" />;
-    //     } else {
-    //         for (var i = 0; i < this._icons.length; i++) {
-    //             if (GestureOverlay.Instance.InkShape === this._buttons[i]) {
-    //                 currIcon = this._icons[i];
-    //             }
-    //         }
-    //     }
-    //     var shapePicker = <button
-    //         className="antimodeMenu-button"
-    //         key="shape"
-    //         onPointerDown={action(e => this._shapeBtn = !this._shapeBtn)}
-    //         style={{ backgroundColor: this._shapeBtn ? "121212" : "" }}>
-    //         {currIcon}
-    //     </button>;
-    //     if (this._shapeBtn) {
-    //         shapePicker = <div className="btn2-group" key="shape">
-    //             {shapePicker}
-    //             {this._buttons.map((btn, i) => {
-    //                 var ttl = btn;
-    //                 if (btn === "") {
-    //                     ttl = "no shape";
-    //                 }
-    //                 if (btn === "noRec") {
-    //                     ttl = "disable shape recognition";
-    //                 }
-    //                 return <button
-    //                     className="antimodeMenu-button"
-    //                     title={`Draw ${btn}`}
-    //                     key={ttl}
-    //                     onPointerDown={action((e) => { GestureOverlay.Instance.InkShape = btn; this._shapeBtn = false; })}
-    //                     style={{ backgroundColor: this._shapeBtn ? "121212" : "", fontSize: "20" }}>
-    //                     {this._icons[i]}
-    //                 </button>;
-    //             })}
-    //         </div>;
-    //     }
-    //     return shapePicker;
-    // }
-
-    @computed get bezierButton() {
-        return <button
-            className="antimodeMenu-button"
-            title="Bezier changer"
-            key="bezier"
-            onPointerDown={e => this.changeBezier(e)}
-            style={{ backgroundColor: ActiveInkBezierApprox() ? "121212" : "" }}>
-            <FontAwesomeIcon icon="bezier-curve" size="lg" />
-
-        </button>;
-    }
-
-    @computed get dashButton() {
-        return <button
-            className="antimodeMenu-button"
-            title="dash changer"
-            key="dash"
-            onPointerDown={e => this.changeDash(e)}
-            style={{ backgroundColor: ActiveDash() !== "0" ? "121212" : "" }}>
-            <FontAwesomeIcon icon="ellipsis-h" size="lg" />
-
+    @computed get formatPane() {
+        return <button className="antimodeMenu-button" key="format" title="toggle foramatting pane"
+            onPointerDown={action(e => FormatShapePane.Instance.Pinned = !FormatShapePane.Instance.Pinned)}
+            style={{ backgroundColor: this._fillBtn ? "121212" : "" }}>
+            <FontAwesomeIcon icon="chevron-right" size="lg" />
         </button>;
     }
 
     render() {
-        const buttons = [
-            // <button className="antimodeMenu-button" title="Drag" key="drag" onPointerDown={e => this.dragStart(e)}>
-            //     <FontAwesomeIcon icon="arrows-alt" size="lg" />
-            // </button>,
-            // this.shapePicker,
-            // this.bezierButton,
+        return this.getElement([
             this.widthPicker,
             this.colorPicker,
             this.fillPicker,
             this.drawButtons,
             this.formatPane,
-            // this.arrowPicker,
-            // this.dashButton,
-            <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this.collapsed ? "none" : undefined }}>
+            <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this._collapsed ? "none" : undefined }}>
                 <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
             </button>
-        ];
-
-        // return this.getElement(buttons);
-        return this.getElement(buttons);
+        ]);
     }
 }
 Scripting.addGlobal(function activatePen(penBtn: any) {
-- 
cgit v1.2.3-70-g09d2


From 6c96bd6c8ef06e1ecb75ca39a575155dafcc26f1 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 14 Jul 2020 21:56:48 -0400
Subject: moving collectionViewChrome to CollectionMenu

---
 src/client/views/MainView.tsx                    |   2 +
 src/client/views/collections/CollectionMenu.scss |  12 +
 src/client/views/collections/CollectionMenu.tsx  | 331 +++++++++++++++++++++++
 src/client/views/collections/CollectionView.tsx  |   2 +
 4 files changed, 347 insertions(+)
 create mode 100644 src/client/views/collections/CollectionMenu.scss
 create mode 100644 src/client/views/collections/CollectionMenu.tsx

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 41efe246c..428fb835b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -66,6 +66,7 @@ import { LinkCreatedBox } from './nodes/LinkCreatedBox';
 import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
 import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane";
 import HypothesisAuthenticationManager from '../apis/HypothesisAuthenticationManager';
+import CollectionMenu from './collections/CollectionMenu';
 
 @observer
 export class MainView extends React.Component {
@@ -612,6 +613,7 @@ export class MainView extends React.Component {
             <GoogleAuthenticationManager />
             <HypothesisAuthenticationManager />
             <DocumentDecorations />
+            <CollectionMenu />
             <InkOptionsMenu />
             <FormatShapePane />
             <RichTextMenu key="rich" />
diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss
new file mode 100644
index 000000000..6280670d8
--- /dev/null
+++ b/src/client/views/collections/CollectionMenu.scss
@@ -0,0 +1,12 @@
+.antimodeMenu-button {
+    padding:0;
+    width:40px;
+    display: flex;
+    svg {
+        margin:auto;
+    }
+}
+.collectionViewChrome-cont {
+    position:relative;
+    display:inline-flex;
+}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
new file mode 100644
index 000000000..d79ffffa1
--- /dev/null
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -0,0 +1,331 @@
+import React = require("react");
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, observable, reaction, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import { Doc, DocListCast } from "../../../fields/Doc";
+import { BoolCast, Cast, StrCast, NumCast } from "../../../fields/Types";
+import AntimodeMenu from "../AntimodeMenu";
+import "./CollectionMenu.scss";
+import { undoBatch } from "../../util/UndoManager";
+import { CollectionViewType, CollectionView } from "./CollectionView";
+import { emptyFunction, setupMoveUpEvents, Utils } from "../../../Utils";
+import { CollectionGridViewChrome } from "./CollectionViewChromes";
+import { DragManager } from "../../util/DragManager";
+import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
+import { List } from "../../../fields/List";
+import { SelectionManager } from "../../util/SelectionManager";
+
+@observer
+export default class CollectionMenu extends AntimodeMenu {
+    static Instance: CollectionMenu;
+
+    @observable SelectedCollection: CollectionView | undefined;
+
+    constructor(props: Readonly<{}>) {
+        super(props);
+        CollectionMenu.Instance = this;
+        this._canFade = false; // don't let the inking menu fade away
+        this.Pinned = Cast(Doc.UserDoc()["menuCollections-pinned"], "boolean", true);
+    }
+
+    @action
+    toggleMenuPin = (e: React.MouseEvent) => {
+        Doc.UserDoc()["menuCollections-pinned"] = this.Pinned = !this.Pinned;
+    }
+
+    @computed get aButton() {
+        return <div className="btn-draw" key="draw">
+            <button className="antimodeMenu-button" key="abutton" onClick={() => alert("clicked")} style={{ fontSize: "20" }}>
+                <FontAwesomeIcon icon="palette" size="lg" />
+            </button>
+        </div>;
+    }
+
+    render() {
+        return this.getElement([
+            this.aButton,
+            !this.SelectedCollection ? <></> : <CollectionViewBaseChrome CollectionView={this.SelectedCollection} type={StrCast(this.SelectedCollection.props.Document._viewType) as CollectionViewType} />,
+            <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "" }}>
+                <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
+            </button>
+        ]);
+    }
+}
+
+interface CollectionViewChromeProps {
+    CollectionView: CollectionView;
+    type: CollectionViewType;
+    collapse?: (value: boolean) => any;
+}
+
+const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation();
+
+@observer
+export class CollectionViewBaseChrome extends React.Component<CollectionViewChromeProps> {
+    //(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
+
+    get target() { return this.props.CollectionView.props.Document; }
+    _templateCommand = {
+        params: ["target", "source"], title: "=> item view",
+        script: "this.target.childLayout = getDocTemplate(this.source?.[0])",
+        immediate: undoBatch((source: Doc[]) => source.length && (this.target.childLayout = Doc.getDocTemplate(source?.[0]))),
+        initialize: emptyFunction,
+    };
+    _narrativeCommand = {
+        params: ["target", "source"], title: "=> child click view",
+        script: "this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])",
+        immediate: undoBatch((source: Doc[]) => source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]))),
+        initialize: emptyFunction,
+    };
+    _contentCommand = {
+        params: ["target", "source"], title: "=> clear content",
+        script: "getProto(this.target).data = copyField(this.source);",
+        immediate: undoBatch((source: Doc[]) => Doc.GetProto(this.target).data = new List<Doc>(source)), // Doc.aliasDocs(source),
+        initialize: emptyFunction,
+    };
+    _viewCommand = {
+        params: ["target"], title: "=> reset view",
+        script: "this.target._panX = this.restoredPanX; this.target._panY = this.restoredPanY; this.target.scale = this.restoredScale;",
+        immediate: undoBatch((source: Doc[]) => { this.target._panX = 0; this.target._panY = 0; this.target.scale = 1; }),
+        initialize: (button: Doc) => { button.restoredPanX = this.target._panX; button.restoredPanY = this.target._panY; button.restoredScale = this.target.scale; },
+    };
+    _clusterCommand = {
+        params: ["target"], title: "=> fit content",
+        script: "this.target._fitToBox = !this.target._fitToBox;",
+        immediate: undoBatch((source: Doc[]) => this.target._fitToBox = !this.target._fitToBox),
+        initialize: emptyFunction
+    };
+    _fitContentCommand = {
+        params: ["target"], title: "=> toggle clusters",
+        script: "this.target.useClusters = !this.target.useClusters;",
+        immediate: undoBatch((source: Doc[]) => this.target.useClusters = !this.target.useClusters),
+        initialize: emptyFunction
+    };
+
+    _freeform_commands = [this._viewCommand, this._fitContentCommand, this._clusterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand];
+    _stacking_commands = [this._contentCommand, this._templateCommand];
+    _masonry_commands = [this._contentCommand, this._templateCommand];
+    _schema_commands = [this._templateCommand, this._narrativeCommand];
+    _tree_commands = [];
+    private get _buttonizableCommands() {
+        switch (this.props.type) {
+            case CollectionViewType.Tree: return this._tree_commands;
+            case CollectionViewType.Schema: return this._schema_commands;
+            case CollectionViewType.Stacking: return this._stacking_commands;
+            case CollectionViewType.Masonry: return this._stacking_commands;
+            case CollectionViewType.Freeform: return this._freeform_commands;
+            case CollectionViewType.Time: return this._freeform_commands;
+            case CollectionViewType.Carousel: return this._freeform_commands;
+            case CollectionViewType.Carousel3D: return this._freeform_commands;
+        }
+        return [];
+    }
+    private _picker: any;
+    private _commandRef = React.createRef<HTMLInputElement>();
+    private _viewRef = React.createRef<HTMLInputElement>();
+    @observable private _currentKey: string = "";
+
+    componentDidMount = action(() => {
+        this._currentKey = this._currentKey || (this._buttonizableCommands.length ? this._buttonizableCommands[0]?.title : "");
+    });
+
+    @undoBatch
+    viewChanged = (e: React.ChangeEvent) => {
+        //@ts-ignore
+        this.document._viewType = e.target.selectedOptions[0].value;
+    }
+
+    commandChanged = (e: React.ChangeEvent) => {
+        //@ts-ignore
+        runInAction(() => this._currentKey = e.target.selectedOptions[0].value);
+    }
+
+    @action
+    toggleViewSpecs = (e: React.SyntheticEvent) => {
+        this.document._facetWidth = this.document._facetWidth ? 0 : 200;
+        e.stopPropagation();
+    }
+
+    @action closeViewSpecs = () => {
+        this.document._facetWidth = 0;
+    }
+
+
+    @computed get subChrome() {
+        switch (this.props.type) {
+            case CollectionViewType.Freeform: return (<CollectionFreeFormViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+            // case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
+            // case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
+            // case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
+            // case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
+            // case CollectionViewType.Carousel3D: return (<Collection3DCarouselViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
+            // case CollectionViewType.Grid: return (<CollectionGridViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+            default: return null;
+        }
+    }
+
+    private get document() {
+        return this.props.CollectionView.props.Document;
+    }
+
+    private dropDisposer?: DragManager.DragDropDisposer;
+    protected createDropTarget = (ele: HTMLDivElement) => {
+        this.dropDisposer?.();
+        if (ele) {
+            this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.document);
+        }
+    }
+
+    @undoBatch
+    @action
+    protected drop(e: Event, de: DragManager.DropEvent): boolean {
+        const docDragData = de.complete.docDragData;
+        if (docDragData?.draggedDocuments.length) {
+            this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate(docDragData.draggedDocuments || []));
+            e.stopPropagation();
+        }
+        return true;
+    }
+
+    dragViewDown = (e: React.PointerEvent) => {
+        setupMoveUpEvents(this, e, (e, down, delta) => {
+            const vtype = this.props.CollectionView.collectionViewType;
+            const c = {
+                params: ["target"], title: vtype,
+                script: `this.target._viewType = '${StrCast(this.props.CollectionView.props.Document._viewType)}'`,
+                immediate: (source: Doc[]) => this.props.CollectionView.props.Document._viewType = Doc.getDocTemplate(source?.[0]),
+                initialize: emptyFunction,
+            };
+            DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title),
+                { target: this.props.CollectionView.props.Document }, c.params, c.initialize, e.clientX, e.clientY);
+            return true;
+        }, emptyFunction, emptyFunction);
+    }
+    dragCommandDown = (e: React.PointerEvent) => {
+        setupMoveUpEvents(this, e, (e, down, delta) => {
+            this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c =>
+                DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title,
+                    { target: this.props.CollectionView.props.Document }, c.params, c.initialize, e.clientX, e.clientY));
+            return true;
+        }, emptyFunction, () => {
+            this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate([]));
+        });
+    }
+
+    @computed get templateChrome() {
+        return <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} >
+            <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
+                <div className="commandEntry-drop">
+                    <FontAwesomeIcon icon="bullseye" size="2x" />
+                </div>
+                <select
+                    className="collectionViewBaseChrome-cmdPicker" onPointerDown={stopPropagation} onChange={this.commandChanged} value={this._currentKey}>
+                    <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={"empty"} value={""} />
+                    {this._buttonizableCommands.map(cmd =>
+                        <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={cmd.title} value={cmd.title}>{cmd.title}</option>
+                    )}
+                </select>
+            </div>
+        </div>;
+    }
+
+    @computed get viewModes() {
+        const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
+        return <div className="collectionViewBaseChrome-viewModes" style={{ display: collapsed ? "none" : undefined }}>
+            <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._viewRef} onPointerDown={this.dragViewDown}>
+                <div className="commandEntry-drop">
+                    <FontAwesomeIcon icon="bullseye" size="2x" />
+                </div>
+                <select
+                    className="collectionViewBaseChrome-viewPicker"
+                    onPointerDown={stopPropagation}
+                    onChange={this.viewChanged}
+                    value={StrCast(this.props.CollectionView.props.Document._viewType)}>
+                    {Object.values(CollectionViewType).map(type => [CollectionViewType.Invalid, CollectionViewType.Docking].includes(type) ? (null) : (
+                        <option
+                            key={Utils.GenerateGuid()}
+                            className="collectionViewBaseChrome-viewOption"
+                            onPointerDown={stopPropagation}
+                            value={type}>
+                            {type[0].toUpperCase() + type.substring(1)}
+                        </option>
+                    ))}
+                </select>
+            </div>
+        </div>;
+    }
+
+    render() {
+        const scale = Math.min(1, this.props.CollectionView.props.ScreenToLocalTransform()?.Scale);
+        return (
+            <div className="collectionViewChrome-cont" style={{
+                top: 0,
+                transform: `scale(${scale})`,
+                width: "100%"
+            }}>
+                <div className="collectionViewChrome" style={{ border: "unset" }}>
+                    <div className="collectionViewBaseChrome">
+                        {this.viewModes}
+                        <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: "grid" }}>
+                            <div className="collectionViewBaseChrome-filterIcon" onPointerDown={this.toggleViewSpecs} >
+                                <FontAwesomeIcon icon="filter" size="2x" />
+                            </div>
+                        </div>
+                        {this.templateChrome}
+                    </div>
+                    {this.subChrome}
+                </div>
+            </div>
+        );
+    }
+}
+
+@observer
+export class CollectionFreeFormViewChrome extends React.Component<CollectionViewChromeProps> {
+
+    get Document() { return this.props.CollectionView.props.Document; }
+    @computed get dataField() {
+        return this.props.CollectionView.props.Document[Doc.LayoutFieldKey(this.props.CollectionView.props.Document)];
+    }
+    @computed get childDocs() {
+        return DocListCast(this.dataField);
+    }
+    @undoBatch
+    @action
+    nextKeyframe = (): void => {
+        const currentFrame = NumCast(this.Document.currentFrame);
+        if (currentFrame === undefined) {
+            this.Document.currentFrame = 0;
+            CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
+        }
+        CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0);
+        this.Document.currentFrame = Math.max(0, (currentFrame || 0) + 1);
+        this.Document.lastFrame = Math.max(NumCast(this.Document.currentFrame), NumCast(this.Document.lastFrame));
+    }
+    @undoBatch
+    @action
+    prevKeyframe = (): void => {
+        const currentFrame = NumCast(this.Document.currentFrame);
+        if (currentFrame === undefined) {
+            this.Document.currentFrame = 0;
+            CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
+        }
+        CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice());
+        this.Document.currentFrame = Math.max(0, (currentFrame || 0) - 1);
+    }
+    render() {
+        return this.Document.isAnnotationOverlay ? (null) :
+            <div className="collectionFreeFormViewChrome-cont">
+                <div key="back" title="back frame" className="backKeyframe" onClick={this.prevKeyframe}>
+                    <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
+                </div>
+                <div key="num" title="toggle view all" className="numKeyframe" style={{ backgroundColor: this.Document.editing ? "#759c75" : "#c56565" }}
+                    onClick={action(() => this.Document.editing = !this.Document.editing)} >
+                    {NumCast(this.Document.currentFrame)}
+                </div>
+                <div key="fwd" title="forward frame" className="fwdKeyframe" onClick={this.nextKeyframe}>
+                    <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
+                </div>
+            </div>;
+    }
+}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index cbd1ac9af..e18fe319e 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -48,6 +48,7 @@ import { CollectionTimeView } from './CollectionTimeView';
 import { CollectionTreeView } from "./CollectionTreeView";
 import './CollectionView.scss';
 import { CollectionViewBaseChrome } from './CollectionViewChromes';
+import CollectionMenu from './CollectionMenu';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -549,6 +550,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             ChildLayoutTemplate: this.childLayoutTemplate,
             ChildLayoutString: this.childLayoutString,
         };
+        setTimeout(action(() => this.props.isSelected() && (CollectionMenu.Instance.SelectedCollection = this)), 0);
         const boxShadow = Doc.UserDoc().renderStyle === "comic" || this.props.Document.isBackground || this.collectionViewType === CollectionViewType.Linear ? undefined :
             `${Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "rgb(30, 32, 31) " : "#9c9396 "} ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`;
         return (<div className={"collectionView"} onContextMenu={this.onContextMenu}
-- 
cgit v1.2.3-70-g09d2


From 95429adb77402ce2a218c5585ceef4db0c8494db Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 14 Jul 2020 23:55:59 -0400
Subject: converted collectionChrome into a menu bar

---
 src/client/views/MainView.tsx                      |   6 +-
 .../views/collections/CollectionDockingView.tsx    |   2 +-
 src/client/views/collections/CollectionMenu.scss   | 475 ++++++++++++++++++++-
 src/client/views/collections/CollectionMenu.tsx    | 443 +++++++++++++++++--
 src/client/views/collections/CollectionView.tsx    |  11 +-
 5 files changed, 888 insertions(+), 49 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 428fb835b..95b2dfcdb 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -392,7 +392,7 @@ export class MainView extends React.Component {
             doc.dockingConfig ? this.openWorkspace(doc) :
                 CollectionDockingView.AddRightSplit(doc, libraryPath);
     }
-    sidebarScreenToLocal = () => new Transform(0, RichTextMenu.Instance.Pinned ? -35 : 0, 1);
+    sidebarScreenToLocal = () => new Transform(0, (RichTextMenu.Instance.Pinned ? -35 : 0) + (InkOptionsMenu.Instance.Pinned ? -35 : 0) + (CollectionMenu.Instance.Pinned ? -35 : 0), 1);
     mainContainerXf = () => this.sidebarScreenToLocal().translate(0, -this._buttonBarHeight);
 
     @computed get flyout() {
@@ -469,11 +469,13 @@ export class MainView extends React.Component {
 
     @computed get mainContent() {
         const sidebar = this.userDoc?.["tabs-panelContainer"];
+        const n = (RichTextMenu.Instance?.Pinned ? 1 : 0) + (InkOptionsMenu.Instance?.Pinned ? 1 : 0) + (CollectionMenu.Instance?.Pinned ? 1 : 0);
+        const height = `calc(100% - ${n * Number(ANTIMODEMENU_HEIGHT.replace("px", ""))}px)`;
         return !this.userDoc || !(sidebar instanceof Doc) ? (null) : (
             <div className="mainView-mainContent" style={{
                 color: this.darkScheme ? "rgb(205,205,205)" : "black",
                 //change to times 2 for both pinned
-                height: (RichTextMenu.Instance?.Pinned || InkOptionsMenu.Instance?.Pinned) ? (RichTextMenu.Instance?.Pinned && InkOptionsMenu.Instance?.Pinned) ? `calc(100% - 2*${ANTIMODEMENU_HEIGHT})` : `calc(100% - ${ANTIMODEMENU_HEIGHT})` : "100%",
+                height,
                 width: (FormatShapePane.Instance?.Pinned) ? `calc(100% - 200px)` : "100%"
             }} >
                 <div style={{ display: "contents", flexDirection: "row", position: "relative" }}>
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index f408e24a8..657296566 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -436,7 +436,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         const cur = this._containerRef.current;
 
         // bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed
-        this._goldenLayout && this._goldenLayout.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height);
+        this._goldenLayout?.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height);
     }
 
     @action
diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss
index 6280670d8..c1a3f9c09 100644
--- a/src/client/views/collections/CollectionMenu.scss
+++ b/src/client/views/collections/CollectionMenu.scss
@@ -1,12 +1,471 @@
-.antimodeMenu-button {
-    padding:0;
-    width:40px;
+@import "../globalCssVariables";
+
+
+.collectionMenu-cont {
+    position:relative;
+    display:inline-flex;
+    width: 100%;
+    opacity: 0.9;
+    z-index: 9001;
+    transition: top .5s;
+    background: #121721;
+    color: white;
+    transform-origin: top left;
+
+    .antimodeMenu-button {
+        padding:0;
+        width:40px;
+        display: flex;
+        svg {
+            margin:auto;
+        }
+    }
+
+    .collectionViewChrome {
+        display: flex;
+        padding-bottom: 1px;
+        height: 32px;
+        border-bottom: .5px solid rgb(180, 180, 180);
+        overflow: visible;
+        z-index: 9001;
+
+        .collectionViewBaseChrome {
+            display: flex;
+
+            .collectionViewBaseChrome-viewPicker {
+                font-size: 75%;
+                //text-transform: uppercase;
+                //letter-spacing: 2px;
+                background: #121721;
+                color: white;
+                outline-color: black;
+                border: none;
+                //padding: 12px 10px 11px 10px;
+            }
+
+            .collectionViewBaseChrome-viewPicker:active {
+                outline-color: black;
+            }
+
+            .collectionViewBaseChrome-button {
+                font-size: 75%;
+                text-transform: uppercase;
+                letter-spacing: 2px;
+                background: rgb(238, 238, 238);
+                color: purple;
+                outline-color: black;
+                border: none;
+                padding: 12px 10px 11px 10px;
+                margin-left: 10px;
+            }
+
+            .collectionViewBaseChrome-cmdPicker {
+                margin-left: 3px;
+                margin-right: 0px;
+                font-size: 75%;
+                background: #121721;
+                border: none;
+                color: white;
+            }
+
+            .commandEntry-outerDiv {
+                pointer-events: all;
+                background-color: #121721;
+                display: flex;
+                flex-direction: row;
+                height: 30px;
+
+                .commandEntry-drop {
+                    color: white;
+                    width: 30px;
+                    margin-top: auto;
+                    margin-bottom: auto;
+                }
+            }
+
+            .collectionViewBaseChrome-collapse {
+                transition: all .5s, opacity 0.3s;
+                position: absolute;
+                width: 40px;
+                transform-origin: top left;
+                pointer-events: all;
+                // margin-top: 10px;
+            }
+
+            @media only screen and (max-device-width: 480px) {
+                .collectionViewBaseChrome-collapse {
+                    display: none;
+                }
+            }
+
+            .collectionViewBaseChrome-template,
+            .collectionViewBaseChrome-viewModes {
+                display: grid;
+                background: rgb(238, 238, 238);
+                color: grey;
+                margin-top: auto;
+                margin-bottom: auto;
+            }
+            .collectionViewBaseChrome-viewSpecs {
+                margin-left: 5px;
+                display: grid;
+
+                .collectionViewBaseChrome-filterIcon {
+                    position: relative;
+                    display: flex;
+                    margin: auto;
+                    background: #121721;
+                    color: white;
+                    width: 30px;
+                    height: 30px;
+                    align-items: center;
+                    justify-content: center;
+                }
+
+                .collectionViewBaseChrome-viewSpecsInput {
+                    padding: 12px 10px 11px 10px;
+                    border: 0px;
+                    color: grey;
+                    text-align: center;
+                    letter-spacing: 2px;
+                    outline-color: black;
+                    font-size: 75%;
+                    background: rgb(238, 238, 238);
+                    height: 100%;
+                    width: 75px;
+                }
+
+                .collectionViewBaseChrome-viewSpecsMenu {
+                    overflow: hidden;
+                    transition: height .5s, display .5s;
+                    position: absolute;
+                    top: 60px;
+                    z-index: 100;
+                    display: flex;
+                    flex-direction: column;
+                    background: rgb(238, 238, 238);
+                    box-shadow: grey 2px 2px 4px;
+
+                    .qs-datepicker {
+                        left: unset;
+                        right: 0;
+                    }
+
+                    .collectionViewBaseChrome-viewSpecsMenu-row {
+                        display: grid;
+                        grid-template-columns: 150px 200px 150px;
+                        margin-top: 10px;
+                        margin-right: 10px;
+
+                        .collectionViewBaseChrome-viewSpecsMenu-rowLeft,
+                        .collectionViewBaseChrome-viewSpecsMenu-rowMiddle,
+                        .collectionViewBaseChrome-viewSpecsMenu-rowRight {
+                            font-size: 75%;
+                            letter-spacing: 2px;
+                            color: grey;
+                            margin-left: 10px;
+                            padding: 5px;
+                            border: none;
+                            outline-color: black;
+                        }
+                    }
+
+                    .collectionViewBaseChrome-viewSpecsMenu-lastRow {
+                        display: grid;
+                        grid-template-columns: 1fr 1fr 1fr;
+                        grid-gap: 10px;
+                        margin: 10px;
+                    }
+                }
+            }
+        }
+
+        .collectionStackingViewChrome-cont,
+        .collectionTreeViewChrome-cont,
+        .collection3DCarouselViewChrome-cont {
+            display: flex;
+            justify-content: space-between;
+        }
+
+        .collectionGridViewChrome-cont {
+            display: flex;
+            margin-left: 10;
+
+            .collectionGridViewChrome-viewPicker {
+                font-size: 75%;
+                //text-transform: uppercase;
+                //letter-spacing: 2px;
+                background: #121721;
+                color: white;
+                outline-color: black;
+                border: none;
+                //padding: 12px 10px 11px 10px;
+            }
+
+            .collectionGridViewChrome-viewPicker:active {
+                outline-color: black;
+            }
+
+            .grid-control {
+                align-self: center;
+                display: flex;
+                flex-direction: row;
+                margin-right: 5px;
+
+                .grid-icon {
+                    margin-right: 5px;
+                    align-self: center;
+                }
+
+                .flexLabel {
+                    margin-bottom: 0;
+                }
+            }
+
+            .collectionGridViewChrome-entryBox {
+                width: 50%;
+            }
+        }
+
+
+        .collectionStackingViewChrome-sort,
+        .collectionTreeViewChrome-sort {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+
+            .collectionStackingViewChrome-sortIcon,
+            .collectionTreeViewChrome-sortIcon {
+                transition: transform .5s;
+                margin-left: 10px;
+            }
+        }
+
+        button:hover {
+            transform: scale(1);
+        }
+
+
+        .collectionStackingViewChrome-pivotField-cont,
+        .collectionTreeViewChrome-pivotField-cont,
+        .collection3DCarouselViewChrome-scrollSpeed-cont {
+            justify-self: right;
+            display: flex;
+            font-size: 75%;
+            letter-spacing: 2px;
+
+            .collectionStackingViewChrome-pivotField-label,
+            .collectionTreeViewChrome-pivotField-label,
+            .collection3DCarouselViewChrome-scrollSpeed-label {
+                vertical-align: center;
+                padding-left: 10px;
+                margin: auto;
+            }
+
+            .collectionStackingViewChrome-pivotField,
+            .collectionTreeViewChrome-pivotField,
+            .collection3DCarouselViewChrome-scrollSpeed {
+                color: white;
+                width: 100%;
+                min-width: 100px;
+                display: flex;
+                align-items: center;
+                background: rgb(238, 238, 238);
+
+                .editable-view-input,
+                input,
+                .editableView-container-editing-oneLine,
+                .editableView-container-editing {
+                    margin: auto;
+                    border: 0px;
+                    color: grey !important;
+                    text-align: center;
+                    letter-spacing: 2px;
+                    outline-color: black;
+                    height: 100%;
+                }
+
+                .react-autosuggest__container {
+                    margin: 0;
+                    color: grey;
+                    padding: 0px;
+                }
+            }
+        }
+
+        .collectionStackingViewChrome-pivotField:hover,
+        .collectionTreeViewChrome-pivotField:hover,
+        .collection3DCarouselViewChrome-scrollSpeed:hover {
+            cursor: text;
+        }
+
+    }
+}
+
+.collectionFreeFormMenu-cont {
+    width: 60px;
     display: flex;
-    svg {
-        margin:auto;
+    position: relative;
+    align-items: center;
+
+    .fwdKeyframe,
+    .numKeyframe,
+    .backKeyframe {
+        cursor: pointer;
+        position: absolute;
+        width: 20;
+        height: 30;
+        bottom: 0;
+        background: #121721;
+        display: flex;
+        align-items: center;
+        color: white;
+    }
+
+    .backKeyframe {
+        left: 0;
+
+        svg {
+            display: block;
+            margin: auto;
+        }
+    }
+
+    .numKeyframe {
+        left: 20;
+        display: flex;
+        flex-direction: column;
+        padding: 5px;
+    }
+
+    .fwdKeyframe {
+        left: 40;
+
+        svg {
+            display: block;
+            margin: auto;
+        }
     }
 }
-.collectionViewChrome-cont {
-    position:relative;
-    display:inline-flex;
+
+.collectionSchemaViewChrome-cont {
+    display: flex;
+    font-size: 10.5px;
+
+    .collectionSchemaViewChrome-toggle {
+        display: flex;
+        margin-left: 10px;
+    }
+
+    .collectionSchemaViewChrome-label {
+        text-transform: uppercase;
+        letter-spacing: 2px;
+        margin-right: 5px;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+    }
+
+    .collectionSchemaViewChrome-toggler {
+        width: 100px;
+        height: 41px;
+        background-color: black;
+        position: relative;
+    }
+
+    .collectionSchemaViewChrome-togglerButton {
+        width: 47px;
+        height: 35px;
+        background-color: $light-color-secondary;
+        // position: absolute;
+        transition: all 0.5s ease;
+        // top: 3px;
+        margin-top: 3px;
+        color: gray;
+        letter-spacing: 2px;
+        text-transform: uppercase;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        text-align: center;
+
+        &.on {
+            margin-left: 3px;
+        }
+
+        &.off {
+            margin-left: 50px;
+        }
+    }
+}
+
+
+.commandEntry-outerDiv {
+    display: flex;
+    flex-direction: column;
+    height: 40px;
+}
+
+.commandEntry-inputArea {
+    display: flex;
+    flex-direction: row;
+    width: 150px;
+    margin: auto auto auto auto;
+}
+
+.react-autosuggest__container {
+    position: relative;
+    width: 100%;
+    margin-left: 5px;
+    margin-right: 5px;
+}
+
+.react-autosuggest__input {
+    border: 1px solid #aaa;
+    border-radius: 4px;
+    width: 100%;
+}
+
+.react-autosuggest__input--focused {
+    outline: none;
+}
+
+.react-autosuggest__input--open {
+    border-bottom-left-radius: 0;
+    border-bottom-right-radius: 0;
+}
+
+.react-autosuggest__suggestions-container {
+    display: none;
+}
+
+.react-autosuggest__suggestions-container--open {
+    display: block;
+    position: fixed;
+    overflow-y: auto;
+    max-height: 400px;
+    width: 180px;
+    border: 1px solid #aaa;
+    background-color: #fff;
+    font-family: Helvetica, sans-serif;
+    font-weight: 300;
+    font-size: 16px;
+    border-bottom-left-radius: 4px;
+    border-bottom-right-radius: 4px;
+    z-index: 2;
+}
+
+.react-autosuggest__suggestions-list {
+    margin: 0;
+    padding: 0;
+    list-style-type: none;
+}
+
+.react-autosuggest__suggestion {
+    cursor: pointer;
+    padding: 10px 20px;
+}
+
+.react-autosuggest__suggestion--highlighted {
+    background-color: #ddd;
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index d79ffffa1..1644bc726 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -1,19 +1,20 @@
 import React = require("react");
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, reaction, runInAction } from "mobx";
+import { action, computed, observable, reaction, runInAction, Lambda } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast } from "../../../fields/Doc";
+import { Doc, DocListCast, Opt } from "../../../fields/Doc";
 import { BoolCast, Cast, StrCast, NumCast } from "../../../fields/Types";
 import AntimodeMenu from "../AntimodeMenu";
 import "./CollectionMenu.scss";
 import { undoBatch } from "../../util/UndoManager";
-import { CollectionViewType, CollectionView } from "./CollectionView";
+import { CollectionViewType, CollectionView, COLLECTION_BORDER_WIDTH } from "./CollectionView";
 import { emptyFunction, setupMoveUpEvents, Utils } from "../../../Utils";
-import { CollectionGridViewChrome } from "./CollectionViewChromes";
 import { DragManager } from "../../util/DragManager";
 import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
 import { List } from "../../../fields/List";
-import { SelectionManager } from "../../util/SelectionManager";
+import { EditableView } from "../EditableView";
+import { Id } from "../../../fields/FieldSymbols";
+import { listSpec } from "../../../fields/Schema";
 
 @observer
 export default class CollectionMenu extends AntimodeMenu {
@@ -33,19 +34,10 @@ export default class CollectionMenu extends AntimodeMenu {
         Doc.UserDoc()["menuCollections-pinned"] = this.Pinned = !this.Pinned;
     }
 
-    @computed get aButton() {
-        return <div className="btn-draw" key="draw">
-            <button className="antimodeMenu-button" key="abutton" onClick={() => alert("clicked")} style={{ fontSize: "20" }}>
-                <FontAwesomeIcon icon="palette" size="lg" />
-            </button>
-        </div>;
-    }
-
     render() {
         return this.getElement([
-            this.aButton,
             !this.SelectedCollection ? <></> : <CollectionViewBaseChrome CollectionView={this.SelectedCollection} type={StrCast(this.SelectedCollection.props.Document._viewType) as CollectionViewType} />,
-            <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "" }}>
+            <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: "#121721" }}>
                 <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
             </button>
         ]);
@@ -55,7 +47,6 @@ export default class CollectionMenu extends AntimodeMenu {
 interface CollectionViewChromeProps {
     CollectionView: CollectionView;
     type: CollectionViewType;
-    collapse?: (value: boolean) => any;
 }
 
 const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation();
@@ -154,12 +145,12 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     @computed get subChrome() {
         switch (this.props.type) {
             case CollectionViewType.Freeform: return (<CollectionFreeFormViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
-            // case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
-            // case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
-            // case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
-            // case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
-            // case CollectionViewType.Carousel3D: return (<Collection3DCarouselViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
-            // case CollectionViewType.Grid: return (<CollectionGridViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+            case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+            case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+            case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+            case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+            case CollectionViewType.Carousel3D: return (<Collection3DCarouselViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+            case CollectionViewType.Grid: return (<CollectionGridViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
             default: return null;
         }
     }
@@ -230,8 +221,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     }
 
     @computed get viewModes() {
-        const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
-        return <div className="collectionViewBaseChrome-viewModes" style={{ display: collapsed ? "none" : undefined }}>
+        return <div className="collectionViewBaseChrome-viewModes" >
             <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._viewRef} onPointerDown={this.dragViewDown}>
                 <div className="commandEntry-drop">
                     <FontAwesomeIcon icon="bullseye" size="2x" />
@@ -258,7 +248,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     render() {
         const scale = Math.min(1, this.props.CollectionView.props.ScreenToLocalTransform()?.Scale);
         return (
-            <div className="collectionViewChrome-cont" style={{
+            <div className="collectionMenu-cont" style={{
                 top: 0,
                 transform: `scale(${scale})`,
                 width: "100%"
@@ -293,7 +283,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
     @undoBatch
     @action
     nextKeyframe = (): void => {
-        const currentFrame = NumCast(this.Document.currentFrame);
+        const currentFrame = Cast(this.Document.currentFrame, "number", null);
         if (currentFrame === undefined) {
             this.Document.currentFrame = 0;
             CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
@@ -305,7 +295,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
     @undoBatch
     @action
     prevKeyframe = (): void => {
-        const currentFrame = NumCast(this.Document.currentFrame);
+        const currentFrame = Cast(this.Document.currentFrame, "number", null);
         if (currentFrame === undefined) {
             this.Document.currentFrame = 0;
             CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
@@ -315,7 +305,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
     }
     render() {
         return this.Document.isAnnotationOverlay ? (null) :
-            <div className="collectionFreeFormViewChrome-cont">
+            <div className="collectionFreeFormMenu-cont">
                 <div key="back" title="back frame" className="backKeyframe" onClick={this.prevKeyframe}>
                     <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
                 </div>
@@ -329,3 +319,400 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
             </div>;
     }
 }
+@observer
+export class CollectionStackingViewChrome extends React.Component<CollectionViewChromeProps> {
+    @observable private _currentKey: string = "";
+    @observable private suggestions: string[] = [];
+
+    @computed private get descending() { return StrCast(this.props.CollectionView.props.Document._columnsSort) === "descending"; }
+    @computed get pivotField() { return StrCast(this.props.CollectionView.props.Document._pivotField); }
+
+    getKeySuggestions = async (value: string): Promise<string[]> => {
+        value = value.toLowerCase();
+        const docs = DocListCast(this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey]);
+        if (docs instanceof Doc) {
+            return Object.keys(docs).filter(key => key.toLowerCase().startsWith(value));
+        } else {
+            const keys = new Set<string>();
+            docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
+            return Array.from(keys).filter(key => key.toLowerCase().startsWith(value));
+        }
+    }
+
+    @action
+    onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => {
+        this._currentKey = newValue;
+    }
+
+    getSuggestionValue = (suggestion: string) => suggestion;
+
+    renderSuggestion = (suggestion: string) => {
+        return <p>{suggestion}</p>;
+    }
+
+    onSuggestionFetch = async ({ value }: { value: string }) => {
+        const sugg = await this.getKeySuggestions(value);
+        runInAction(() => {
+            this.suggestions = sugg;
+        });
+    }
+
+    @action
+    onSuggestionClear = () => {
+        this.suggestions = [];
+    }
+
+    @action
+    setValue = (value: string) => {
+        this.props.CollectionView.props.Document._pivotField = value;
+        return true;
+    }
+
+    @action toggleSort = () => {
+        this.props.CollectionView.props.Document._columnsSort =
+            this.props.CollectionView.props.Document._columnsSort === "descending" ? "ascending" :
+                this.props.CollectionView.props.Document._columnsSort === "ascending" ? undefined : "descending";
+    }
+    @action resetValue = () => { this._currentKey = this.pivotField; };
+
+    render() {
+        return (
+            <div className="collectionStackingViewChrome-cont">
+                <div className="collectionStackingViewChrome-pivotField-cont">
+                    <div className="collectionStackingViewChrome-pivotField-label">
+                        GROUP BY:
+                    </div>
+                    <div className="collectionStackingViewChrome-sortIcon" onClick={this.toggleSort} style={{ transform: `rotate(${this.descending ? "180" : "0"}deg)` }}>
+                        <FontAwesomeIcon icon="caret-up" size="2x" color="white" />
+                    </div>
+                    <div className="collectionStackingViewChrome-pivotField">
+                        <EditableView
+                            GetValue={() => this.pivotField}
+                            autosuggestProps={
+                                {
+                                    resetValue: this.resetValue,
+                                    value: this._currentKey,
+                                    onChange: this.onKeyChange,
+                                    autosuggestProps: {
+                                        inputProps:
+                                        {
+                                            value: this._currentKey,
+                                            onChange: this.onKeyChange
+                                        },
+                                        getSuggestionValue: this.getSuggestionValue,
+                                        suggestions: this.suggestions,
+                                        alwaysRenderSuggestions: true,
+                                        renderSuggestion: this.renderSuggestion,
+                                        onSuggestionsFetchRequested: this.onSuggestionFetch,
+                                        onSuggestionsClearRequested: this.onSuggestionClear
+                                    }
+                                }}
+                            oneLine
+                            SetValue={this.setValue}
+                            contents={this.pivotField ? this.pivotField : "N/A"}
+                        />
+                    </div>
+                </div>
+            </div>
+        );
+    }
+}
+
+
+@observer
+export class CollectionSchemaViewChrome extends React.Component<CollectionViewChromeProps> {
+    // private _textwrapAllRows: boolean = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
+
+    @undoBatch
+    togglePreview = () => {
+        const dividerWidth = 4;
+        const borderWidth = Number(COLLECTION_BORDER_WIDTH);
+        const panelWidth = this.props.CollectionView.props.PanelWidth();
+        const previewWidth = NumCast(this.props.CollectionView.props.Document.schemaPreviewWidth);
+        const tableWidth = panelWidth - 2 * borderWidth - dividerWidth - previewWidth;
+        this.props.CollectionView.props.Document.schemaPreviewWidth = previewWidth === 0 ? Math.min(tableWidth / 3, 200) : 0;
+    }
+
+    @undoBatch
+    @action
+    toggleTextwrap = async () => {
+        const textwrappedRows = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []);
+        if (textwrappedRows.length) {
+            this.props.CollectionView.props.Document.textwrappedSchemaRows = new List<string>([]);
+        } else {
+            const docs = DocListCast(this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey]);
+            const allRows = docs instanceof Doc ? [docs[Id]] : docs.map(doc => doc[Id]);
+            this.props.CollectionView.props.Document.textwrappedSchemaRows = new List<string>(allRows);
+        }
+    }
+
+
+    render() {
+        const previewWidth = NumCast(this.props.CollectionView.props.Document.schemaPreviewWidth);
+        const textWrapped = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
+
+        return (
+            <div className="collectionSchemaViewChrome-cont">
+                <div className="collectionSchemaViewChrome-toggle">
+                    <div className="collectionSchemaViewChrome-label">Show Preview: </div>
+                    <div className="collectionSchemaViewChrome-toggler" onClick={this.togglePreview}>
+                        <div className={"collectionSchemaViewChrome-togglerButton" + (previewWidth !== 0 ? " on" : " off")}>
+                            {previewWidth !== 0 ? "on" : "off"}
+                        </div>
+                    </div>
+                </div>
+            </div >
+        );
+    }
+}
+
+@observer
+export class CollectionTreeViewChrome extends React.Component<CollectionViewChromeProps> {
+
+    get sortAscending() {
+        return this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "-sortAscending"];
+    }
+    set sortAscending(value) {
+        this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "-sortAscending"] = value;
+    }
+    @computed private get ascending() {
+        return Cast(this.sortAscending, "boolean", null);
+    }
+
+    @action toggleSort = () => {
+        if (this.sortAscending) this.sortAscending = undefined;
+        else if (this.sortAscending === undefined) this.sortAscending = false;
+        else this.sortAscending = true;
+    }
+
+    render() {
+        return (
+            <div className="collectionTreeViewChrome-cont">
+                <button className="collectionTreeViewChrome-sort" onClick={this.toggleSort}>
+                    <div className="collectionTreeViewChrome-sortLabel">
+                        Sort
+                        </div>
+                    <div className="collectionTreeViewChrome-sortIcon" style={{ transform: `rotate(${this.ascending === undefined ? "90" : this.ascending ? "180" : "0"}deg)` }}>
+                        <FontAwesomeIcon icon="caret-up" size="2x" color="white" />
+                    </div>
+                </button>
+            </div>
+        );
+    }
+}
+
+// Enter scroll speed for 3D Carousel 
+@observer
+export class Collection3DCarouselViewChrome extends React.Component<CollectionViewChromeProps> {
+    @computed get scrollSpeed() {
+        return this.props.CollectionView.props.Document._autoScrollSpeed;
+    }
+
+    @action
+    setValue = (value: string) => {
+        const numValue = Number(StrCast(value));
+        if (numValue > 0) {
+            this.props.CollectionView.props.Document._autoScrollSpeed = numValue;
+            return true;
+        }
+        return false;
+    }
+
+    render() {
+        return (
+            <div className="collection3DCarouselViewChrome-cont">
+                <div className="collection3DCarouselViewChrome-scrollSpeed-cont">
+                    <div className="collectionStackingViewChrome-scrollSpeed-label">
+                        AUTOSCROLL SPEED:
+                    </div>
+                    <div className="collection3DCarouselViewChrome-scrollSpeed">
+                        <EditableView
+                            GetValue={() => StrCast(this.scrollSpeed)}
+                            oneLine
+                            SetValue={this.setValue}
+                            contents={this.scrollSpeed ? this.scrollSpeed : 1000} />
+                    </div>
+                </div>
+            </div>
+        );
+    }
+}
+
+/**
+ * Chrome for grid view.
+ */
+@observer
+export class CollectionGridViewChrome extends React.Component<CollectionViewChromeProps> {
+
+    private clicked: boolean = false;
+    private entered: boolean = false;
+    private decrementLimitReached: boolean = false;
+    @observable private resize = false;
+    private resizeListenerDisposer: Opt<Lambda>;
+
+    componentDidMount() {
+
+        runInAction(() => this.resize = this.props.CollectionView.props.PanelWidth() < 700);
+
+        // listener to reduce text on chrome resize (panel resize)
+        this.resizeListenerDisposer = computed(() => this.props.CollectionView.props.PanelWidth()).observe(({ newValue }) => {
+            runInAction(() => this.resize = newValue < 700);
+        });
+    }
+
+    componentWillUnmount() {
+        this.resizeListenerDisposer?.();
+    }
+
+    get numCols() { return NumCast(this.props.CollectionView.props.Document.gridNumCols, 10); }
+
+    /**
+     * Sets the value of `numCols` on the grid's Document to the value entered.
+     */
+    @undoBatch
+    onNumColsEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
+        if (e.key === "Enter" || e.key === "Tab") {
+            if (e.currentTarget.valueAsNumber > 0) {
+                this.props.CollectionView.props.Document.gridNumCols = e.currentTarget.valueAsNumber;
+            }
+
+        }
+    }
+
+    /**
+     * Sets the value of `rowHeight` on the grid's Document to the value entered.
+     */
+    // @undoBatch
+    // onRowHeightEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
+    //     if (e.key === "Enter" || e.key === "Tab") {
+    //         if (e.currentTarget.valueAsNumber > 0 && this.props.CollectionView.props.Document.rowHeight as number !== e.currentTarget.valueAsNumber) {
+    //             this.props.CollectionView.props.Document.rowHeight = e.currentTarget.valueAsNumber;
+    //         }
+    //     }
+    // }
+
+    /**
+     * Sets whether the grid is flexible or not on the grid's Document.
+     */
+    @undoBatch
+    toggleFlex = () => {
+        this.props.CollectionView.props.Document.gridFlex = !BoolCast(this.props.CollectionView.props.Document.gridFlex, true);
+    }
+
+    /**
+     * Increments the value of numCols on button click
+     */
+    onIncrementButtonClick = () => {
+        this.clicked = true;
+        this.entered && (this.props.CollectionView.props.Document.gridNumCols as number)--;
+        undoBatch(() => this.props.CollectionView.props.Document.gridNumCols = this.numCols + 1)();
+        this.entered = false;
+    }
+
+    /**
+     * Decrements the value of numCols on button click
+     */
+    onDecrementButtonClick = () => {
+        this.clicked = true;
+        if (!this.decrementLimitReached) {
+            this.entered && (this.props.CollectionView.props.Document.gridNumCols as number)++;
+            undoBatch(() => this.props.CollectionView.props.Document.gridNumCols = this.numCols - 1)();
+        }
+        this.entered = false;
+    }
+
+    /**
+     * Increments the value of numCols on button hover
+     */
+    incrementValue = () => {
+        this.entered = true;
+        if (!this.clicked && !this.decrementLimitReached) {
+            this.props.CollectionView.props.Document.gridNumCols = this.numCols + 1;
+        }
+        this.decrementLimitReached = false;
+        this.clicked = false;
+    }
+
+    /**
+     * Decrements the value of numCols on button hover
+     */
+    decrementValue = () => {
+        this.entered = true;
+        if (!this.clicked) {
+            if (this.numCols !== 1) {
+                this.props.CollectionView.props.Document.gridNumCols = this.numCols - 1;
+            }
+            else {
+                this.decrementLimitReached = true;
+            }
+        }
+
+        this.clicked = false;
+    }
+
+    /**
+     * Toggles the value of preventCollision
+     */
+    toggleCollisions = () => {
+        this.props.CollectionView.props.Document.gridPreventCollision = !this.props.CollectionView.props.Document.gridPreventCollision;
+    }
+
+    /**
+     * Changes the value of the compactType
+     */
+    changeCompactType = (e: React.ChangeEvent<HTMLSelectElement>) => {
+        // need to change startCompaction so that this operation will be undoable.
+        this.props.CollectionView.props.Document.gridStartCompaction = e.target.selectedOptions[0].value;
+    }
+
+    render() {
+        return (
+            <div className="collectionGridViewChrome-cont" >
+                <span className="grid-control" style={{ width: this.resize ? "25%" : "30%" }}>
+                    <span className="grid-icon">
+                        <FontAwesomeIcon icon="columns" size="1x" />
+                    </span>
+                    <input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.numCols.toString()} onKeyDown={this.onNumColsEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} />
+                    <input className="columnButton" onClick={this.onIncrementButtonClick} onMouseEnter={this.incrementValue} onMouseLeave={this.decrementValue} type="button" value="↑" />
+                    <input className="columnButton" style={{ marginRight: 5 }} onClick={this.onDecrementButtonClick} onMouseEnter={this.decrementValue} onMouseLeave={this.incrementValue} type="button" value="↓" />
+                </span>
+                {/* <span className="grid-control">
+                    <span className="grid-icon">
+                        <FontAwesomeIcon icon="text-height" size="1x" />
+                    </span>
+                    <input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.props.CollectionView.props.Document.rowHeight as string} onKeyDown={this.onRowHeightEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} />
+                </span> */}
+                <span className="grid-control" style={{ width: this.resize ? "12%" : "20%" }}>
+                    <input type="checkbox" style={{ marginRight: 5 }} onChange={this.toggleCollisions} checked={!this.props.CollectionView.props.Document.gridPreventCollision} />
+                    <label className="flexLabel">{this.resize ? "Coll" : "Collisions"}</label>
+                </span>
+
+                <select className="collectionGridViewChrome-viewPicker"
+                    style={{ marginRight: 5 }}
+                    onPointerDown={stopPropagation}
+                    onChange={this.changeCompactType}
+                    value={StrCast(this.props.CollectionView.props.Document.gridStartCompaction, StrCast(this.props.CollectionView.props.Document.gridCompaction))}>
+                    {["vertical", "horizontal", "none"].map(type =>
+                        <option className="collectionGridViewChrome-viewOption"
+                            onPointerDown={stopPropagation}
+                            value={type}>
+                            {this.resize ? type[0].toUpperCase() + type.substring(1) : "Compact: " + type}
+                        </option>
+                    )}
+                </select>
+
+                <span className="grid-control" style={{ width: this.resize ? "12%" : "20%" }}>
+                    <input style={{ marginRight: 5 }} type="checkbox" onChange={this.toggleFlex}
+                        checked={BoolCast(this.props.CollectionView.props.Document.gridFlex, true)} />
+                    <label className="flexLabel">{this.resize ? "Flex" : "Flexible"}</label>
+                </span>
+
+                <button onClick={() => this.props.CollectionView.props.Document.gridResetLayout = true}>
+                    {!this.resize ? "Reset" :
+                        <FontAwesomeIcon icon="redo-alt" size="1x" />}
+                </button>
+
+            </div>
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index e18fe319e..6cd6a827f 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -47,7 +47,6 @@ import { SubCollectionViewProps } from './CollectionSubView';
 import { CollectionTimeView } from './CollectionTimeView';
 import { CollectionTreeView } from "./CollectionTreeView";
 import './CollectionView.scss';
-import { CollectionViewBaseChrome } from './CollectionViewChromes';
 import CollectionMenu from './CollectionMenu';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
@@ -235,16 +234,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
         }
     }
 
-    @action
-    private collapse = (value: boolean) => {
-        this.props.Document._chromeStatus = value ? "collapsed" : "enabled";
-    }
-
     private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
-        // currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip
-        const chrome = this.props.Document._chromeStatus === "disabled" || this.props.Document._chromeStatus === "replaced" || type === CollectionViewType.Docking ? (null) :
-            <CollectionViewBaseChrome key="chrome" CollectionView={this} PanelWidth={this.bodyPanelWidth} type={type} collapse={this.collapse} />;
-        return <>{chrome} {this.SubViewHelper(type, renderProps)}</>;
+        return this.SubViewHelper(type, renderProps);
     }
 
 
-- 
cgit v1.2.3-70-g09d2


From 2df5e1fb9c249ab29c0d8fc56d0a2ce394ae6643 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 17 Jul 2020 17:10:43 -0400
Subject: got rid of InkOptionsMenu.  Merged into CollectionFreeFormViewChrome.

---
 src/client/util/CurrentUserUtils.ts                |   8 +-
 src/client/views/GestureOverlay.tsx                |  10 +-
 src/client/views/GlobalKeyHandler.ts               |   2 +
 src/client/views/MainView.tsx                      |   6 +-
 src/client/views/collections/CollectionMenu.scss   |  61 +++++--
 src/client/views/collections/CollectionMenu.tsx    | 161 ++++++++++++++++-
 .../collectionFreeForm/InkOptionsMenu.scss         |  83 ---------
 .../collectionFreeForm/InkOptionsMenu.tsx          | 194 ---------------------
 src/mobile/MobileInterface.tsx                     |   4 +-
 9 files changed, 213 insertions(+), 316 deletions(-)
 delete mode 100644 src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
 delete mode 100644 src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 892d0ca1d..58d3848a3 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -732,12 +732,6 @@ 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 setupDockedButtons(doc: Doc) {
-        if (doc["dockedBtn-pen"] === undefined) {
-            doc["dockedBtn-pen"] = CurrentUserUtils.ficon({
-                onClick: ScriptField.MakeScript("activatePen(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)"),
-                author: "systemTemplates", toolTip: "open drawing tools", title: "draw", icon: "pen-nib", ischecked: ComputedField.MakeFunction(`sameDocs(this.activeInkPen,  this)`), activeInkPen: doc
-            });
-        }
         if (doc["dockedBtn-undo"] === undefined) {
             doc["dockedBtn-undo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("undo()"), toolTip: "click to undo", title: "undo", icon: "undo-alt" });
         }
@@ -745,7 +739,7 @@ export class CurrentUserUtils {
             doc["dockedBtn-redo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("redo()"), toolTip: "click to redo", title: "redo", icon: "redo-alt" });
         }
         if (doc.dockedBtns === undefined) {
-            doc.dockedBtns = CurrentUserUtils.blist({ title: "docked buttons", ignoreClick: true }, [doc["dockedBtn-undo"] as Doc, doc["dockedBtn-redo"] as Doc, doc["dockedBtn-pen"] as Doc]);
+            doc.dockedBtns = CurrentUserUtils.blist({ title: "docked buttons", ignoreClick: true }, [doc["dockedBtn-undo"] as Doc, doc["dockedBtn-redo"] as Doc]);
         }
     }
     // sets up the default set of documents to be shown in the Overlay layer
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 2f34cbc05..b0b0d72b1 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -22,7 +22,7 @@ import { RadialMenu } from "./nodes/RadialMenu";
 import HorizontalPalette from "./Palette";
 import { Touchable } from "./Touchable";
 import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu";
-import InkOptionsMenu from "./collections/collectionFreeForm/InkOptionsMenu";
+import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu";
 
 @observer
 export default class GestureOverlay extends Touchable {
@@ -603,12 +603,12 @@ export default class GestureOverlay extends Touchable {
                         break;
                 }
             }
-            //if any of the shape is activated in the InkOptionsMenu
+            //if any of the shape is activated in the CollectionFreeFormViewChrome
             else if (this.InkShape) {
                 this.makePolygon(this.InkShape, false);
                 this.dispatchGesture(GestureUtils.Gestures.Stroke);
                 this._points = [];
-                if (!InkOptionsMenu.Instance._keepMode) {
+                if (!CollectionFreeFormViewChrome.Instance._keepMode) {
                     this.InkShape = "";
                 }
             }
@@ -639,9 +639,9 @@ export default class GestureOverlay extends Touchable {
             this._points = [];
         }
         //get out of ink mode after each stroke=
-        if (!InkOptionsMenu.Instance._keepMode) {
+        if (!CollectionFreeFormViewChrome.Instance._keepMode) {
             Doc.SetSelectedTool(InkTool.None);
-            InkOptionsMenu.Instance._selected = InkOptionsMenu.Instance._shapesNum;
+            CollectionFreeFormViewChrome.Instance._selected = CollectionFreeFormViewChrome.Instance._shapesNum;
             SetActiveArrowStart("none");
             GestureOverlay.Instance.SavedArrowStart = ActiveArrowStart();
             SetActiveArrowEnd("none");
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index a3a023164..8c233489e 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -22,6 +22,7 @@ import { DocumentView } from "./nodes/DocumentView";
 import { DocumentLinksButton } from "./nodes/DocumentLinksButton";
 import PDFMenu from "./pdf/PDFMenu";
 import { ContextMenu } from "./ContextMenu";
+import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu";
 
 const modifiers = ["control", "meta", "shift", "alt"];
 type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
@@ -107,6 +108,7 @@ export default class KeyManager {
                 GoogleAuthenticationManager.Instance.cancel();
                 HypothesisAuthenticationManager.Instance.cancel();
                 SharingManager.Instance.close();
+                CollectionFreeFormViewChrome.Instance.clearKeep();
                 break;
             case "delete":
             case "backspace":
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index e50ff342c..46eeac77a 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -38,7 +38,6 @@ import SharingManager from '../util/SharingManager';
 import { Transform } from '../util/Transform';
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import MarqueeOptionsMenu from './collections/collectionFreeForm/MarqueeOptionsMenu';
-import InkOptionsMenu from './collections/collectionFreeForm/InkOptionsMenu';
 import { CollectionLinearView } from './collections/CollectionLinearView';
 import { CollectionView, CollectionViewType } from './collections/CollectionView';
 import { ContextMenu } from './ContextMenu';
@@ -392,7 +391,7 @@ export class MainView extends React.Component {
             doc.dockingConfig ? this.openWorkspace(doc) :
                 CollectionDockingView.AddRightSplit(doc, libraryPath);
     }
-    sidebarScreenToLocal = () => new Transform(0, (RichTextMenu.Instance.Pinned ? -35 : 0) + (InkOptionsMenu.Instance.Pinned ? -35 : 0) + (CollectionMenu.Instance.Pinned ? -35 : 0), 1);
+    sidebarScreenToLocal = () => new Transform(0, (RichTextMenu.Instance.Pinned ? -35 : 0) + (CollectionMenu.Instance.Pinned ? -35 : 0), 1);
     mainContainerXf = () => this.sidebarScreenToLocal().translate(0, -this._buttonBarHeight);
 
     @computed get flyout() {
@@ -469,7 +468,7 @@ export class MainView extends React.Component {
 
     @computed get mainContent() {
         const sidebar = this.userDoc?.["tabs-panelContainer"];
-        const n = (RichTextMenu.Instance?.Pinned ? 1 : 0) + (InkOptionsMenu.Instance?.Pinned ? 1 : 0) + (CollectionMenu.Instance?.Pinned ? 1 : 0);
+        const n = (RichTextMenu.Instance?.Pinned ? 1 : 0) + (CollectionMenu.Instance?.Pinned ? 1 : 0);
         const height = `calc(100% - ${n * Number(ANTIMODEMENU_HEIGHT.replace("px", ""))}px)`;
         return !this.userDoc || !(sidebar instanceof Doc) ? (null) : (
             <div className="mainView-mainContent" style={{
@@ -616,7 +615,6 @@ export class MainView extends React.Component {
             <HypothesisAuthenticationManager />
             <DocumentDecorations />
             <CollectionMenu />
-            <InkOptionsMenu />
             <FormatShapePane />
             <RichTextMenu key="rich" />
             {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss
index 0da78df9f..9da204787 100644
--- a/src/client/views/collections/CollectionMenu.scss
+++ b/src/client/views/collections/CollectionMenu.scss
@@ -8,15 +8,15 @@
     opacity: 0.9;
     z-index: 9001;
     transition: top .5s;
-    background: #121721;
+    background: #323232;
     color: white;
     transform-origin: top left;
     top: 0;
     width:100%;
 
     .antimodeMenu-button {
-        padding:0;
-        width:30px;
+        padding: 0;
+        width: 30px;
         display: flex;
         svg {
             margin:auto;
@@ -37,7 +37,7 @@
 
             .collectionViewBaseChrome-viewPicker {
                 font-size: 75%;
-                background: #121721;
+                background: #323232;
                 outline-color: black;
                 color: white;
                 border: none;
@@ -64,7 +64,7 @@
                 margin-left: 3px;
                 margin-right: 0px;
                 font-size: 75%;
-                background: #121721;
+                background: #323232;
                 color: white;
                 border: none;
                 border-right: solid gray 1px;
@@ -72,7 +72,7 @@
 
             .commandEntry-outerDiv {
                 pointer-events: all;
-                background-color: #121721;
+                background-color: #323232;
                 display: flex;
                 flex-direction: row;
                 height: 30px;
@@ -118,7 +118,7 @@
                     position: relative;
                     display: flex;
                     margin: auto;
-                    background: #121721;
+                    background: #323232;
                     color: white;
                     width: 30px;
                     height: 30px;
@@ -310,28 +310,54 @@
 }
 
 .collectionFreeFormMenu-cont {
-    width: 60px;
-    display: flex;
+    display: inline-flex;
     position: relative;
     align-items: center;
 
+    .antimodeMenu-button {
+        text-align: center;
+        display: block;
+    }
+    .color-previewI {
+        width: 80%;
+        height: 20%;
+        bottom: 0;
+        position: absolute;
+    }
+    .color-previewII {
+        width: 80%;
+        height: 80%;
+        margin-left: 10%;
+    }
+
+    .btn-group {
+        display: grid;
+        grid-template-columns: auto auto auto auto;
+        margin: auto;
+        /* Make the buttons appear below each other */
+    }
+    
+    .btn-draw {
+        display: inline-flex;
+        margin: auto;
+        /* Make the buttons appear below each other */
+    }
+
     .fwdKeyframe,
     .numKeyframe,
     .backKeyframe {
         cursor: pointer;
-        position: absolute;
+        position: relative;
         width: 20;
         height: 30;
         bottom: 0;
-        background: #121721;
-        display: flex;
+        background: #323232;
+        display: inline-flex;
         align-items: center;
         color: white;
     }
 
     .backKeyframe {
-        left: 0;
-
         svg {
             display: block;
             margin: auto;
@@ -339,19 +365,16 @@
     }
 
     .numKeyframe {
-        left: 20;
-        display: flex;
         flex-direction: column;
-        padding: 5px;
+        padding-top: 5px;
     }
 
     .fwdKeyframe {
-        left: 40;
-
         svg {
             display: block;
             margin: auto;
         }
+        border-right: solid gray 1px;
     }
 }
 
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index f3ad21949..2be57b6d2 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -1,5 +1,5 @@
 import React = require("react");
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome";
 import { action, computed, observable, reaction, runInAction, Lambda } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast, Opt } from "../../../fields/Doc";
@@ -15,6 +15,15 @@ import { List } from "../../../fields/List";
 import { EditableView } from "../EditableView";
 import { Id } from "../../../fields/FieldSymbols";
 import { listSpec } from "../../../fields/Schema";
+import FormatShapePane from "./collectionFreeForm/FormatShapePane";
+import { ActiveFillColor, SetActiveInkWidth, ActiveInkColor, SetActiveBezierApprox, SetActiveArrowEnd, SetActiveArrowStart, SetActiveFillColor, SetActiveInkColor } from "../InkingStroke";
+import GestureOverlay from "../GestureOverlay";
+import { InkTool } from "../../../fields/InkField";
+import { DocumentType } from "../../documents/DocumentTypes";
+import { Document } from "../../../fields/documentSchemas";
+import { SelectionManager } from "../../util/SelectionManager";
+import { DocumentView } from "../nodes/DocumentView";
+import { ColorState } from "react-color";
 
 @observer
 export default class CollectionMenu extends AntimodeMenu {
@@ -272,7 +281,11 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
 
 @observer
 export class CollectionFreeFormViewChrome extends React.Component<CollectionMenuProps> {
-
+    public static Instance: CollectionFreeFormViewChrome;
+    constructor(props: any) {
+        super(props);
+        CollectionFreeFormViewChrome.Instance = this;
+    }
     get Document() { return this.props.CollectionView.props.Document; }
     @computed get dataField() {
         return this.props.CollectionView.props.Document[Doc.LayoutFieldKey(this.props.CollectionView.props.Document)];
@@ -303,6 +316,144 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
         CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice());
         this.Document.currentFrame = Math.max(0, (currentFrame || 0) - 1);
     }
+    private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF", ""];
+    private _width = ["1", "5", "10", "100"];
+    private _draw = ["⎯", "→", "↔︎", "∿", "↝", "↭", "ロ", "O", "∆"];
+    private _head = ["", "", "arrow", "", "", "arrow", "", "", ""];
+    private _end = ["", "arrow", "arrow", "", "arrow", "arrow", "", "", ""];
+    private _shape = ["line", "line", "line", "", "", "", "rectangle", "circle", "triangle"];
+
+    @observable _shapesNum = this._shape.length;
+    @observable _selected = this._shapesNum;
+
+    @observable _keepMode = false;
+
+    @observable _colorBtn = false;
+    @observable _widthBtn = false;
+    @observable _fillBtn = false;
+
+    @action
+    clearKeep() { this._selected = this._shapesNum; }
+
+    @action
+    changeColor = (color: string, type: string) => {
+        const col: ColorState = {
+            hex: color, hsl: { a: 0, h: 0, s: 0, l: 0, source: "" }, hsv: { a: 0, h: 0, s: 0, v: 0, source: "" },
+            rgb: { a: 0, r: 0, b: 0, g: 0, source: "" }, oldHue: 0, source: "",
+        };
+        if (type === "color") {
+            SetActiveInkColor(Utils.colorString(col));
+        } else if (type === "fill") {
+            SetActiveFillColor(Utils.colorString(col));
+        }
+    }
+
+    @action
+    editProperties = (value: any, field: string) => {
+        SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
+            const doc = Document(element.rootDoc);
+            if (doc.type === DocumentType.INK) {
+                switch (field) {
+                    case "width": doc.strokeWidth = Number(value); break;
+                    case "color": doc.color = String(value); break;
+                    case "fill": doc.fillColor = String(value); break;
+                    case "dash": doc.strokeDash = value;
+                }
+            }
+        }));
+    }
+
+    @computed get drawButtons() {
+        const func = action((i: number, keep: boolean) => {
+            this._keepMode = keep;
+            if (this._selected !== i) {
+                this._selected = i;
+                Doc.SetSelectedTool(InkTool.Pen);
+                SetActiveArrowStart(this._head[i]);
+                SetActiveArrowEnd(this._end[i]);
+                SetActiveBezierApprox("300");
+
+                GestureOverlay.Instance.InkShape = this._shape[i];
+            } else {
+                this._selected = this._shapesNum;
+                Doc.SetSelectedTool(InkTool.None);
+                SetActiveArrowStart("");
+                SetActiveArrowEnd("");
+                GestureOverlay.Instance.InkShape = "";
+                SetActiveBezierApprox("0");
+            }
+        });
+        return <div className="btn-draw" key="draw">
+            {this._draw.map((icon, i) =>
+                <button className="antimodeMenu-button" key={icon} onPointerDown={() => func(i, false)} onDoubleClick={() => func(i, true)}
+                    style={{ backgroundColor: i === this._selected ? "121212" : "", fontSize: "20" }}>
+                    {this._draw[i]}
+                </button>)}
+        </div>;
+    }
+
+    toggleButton = (key: string, value: boolean, setter: () => {}, icon: FontAwesomeIconProps["icon"], ele: JSX.Element | null) => {
+        return <button className="antimodeMenu-button" key={key} title={key}
+            onPointerDown={action(e => setter())}
+            style={{ backgroundColor: value ? "121212" : "" }}>
+            <FontAwesomeIcon icon={icon} size="lg" />
+            {ele}
+        </button>;
+    }
+
+    @computed get widthPicker() {
+        var widthPicker = this.toggleButton("stroke width", this._widthBtn, () => this._widthBtn = !this._widthBtn, "bars", null);
+        return !this._widthBtn ? widthPicker :
+            <div className="btn2-group" key="width">
+                {widthPicker}
+                {this._width.map(wid =>
+                    <button className="antimodeMenu-button" key={wid}
+                        onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; this.editProperties(wid, "width"); })}
+                        style={{ backgroundColor: this._widthBtn ? "121212" : "", zIndex: 1001 }}>
+                        {wid}
+                    </button>)}
+            </div>;
+    }
+
+    @computed get colorPicker() {
+        var colorPicker = this.toggleButton("stroke color", this._colorBtn, () => this._colorBtn = !this._colorBtn, "pen-nib",
+            <div className="color-previewI" style={{ backgroundColor: ActiveInkColor() ?? "121212" }} />);
+        return !this._colorBtn ? colorPicker :
+            <div className="btn-group" key="color">
+                {colorPicker}
+                {this._palette.map(color =>
+                    <button className="antimodeMenu-button" key={color}
+                        onPointerDown={action(() => { this.changeColor(color, "color"); this._colorBtn = false; this.editProperties(color, "color"); })}
+                        style={{ backgroundColor: this._colorBtn ? "121212" : "", zIndex: 1001 }}>
+                        {/* <FontAwesomeIcon icon="pen-nib" size="lg" /> */}
+                        <div className="color-previewII" style={{ backgroundColor: color }} />
+                    </button>)}
+            </div>;
+    }
+    @computed get fillPicker() {
+        var fillPicker = this.toggleButton("shape fill color", this._fillBtn, () => this._fillBtn = !this._fillBtn, "fill-drip",
+            <div className="color-previewI" style={{ backgroundColor: ActiveFillColor() ?? "121212" }} />);
+        return !this._fillBtn ? fillPicker :
+            <div className="btn-group" key="fill" >
+                {fillPicker}
+                {this._palette.map(color =>
+                    <button className="antimodeMenu-button" key={color}
+                        onPointerDown={action(() => { this.changeColor(color, "fill"); this._fillBtn = false; this.editProperties(color, "fill"); })}
+                        style={{ backgroundColor: this._fillBtn ? "121212" : "", zIndex: 1001 }}>
+                        <div className="color-previewII" style={{ backgroundColor: color }}></div>
+                    </button>)}
+
+            </div>;
+    }
+
+    @computed get formatPane() {
+        return <button className="antimodeMenu-button" key="format" title="toggle foramatting pane"
+            onPointerDown={action(e => FormatShapePane.Instance.Pinned = !FormatShapePane.Instance.Pinned)}
+            style={{ backgroundColor: this._fillBtn ? "121212" : "" }}>
+            <FontAwesomeIcon icon="chevron-right" size="lg" />
+        </button>;
+    }
+
     render() {
         return this.Document.isAnnotationOverlay ? (null) :
             <div className="collectionFreeFormMenu-cont">
@@ -316,6 +467,12 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
                 <div key="fwd" title="forward frame" className="fwdKeyframe" onClick={this.nextKeyframe}>
                     <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
                 </div>
+
+                {this.widthPicker}
+                {this.colorPicker}
+                {this.fillPicker}
+                {this.drawButtons}
+                {this.formatPane}
             </div>;
     }
 }
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
deleted file mode 100644
index 03c6c97ab..000000000
--- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
+++ /dev/null
@@ -1,83 +0,0 @@
-.antimodeMenu-button {
-    .color-previewI {
-        width: 100%;
-        height: 40%;
-    }
-
-    .color-previewII {
-        width: 100%;
-        height: 100%;
-    }
-
-}
-
-.sketch-picker {
-    background: #323232;
-
-    .flexbox-fit {
-        background: #323232;
-    }
-}
-
-.btn-group {
-    display: grid;
-    grid-template-columns: auto auto auto auto;
-    /* Make the buttons appear below each other */
-}
-
-.btn-draw {
-    display: inline;
-    /* Make the buttons appear below each other */
-}
-
-.btn2-group {
-    display: grid;
-    background: #323232;
-    grid-template-columns: auto;
-
-    /* Make the buttons appear below each other */
-    .antimodeMenu-button {
-        background: #323232;
-        display: block;
-
-    }
-}
-
-@media only screen and (max-device-width: 480px) {
-    .antimodeMenu-button {
-        font-size: 50%;
-
-        .color-preview {
-            width: 100%;
-            height: 100%;
-        }
-
-    }
-
-    .sketch-picker {
-        background: #323232;
-
-        .flexbox-fit {
-            background: #323232;
-        }
-    }
-
-    .btn-group {
-        display: grid;
-        grid-template-columns: auto auto;
-        /* Make the buttons appear below each other */
-    }
-
-    .btn2-group {
-        display: block;
-        background: #323232;
-        grid-template-columns: auto;
-
-        /* Make the buttons appear below each other */
-        .antimodeMenu-button {
-            background: #323232;
-            display: block;
-            font-size: 50%;
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
deleted file mode 100644
index 23e1c194a..000000000
--- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
+++ /dev/null
@@ -1,194 +0,0 @@
-import React = require("react");
-import AntimodeMenu from "../../AntimodeMenu";
-import { observer } from "mobx-react";
-import { observable, action, computed } from "mobx";
-import "./InkOptionsMenu.scss";
-import { ActiveInkColor, ActiveFillColor, SetActiveInkWidth, SetActiveInkColor, SetActiveBezierApprox, SetActiveFillColor, SetActiveArrowStart, SetActiveArrowEnd, ActiveDash, SetActiveDash } from "../../InkingStroke";
-import { Scripting } from "../../../util/Scripting";
-import { InkTool } from "../../../../fields/InkField";
-import { ColorState } from "react-color";
-import { Utils } from "../../../../Utils";
-import GestureOverlay from "../../GestureOverlay";
-import { Doc } from "../../../../fields/Doc";
-import { SelectionManager } from "../../../util/SelectionManager";
-import { DocumentView } from "../../../views/nodes/DocumentView";
-import { Document } from "../../../../fields/documentSchemas";
-import { DocumentType } from "../../../documents/DocumentTypes";
-import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome";
-import { BoolCast } from "../../../../fields/Types";
-import FormatShapePane from "./FormatShapePane";
-
-@observer
-export default class InkOptionsMenu extends AntimodeMenu {
-    static Instance: InkOptionsMenu;
-
-    private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF", ""];
-    private _width = ["1", "5", "10", "100"];
-    private _draw = ["⎯", "→", "↔︎", "∿", "↝", "↭", "ロ", "O", "∆"];
-    private _head = ["", "", "arrow", "", "", "arrow", "", "", ""];
-    private _end = ["", "arrow", "arrow", "", "arrow", "arrow", "", "", ""];
-    private _shape = ["line", "line", "line", "", "", "", "rectangle", "circle", "triangle"];
-
-    @observable _shapesNum = this._shape.length;
-    @observable _selected = this._shapesNum;
-
-    @observable _collapsed = false;
-    @observable _keepMode = false;
-
-    @observable _colorBtn = false;
-    @observable _widthBtn = false;
-    @observable _fillBtn = false;
-
-    constructor(props: Readonly<{}>) {
-        super(props);
-        InkOptionsMenu.Instance = this;
-        this._canFade = false; // don't let the inking menu fade away
-        this.Pinned = BoolCast(Doc.UserDoc()["menuInkOptions-pinned"]);
-    }
-
-    @action
-    toggleMenuPin = (e: React.MouseEvent) => {
-        Doc.UserDoc()["menuInkOptions-pinned"] = this.Pinned = !this.Pinned;
-    }
-
-    @action
-    changeColor = (color: string, type: string) => {
-        const col: ColorState = {
-            hex: color, hsl: { a: 0, h: 0, s: 0, l: 0, source: "" }, hsv: { a: 0, h: 0, s: 0, v: 0, source: "" },
-            rgb: { a: 0, r: 0, b: 0, g: 0, source: "" }, oldHue: 0, source: "",
-        };
-        if (type === "color") {
-            SetActiveInkColor(Utils.colorString(col));
-        } else if (type === "fill") {
-            SetActiveFillColor(Utils.colorString(col));
-        }
-    }
-
-    @action
-    editProperties = (value: any, field: string) => {
-        SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
-            const doc = Document(element.rootDoc);
-            if (doc.type === DocumentType.INK) {
-                switch (field) {
-                    case "width": doc.strokeWidth = Number(value); break;
-                    case "color": doc.color = String(value); break;
-                    case "fill": doc.fillColor = String(value); break;
-                    case "dash": doc.strokeDash = value;
-                }
-            }
-        }));
-    }
-
-    @computed get drawButtons() {
-        const func = action((i: number, keep: boolean) => {
-            this._keepMode = keep;
-            if (this._selected !== i) {
-                this._selected = i;
-                Doc.SetSelectedTool(InkTool.Pen);
-                SetActiveArrowStart(this._head[i]);
-                SetActiveArrowEnd(this._end[i]);
-                SetActiveBezierApprox("300");
-
-                GestureOverlay.Instance.InkShape = this._shape[i];
-            } else {
-                this._selected = this._shapesNum;
-                Doc.SetSelectedTool(InkTool.None);
-                SetActiveArrowStart("");
-                SetActiveArrowEnd("");
-                GestureOverlay.Instance.InkShape = "";
-                SetActiveBezierApprox("0");
-            }
-        });
-        return <div className="btn-draw" key="draw">
-            {this._draw.map((icon, i) =>
-                <button className="antimodeMenu-button" key={icon} onPointerDown={() => func(i, false)} onDoubleClick={() => func(i, true)}
-                    style={{ backgroundColor: i === this._selected ? "121212" : "", fontSize: "20" }}>
-                    {this._draw[i]}
-                </button>)}
-        </div>;
-    }
-
-    toggleButton = (key: string, value: boolean, setter: () => {}, icon: FontAwesomeIconProps["icon"], ele: JSX.Element | null) => {
-        return <button className="antimodeMenu-button" key={key} title={key}
-            onPointerDown={action(e => setter())}
-            style={{ backgroundColor: value ? "121212" : "" }}>
-            <FontAwesomeIcon icon={icon} size="lg" />
-            {ele}
-        </button>;
-    }
-
-    @computed get widthPicker() {
-        var widthPicker = this.toggleButton("stroke width", this._widthBtn, () => this._widthBtn = !this._widthBtn, "bars", null);
-        return !this._widthBtn ? widthPicker :
-            <div className="btn2-group" key="width">
-                {widthPicker}
-                {this._width.map(wid =>
-                    <button className="antimodeMenu-button" key={wid}
-                        onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; this.editProperties(wid, "width"); })}
-                        style={{ backgroundColor: this._widthBtn ? "121212" : "", zIndex: 1001 }}>
-                        {wid}
-                    </button>)}
-            </div>;
-    }
-
-    @computed get colorPicker() {
-        var colorPicker = this.toggleButton("stroke color", this._colorBtn, () => this._colorBtn = !this._colorBtn, "pen-nib",
-            <div className="color-previewI" style={{ backgroundColor: ActiveInkColor() ?? "121212" }} />);
-        return !this._colorBtn ? colorPicker :
-            <div className="btn-group" key="color">
-                {colorPicker}
-                {this._palette.map(color =>
-                    <button className="antimodeMenu-button" key={color}
-                        onPointerDown={action(() => { this.changeColor(color, "color"); this._colorBtn = false; this.editProperties(color, "color"); })}
-                        style={{ backgroundColor: this._colorBtn ? "121212" : "", zIndex: 1001 }}>
-                        {/* <FontAwesomeIcon icon="pen-nib" size="lg" /> */}
-                        <div className="color-previewII" style={{ backgroundColor: color }} />
-                    </button>)}
-            </div>;
-    }
-    @computed get fillPicker() {
-        var fillPicker = this.toggleButton("shape fill color", this._fillBtn, () => this._fillBtn = !this._fillBtn, "fill-drip",
-            <div className="color-previewI" style={{ backgroundColor: ActiveFillColor() ?? "121212" }} />);
-        return !this._fillBtn ? fillPicker :
-            <div className="btn-group" key="fill" >
-                {fillPicker}
-                {this._palette.map(color =>
-                    <button className="antimodeMenu-button" key={color}
-                        onPointerDown={action(() => { this.changeColor(color, "fill"); this._fillBtn = false; this.editProperties(color, "fill"); })}
-                        style={{ backgroundColor: this._fillBtn ? "121212" : "", zIndex: 1001 }}>
-                        <div className="color-previewII" style={{ backgroundColor: color }}></div>
-                    </button>)}
-
-            </div>;
-    }
-
-    @computed get formatPane() {
-        return <button className="antimodeMenu-button" key="format" title="toggle foramatting pane"
-            onPointerDown={action(e => FormatShapePane.Instance.Pinned = !FormatShapePane.Instance.Pinned)}
-            style={{ backgroundColor: this._fillBtn ? "121212" : "" }}>
-            <FontAwesomeIcon icon="chevron-right" size="lg" />
-        </button>;
-    }
-
-    render() {
-        return this.getElement([
-            this.widthPicker,
-            this.colorPicker,
-            this.fillPicker,
-            this.drawButtons,
-            this.formatPane,
-            <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this._collapsed ? "none" : undefined }}>
-                <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
-            </button>
-        ]);
-    }
-}
-Scripting.addGlobal(function activatePen(penBtn: any) {
-    if (penBtn) {
-        InkOptionsMenu.Instance.jumpTo(300, 300);
-        InkOptionsMenu.Instance.Pinned = true;
-    } else {
-        InkOptionsMenu.Instance.Pinned = false;
-        InkOptionsMenu.Instance.fadeOut(true);
-    }
-});
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index fafa94a11..3a889b0db 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -28,7 +28,6 @@ import { DockedFrameRenderer } from '../client/views/collections/CollectionDocki
 import { InkTool } from '../fields/InkField';
 import GestureOverlay from "../client/views/GestureOverlay";
 import { ScriptField } from "../fields/ScriptField";
-import InkOptionsMenu from "../client/views/collections/collectionFreeForm/InkOptionsMenu";
 import { RadialMenu } from "../client/views/nodes/RadialMenu";
 import { UndoManager } from "../client/util/UndoManager";
 import { List } from "../fields/List";
@@ -38,6 +37,7 @@ import RichTextMenu from "../client/views/nodes/formattedText/RichTextMenu";
 import { AudioBox } from "../client/views/nodes/AudioBox";
 import { CollectionViewType } from "../client/views/collections/CollectionView";
 import { DocumentType } from "../client/documents/DocumentTypes";
+import { CollectionFreeFormViewChrome } from "../client/views/collections/CollectionMenu";
 
 library.add(faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, faAngleDoubleLeft, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
     faTerminal, faToggleOn, fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
@@ -429,7 +429,7 @@ export class MobileInterface extends React.Component {
     @computed get inkMenu() {
         return this._activeDoc._viewType !== CollectionViewType.Docking || !this._ink ? (null) :
             <div className="colorSelector">
-                <InkOptionsMenu />
+                {/* <CollectionFreeFormViewChrome /> */}
             </div>;
     }
 
-- 
cgit v1.2.3-70-g09d2


From 7b7d83f6f7070334fdfc4dd7cca03699b8e21078 Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Mon, 20 Jul 2020 19:21:54 +0530
Subject: fixed some addonly stuff + added annotations to playground fields +
 recursively distributes acls + some modal stuff

---
 src/client/apis/GoogleAuthenticationManager.tsx    |  7 +--
 .../apis/HypothesisAuthenticationManager.tsx       |  6 ++-
 src/client/util/GroupManager.scss                  |  2 +-
 src/client/util/GroupManager.tsx                   |  6 +--
 src/client/util/SettingsManager.tsx                |  4 +-
 src/client/util/SharingManager.tsx                 | 58 ++++++++++++++++++----
 src/client/views/DocComponent.tsx                  | 26 ++++++++--
 src/client/views/MainView.tsx                      |  2 +-
 src/client/views/collections/CollectionView.tsx    | 25 +++++++++-
 src/fields/Doc.ts                                  | 14 +++---
 src/fields/util.ts                                 | 21 ++++----
 11 files changed, 128 insertions(+), 43 deletions(-)

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

diff --git a/src/client/apis/GoogleAuthenticationManager.tsx b/src/client/apis/GoogleAuthenticationManager.tsx
index 5a2bdb13b..117d1fa1e 100644
--- a/src/client/apis/GoogleAuthenticationManager.tsx
+++ b/src/client/apis/GoogleAuthenticationManager.tsx
@@ -146,7 +146,7 @@ export default class GoogleAuthenticationManager extends React.Component<{}> {
 
     private get dialogueBoxStyle() {
         const borderColor = this.success === undefined ? "black" : this.success ? "green" : "red";
-        return { borderColor, transition: "0.2s borderColor ease" };
+        return { borderColor, transition: "0.2s borderColor ease", zIndex: 1002 };
     }
 
     render() {
@@ -155,9 +155,10 @@ export default class GoogleAuthenticationManager extends React.Component<{}> {
                 isDisplayed={this.openState}
                 interactive={true}
                 contents={this.renderPrompt}
-                overlayDisplayedOpacity={0.9}
+                // overlayDisplayedOpacity={0.9}
                 dialogueBoxStyle={this.dialogueBoxStyle}
-                closeOnExternalClick={() => this.isOpen = false}
+                overlayStyle={{ zIndex: 1001 }}
+                closeOnExternalClick={action(() => this.isOpen = false)}
             />
         );
     }
diff --git a/src/client/apis/HypothesisAuthenticationManager.tsx b/src/client/apis/HypothesisAuthenticationManager.tsx
index a7fcf86a4..c3e8d2fff 100644
--- a/src/client/apis/HypothesisAuthenticationManager.tsx
+++ b/src/client/apis/HypothesisAuthenticationManager.tsx
@@ -138,7 +138,7 @@ export default class HypothesisAuthenticationManager extends React.Component<{}>
 
     private get dialogueBoxStyle() {
         const borderColor = this.success === undefined ? "black" : this.success ? "green" : "red";
-        return { borderColor, transition: "0.2s borderColor ease" };
+        return { borderColor, transition: "0.2s borderColor ease", zIndex: 1002 };
     }
 
     render() {
@@ -147,8 +147,10 @@ export default class HypothesisAuthenticationManager extends React.Component<{}>
                 isDisplayed={this.openState}
                 interactive={true}
                 contents={this.renderPrompt}
-                overlayDisplayedOpacity={0.9}
+                // overlayDisplayedOpacity={0.9}
                 dialogueBoxStyle={this.dialogueBoxStyle}
+                overlayStyle={{ zIndex: 1001 }}
+                closeOnExternalClick={action(() => this.isOpen = false)}
             />
         );
     }
diff --git a/src/client/util/GroupManager.scss b/src/client/util/GroupManager.scss
index 34d4f40f8..51e4fa9e2 100644
--- a/src/client/util/GroupManager.scss
+++ b/src/client/util/GroupManager.scss
@@ -1,5 +1,5 @@
 .group-interface {
-    width: 550px;
+    width: 380px;
     height: 300px;
 
     .dialogue-box {
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 12951f2ab..2e5ecc543 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -354,7 +354,7 @@ export default class GroupManager extends React.Component<{}> {
                 isDisplayed={this.createGroupModalOpen}
                 interactive={true}
                 contents={contents}
-                dialogueBoxStyle={{ width: "70%", height: "70%" }}
+                dialogueBoxStyle={{ width: "90%", height: "70%" }}
                 closeOnExternalClick={action(() => this.createGroupModalOpen = false)}
             />
         );
@@ -424,8 +424,8 @@ export default class GroupManager extends React.Component<{}> {
                 contents={this.groupInterface}
                 isDisplayed={this.isOpen}
                 interactive={true}
-                dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
-                overlayDisplayedOpacity={this.overlayOpacity}
+                dialogueBoxStyle={{ zIndex: 1002 }}
+                overlayStyle={{ zIndex: 1001 }}
                 closeOnExternalClick={this.close}
             />
         );
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index fc5fdd869..90d59aa51 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -119,10 +119,10 @@ export default class SettingsManager extends React.Component<{}> {
                         <button onClick={this.togglePlaygroundMode}>{`${this.playgroundMode ? "Disable" : "Enable"} playground mode`}</button>
                         <button onClick={this.googleAuthorize} value="data">{`Link to Google`}</button>
                         <button onClick={this.hypothesisAuthorize} value="data">{`Link to Hypothes.is`}</button>
+                        <button onClick={() => GroupManager.Instance.open()}>Manage groups</button>
                         <button onClick={() => window.location.assign(Utils.prepend("/logout"))}>
                             {CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"}
                         </button>
-                        <button onClick={() => GroupManager.Instance.open()}>Manage groups</button>
                     </div>
                     {this.settingsContent === "password" ?
                         <div className="settings-content">
@@ -155,8 +155,6 @@ export default class SettingsManager extends React.Component<{}> {
                 contents={this.settingsInterface}
                 isDisplayed={this.isOpen}
                 interactive={true}
-                dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
-                overlayDisplayedOpacity={this.overlayOpacity}
                 closeOnExternalClick={this.close}
             />
         );
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index fe7324d5c..8b3ac2613 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,13 +1,12 @@
 import { observable, runInAction, action } from "mobx";
 import * as React from "react";
 import MainViewModal from "../views/MainViewModal";
-import { Doc, Opt, DocListCastAsync } from "../../fields/Doc";
+import { Doc, Opt, DocListCastAsync, DataSym, DocListCast } from "../../fields/Doc";
 import { DocServer } from "../DocServer";
 import { Cast, StrCast } from "../../fields/Types";
 import * as RequestPromise from "request-promise";
 import { Utils } from "../../Utils";
 import "./SharingManager.scss";
-import { Id } from "../../fields/FieldSymbols";
 import { observer } from "mobx-react";
 import { library } from '@fortawesome/fontawesome-svg-core';
 import * as fa from '@fortawesome/free-solid-svg-icons';
@@ -82,6 +81,7 @@ export default class SharingManager extends React.Component<{}> {
             this.targetDoc = target.props.Document;
             DictationOverlay.Instance.hasActiveModal = true;
             this.isOpen = true;
+            this.permissions = SharingPermissions.Edit;
         }));
 
     }
@@ -127,9 +127,11 @@ export default class SharingManager extends React.Component<{}> {
 
         const target = this.targetDoc!;
         const ACL = `ACL-${StrCast(group.groupName)}`;
+        // fix this - not needed (here and setinternalsharing and removegroup)
+        // target[ACL] = permission;
+        // Doc.GetProto(target)[ACL] = permission;
 
-        target[ACL] = permission;
-        Doc.GetProto(target)[ACL] = permission;
+        this.distributeAcls(ACL, permission as SharingPermissions);
 
         group.docsShared ? DocListCastAsync(group.docsShared).then(resolved => Doc.IndexOf(target, resolved!) === -1 && (group.docsShared as List<Doc>).push(target)) : group.docsShared = new List<Doc>([target]);
 
@@ -170,7 +172,9 @@ export default class SharingManager extends React.Component<{}> {
             DocListCastAsync(group.docsShared).then(resolved => {
                 resolved?.forEach(doc => {
                     const ACL = `ACL-${StrCast(group.groupName)}`;
-                    doc[ACL] = "Not Shared";
+                    // doc[ACL] = doc[DataSym][ACL] = "Not Shared";
+
+                    this.distributeAcls(ACL, SharingPermissions.None, doc);
 
                     const members: string[] = JSON.parse(StrCast(group.members));
                     const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
@@ -189,8 +193,10 @@ export default class SharingManager extends React.Component<{}> {
 
         const ACL = `ACL-${key}`;
 
-        target[ACL] = permission;
-        Doc.GetProto(target)[ACL] = permission;
+        // target[ACL] = permission;
+        // Doc.GetProto(target)[ACL] = permission;
+
+        this.distributeAcls(ACL, permission as SharingPermissions);
 
         if (permission !== SharingPermissions.None) {
             DocListCastAsync(notificationDoc[storage]).then(resolved => {
@@ -202,6 +208,40 @@ export default class SharingManager extends React.Component<{}> {
                 Doc.IndexOf(target, resolved!) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target);
             });
         }
+    }
+
+    @action
+    distributeAcls = (key: string, acl: SharingPermissions, doc?: Doc) => {
+        const target = doc ? doc : this.targetDoc!;
+        const dataDoc = target[DataSym];
+        target[key] = acl;
+        if (dataDoc) dataDoc[key] = acl;
+        // dataDoc[key] = target[key] = acl;
+        // next line distributes the acl to all children of the target
+        DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).map(d => {
+            if (d.author === Doc.CurrentUserEmail) {
+                this.distributeAcls(key, acl, d);
+                d[key] = acl;
+            }
+            const data = d[DataSym];
+            if (data && data.author === Doc.CurrentUserEmail) {
+                this.distributeAcls(key, acl, data);
+                data[key] = acl;
+            }
+        });
+
+        DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + "-annotations"]).map(d => {
+            if (d.author === Doc.CurrentUserEmail) {
+                this.distributeAcls(key, acl, d);
+                d[key] = acl;
+            }
+            const data = d[DataSym];
+            if (data && data.author === Doc.CurrentUserEmail) {
+                this.distributeAcls(key, acl, data);
+                data[key] = acl;
+            }
+            console.log(d, d[DataSym]);
+        });
 
     }
 
@@ -308,9 +348,9 @@ export default class SharingManager extends React.Component<{}> {
         const groupList = GroupManager.Instance?.getAllGroups() || [];
 
         const sortedUsers = this.users.sort(this.sortUsers)
-            .map(({ user: { email } }) => ({ label: email, value: "!indType/" + email }));
+            .map(({ user: { email } }) => ({ label: email, value: indType + email }));
         const sortedGroups = groupList.sort(this.sortGroups)
-            .map(({ groupName }) => ({ label: StrCast(groupName), value: "!groupType/" + StrCast(groupName) }));
+            .map(({ groupName }) => ({ label: StrCast(groupName), value: groupType + StrCast(groupName) }));
 
         const options: GroupOptions[] = GroupManager.Instance ?
             [
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index eb58d8a3e..8740d17c2 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,4 +1,4 @@
-import { Doc, Opt, DataSym, DocListCast, AclReadonly, AclAddonly } from '../../fields/Doc';
+import { Doc, Opt, DataSym, DocListCast, AclReadonly, AclAddonly, AclPrivate, AclEdit, AclSym } from '../../fields/Doc';
 import { Touchable } from './Touchable';
 import { computed, action, observable } from 'mobx';
 import { Cast, BoolCast, ScriptCast } from '../../fields/Types';
@@ -7,7 +7,8 @@ import { InteractionUtils } from '../util/InteractionUtils';
 import { List } from '../../fields/List';
 import { DateField } from '../../fields/DateField';
 import { ScriptField } from '../../fields/ScriptField';
-import { GetEffectiveAcl } from '../../fields/util';
+import { GetEffectiveAcl, getPlaygroundMode } from '../../fields/util';
+import { SharingPermissions } from '../util/SharingManager';
 
 
 ///  DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
@@ -92,6 +93,13 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
         // key where data is stored
         @computed get fieldKey() { return this.props.fieldKey; }
 
+        private AclMap = new Map<symbol, string>([
+            [AclPrivate, SharingPermissions.None],
+            [AclReadonly, SharingPermissions.View],
+            [AclAddonly, SharingPermissions.Add],
+            [AclEdit, SharingPermissions.Edit]
+        ]);
+
         lookupField = (field: string) => ScriptCast((this.layoutDoc as any).lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field }).result;
 
         styleFromLayoutString = (scale: number) => {
@@ -139,11 +147,21 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
             const docList = DocListCast(targetDataDoc[this.annotationKey]);
             const added = docs.filter(d => !docList.includes(d));
             const effectiveAcl = GetEffectiveAcl(this.dataDoc);
+
+            if (this.props.Document[AclSym]) {
+                added.forEach(d => {
+                    const dataDoc = d[DataSym];
+                    dataDoc[AclSym] = d[AclSym] = this.props.Document[AclSym];
+                    for (const [key, value] of Object.entries(this.props.Document[AclSym])) {
+                        dataDoc[key] = d[key] = this.AclMap.get(value);
+                    }
+                });
+            }
             if (added.length) {
-                if (effectiveAcl === AclReadonly) {
+                if (effectiveAcl === AclReadonly && !getPlaygroundMode()) {
                     return false;
                 } else if (effectiveAcl === AclAddonly) {
-                    added.map(doc => Doc.AddDocToList(targetDataDoc, this.annotationKey, doc));
+                    added.map(doc => console.log(Doc.AddDocToList(targetDataDoc, this.annotationKey, doc)));
                 } else {
                     added.map(doc => doc.context = this.props.Document);
                     targetDataDoc[this.annotationKey] = new List<Doc>([...docList, ...added]);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 5c6781f4c..61d2246db 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -91,7 +91,7 @@ export class MainView extends React.Component {
     public isPointerDown = false;
 
     componentDidMount() {
-        DocServer.setPlaygroundFields(["dataTransition", "_viewTransition", "_panX", "_panY", "_viewScale", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's
+        DocServer.setPlaygroundFields(["dataTransition", "_viewTransition", "_panX", "_panY", "_viewScale", "_viewType", "_chromeStatus", "data-annotations"]); // can play with these fields on someone else's
 
         const tag = document.createElement('script');
 
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 50d66c567..17567ea73 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -8,7 +8,7 @@ import * as React from 'react';
 import Lightbox from 'react-image-lightbox-with-rotate';
 import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
 import { DateField } from '../../../fields/DateField';
-import { AclAddonly, AclReadonly, DataSym, Doc, DocListCast, Field, Opt, AclEdit } from '../../../fields/Doc';
+import { AclAddonly, AclReadonly, DataSym, Doc, DocListCast, Field, Opt, AclEdit, AclSym, AclPrivate } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
@@ -48,6 +48,7 @@ import { CollectionTimeView } from './CollectionTimeView';
 import { CollectionTreeView } from "./CollectionTreeView";
 import './CollectionView.scss';
 import CollectionMenu from './CollectionMenu';
+import { SharingPermissions } from '../../util/SharingManager';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -106,6 +107,13 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
 
     protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
+    private AclMap = new Map<symbol, string>([
+        [AclPrivate, SharingPermissions.None],
+        [AclReadonly, SharingPermissions.View],
+        [AclAddonly, SharingPermissions.Add],
+        [AclEdit, SharingPermissions.Edit]
+    ]);
+
     get collectionViewType(): CollectionViewType | undefined {
         const viewField = StrCast(this.props.Document._viewType);
         if (CollectionView._safeMode) {
@@ -128,11 +136,26 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
         if (this.props.filterAddDocument?.(doc) === false) {
             return false;
         }
+
         const docs = doc instanceof Doc ? [doc] : doc;
         const targetDataDoc = this.props.Document[DataSym];
         const docList = DocListCast(targetDataDoc[this.props.fieldKey]);
         const added = docs.filter(d => !docList.includes(d));
         const effectiveAcl = GetEffectiveAcl(this.props.Document);
+        if (this.props.Document[AclSym]) {
+            // change so it only adds if more restrictive
+            added.forEach(d => {
+                console.log(d[Id]);
+                const dataDoc = d[DataSym];
+                console.log(dataDoc[Id]);
+                for (const [key, value] of Object.entries(this.props.Document[AclSym])) {
+                    dataDoc[key] = d[key] = this.AclMap.get(value);
+                }
+                dataDoc[AclSym] = d[AclSym] = this.props.Document[AclSym];
+
+            });
+        }
+
         if (added.length) {
             if (effectiveAcl === AclReadonly && !getPlaygroundMode()) {
                 return false;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 5dfc14a4a..ef57171bf 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -109,15 +109,15 @@ const AclMap = new Map<string, symbol>([
 ]);
 
 export function fetchProto(doc: Doc) {
-    if (doc.author !== Doc.CurrentUserEmail) {
-        untracked(() => {
-            const permissions: { [key: string]: symbol } = {};
+    // if (doc.author !== Doc.CurrentUserEmail) {
+    untracked(() => {
+        const permissions: { [key: string]: symbol } = {};
 
-            Object.keys(doc).filter(key => key.startsWith("ACL")).forEach(key => permissions[key] = AclMap.get(StrCast(doc[key]))!);
+        Object.keys(doc).filter(key => key.startsWith("ACL")).forEach(key => permissions[key] = AclMap.get(StrCast(doc[key]))!);
 
-            if (Object.keys(permissions).length) doc[AclSym] = permissions;
-        });
-    }
+        if (Object.keys(permissions).length) doc[AclSym] = permissions;
+    });
+    // }
 
     if (doc.proto instanceof Promise) {
         doc.proto.then(fetchProto);
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 6d2d715bd..ee01f6213 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -129,28 +129,31 @@ export function setGroups(groups: string[]) {
 export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol {
     if (in_prop === UpdatingFromServer || target[UpdatingFromServer]) return AclEdit;
 
-    const HierarchyMapping = new Map<symbol, number>([
-        [AclPrivate, 0],
-        [AclReadonly, 1],
-        [AclAddonly, 2],
-        [AclEdit, 3]
-    ]);
-
     if (!target[AclSym] && target instanceof Doc) {
         fetchProto(target);
     }
 
+
     if (target[AclSym] && Object.keys(target[AclSym]).length) {
 
-        if (target.author === Doc.CurrentUserEmail || currentUserGroups.includes("admin")) return AclEdit;
+        // console.log(target[AclSym]);
+
+        if (target.__fields?.author === Doc.CurrentUserEmail || target.author === Doc.CurrentUserEmail || currentUserGroups.includes("admin")) return AclEdit;
 
         if (_overrideAcl || (in_prop && DocServer.PlaygroundFields?.includes(in_prop.toString()))) return AclEdit;
 
-        if (target[AclSym].ACL) return target[AclSym].ACL;
+        // if (target[AclSym].ACL) return target[AclSym].ACL;
 
         let effectiveAcl = AclPrivate;
         let aclPresent = false;
 
+        const HierarchyMapping = new Map<symbol, number>([
+            [AclPrivate, 0],
+            [AclReadonly, 1],
+            [AclAddonly, 2],
+            [AclEdit, 3]
+        ]);
+
         for (const [key, value] of Object.entries(target[AclSym])) {
             if (currentUserGroups.includes(key.substring(4)) || Doc.CurrentUserEmail === key.substring(4).replace("_", ".")) {
                 if (HierarchyMapping.get(value as symbol)! >= HierarchyMapping.get(effectiveAcl)!) {
-- 
cgit v1.2.3-70-g09d2


From fa68e59c31c9ad4b4458933a246440807529794b Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Tue, 21 Jul 2020 12:15:39 +0530
Subject: more addonly fixes

---
 src/client/documents/Documents.ts                  |  3 +++
 src/client/views/DocComponent.tsx                  | 10 ++++++----
 src/client/views/MainView.tsx                      |  2 +-
 src/client/views/collections/CollectionSubView.tsx |  6 ++++++
 4 files changed, 16 insertions(+), 5 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 8e7d125b0..c783a761a 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -549,6 +549,9 @@ export namespace Docs {
             const dataDoc = MakeDataDelegate(proto, protoProps, data, fieldKey);
             const viewDoc = Doc.MakeDelegate(dataDoc, delegId);
 
+            // so that the list of annotations is already initialised, prevents issues in addonly
+            dataDoc[fieldKey + "-annotations"] = new List<Doc>();
+
             proto.links = ComputedField.MakeFunction("links(self)");
 
             viewDoc.author = Doc.CurrentUserEmail;
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 8740d17c2..2519360da 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,4 +1,4 @@
-import { Doc, Opt, DataSym, DocListCast, AclReadonly, AclAddonly, AclPrivate, AclEdit, AclSym } from '../../fields/Doc';
+import { Doc, Opt, DataSym, AclReadonly, AclAddonly, AclPrivate, AclEdit, AclSym, DocListCastAsync, DocListCast } from '../../fields/Doc';
 import { Touchable } from './Touchable';
 import { computed, action, observable } from 'mobx';
 import { Cast, BoolCast, ScriptCast } from '../../fields/Types';
@@ -126,11 +126,13 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
             docs.map(doc => doc.annotationOn = undefined);
             const targetDataDoc = this.dataDoc;
             const value = DocListCast(targetDataDoc[this.annotationKey]);
-            const result = value.filter(v => !docs.includes(v));
-            if (result.length !== value.length) {
-                targetDataDoc[this.annotationKey] = new List<Doc>(result);
+            const toRemove = value.filter(v => docs.includes(v));
+            // can't assign new List<Doc>(result) to this because you can't assign new values in addonly
+            if (toRemove.length !== 0) {
+                toRemove.forEach(doc => Doc.RemoveDocFromList(targetDataDoc, this.annotationKey, doc));
                 return true;
             }
+
             return false;
         }
         // if the moved document is already in this overlay collection nothing needs to be done.
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 7184e8225..aadfdef21 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -90,7 +90,7 @@ export class MainView extends React.Component {
     public isPointerDown = false;
 
     componentDidMount() {
-        DocServer.setPlaygroundFields(["dataTransition", "_viewTransition", "_panX", "_panY", "_viewScale", "_viewType", "_chromeStatus", "data-annotations"]); // can play with these fields on someone else's
+        DocServer.setPlaygroundFields(["dataTransition", "_viewTransition", "_panX", "_panY", "_viewScale", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's
 
         const tag = document.createElement('script');
 
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 3794088d4..8a3c2144e 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -91,6 +91,11 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
         // to its children which may be templates.
         // If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey'
         @computed get dataField() {
+            // sets the dataDoc's data field to an empty list if the data field is undefined - prevents issues with addonly
+            // setTimeout changes it outside of the @computed section
+            setTimeout(() => {
+                if (!this.dataDoc[this.props.annotationsKey || this.props.fieldKey]) this.dataDoc[this.props.annotationsKey || this.props.fieldKey] = new List<Doc>();
+            }, 1000);
             return this.dataDoc[this.props.annotationsKey || this.props.fieldKey];
         }
 
@@ -418,4 +423,5 @@ import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTex
 import { CollectionView } from "./CollectionView";
 import { SelectionManager } from "../../util/SelectionManager";
 import { OverlayView } from "../OverlayView";
+import { setTimeout } from "timers";
 
-- 
cgit v1.2.3-70-g09d2


From c81e39da3e1ba830a195f97528510758989cf43c Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 23 Jul 2020 00:55:57 -0400
Subject: added minimap toggle

---
 src/client/views/MainView.tsx                   | 4 ++--
 src/client/views/collections/CollectionMenu.tsx | 8 ++++++++
 2 files changed, 10 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index aadfdef21..225fb2e8e 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -8,7 +8,7 @@ import {
     faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTimesCircle,
     faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
     faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper,
-    faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper
+    faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap
 } from '@fortawesome/free-solid-svg-icons';
 import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -150,7 +150,7 @@ export class MainView extends React.Component {
             faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
             faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
             faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper,
-            faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper);
+            faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap);
         this.initEventListeners();
         this.initAuthenticationRouters();
     }
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index e7c5ca86b..5d95b214a 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -316,6 +316,11 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
         CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice());
         this.Document.currentFrame = Math.max(0, (currentFrame || 0) - 1);
     }
+    @undoBatch
+    @action
+    miniMap = (): void => {
+        this.Document.hideMinimap = !this.Document.hideMinimap;
+    }
     private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF", ""];
     private _width = ["1", "5", "10", "100"];
     private _draw = ["⎯", "→", "↔︎", "∿", "↝", "↭", "ロ", "O", "∆"];
@@ -457,6 +462,9 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
     render() {
         return this.Document.isAnnotationOverlay ? (null) :
             <div className="collectionFreeFormMenu-cont">
+                <div key="back" title="mini map" className="backKeyframe" onClick={this.miniMap}>
+                    <FontAwesomeIcon icon={"map"} size={"lg"} />
+                </div>
                 <div key="back" title="back frame" className="backKeyframe" onClick={this.prevKeyframe}>
                     <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
                 </div>
-- 
cgit v1.2.3-70-g09d2


From 2f49497e9d74eda97b2327ca4dbcb0a11ac6c6c9 Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Fri, 24 Jul 2020 17:11:09 +0530
Subject: added admin acl + renamed LinkCreatedBox to TaskCompletedBox and
 added custom text functionality + popups on sharing docs and creating groups
 + relocated SharingPermissions

---
 src/client/util/GroupManager.tsx                | 15 +++++++-
 src/client/util/SharingManager.tsx              | 46 +++++++++++++++----------
 src/client/views/DocComponent.tsx               |  3 +-
 src/client/views/MainView.tsx                   |  4 +--
 src/client/views/collections/CollectionView.tsx |  8 ++---
 src/client/views/nodes/DocumentLinksButton.tsx  | 18 +++++-----
 src/client/views/nodes/DocumentView.tsx         | 17 +++++----
 src/client/views/nodes/LinkCreatedBox.scss      | 21 -----------
 src/client/views/nodes/LinkCreatedBox.tsx       | 31 -----------------
 src/client/views/nodes/LinkDescriptionPopup.tsx |  4 +--
 src/client/views/nodes/TaskCompletedBox.scss    | 20 +++++++++++
 src/client/views/nodes/TaskCompletedBox.tsx     | 32 +++++++++++++++++
 src/fields/Doc.ts                               |  7 ++--
 src/fields/util.ts                              | 28 +++++++++------
 14 files changed, 143 insertions(+), 111 deletions(-)
 delete mode 100644 src/client/views/nodes/LinkCreatedBox.scss
 delete mode 100644 src/client/views/nodes/LinkCreatedBox.tsx
 create mode 100644 src/client/views/nodes/TaskCompletedBox.scss
 create mode 100644 src/client/views/nodes/TaskCompletedBox.tsx

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

diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index a727d21d0..72fba5c1b 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -16,6 +16,7 @@ import { StrCast, Cast } from "../../fields/Types";
 import GroupMemberView from "./GroupMemberView";
 import { setGroups } from "../../fields/util";
 import { DocServer } from "../DocServer";
+import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
 
 library.add(fa.faPlus, fa.faTimes, fa.faInfoCircle);
 
@@ -36,11 +37,13 @@ export default class GroupManager extends React.Component<{}> {
     @observable currentGroup: Opt<Doc>; // the currently selected group.
     @observable private createGroupModalOpen: boolean = false;
     private inputRef: React.RefObject<HTMLInputElement> = React.createRef(); // the ref for the input box.
+    private createGroupButtonRef: React.RefObject<HTMLButtonElement> = React.createRef();
     private currentUserGroups: string[] = [];
     @observable private buttonColour: "#979797" | "black" = "#979797";
     @observable private groupSort: "ascending" | "descending" | "none" = "none";
 
 
+
     constructor(props: Readonly<{}>) {
         super(props);
         GroupManager.Instance = this;
@@ -303,6 +306,14 @@ export default class GroupManager extends React.Component<{}> {
         this.selectedUsers = null;
         this.inputRef.current.value = "";
         this.buttonColour = "#979797";
+
+        const { left, width, top } = this.createGroupButtonRef.current!.getBoundingClientRect();
+        TaskCompletionBox.popupX = left - 2 * width;
+        TaskCompletionBox.popupY = top;
+        TaskCompletionBox.textDisplayed = "Group created!";
+        TaskCompletionBox.taskCompleted = true;
+        setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2000);
+
     }
 
     private get groupCreationModal() {
@@ -345,7 +356,9 @@ export default class GroupManager extends React.Component<{}> {
                         })
                     }}
                 />
-                <button onClick={this.createGroup}
+                <button
+                    ref={this.createGroupButtonRef}
+                    onClick={this.createGroup}
                     style={{ background: this.buttonColour }}
                     disabled={this.buttonColour === "#979797"}
                 >
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 9c857a7c0..452a58d21 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -20,7 +20,8 @@ import GroupMemberView from "./GroupMemberView";
 import Select from "react-select";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { List } from "../../fields/List";
-import { distributeAcls } from "../../fields/util";
+import { distributeAcls, SharingPermissions } from "../../fields/util";
+import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
 
 library.add(fa.faCopy, fa.faTimes);
 
@@ -29,13 +30,6 @@ export interface User {
     userDocumentId: string;
 }
 
-export enum SharingPermissions {
-    Edit = "Can Edit",
-    Add = "Can Add",
-    View = "Can View",
-    None = "Not Shared"
-}
-
 interface GroupOptions {
     label: string;
     options: UserOptions[];
@@ -69,6 +63,8 @@ export default class SharingManager extends React.Component<{}> {
     @observable private permissions: SharingPermissions = SharingPermissions.Edit;
     @observable private individualSort: "ascending" | "descending" | "none" = "none";
     @observable private groupSort: "ascending" | "descending" | "none" = "none";
+    private shareDocumentButtonRef: React.RefObject<HTMLButtonElement> = React.createRef();
+
 
 
     // private get linkVisible() {
@@ -90,6 +86,8 @@ export default class SharingManager extends React.Component<{}> {
     public close = action(() => {
         this.isOpen = false;
         this.users = [];
+        this.selectedUsers = null;
+
         setTimeout(action(() => {
             // this.copied = false;
             DictationOverlay.Instance.hasActiveModal = false;
@@ -235,7 +233,7 @@ export default class SharingManager extends React.Component<{}> {
     private get sharingOptions() {
         return Object.values(SharingPermissions).map(permission => {
             return (
-                <option key={permission} value={permission}>
+                <option key={permission} value={permission} selected={permission === SharingPermissions.Edit}>
                     {permission}
                 </option>
             );
@@ -284,15 +282,25 @@ export default class SharingManager extends React.Component<{}> {
 
     @action
     share = () => {
-        this.selectedUsers?.forEach(user => {
-            if (user.value.includes(indType)) {
-                this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions);
-            }
-            else {
-                this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions);
-            }
-        });
-        this.selectedUsers = null;
+        if (this.selectedUsers) {
+            this.selectedUsers.forEach(user => {
+                if (user.value.includes(indType)) {
+                    this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions);
+                }
+                else {
+                    this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions);
+                }
+            });
+
+            const { left, width, top, height } = this.shareDocumentButtonRef.current!.getBoundingClientRect();
+            TaskCompletionBox.popupX = left - 1.5 * width;
+            TaskCompletionBox.popupY = top - height;
+            TaskCompletionBox.textDisplayed = "Document shared!";
+            TaskCompletionBox.taskCompleted = true;
+            setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2000);
+
+            this.selectedUsers = null;
+        }
     }
 
     sortUsers = (u1: ValidatedUser, u2: ValidatedUser) => {
@@ -456,7 +464,7 @@ export default class SharingManager extends React.Component<{}> {
                             <select className="permissions-select" onChange={this.handlePermissionsChange}>
                                 {this.sharingOptions}
                             </select>
-                            <button className="share-button" onClick={this.share}>
+                            <button ref={this.shareDocumentButtonRef} className="share-button" onClick={this.share}>
                                 Share
                             </button>
                         </div>
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 95c1bcda8..00d570670 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -7,8 +7,7 @@ import { InteractionUtils } from '../util/InteractionUtils';
 import { List } from '../../fields/List';
 import { DateField } from '../../fields/DateField';
 import { ScriptField } from '../../fields/ScriptField';
-import { GetEffectiveAcl, getPlaygroundMode } from '../../fields/util';
-import { SharingPermissions } from '../util/SharingManager';
+import { GetEffectiveAcl, getPlaygroundMode, SharingPermissions } from '../../fields/util';
 
 
 ///  DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 225fb2e8e..81f2feee0 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -61,7 +61,7 @@ import { DocumentManager } from '../util/DocumentManager';
 import { DocumentLinksButton } from './nodes/DocumentLinksButton';
 import { LinkMenu } from './linking/LinkMenu';
 import { LinkDocPreview } from './nodes/LinkDocPreview';
-import { LinkCreatedBox } from './nodes/LinkCreatedBox';
+import { TaskCompletionBox } from './nodes/TaskCompletedBox';
 import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
 import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane";
 import HypothesisAuthenticationManager from '../apis/HypothesisAuthenticationManager';
@@ -623,7 +623,7 @@ export class MainView extends React.Component {
                 {this.mainContent}
             </GestureOverlay>
             <PreviewCursor />
-            <LinkCreatedBox />
+            <TaskCompletionBox />
             <ContextMenu />
             <FormatShapePane />
             <RadialMenu />
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 53fd83f26..4848508be 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -8,7 +8,7 @@ import * as React from 'react';
 import Lightbox from 'react-image-lightbox-with-rotate';
 import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
 import { DateField } from '../../../fields/DateField';
-import { AclAddonly, AclReadonly, DataSym, Doc, DocListCast, Field, Opt, AclEdit, AclSym, AclPrivate } from '../../../fields/Doc';
+import { AclAddonly, AclReadonly, DataSym, Doc, DocListCast, Field, Opt, AclEdit, AclSym, AclPrivate, AclAdmin } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
@@ -17,7 +17,7 @@ import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
-import { TraceMobx, GetEffectiveAcl, getPlaygroundMode, distributeAcls } from '../../../fields/util';
+import { TraceMobx, GetEffectiveAcl, getPlaygroundMode, distributeAcls, SharingPermissions } from '../../../fields/util';
 import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -48,7 +48,6 @@ import { CollectionTimeView } from './CollectionTimeView';
 import { CollectionTreeView } from "./CollectionTreeView";
 import './CollectionView.scss';
 import CollectionMenu from './CollectionMenu';
-import { SharingPermissions } from '../../util/SharingManager';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -111,7 +110,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
         [AclPrivate, SharingPermissions.None],
         [AclReadonly, SharingPermissions.View],
         [AclAddonly, SharingPermissions.Add],
-        [AclEdit, SharingPermissions.Edit]
+        [AclEdit, SharingPermissions.Edit],
+        [AclAdmin, SharingPermissions.Admin]
     ]);
 
     get collectionViewType(): CollectionViewType | undefined {
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 13bec5d7f..f0e80045d 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -10,7 +10,7 @@ import React = require("react");
 import { DocUtils } from "../../documents/Documents";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { LinkDocPreview } from "./LinkDocPreview";
-import { LinkCreatedBox } from "./LinkCreatedBox";
+import { TaskCompletionBox } from "./TaskCompletedBox";
 import { LinkDescriptionPopup } from "./LinkDescriptionPopup";
 import { LinkManager } from "../../util/LinkManager";
 import { Tooltip } from "@material-ui/core";
@@ -101,15 +101,15 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
 
                         runInAction(() => {
                             if (linkDoc) {
-                                LinkCreatedBox.popupX = e.screenX;
-                                LinkCreatedBox.popupY = e.screenY - 133;
-                                LinkCreatedBox.linkCreated = true;
+                                TaskCompletionBox.popupX = e.screenX;
+                                TaskCompletionBox.popupY = e.screenY - 133;
+                                TaskCompletionBox.taskCompleted = true;
 
                                 LinkDescriptionPopup.popupX = e.screenX;
                                 LinkDescriptionPopup.popupY = e.screenY - 100;
                                 LinkDescriptionPopup.descriptionPopup = true;
 
-                                setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+                                setTimeout(action(() => { TaskCompletionBox.taskCompleted = false; }), 2500);
                             }
 
                         });
@@ -134,9 +134,9 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
 
                     runInAction(() => {
                         if (linkDoc) {
-                            LinkCreatedBox.popupX = e.screenX;
-                            LinkCreatedBox.popupY = e.screenY - 133;
-                            LinkCreatedBox.linkCreated = true;
+                            TaskCompletionBox.popupX = e.screenX;
+                            TaskCompletionBox.popupY = e.screenY - 133;
+                            TaskCompletionBox.taskCompleted = true;
 
                             if (LinkDescriptionPopup.showDescriptions === "ON" || !LinkDescriptionPopup.showDescriptions) {
                                 LinkDescriptionPopup.popupX = e.screenX;
@@ -144,7 +144,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                                 LinkDescriptionPopup.descriptionPopup = true;
                             }
 
-                            setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+                            setTimeout(action(() => { TaskCompletionBox.taskCompleted = false; }), 2500);
                         }
                     });
                 }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 9e492650b..1d4b61545 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -11,7 +11,7 @@ import { listSpec } from "../../../fields/Schema";
 import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from "../../../fields/Types";
-import { TraceMobx, GetEffectiveAcl } from '../../../fields/util';
+import { TraceMobx, GetEffectiveAcl, SharingPermissions } from '../../../fields/util';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
 import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils, emptyPath } from "../../../Utils";
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
@@ -25,7 +25,7 @@ import { InteractionUtils } from '../../util/InteractionUtils';
 import { Scripting } from '../../util/Scripting';
 import { SearchUtil } from '../../util/SearchUtil';
 import { SelectionManager } from "../../util/SelectionManager";
-import SharingManager, { SharingPermissions } from '../../util/SharingManager';
+import SharingManager from '../../util/SharingManager';
 import { Transform } from "../../util/Transform";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { CollectionView, CollectionViewType } from '../collections/CollectionView';
@@ -41,7 +41,7 @@ import { RadialMenu } from './RadialMenu';
 import React = require("react");
 import { DocumentLinksButton } from './DocumentLinksButton';
 import { MobileInterface } from '../../../mobile/MobileInterface';
-import { LinkCreatedBox } from './LinkCreatedBox';
+import { TaskCompletionBox } from './TaskCompletedBox';
 import { LinkDescriptionPopup } from './LinkDescriptionPopup';
 import { LinkManager } from '../../util/LinkManager';
 
@@ -629,15 +629,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const makeLink = action((linkDoc: Doc) => {
             LinkManager.currentLink = linkDoc;
 
-            LinkCreatedBox.popupX = de.x;
-            LinkCreatedBox.popupY = de.y - 33;
-            LinkCreatedBox.linkCreated = true;
+            TaskCompletionBox.popupX = de.x;
+            TaskCompletionBox.popupY = de.y - 33;
+            TaskCompletionBox.taskCompleted = true;
 
             LinkDescriptionPopup.popupX = de.x;
             LinkDescriptionPopup.popupY = de.y;
             LinkDescriptionPopup.descriptionPopup = true;
 
-            setTimeout(action(() => LinkCreatedBox.linkCreated = false), 2500);
+            setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2500);
         });
         if (de.complete.annoDragData) {
             /// this whole section for handling PDF annotations looks weird.  Need to rethink this to make it cleaner
@@ -818,6 +818,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         !Doc.UserDoc().novice && helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
         cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" });
 
+        // to be removed for baseline
+        cm.addItem({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" });
+
         // const existingAcls = cm.findByDescription("Privacy...");
         // const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : [];
         // aclItems.push({ description: "Make Add Only", event: () => this.setAcl(SharingPermissions.Add), icon: "concierge-bell" });
diff --git a/src/client/views/nodes/LinkCreatedBox.scss b/src/client/views/nodes/LinkCreatedBox.scss
deleted file mode 100644
index 3cbd38b55..000000000
--- a/src/client/views/nodes/LinkCreatedBox.scss
+++ /dev/null
@@ -1,21 +0,0 @@
-.linkCreatedBox-fade {
-    border: 1px solid rgb(100, 100, 100);
-
-
-    width: auto;
-    position: absolute;
-
-    height: auto;
-    z-index: 10000;
-    border-radius: 13px;
-    font-size: 13px;
-    white-space: nowrap;
-
-    color: rgb(100, 100, 100);
-    background-color: rgba(250, 250, 250, 0.85);
-    padding-top: 6.5px;
-    padding-bottom: 6.5px;
-    font-weight: bold;
-    padding-left: 9px;
-    padding-right: 9px;
-}
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkCreatedBox.tsx b/src/client/views/nodes/LinkCreatedBox.tsx
deleted file mode 100644
index 648ae23c8..000000000
--- a/src/client/views/nodes/LinkCreatedBox.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import React = require("react");
-import { observer } from "mobx-react";
-import { documentSchema } from "../../../fields/documentSchemas";
-import { makeInterface } from "../../../fields/Schema";
-import "./LinkCreatedBox.scss";
-import { observable, action } from "mobx";
-import { Fade } from "@material-ui/core";
-
-
-@observer
-export class LinkCreatedBox extends React.Component<{}> {
-
-    @observable public static linkCreated: boolean = false;
-    @observable public static popupX: number = 500;
-    @observable public static popupY: number = 150;
-
-    @action
-    public static changeLinkCreated = () => {
-        LinkCreatedBox.linkCreated = !LinkCreatedBox.linkCreated;
-    }
-
-    render() {
-        return <Fade in={LinkCreatedBox.linkCreated}>
-            <div className="linkCreatedBox-fade"
-                style={{
-                    left: LinkCreatedBox.popupX ? LinkCreatedBox.popupX : 500,
-                    top: LinkCreatedBox.popupY ? LinkCreatedBox.popupY : 150,
-                }}>Link Created</div>
-        </Fade>;
-    }
-} 
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index 06e8d30d1..d8fe47f4e 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -4,7 +4,7 @@ import "./LinkDescriptionPopup.scss";
 import { observable, action } from "mobx";
 import { EditableView } from "../EditableView";
 import { LinkManager } from "../../util/LinkManager";
-import { LinkCreatedBox } from "./LinkCreatedBox";
+import { TaskCompletionBox } from "./TaskCompletedBox";
 
 
 @observer
@@ -31,7 +31,7 @@ export class LinkDescriptionPopup extends React.Component<{}> {
     onClick = (e: PointerEvent) => {
         if (this.popupRef && !!!this.popupRef.current?.contains(e.target as any)) {
             LinkDescriptionPopup.descriptionPopup = false;
-            LinkCreatedBox.linkCreated = false;
+            TaskCompletionBox.taskCompleted = false;
         }
     }
 
diff --git a/src/client/views/nodes/TaskCompletedBox.scss b/src/client/views/nodes/TaskCompletedBox.scss
new file mode 100644
index 000000000..80b750b39
--- /dev/null
+++ b/src/client/views/nodes/TaskCompletedBox.scss
@@ -0,0 +1,20 @@
+.taskCompletedBox-fade {
+    border: 1px solid rgb(100, 100, 100);
+
+    width: auto;
+    position: absolute;
+
+    height: auto;
+    z-index: 10000;
+    border-radius: 13px;
+    font-size: 13px;
+    white-space: nowrap;
+
+    color: rgb(100, 100, 100);
+    background-color: rgba(250, 250, 250, 0.85);
+    padding-top: 6.5px;
+    padding-bottom: 6.5px;
+    font-weight: bold;
+    padding-left: 9px;
+    padding-right: 9px;
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/TaskCompletedBox.tsx b/src/client/views/nodes/TaskCompletedBox.tsx
new file mode 100644
index 000000000..1ba2d1713
--- /dev/null
+++ b/src/client/views/nodes/TaskCompletedBox.tsx
@@ -0,0 +1,32 @@
+import React = require("react");
+import { observer } from "mobx-react";
+import { documentSchema } from "../../../fields/documentSchemas";
+import { makeInterface } from "../../../fields/Schema";
+import "./TaskCompletedBox.scss";
+import { observable, action } from "mobx";
+import { Fade } from "@material-ui/core";
+
+
+@observer
+export class TaskCompletionBox extends React.Component<{}> {
+
+    @observable public static taskCompleted: boolean = false;
+    @observable public static popupX: number = 500;
+    @observable public static popupY: number = 150;
+    @observable public static textDisplayed: string = "Link Created";
+
+    @action
+    public static toggleTaskCompleted = () => {
+        TaskCompletionBox.taskCompleted = !TaskCompletionBox.taskCompleted;
+    }
+
+    render() {
+        return <Fade in={TaskCompletionBox.taskCompleted}>
+            <div className="taskCompletedBox-fade"
+                style={{
+                    left: TaskCompletionBox.popupX ? TaskCompletionBox.popupX : 500,
+                    top: TaskCompletionBox.popupY ? TaskCompletionBox.popupY : 150,
+                }}>{TaskCompletionBox.textDisplayed}</div>
+        </Fade>;
+    }
+} 
\ No newline at end of file
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 917a6853c..0c816461c 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -19,9 +19,8 @@ import { DateField } from "./DateField";
 import { listSpec } from "./Schema";
 import { ComputedField } from "./ScriptField";
 import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types";
-import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction, GetEffectiveAcl } from "./util";
+import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction, GetEffectiveAcl, SharingPermissions } from "./util";
 import { LinkManager } from "../client/util/LinkManager";
-import { SharingPermissions } from "../client/util/SharingManager";
 import JSZip = require("jszip");
 import { saveAs } from "file-saver";
 
@@ -103,6 +102,7 @@ export const AclPrivate = Symbol("AclOwnerOnly");
 export const AclReadonly = Symbol("AclReadOnly");
 export const AclAddonly = Symbol("AclAddonly");
 export const AclEdit = Symbol("AclEdit");
+export const AclAdmin = Symbol("AclAdmin");
 export const UpdatingFromServer = Symbol("UpdatingFromServer");
 const CachedUpdates = Symbol("Cached updates");
 
@@ -110,7 +110,8 @@ const AclMap = new Map<string, symbol>([
     [SharingPermissions.None, AclPrivate],
     [SharingPermissions.View, AclReadonly],
     [SharingPermissions.Add, AclAddonly],
-    [SharingPermissions.Edit, AclEdit]
+    [SharingPermissions.Edit, AclEdit],
+    [SharingPermissions.Admin, AclAdmin]
 ]);
 
 export function fetchProto(doc: Doc) {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index cd1e24826..eba398dfb 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,5 +1,5 @@
 import { UndoManager } from "../client/util/UndoManager";
-import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, fetchProto, DataSym, DocListCast } from "./Doc";
+import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, fetchProto, DataSym, DocListCast, AclAdmin } from "./Doc";
 import { SerializationHelper } from "../client/util/SerializationHelper";
 import { ProxyField, PrefetchProxy } from "./Proxy";
 import { RefField } from "./RefField";
@@ -9,7 +9,6 @@ import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols";
 import { DocServer } from "../client/DocServer";
 import { ComputedField } from "./ScriptField";
 import { ScriptCast, StrCast } from "./Types";
-import { SharingPermissions } from "../client/util/SharingManager";
 
 
 function _readOnlySetter(): never {
@@ -127,12 +126,20 @@ export function setGroups(groups: string[]) {
     currentUserGroups = groups;
 }
 
+export enum SharingPermissions {
+    Admin = "Admin",
+    Edit = "Can Edit",
+    Add = "Can Add",
+    View = "Can View",
+    None = "Not Shared"
+}
+
 export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol {
     if (in_prop === UpdatingFromServer || target[UpdatingFromServer]) return AclEdit;
 
     if (target[AclSym] && Object.keys(target[AclSym]).length) {
 
-        if (target.__fields?.author === Doc.CurrentUserEmail || target.author === Doc.CurrentUserEmail || currentUserGroups.includes("admin")) return AclEdit;
+        if (target.__fields?.author === Doc.CurrentUserEmail || target.author === Doc.CurrentUserEmail || currentUserGroups.includes("admin")) return AclAdmin;
 
         if (_overrideAcl || (in_prop && DocServer.PlaygroundFields?.includes(in_prop.toString()))) return AclEdit;
 
@@ -143,7 +150,8 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number)
             [AclPrivate, 0],
             [AclReadonly, 1],
             [AclAddonly, 2],
-            [AclEdit, 3]
+            [AclEdit, 3],
+            [AclAdmin, 4]
         ]);
 
         for (const [key, value] of Object.entries(target[AclSym])) {
@@ -157,7 +165,7 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number)
         }
         return aclPresent ? effectiveAcl : AclEdit;
     }
-    return AclEdit;
+    return AclAdmin;
 }
 
 export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, inheritingFromCollection?: boolean) {
@@ -166,7 +174,8 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
         ["Not Shared", 0],
         ["Can View", 1],
         ["Can Add", 2],
-        ["Can Edit", 3]
+        ["Can Edit", 3],
+        ["Admin", 4]
     ]);
 
     const dataDoc = target[DataSym];
@@ -206,11 +215,10 @@ const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHe
     "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"];
 export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
     let prop = in_prop;
-    if (GetEffectiveAcl(target, in_prop) !== AclEdit) {
-        return true;
-    }
+    const effectiveAcl = GetEffectiveAcl(target, in_prop);
+    if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin) return true;
 
-    if (typeof prop === "string" && prop.startsWith("ACL") && ((target.author && Doc.CurrentUserEmail !== target.author) || !["Can Edit", "Can Add", "Can View", "Not Shared", undefined].includes(value))) return true;
+    if (typeof prop === "string" && prop.startsWith("ACL") && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true;
     // if (typeof prop === "string" && prop.startsWith("ACL") && !["Can Edit", "Can Add", "Can View", "Not Shared", undefined].includes(value)) return true;
 
     if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) {
-- 
cgit v1.2.3-70-g09d2


From 5454385b6fabbcafee4d797829fb9ceaa8a71ed1 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 25 Jul 2020 20:14:16 -0400
Subject: made buttons turn into fonticons on iconify.  droppin fonticons onto
 linear views keeps them as buttons.  droppin buttons on linear view makes
 them fonticons. fixed ColletionMenu scripts to work with templates by usin
 self instead of this. fixed pointer events with text boxes for filterview &
 normal.  fixed fieldKey deletin and addin

---
 src/client/util/CurrentUserUtils.ts                | 12 +++++++++--
 src/client/util/DragManager.ts                     |  7 +++----
 src/client/util/DropConverter.ts                   |  2 +-
 src/client/views/MainView.tsx                      |  4 ++--
 src/client/views/collections/CollectionMenu.tsx    | 23 ++++++++++++++--------
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/FontIconBox.tsx             |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  4 ++--
 src/fields/util.ts                                 |  4 ++--
 9 files changed, 37 insertions(+), 23 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index b47cfb33a..6d752832a 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -346,6 +346,14 @@ export class CurrentUserUtils {
             iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF);
             doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView);
         }
+        if (doc["template-icon-view-button"] === undefined) {
+            const iconBtnView = Docs.Create.FontIconDocument({
+                title: "icon_" + DocumentType.BUTTON, _nativeHeight: 30, _nativeWidth: 30,
+                _width: 30, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)")
+            });
+            iconBtnView.isTemplateDoc = makeTemplate(iconBtnView, true, "icon_" + DocumentType.BUTTON);
+            doc["template-icon-view-button"] = new PrefetchProxy(iconBtnView);
+        }
         if (doc["template-icon-view-img"] === undefined) {
             const iconImageView = Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", {
                 title: "data", _width: 50, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)")
@@ -359,11 +367,11 @@ export class CurrentUserUtils {
             doc["template-icon-view-col"] = new PrefetchProxy(iconColView);
         }
         if (doc["template-icons"] === undefined) {
-            doc["template-icons"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc,
+            doc["template-icons"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc,
             doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-pdf"] as Doc], { title: "icon templates", _height: 75 }));
         } else {
             const templateIconsDoc = Cast(doc["template-icons"], Doc, null);
-            const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc,
+            const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc,
             doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc];
             DocListCastAsync(templateIconsDoc.data).then(async curIcons => {
                 await Promise.all(curIcons!);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 837f0b1db..4b1860b5c 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -224,11 +224,10 @@ export namespace DragManager {
     }
 
     // drag a button template and drop a new button 
-    export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
+    export function
+        StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
         const finishDrag = (e: DragCompleteEvent) => {
-            const bd = params.length > Object.keys(vars).length ?
-                Docs.Create.ButtonDocument({ toolTip: title, z: 1, _width: 150, _height: 50, title, onClick: ScriptField.MakeScript(script) }) :
-                Docs.Create.FontIconDocument({ toolTip: title, z: 1, _nativeWidth: 30, _nativeHeight: 30, _width: 30, _height: 30, title, onClick: ScriptField.MakeScript(script) });
+            const bd = Docs.Create.ButtonDocument({ toolTip: title, z: 1, _width: 150, _height: 50, title, onClick: ScriptField.MakeScript(script) });
             params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc))); // copy all "captured" arguments into document parameterfields
             initialize?.(bd);
             Doc.GetProto(bd)["onClick-paramFieldKeys"] = new List<string>(params);
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index f9837298d..d0acf14c3 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -57,7 +57,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
     data?.draggedDocuments.map((doc, i) => {
         let dbox = doc;
         // bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant
-        if (doc.type === DocumentType.FONTICON) {
+        if (doc.type === DocumentType.FONTICON || StrCast(Doc.Layout(doc).layout).includes("FontIconBox")) {
             dbox = Doc.MakeAlias(doc);
         } else if (!doc.onDragStart && !doc.isButtonBar) {
             const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 81f2feee0..ae3f05fb7 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -8,7 +8,7 @@ import {
     faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTimesCircle,
     faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
     faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper,
-    faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap
+    faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap, faUser
 } from '@fortawesome/free-solid-svg-icons';
 import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -150,7 +150,7 @@ export class MainView extends React.Component {
             faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
             faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
             faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper,
-            faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap);
+            faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap, faUser);
         this.initEventListeners();
         this.initAuthenticationRouters();
     }
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 992c1f600..81c349b26 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -2,7 +2,7 @@ import React = require("react");
 import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome";
 import { action, computed, observable, reaction, runInAction, Lambda } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, Opt } from "../../../fields/Doc";
+import { Doc, DocListCast, Opt, Field } from "../../../fields/Doc";
 import { BoolCast, Cast, StrCast, NumCast } from "../../../fields/Types";
 import AntimodeMenu from "../AntimodeMenu";
 import "./CollectionMenu.scss";
@@ -24,6 +24,7 @@ import { Document } from "../../../fields/documentSchemas";
 import { SelectionManager } from "../../util/SelectionManager";
 import { DocumentView } from "../nodes/DocumentView";
 import { ColorState } from "react-color";
+import { ObjectField } from "../../../fields/ObjectField";
 
 @observer
 export default class CollectionMenu extends AntimodeMenu {
@@ -72,42 +73,48 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
     get target() { return this.props.CollectionView.props.Document; }
     _templateCommand = {
         params: ["target", "source"], title: "item view",
-        script: "this.target.childLayoutTemplate = getDocTemplate(this.source?.[0])",
+        script: "self.target.childLayoutTemplate = getDocTemplate(self.source?.[0])",
         immediate: undoBatch((source: Doc[]) => source.length && (this.target.childLayoutTemplate = Doc.getDocTemplate(source?.[0]))),
         initialize: emptyFunction,
     };
     _narrativeCommand = {
         params: ["target", "source"], title: "child click view",
-        script: "this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])",
+        script: "self.target.childClickedOpenTemplateView = getDocTemplate(self.source?.[0])",
         immediate: undoBatch((source: Doc[]) => source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]))),
         initialize: emptyFunction,
     };
     _contentCommand = {
         params: ["target", "source"], title: "clear content",
-        script: "getProto(this.target).data = copyField(this.source);",
+        script: "getProto(self.target).data = copyField(self.source);",
         immediate: undoBatch((source: Doc[]) => Doc.GetProto(this.target).data = new List<Doc>(source)), // Doc.aliasDocs(source),
         initialize: emptyFunction,
     };
     _viewCommand = {
         params: ["target"], title: "bookmark view",
-        script: "this.target._panX = this['target-panX']; this.target._panY = this['target-panY']; this.target._viewScale = this['target-viewScale'];",
+        script: "self.target._panX = self['target-panX']; self.target._panY = self['target-panY']; self.target._viewScale = self['target-viewScale'];",
         immediate: undoBatch((source: Doc[]) => { this.target._panX = 0; this.target._panY = 0; this.target._viewScale = 1; }),
         initialize: (button: Doc) => { button['target-panX'] = this.target._panX; button['target-panY'] = this.target._panY; button['target-viewScale'] = this.target._viewScale; },
     };
     _clusterCommand = {
         params: ["target"], title: "fit content",
-        script: "this.target._fitToBox = !this.target._fitToBox;",
+        script: "self.target._fitToBox = !self.target._fitToBox;",
         immediate: undoBatch((source: Doc[]) => this.target._fitToBox = !this.target._fitToBox),
         initialize: emptyFunction
     };
     _fitContentCommand = {
         params: ["target"], title: "toggle clusters",
-        script: "this.target.useClusters = !this.target.useClusters;",
+        script: "self.target.useClusters = !self.target.useClusters;",
         immediate: undoBatch((source: Doc[]) => this.target.useClusters = !this.target.useClusters),
         initialize: emptyFunction
     };
+    _saveFilterCommand = {
+        params: ["target"], title: "save filter",
+        script: "self.target._docFilters = copyField(self['target-docFilters']);",
+        immediate: undoBatch((source: Doc[]) => this.target._docFilters = undefined),
+        initialize: (button: Doc) => { button['target-docFilters'] = this.target._docFilters instanceof ObjectField && ObjectField.MakeCopy(this.target._docFilters as any as ObjectField); },
+    };
 
-    _freeform_commands = [this._viewCommand, this._fitContentCommand, this._clusterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand];
+    _freeform_commands = [this._viewCommand, this._saveFilterCommand, this._fitContentCommand, this._clusterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand];
     _stacking_commands = [this._contentCommand, this._templateCommand];
     _masonry_commands = [this._contentCommand, this._templateCommand];
     _schema_commands = [this._templateCommand, this._narrativeCommand];
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 8cadafccd..df4576243 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -294,7 +294,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             let stopPropagate = true;
             let preventDefault = true;
             !this.props.Document.isBackground && this.props.bringToFront(this.props.Document);
-            if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
+            if (this._doubleTap && this.props.renderDepth) {// && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
                 if (!(e.nativeEvent as any).formattedHandled) {
                     if (this.onDoubleClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
                         const func = () => this.onDoubleClickHandler.script.run({
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index ab34e13b0..2611d2ca7 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -67,7 +67,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
                 background: StrCast(refLayout._backgroundColor, StrCast(refLayout.backgroundColor)),
                 boxShadow: this.layoutDoc.ischecked ? `4px 4px 12px black` : undefined
             }}>
-            <FontAwesomeIcon className="fontIconBox-icon" icon={this.dataDoc.icon as any} color={StrCast(this.layoutDoc.color, this._foregroundColor)} size="sm" />
+            <FontAwesomeIcon className="fontIconBox-icon" icon={StrCast(this.dataDoc.icon, "user") as any} color={StrCast(this.layoutDoc.color, this._foregroundColor)} size="sm" />
             {!this.rootDoc.title ? (null) : <div className="fontIconBox-label" style={{ width: this.rootDoc.label ? "max-content" : undefined }}> {StrCast(this.rootDoc.label, StrCast(this.rootDoc.title).substring(0, 6))} </div>}
         </button>;
         return !this.layoutDoc.toolTip ? button :
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 966aaa895..6b6fc5da2 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1401,12 +1401,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     onDoubleClick={this.onDoubleClick}
                 >
                     <div className={`formattedTextBox-outer`} ref={this._scrollRef}
-                        style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: !this.props.isSelected() ? "none" : undefined }}
+                        style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: !this.props.active() ? "none" : undefined }}
                         onScroll={this.onscrolled} onDrop={this.ondrop} >
                         <div className={`formattedTextBox-inner${rounded}${selclass}`} ref={this.createDropTarget}
                             style={{
                                 padding: this.layoutDoc._textBoxPadding ? StrCast(this.layoutDoc._textBoxPadding) : `${Math.max(0, NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0) + selPad)}px  ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0) + selPad}px`,
-                                pointerEvents: !this.props.isSelected() ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : undefined) : undefined
+                                pointerEvents: !this.props.active() ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : undefined) : undefined
                             }}
                         />
                     </div>
diff --git a/src/fields/util.ts b/src/fields/util.ts
index e0b1843a1..47da7d7bf 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -77,10 +77,10 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
 
     if (writeToDoc) {
         if (value === undefined) {
-            delete target.__fieldKeys[prop];
+            target.__fieldKeys && (delete target.__fieldKeys[prop]);
             delete target.__fields[prop];
         } else {
-            target.__fieldKeys[prop] = true;
+            target.__fieldKeys && (target.__fieldKeys[prop] = true);
             target.__fields[prop] = value;
         }
         //if (typeof value === "object" && !(value instanceof ObjectField)) debugger;
-- 
cgit v1.2.3-70-g09d2


From f869c7a75eb6a2f6e6301aa76c57e383b41d3fea Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 27 Jul 2020 11:52:37 -0400
Subject: added hypothesis icon to toolbar. fixed toolbar to update to any
 document selection with specific items for each type.

---
 package-lock.json                                  |  15 ++
 package.json                                       |   1 +
 src/client/views/MainView.tsx                      |   5 +-
 src/client/views/collections/CollectionMenu.tsx    | 221 ++++++++++++---------
 src/client/views/collections/CollectionSubView.tsx |   4 +-
 src/client/views/collections/CollectionView.tsx    |   2 +-
 src/client/views/nodes/DocumentView.tsx            |   1 +
 7 files changed, 149 insertions(+), 100 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index 698bd60cc..51105842a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -301,6 +301,21 @@
                 "@fortawesome/fontawesome-common-types": "^0.2.29"
             }
         },
+        "@fortawesome/free-brands-svg-icons": {
+            "version": "5.14.0",
+            "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.14.0.tgz",
+            "integrity": "sha512-WsqPFTvJFI7MYkcy0jeFE2zY+blC4OrnB9MJOcn1NxRXT/sSfEEhrI7CwzIkiYajLiVDBKWeErYOvpsMeodmCQ==",
+            "requires": {
+                "@fortawesome/fontawesome-common-types": "^0.2.30"
+            },
+            "dependencies": {
+                "@fortawesome/fontawesome-common-types": {
+                    "version": "0.2.30",
+                    "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.30.tgz",
+                    "integrity": "sha512-TsRwpTuKwFNiPhk1UfKgw7zNPeV5RhNp2Uw3pws+9gDAkPGKrtjR1y2lI3SYn7+YzyfuNknflpBA1LRKjt7hMg=="
+                }
+            }
+        },
         "@fortawesome/free-regular-svg-icons": {
             "version": "5.13.1",
             "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.13.1.tgz",
diff --git a/package.json b/package.json
index 6c466825e..3b7b5f391 100644
--- a/package.json
+++ b/package.json
@@ -117,6 +117,7 @@
     },
     "dependencies": {
         "@fortawesome/fontawesome-svg-core": "^1.2.29",
+        "@fortawesome/free-brands-svg-icons": "^5.14.0",
         "@fortawesome/free-regular-svg-icons": "^5.13.1",
         "@fortawesome/free-solid-svg-icons": "^5.13.1",
         "@fortawesome/react-fontawesome": "^0.1.11",
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index ae3f05fb7..9fad612ee 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,5 +1,8 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 
+import {
+    faHireAHelper
+} from '@fortawesome/free-brands-svg-icons';
 import {
     faTasks, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
     faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
@@ -150,7 +153,7 @@ export class MainView extends React.Component {
             faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
             faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
             faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper,
-            faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap, faUser);
+            faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap, faUser, faHireAHelper);
         this.initEventListeners();
         this.initAuthenticationRouters();
     }
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 24be69050..0b30b5a5f 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -30,16 +30,28 @@ import { ObjectField } from "../../../fields/ObjectField";
 export default class CollectionMenu extends AntimodeMenu {
     static Instance: CollectionMenu;
 
-    @observable SelectedCollection: CollectionView | undefined;
+    @observable SelectedCollection: DocumentView | undefined;
+    @observable FieldKey: string;
 
     constructor(props: Readonly<{}>) {
         super(props);
+        this.FieldKey = "";
         CollectionMenu.Instance = this;
         this._canFade = false; // don't let the inking menu fade away
         this.Pinned = Cast(Doc.UserDoc()["menuCollections-pinned"], "boolean", true);
         this.jumpTo(300, 300);
     }
 
+    componentDidMount() {
+        reaction(() => SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0],
+            (doc) => doc && this.SetSelection(doc))
+    }
+
+    @action
+    SetSelection(view: DocumentView) {
+        this.SelectedCollection = view;
+    }
+
     @action
     toggleMenuPin = (e: React.MouseEvent) => {
         Doc.UserDoc()["menuCollections-pinned"] = this.Pinned = !this.Pinned;
@@ -54,14 +66,18 @@ export default class CollectionMenu extends AntimodeMenu {
         </button>;
 
         return this.getElement(!this.SelectedCollection ? [button] :
-            [<CollectionViewBaseChrome key="chrome" CollectionView={this.SelectedCollection} type={StrCast(this.SelectedCollection.props.Document._viewType) as CollectionViewType} />,
+            [<CollectionViewBaseChrome key="chrome"
+                docView={this.SelectedCollection}
+                fieldKey={Doc.LayoutFieldKey(this.SelectedCollection?.props.Document)}
+                type={StrCast(this.SelectedCollection?.props.Document._viewType, CollectionViewType.Invalid) as CollectionViewType} />,
                 button]);
     }
 }
 
 interface CollectionMenuProps {
-    CollectionView: CollectionView;
     type: CollectionViewType;
+    fieldKey: string;
+    docView: DocumentView;
 }
 
 const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation();
@@ -70,7 +86,8 @@ const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation();
 export class CollectionViewBaseChrome extends React.Component<CollectionMenuProps> {
     //(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
 
-    get target() { return this.props.CollectionView.props.Document; }
+    get document() { return this.props.docView?.props.Document; }
+    get target() { return this.document; }
     _templateCommand = {
         params: ["target", "source"], title: "item view",
         script: "self.target.childLayoutTemplate = getDocTemplate(self.source?.[0])",
@@ -121,16 +138,16 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
     _tree_commands = [];
     private get _buttonizableCommands() {
         switch (this.props.type) {
+            default:
+            case CollectionViewType.Freeform: return this._freeform_commands;
             case CollectionViewType.Tree: return this._tree_commands;
             case CollectionViewType.Schema: return this._schema_commands;
             case CollectionViewType.Stacking: return this._stacking_commands;
             case CollectionViewType.Masonry: return this._stacking_commands;
-            case CollectionViewType.Freeform: return this._freeform_commands;
             case CollectionViewType.Time: return this._freeform_commands;
             case CollectionViewType.Carousel: return this._freeform_commands;
             case CollectionViewType.Carousel3D: return this._freeform_commands;
         }
-        return [];
     }
     private _picker: any;
     private _commandRef = React.createRef<HTMLInputElement>();
@@ -165,21 +182,16 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
 
     @computed get subChrome() {
         switch (this.props.type) {
-            case CollectionViewType.Freeform: return (<CollectionFreeFormViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
-            case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
-            case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
-            case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
-            case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
-            case CollectionViewType.Carousel3D: return (<Collection3DCarouselViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
-            case CollectionViewType.Grid: return (<CollectionGridViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
-            default: return null;
+            default:
+            case CollectionViewType.Freeform: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={this.props.type === CollectionViewType.Invalid} />);
+            case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" {...this.props} />);
+            case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" {...this.props} />);
+            case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" {...this.props} />);
+            case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" {...this.props} />);
+            case CollectionViewType.Carousel3D: return (<Collection3DCarouselViewChrome key="collchrome" {...this.props} />);
+            case CollectionViewType.Grid: return (<CollectionGridViewChrome key="collchrome" {...this.props} />);
         }
     }
-
-    private get document() {
-        return this.props.CollectionView.props.Document;
-    }
-
     private dropDisposer?: DragManager.DragDropDisposer;
     protected createDropTarget = (ele: HTMLDivElement) => {
         this.dropDisposer?.();
@@ -201,15 +213,15 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
 
     dragViewDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(this, e, (e, down, delta) => {
-            const vtype = this.props.CollectionView.collectionViewType;
+            const vtype = this.props.type;
             const c = {
                 params: ["target"], title: vtype,
-                script: `this.target._viewType = '${StrCast(this.props.CollectionView.props.Document._viewType)}'`,
-                immediate: (source: Doc[]) => this.props.CollectionView.props.Document._viewType = Doc.getDocTemplate(source?.[0]),
+                script: `this.target._viewType = '${StrCast(this.props.type)}'`,
+                immediate: (source: Doc[]) => this.document._viewType = Doc.getDocTemplate(source?.[0]),
                 initialize: emptyFunction,
             };
             DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title),
-                { target: this.props.CollectionView.props.Document }, c.params, c.initialize, e.clientX, e.clientY);
+                { target: this.document }, c.params, c.initialize, e.clientX, e.clientY);
             return true;
         }, emptyFunction, emptyFunction);
     }
@@ -217,7 +229,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
         setupMoveUpEvents(this, e, (e, down, delta) => {
             this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c =>
                 DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title,
-                    { target: this.props.CollectionView.props.Document }, c.params, c.initialize, e.clientX, e.clientY));
+                    { target: this.document }, c.params, c.initialize, e.clientX, e.clientY));
             return true;
         }, emptyFunction, () => {
             this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate([]));
@@ -251,7 +263,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
                     className="collectionViewBaseChrome-viewPicker"
                     onPointerDown={stopPropagation}
                     onChange={this.viewChanged}
-                    value={StrCast(this.props.CollectionView.props.Document._viewType)}>
+                    value={StrCast(this.props.type)}>
                     {Object.values(CollectionViewType).map(type => [CollectionViewType.Invalid, CollectionViewType.Docking].includes(type) ? (null) : (
                         <option
                             key={Utils.GenerateGuid()}
@@ -271,7 +283,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
             <div className="collectionMenu-cont" >
                 <div className="collectionMenu">
                     <div className="collectionViewBaseChrome">
-                        {this.viewModes}
+                        {this.props.type === CollectionViewType.Invalid ? (null) : this.viewModes}
                         {this.templateChrome}
                         <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: "grid" }}>
                             <button className={"antimodeMenu-button"} onClick={this.toggleViewSpecs} >
@@ -287,15 +299,15 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
 }
 
 @observer
-export class CollectionFreeFormViewChrome extends React.Component<CollectionMenuProps> {
+export class CollectionFreeFormViewChrome extends React.Component<CollectionMenuProps & { isOverlay: boolean }> {
     public static Instance: CollectionFreeFormViewChrome;
     constructor(props: any) {
         super(props);
         CollectionFreeFormViewChrome.Instance = this;
     }
-    get Document() { return this.props.CollectionView.props.Document; }
+    get document() { return this.props.docView.props.Document; }
     @computed get dataField() {
-        return this.props.CollectionView.props.Document[Doc.LayoutFieldKey(this.props.CollectionView.props.Document)];
+        return this.document[Doc.LayoutFieldKey(this.document)];
     }
     @computed get childDocs() {
         return DocListCast(this.dataField);
@@ -303,30 +315,30 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
     @undoBatch
     @action
     nextKeyframe = (): void => {
-        const currentFrame = Cast(this.Document.currentFrame, "number", null);
+        const currentFrame = Cast(this.document.currentFrame, "number", null);
         if (currentFrame === undefined) {
-            this.Document.currentFrame = 0;
+            this.document.currentFrame = 0;
             CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
         }
         CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0);
-        this.Document.currentFrame = Math.max(0, (currentFrame || 0) + 1);
-        this.Document.lastFrame = Math.max(NumCast(this.Document.currentFrame), NumCast(this.Document.lastFrame));
+        this.document.currentFrame = Math.max(0, (currentFrame || 0) + 1);
+        this.document.lastFrame = Math.max(NumCast(this.document.currentFrame), NumCast(this.document.lastFrame));
     }
     @undoBatch
     @action
     prevKeyframe = (): void => {
-        const currentFrame = Cast(this.Document.currentFrame, "number", null);
+        const currentFrame = Cast(this.document.currentFrame, "number", null);
         if (currentFrame === undefined) {
-            this.Document.currentFrame = 0;
+            this.document.currentFrame = 0;
             CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
         }
         CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice());
-        this.Document.currentFrame = Math.max(0, (currentFrame || 0) - 1);
+        this.document.currentFrame = Math.max(0, (currentFrame || 0) - 1);
     }
     @undoBatch
     @action
     miniMap = (): void => {
-        this.Document.hideMinimap = !this.Document.hideMinimap;
+        this.document.hideMinimap = !this.document.hideMinimap;
     }
     private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF", ""];
     private _width = ["1", "5", "10", "100"];
@@ -467,28 +479,39 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
     }
 
     render() {
-        return this.Document.isAnnotationOverlay ? (null) :
-            <div className="collectionFreeFormMenu-cont">
+        return <div className="collectionFreeFormMenu-cont">
+            {this.props.docView.props.renderDepth !== 0 ? (null) :
                 <div key="map" title="mini map" className="backKeyframe" onClick={this.miniMap}>
                     <FontAwesomeIcon icon={"map"} size={"lg"} />
                 </div>
-                <div key="back" title="back frame" className="backKeyframe" onClick={this.prevKeyframe}>
-                    <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
-                </div>
-                <div key="num" title="toggle view all" className="numKeyframe" style={{ backgroundColor: this.Document.editing ? "#759c75" : "#c56565" }}
-                    onClick={action(() => this.Document.editing = !this.Document.editing)} >
-                    {NumCast(this.Document.currentFrame)}
-                </div>
-                <div key="fwd" title="forward frame" className="fwdKeyframe" onClick={this.nextKeyframe}>
-                    <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
-                </div>
+            }
+            <div key="back" title="back frame" className="backKeyframe" onClick={this.prevKeyframe}>
+                <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
+            </div>
+            <div key="num" title="toggle view all" className="numKeyframe" style={{ backgroundColor: this.document.editing ? "#759c75" : "#c56565" }}
+                onClick={action(() => this.document.editing = !this.document.editing)} >
+                {NumCast(this.document.currentFrame)}
+            </div>
+            <div key="fwd" title="forward frame" className="fwdKeyframe" onClick={this.nextKeyframe}>
+                <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
+            </div>
 
-                {this.widthPicker}
-                {this.colorPicker}
-                {this.fillPicker}
-                {this.drawButtons}
-                {this.formatPane}
-            </div>;
+            {!this.props.isOverlay ? (null) :
+                <button className={"antimodeMenu-button"} key="hypothesis" style={{ backgroundColor: !this.props.docView.layoutDoc.isAnnotating ? "121212" : undefined }} title="Use Hypothesis" onClick={() => this.props.docView.layoutDoc.isAnnotating = !this.props.docView.layoutDoc.isAnnotating}>
+                    <FontAwesomeIcon icon={["fab", "hire-a-helper"]} size={"lg"} />
+                </button>
+            }
+            {!this.props.isOverlay || this.props.docView.layoutDoc.isAnnotating ?
+                <>
+                    {this.widthPicker}
+                    {this.colorPicker}
+                    {this.fillPicker}
+                    {this.drawButtons}
+                    {this.formatPane}
+                </> :
+                (null)
+            }
+        </div>;
     }
 }
 @observer
@@ -496,12 +519,14 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu
     @observable private _currentKey: string = "";
     @observable private suggestions: string[] = [];
 
-    @computed private get descending() { return StrCast(this.props.CollectionView.props.Document._columnsSort) === "descending"; }
-    @computed get pivotField() { return StrCast(this.props.CollectionView.props.Document._pivotField); }
+    get document() { return this.props.docView.props.Document; }
+
+    @computed private get descending() { return StrCast(this.document._columnsSort) === "descending"; }
+    @computed get pivotField() { return StrCast(this.document._pivotField); }
 
     getKeySuggestions = async (value: string): Promise<string[]> => {
         value = value.toLowerCase();
-        const docs = DocListCast(this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey]);
+        const docs = DocListCast(this.document[this.props.fieldKey]);
         if (docs instanceof Doc) {
             return Object.keys(docs).filter(key => key.toLowerCase().startsWith(value));
         } else {
@@ -536,14 +561,14 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu
 
     @action
     setValue = (value: string) => {
-        this.props.CollectionView.props.Document._pivotField = value;
+        this.document._pivotField = value;
         return true;
     }
 
     @action toggleSort = () => {
-        this.props.CollectionView.props.Document._columnsSort =
-            this.props.CollectionView.props.Document._columnsSort === "descending" ? "ascending" :
-                this.props.CollectionView.props.Document._columnsSort === "ascending" ? undefined : "descending";
+        this.document._columnsSort =
+            this.document._columnsSort === "descending" ? "ascending" :
+                this.document._columnsSort === "ascending" ? undefined : "descending";
     }
     @action resetValue = () => { this._currentKey = this.pivotField; };
 
@@ -593,35 +618,36 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu
 
 @observer
 export class CollectionSchemaViewChrome extends React.Component<CollectionMenuProps> {
-    // private _textwrapAllRows: boolean = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
+    // private _textwrapAllRows: boolean = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
+    get document() { return this.props.docView.props.Document; }
 
     @undoBatch
     togglePreview = () => {
         const dividerWidth = 4;
         const borderWidth = Number(COLLECTION_BORDER_WIDTH);
-        const panelWidth = this.props.CollectionView.props.PanelWidth();
-        const previewWidth = NumCast(this.props.CollectionView.props.Document.schemaPreviewWidth);
+        const panelWidth = this.props.docView.props.PanelWidth();
+        const previewWidth = NumCast(this.document.schemaPreviewWidth);
         const tableWidth = panelWidth - 2 * borderWidth - dividerWidth - previewWidth;
-        this.props.CollectionView.props.Document.schemaPreviewWidth = previewWidth === 0 ? Math.min(tableWidth / 3, 200) : 0;
+        this.document.schemaPreviewWidth = previewWidth === 0 ? Math.min(tableWidth / 3, 200) : 0;
     }
 
     @undoBatch
     @action
     toggleTextwrap = async () => {
-        const textwrappedRows = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []);
+        const textwrappedRows = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []);
         if (textwrappedRows.length) {
-            this.props.CollectionView.props.Document.textwrappedSchemaRows = new List<string>([]);
+            this.document.textwrappedSchemaRows = new List<string>([]);
         } else {
-            const docs = DocListCast(this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey]);
+            const docs = DocListCast(this.document[this.props.fieldKey]);
             const allRows = docs instanceof Doc ? [docs[Id]] : docs.map(doc => doc[Id]);
-            this.props.CollectionView.props.Document.textwrappedSchemaRows = new List<string>(allRows);
+            this.document.textwrappedSchemaRows = new List<string>(allRows);
         }
     }
 
 
     render() {
-        const previewWidth = NumCast(this.props.CollectionView.props.Document.schemaPreviewWidth);
-        const textWrapped = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
+        const previewWidth = NumCast(this.document.schemaPreviewWidth);
+        const textWrapped = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
 
         return (
             <div className="collectionSchemaViewChrome-cont">
@@ -641,11 +667,12 @@ export class CollectionSchemaViewChrome extends React.Component<CollectionMenuPr
 @observer
 export class CollectionTreeViewChrome extends React.Component<CollectionMenuProps> {
 
+    get document() { return this.props.docView.props.Document; }
     get sortAscending() {
-        return this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "-sortAscending"];
+        return this.document[this.props.fieldKey + "-sortAscending"];
     }
     set sortAscending(value) {
-        this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "-sortAscending"] = value;
+        this.document[this.props.fieldKey + "-sortAscending"] = value;
     }
     @computed private get ascending() {
         return Cast(this.sortAscending, "boolean", null);
@@ -676,15 +703,16 @@ export class CollectionTreeViewChrome extends React.Component<CollectionMenuProp
 // Enter scroll speed for 3D Carousel 
 @observer
 export class Collection3DCarouselViewChrome extends React.Component<CollectionMenuProps> {
+    get document() { return this.props.docView.props.Document; }
     @computed get scrollSpeed() {
-        return this.props.CollectionView.props.Document._autoScrollSpeed;
+        return this.document._autoScrollSpeed;
     }
 
     @action
     setValue = (value: string) => {
         const numValue = Number(StrCast(value));
         if (numValue > 0) {
-            this.props.CollectionView.props.Document._autoScrollSpeed = numValue;
+            this.document._autoScrollSpeed = numValue;
             return true;
         }
         return false;
@@ -721,13 +749,14 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
     private decrementLimitReached: boolean = false;
     @observable private resize = false;
     private resizeListenerDisposer: Opt<Lambda>;
+    get document() { return this.props.docView.props.Document; }
 
     componentDidMount() {
 
-        runInAction(() => this.resize = this.props.CollectionView.props.PanelWidth() < 700);
+        runInAction(() => this.resize = this.props.docView.props.PanelWidth() < 700);
 
         // listener to reduce text on chrome resize (panel resize)
-        this.resizeListenerDisposer = computed(() => this.props.CollectionView.props.PanelWidth()).observe(({ newValue }) => {
+        this.resizeListenerDisposer = computed(() => this.props.docView.props.PanelWidth()).observe(({ newValue }) => {
             runInAction(() => this.resize = newValue < 700);
         });
     }
@@ -736,7 +765,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
         this.resizeListenerDisposer?.();
     }
 
-    get numCols() { return NumCast(this.props.CollectionView.props.Document.gridNumCols, 10); }
+    get numCols() { return NumCast(this.document.gridNumCols, 10); }
 
     /**
      * Sets the value of `numCols` on the grid's Document to the value entered.
@@ -745,7 +774,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
     onNumColsEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
         if (e.key === "Enter" || e.key === "Tab") {
             if (e.currentTarget.valueAsNumber > 0) {
-                this.props.CollectionView.props.Document.gridNumCols = e.currentTarget.valueAsNumber;
+                this.document.gridNumCols = e.currentTarget.valueAsNumber;
             }
 
         }
@@ -757,8 +786,8 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
     // @undoBatch
     // onRowHeightEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
     //     if (e.key === "Enter" || e.key === "Tab") {
-    //         if (e.currentTarget.valueAsNumber > 0 && this.props.CollectionView.props.Document.rowHeight as number !== e.currentTarget.valueAsNumber) {
-    //             this.props.CollectionView.props.Document.rowHeight = e.currentTarget.valueAsNumber;
+    //         if (e.currentTarget.valueAsNumber > 0 && this.document.rowHeight as number !== e.currentTarget.valueAsNumber) {
+    //             this.document.rowHeight = e.currentTarget.valueAsNumber;
     //         }
     //     }
     // }
@@ -768,7 +797,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
      */
     @undoBatch
     toggleFlex = () => {
-        this.props.CollectionView.props.Document.gridFlex = !BoolCast(this.props.CollectionView.props.Document.gridFlex, true);
+        this.document.gridFlex = !BoolCast(this.document.gridFlex, true);
     }
 
     /**
@@ -776,8 +805,8 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
      */
     onIncrementButtonClick = () => {
         this.clicked = true;
-        this.entered && (this.props.CollectionView.props.Document.gridNumCols as number)--;
-        undoBatch(() => this.props.CollectionView.props.Document.gridNumCols = this.numCols + 1)();
+        this.entered && (this.document.gridNumCols as number)--;
+        undoBatch(() => this.document.gridNumCols = this.numCols + 1)();
         this.entered = false;
     }
 
@@ -787,8 +816,8 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
     onDecrementButtonClick = () => {
         this.clicked = true;
         if (!this.decrementLimitReached) {
-            this.entered && (this.props.CollectionView.props.Document.gridNumCols as number)++;
-            undoBatch(() => this.props.CollectionView.props.Document.gridNumCols = this.numCols - 1)();
+            this.entered && (this.document.gridNumCols as number)++;
+            undoBatch(() => this.document.gridNumCols = this.numCols - 1)();
         }
         this.entered = false;
     }
@@ -799,7 +828,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
     incrementValue = () => {
         this.entered = true;
         if (!this.clicked && !this.decrementLimitReached) {
-            this.props.CollectionView.props.Document.gridNumCols = this.numCols + 1;
+            this.document.gridNumCols = this.numCols + 1;
         }
         this.decrementLimitReached = false;
         this.clicked = false;
@@ -812,7 +841,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
         this.entered = true;
         if (!this.clicked) {
             if (this.numCols !== 1) {
-                this.props.CollectionView.props.Document.gridNumCols = this.numCols - 1;
+                this.document.gridNumCols = this.numCols - 1;
             }
             else {
                 this.decrementLimitReached = true;
@@ -826,7 +855,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
      * Toggles the value of preventCollision
      */
     toggleCollisions = () => {
-        this.props.CollectionView.props.Document.gridPreventCollision = !this.props.CollectionView.props.Document.gridPreventCollision;
+        this.document.gridPreventCollision = !this.document.gridPreventCollision;
     }
 
     /**
@@ -834,7 +863,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
      */
     changeCompactType = (e: React.ChangeEvent<HTMLSelectElement>) => {
         // need to change startCompaction so that this operation will be undoable.
-        this.props.CollectionView.props.Document.gridStartCompaction = e.target.selectedOptions[0].value;
+        this.document.gridStartCompaction = e.target.selectedOptions[0].value;
     }
 
     render() {
@@ -852,10 +881,10 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
                     <span className="grid-icon">
                         <FontAwesomeIcon icon="text-height" size="1x" />
                     </span>
-                    <input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.props.CollectionView.props.Document.rowHeight as string} onKeyDown={this.onRowHeightEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} />
+                    <input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.document.rowHeight as string} onKeyDown={this.onRowHeightEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} />
                 </span> */}
                 <span className="grid-control" style={{ width: this.resize ? "12%" : "20%" }}>
-                    <input type="checkbox" style={{ marginRight: 5 }} onChange={this.toggleCollisions} checked={!this.props.CollectionView.props.Document.gridPreventCollision} />
+                    <input type="checkbox" style={{ marginRight: 5 }} onChange={this.toggleCollisions} checked={!this.document.gridPreventCollision} />
                     <label className="flexLabel">{this.resize ? "Coll" : "Collisions"}</label>
                 </span>
 
@@ -863,7 +892,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
                     style={{ marginRight: 5 }}
                     onPointerDown={stopPropagation}
                     onChange={this.changeCompactType}
-                    value={StrCast(this.props.CollectionView.props.Document.gridStartCompaction, StrCast(this.props.CollectionView.props.Document.gridCompaction))}>
+                    value={StrCast(this.document.gridStartCompaction, StrCast(this.document.gridCompaction))}>
                     {["vertical", "horizontal", "none"].map(type =>
                         <option className="collectionGridViewChrome-viewOption"
                             onPointerDown={stopPropagation}
@@ -875,11 +904,11 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
 
                 <span className="grid-control" style={{ width: this.resize ? "12%" : "20%" }}>
                     <input style={{ marginRight: 5 }} type="checkbox" onChange={this.toggleFlex}
-                        checked={BoolCast(this.props.CollectionView.props.Document.gridFlex, true)} />
+                        checked={BoolCast(this.document.gridFlex, true)} />
                     <label className="flexLabel">{this.resize ? "Flex" : "Flexible"}</label>
                 </span>
 
-                <button onClick={() => this.props.CollectionView.props.Document.gridResetLayout = true}>
+                <button onClick={() => this.document.gridResetLayout = true}>
                     {!this.resize ? "Reset" :
                         <FontAwesomeIcon icon="redo-alt" size="1x" />}
                 </button>
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 8480a56cc..d9d9c0eb8 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -292,7 +292,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                             const reg = new RegExp(Utils.prepend(""), "g");
                             const modHtml = srcUrl ? html.replace(reg, srcUrl) : html;
                             const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: "-web page-", _width: 300, _height: 300 });
-                            Doc.GetProto(htmlDoc)["data-text"] = text;
+                            Doc.GetProto(htmlDoc)["data-text"] = Doc.GetProto(htmlDoc)["text"] = text;
                             this.props.addDocument(htmlDoc);
                             if (srcWeb) {
                                 const focusNode = (SelectionManager.SelectedDocuments()[0].ContentDiv?.getElementsByTagName("iframe")[0].contentDocument?.getSelection()?.focusNode as any);
@@ -300,7 +300,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                                     const rect = "getBoundingClientRect" in focusNode ? focusNode.getBoundingClientRect() : focusNode?.parentElement.getBoundingClientRect();
                                     const x = (rect?.x || 0);
                                     const y = NumCast(srcWeb._scrollTop) + (rect?.y || 0);
-                                    const anchor = Docs.Create.FreeformDocument([], { _backgroundColor: "transparent", _width: 25, _height: 25, x, y, annotationOn: srcWeb });
+                                    const anchor = Docs.Create.FreeformDocument([], { _backgroundColor: "transparent", _width: 75, _height: 40, x, y, annotationOn: srcWeb });
                                     anchor.context = srcWeb;
                                     const key = Doc.LayoutFieldKey(srcWeb);
                                     Doc.AddDocToList(srcWeb, key + "-annotations", anchor);
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index a82c91383..2c17254ed 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -577,7 +577,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             ChildLayoutTemplate: this.childLayoutTemplate,
             ChildLayoutString: this.childLayoutString,
         };
-        setTimeout(action(() => this.props.isSelected(true) && (CollectionMenu.Instance.SelectedCollection = this)), 0);
+        setTimeout(() => this.props.isSelected(true) && CollectionMenu.Instance.SetSelection(this, this.props.fieldKey), 0);
         const boxShadow = Doc.UserDoc().renderStyle === "comic" || this.props.Document.isBackground || this.collectionViewType === CollectionViewType.Linear ? undefined :
             `${Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "rgb(30, 32, 31) " : "#9c9396 "} ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`;
         return (<div className={"collectionView"} onContextMenu={this.onContextMenu}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b59875a7e..7f86ec98e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -44,6 +44,7 @@ import { MobileInterface } from '../../../mobile/MobileInterface';
 import { TaskCompletionBox } from './TaskCompletedBox';
 import { LinkDescriptionPopup } from './LinkDescriptionPopup';
 import { LinkManager } from '../../util/LinkManager';
+import CollectionMenu from '../collections/CollectionMenu';
 
 library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight,
     fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale,
-- 
cgit v1.2.3-70-g09d2


From 7693ca298392cb0551032b83b688366b1956bdd6 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 28 Jul 2020 10:52:02 -0400
Subject: cleaned up some font stuff.  cleaned up DocumentView a bit

---
 src/client/documents/Documents.ts                  |   1 +
 src/client/util/SharingManager.tsx                 |   5 +-
 src/client/views/DocComponent.tsx                  |   6 +-
 src/client/views/GestureOverlay.tsx                |   2 +-
 src/client/views/MainView.tsx                      |  38 +-
 src/client/views/Touchable.tsx                     |   2 +-
 .../views/collections/CollectionLinearView.tsx     |   2 +-
 src/client/views/collections/CollectionSubView.tsx |   8 +-
 src/client/views/collections/CollectionView.tsx    |   7 +-
 .../CollectionFreeFormLayoutEngines.tsx            |  24 --
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   5 -
 src/client/views/nodes/ComparisonBox.tsx           |   2 +-
 src/client/views/nodes/DocumentView.tsx            | 409 ++++-----------------
 src/client/views/nodes/ImageBox.tsx                |   2 +-
 src/client/views/nodes/ScriptingBox.tsx            |   2 +-
 src/fields/Doc.ts                                  |   5 +
 16 files changed, 110 insertions(+), 410 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a1c66c2fe..2cd781a53 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1053,6 +1053,7 @@ export namespace DocUtils {
             }
         });
         batch.end();
+        return doc;
     }
     export function findTemplate(templateName: string, type: string, signature: string) {
         let docLayoutTemplate: Opt<Doc>;
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 452a58d21..0d8b33fbe 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -8,7 +8,6 @@ import * as RequestPromise from "request-promise";
 import { Utils } from "../../Utils";
 import "./SharingManager.scss";
 import { observer } from "mobx-react";
-import { library } from '@fortawesome/fontawesome-svg-core';
 import * as fa from '@fortawesome/free-solid-svg-icons';
 import { DocumentView } from "../views/nodes/DocumentView";
 import { SelectionManager } from "./SelectionManager";
@@ -23,8 +22,6 @@ import { List } from "../../fields/List";
 import { distributeAcls, SharingPermissions } from "../../fields/util";
 import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
 
-library.add(fa.faCopy, fa.faTimes);
-
 export interface User {
     email: string;
     userDocumentId: string;
@@ -447,7 +444,7 @@ export default class SharingManager extends React.Component<{}> {
                 <div className="sharing-contents">
                     <p className={"share-title"}><b>Share </b>{this.focusOn(StrCast(this.targetDoc?.title, "this document"))}</p>
                     <div className={"close-button"} onClick={this.close}>
-                        <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} />
+                        <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} />
                     </div>
                     {this.targetDoc?.author !== Doc.CurrentUserEmail ? null
                         :
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 9fd406407..4c82149e2 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -26,7 +26,7 @@ export function DocComponent<P extends DocComponentProps, T>(schemaCtor: (doc: D
         // This is the data part of a document -- ie, the data that is constant across all views of the document
         @computed get dataDoc() { return this.props.Document[DataSym] as Doc; }
 
-        protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+        protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
     }
     return Component;
 }
@@ -59,7 +59,7 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps, T>(schemaCtor:
         lookupField = (field: string) => ScriptCast(this.layoutDoc.lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field, container: this.props.ContainingCollectionDoc }).result;
 
         active = (outsideReaction?: boolean) => !this.props.Document.isBackground && (this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0 || this.layoutDoc.forceActive);//  && !Doc.SelectedTool();  // bcz: inking state shouldn't affect static tools 
-        protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+        protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
     }
     return Component;
 }
@@ -114,7 +114,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
             return style;
         }
 
-        protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+        protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
         _annotationKey: string = "annotations";
         public get annotationKey() { return this.fieldKey + "-" + this._annotationKey; }
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 2e588ceb5..ed77da8ca 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -63,7 +63,7 @@ export default class GestureOverlay extends Touchable {
     private _hands: Map<number, React.Touch[]> = new Map<number, React.Touch[]>();
     private _holdTimer: NodeJS.Timeout | undefined;
 
-    protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+    protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
     constructor(props: Readonly<{}>) {
         super(props);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 9fad612ee..33cd43678 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,18 +1,6 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
-
-import {
-    faHireAHelper
-} from '@fortawesome/free-brands-svg-icons';
-import {
-    faTasks, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
-    faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
-    faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
-    faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
-    faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTimesCircle,
-    faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
-    faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper,
-    faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap, faUser
-} from '@fortawesome/free-solid-svg-icons';
+import { faHireAHelper } from '@fortawesome/free-brands-svg-icons';
+import * as fa from '@fortawesome/free-solid-svg-icons';
 import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
@@ -146,14 +134,20 @@ export class MainView extends React.Component {
             }
         }
 
-        library.add(faTasks, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
-            faTerminal, faToggleOn, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faTimesCircle, faWindowMaximize, faAddressCard, fileSolid,
-            faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
-            faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
-            faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
-            faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
-            faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper,
-            faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap, faUser, faHireAHelper);
+        library.add(fa.faEdit, fa.faTrash, fa.faTrashAlt, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt,
+            fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock,
+            fa.faLock, fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone, fa.faKeyboard,
+            fa.faQuestion, fa.faTasks, fa.faPalette, fa.faAngleRight, fa.faBell, fa.faCamera, fa.faExpand, fa.faCaretDown, fa.faCaretLeft, fa.faCaretRight,
+            fa.faCaretSquareDown, fa.faCaretSquareRight, fa.faArrowsAltH, fa.faPlus, fa.faMinus, fa.faTerminal, fa.faToggleOn, fa.faFile, fa.faLocationArrow,
+            fa.faSearch, fa.faFileDownload, fa.faStop, fa.faCalculator, fa.faWindowMaximize, fa.faAddressCard, fa.faQuestionCircle, fa.faArrowLeft,
+            fa.faArrowRight, fa.faArrowDown, fa.faArrowUp, fa.faBolt, fa.faBullseye, fa.faCaretUp, fa.faCat, fa.faCheck, fa.faChevronRight, fa.faClipboard,
+            fa.faClone, fa.faCloudUploadAlt, fa.faCommentAlt, fa.faCompressArrowsAlt, fa.faCut, fa.faEllipsisV, fa.faEraser, fa.faExclamation, fa.faFileAlt,
+            fa.faFileAudio, fa.faFilePdf, fa.faFilm, fa.faFilter, fa.faFont, fa.faGlobeAsia, fa.faHighlighter, fa.faLongArrowAltRight, fa.faMousePointer,
+            fa.faMusic, fa.faObjectGroup, fa.faPause, fa.faPen, fa.faPenNib, fa.faPhone, fa.faPlay, fa.faPortrait, fa.faRedoAlt, fa.faStamp, fa.faStickyNote,
+            fa.faTimesCircle, fa.faThumbtack, fa.faTree, fa.faTv, fa.faUndoAlt, fa.faVideo, fa.faAsterisk, fa.faBrain, fa.faImage, fa.faPaintBrush, fa.faTimes,
+            fa.faEye, fa.faArrowsAlt, fa.faQuoteLeft, fa.faSortAmountDown, fa.faAlignLeft, fa.faAlignCenter, fa.faAlignRight, fa.faHeading, fa.faRulerCombined,
+            fa.faFillDrip, fa.faLink, fa.faUnlink, fa.faBold, fa.faItalic, fa.faChevronLeft, fa.faUnderline, fa.faStrikethrough, fa.faSuperscript, fa.faSubscript,
+            fa.faIndent, fa.faEyeDropper, fa.faPaintRoller, fa.faBars, fa.faBrush, fa.faShapes, fa.faEllipsisH, fa.faHandPaper, fa.faMap, fa.faUser, faHireAHelper);
         this.initEventListeners();
         this.initAuthenticationRouters();
     }
diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
index c4cae7e8d..bb9e108cb 100644
--- a/src/client/views/Touchable.tsx
+++ b/src/client/views/Touchable.tsx
@@ -12,7 +12,7 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
     private holdEndDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
 
-    protected abstract multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+    protected abstract _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
     protected _touchDrag: boolean = false;
     protected prevPoints: Map<number, React.Touch> = new Map<number, React.Touch>();
 
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 407524353..3b31947f7 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -176,7 +176,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                 }}
                     onPointerDown={e => e.stopPropagation()} >
                     <span className="bottomPopup-text" >
-                        Creating link from: {DocumentLinksButton.StartLink.title}
+                        Creating link from: {DocumentLinksButton.StartLink.props.Document.title}
                     </span>
 
                     <Tooltip title={<><div className="dash-tooltip">{LinkDescriptionPopup.showDescriptions ? "Turn off description pop-up" :
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d9d9c0eb8..99acfdcc2 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -55,17 +55,17 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
     class CollectionSubView extends DocComponent<X & SubCollectionViewProps, T>(schemaCtor) {
         private dropDisposer?: DragManager.DragDropDisposer;
         private gestureDisposer?: GestureUtils.GestureEventDisposer;
-        protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+        protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
         protected _mainCont?: HTMLDivElement;
         protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
             this.dropDisposer?.();
             this.gestureDisposer?.();
-            this.multiTouchDisposer?.();
+            this._multiTouchDisposer?.();
             if (ele) {
                 this._mainCont = ele;
                 this.dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc, this.onInternalPreDrop.bind(this));
                 this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
-                this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this));
+                this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this));
             }
         }
         protected CreateDropTarget(ele: HTMLDivElement) { //used in schema view
@@ -74,7 +74,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
 
         componentWillUnmount() {
             this.gestureDisposer?.();
-            this.multiTouchDisposer?.();
+            this._multiTouchDisposer?.();
         }
 
         @computed get dataDoc() {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 2c17254ed..e7a504e61 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -27,12 +27,10 @@ import { InteractionUtils } from '../../util/InteractionUtils';
 import { UndoManager } from '../../util/UndoManager';
 import { ContextMenu } from "../ContextMenu";
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
-import { ScriptBox } from '../ScriptBox';
 import { Touchable } from '../Touchable';
 import { CollectionCarousel3DView } from './CollectionCarousel3DView';
 import { CollectionCarouselView } from './CollectionCarouselView';
 import { CollectionDockingView } from "./CollectionDockingView";
-import { AddCustomFreeFormLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
 import { CollectionGridView } from './collectionGrid/CollectionGridView';
 import { CollectionLinearView } from './CollectionLinearView';
@@ -105,7 +103,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
     @observable private static _safeMode = false;
     public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; }
 
-    protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+    protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
     private AclMap = new Map<symbol, string>([
         [AclPrivate, SharingPermissions.None],
@@ -289,9 +287,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
         subItems.push({ description: "Pivot/Time", event: () => func(CollectionViewType.Time), icon: "columns" });
         subItems.push({ description: "Map", event: () => func(CollectionViewType.Map), icon: "globe-americas" });
         subItems.push({ description: "Grid", event: () => func(CollectionViewType.Grid), icon: "th-list" });
-        if (addExtras && this.props.Document._viewType === CollectionViewType.Freeform) {
-            subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
-        }
         addExtras && subItems.push({ description: "lightbox", event: action(() => this._isLightboxOpen = true), icon: "eye" });
 
         const existingVm = ContextMenu.Instance.findByDescription(category);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 183ed3119..b00074cc6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -434,27 +434,3 @@ function normalizeResults(
         payload: gname.payload
     })));
 }
-
-export function AddCustomFreeFormLayout(doc: Doc, dataKey: string): () => void {
-    return () => {
-        const addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
-            let overlayDisposer: () => void = emptyFunction; // filled in below after we have a reference to the scriptingBox
-            const scriptField = Cast(doc[key], ScriptField);
-            const scriptingBox = <ScriptBox initialText={scriptField && scriptField.script.originalScript}
-                // tslint:disable-next-line: no-unnecessary-callback-wrapper
-                onCancel={() => overlayDisposer()}  // don't get rid of the function wrapper-- we don't want to use the current value of overlayDiposer, but the one set below
-                onSave={(text, onError) => {
-                    const script = CompileScript(text, { params, requiredType, typecheck: false });
-                    if (!script.compiled) {
-                        onError(script.errors.map(error => error.messageText).join("\n"));
-                    } else {
-                        doc[key] = new ScriptField(script);
-                        overlayDisposer();
-                    }
-                }} />;
-            overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, options);
-        };
-        addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300, title: "Layout Initialization" }, { collection: "Doc", docs: "Doc[]" }, undefined);
-        addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300, title: "Layout Script" }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
-    };
-}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 792f3af5f..57336131a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -57,7 +57,6 @@ export const panZoomSchema = createSchema({
     currentTimecode: "number",
     displayTimecode: "number",
     currentFrame: "number",
-    arrangeScript: ScriptField,
     arrangeInit: ScriptField,
     useClusters: "boolean",
     fitToBox: "boolean",
@@ -1005,10 +1004,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         return this.props.addDocTab(doc, where);
     });
     getCalculatedPositions(params: { pair: { layout: Doc, data?: Doc }, index: number, collection: Doc, docs: Doc[], state: any }): PoolData {
-        const result = this.Document.arrangeScript?.script.run(params, console.log);
-        if (result?.success) {
-            return { x: 0, y: 0, transition: "transform 1s", ...result, pair: params.pair, replica: "" };
-        }
         const layoutDoc = Doc.Layout(params.pair.layout);
         const { x, y, opacity } = this.Document.currentFrame === undefined ? params.pair.layout :
             CollectionFreeFormDocumentView.getValues(params.pair.layout, this.Document.currentFrame || 0);
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index f140cc6e5..616cddfcf 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -24,7 +24,7 @@ const ComparisonDocument = makeInterface(comparisonSchema, documentSchema);
 @observer
 export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, ComparisonDocument>(ComparisonDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ComparisonBox, fieldKey); }
-    protected multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
+    protected _multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
     private _disposers: (DragManager.DragDropDisposer | undefined)[] = [undefined, undefined];
 
     @observable _animating = "";
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3b4dc3741..c47edefd6 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,31 +1,31 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import * as fa from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, DataSym, AclPrivate, AclEdit, AclAdmin } from "../../../fields/Doc";
+import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
 import { Document } from '../../../fields/documentSchemas';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { listSpec } from "../../../fields/Schema";
 import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
 import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from "../../../fields/Types";
-import { TraceMobx, GetEffectiveAcl, SharingPermissions } from '../../../fields/util';
+import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
+import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
+import { MobileInterface } from '../../../mobile/MobileInterface';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
-import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils, emptyPath } from "../../../Utils";
+import { emptyFunction, emptyPath, OmitKeys, returnOne, returnTransparent, Utils } from "../../../Utils";
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { ClientRecommender } from '../../ClientRecommender';
 import { Docs, DocUtils } from "../../documents/Documents";
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from "../../util/DocumentManager";
-import { SnappingManager } from '../../util/SnappingManager';
 import { DragManager, dropActionType } from "../../util/DragManager";
 import { InteractionUtils } from '../../util/InteractionUtils';
+import { LinkManager } from '../../util/LinkManager';
 import { Scripting } from '../../util/Scripting';
 import { SearchUtil } from '../../util/SearchUtil';
 import { SelectionManager } from "../../util/SelectionManager";
 import SharingManager from '../../util/SharingManager';
+import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from "../../util/Transform";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { CollectionView, CollectionViewType } from '../collections/CollectionView';
@@ -35,20 +35,13 @@ import { DocComponent } from "../DocComponent";
 import { EditableView } from '../EditableView';
 import { KeyphraseQueryView } from '../KeyphraseQueryView';
 import { DocumentContentsView } from "./DocumentContentsView";
+import { DocumentLinksButton } from './DocumentLinksButton';
 import "./DocumentView.scss";
 import { LinkAnchorBox } from './LinkAnchorBox';
+import { LinkDescriptionPopup } from './LinkDescriptionPopup';
 import { RadialMenu } from './RadialMenu';
-import React = require("react");
-import { DocumentLinksButton } from './DocumentLinksButton';
-import { MobileInterface } from '../../../mobile/MobileInterface';
 import { TaskCompletionBox } from './TaskCompletedBox';
-import { LinkDescriptionPopup } from './LinkDescriptionPopup';
-import { LinkManager } from '../../util/LinkManager';
-import CollectionMenu from '../collections/CollectionMenu';
-
-library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight,
-    fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale,
-    fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone, fa.faKeyboard, fa.faQuestion);
+import React = require("react");
 
 export type DocFocusFunc = () => boolean;
 
@@ -105,24 +98,25 @@ export interface DocumentViewProps {
 
 @observer
 export class DocumentView extends DocComponent<DocumentViewProps, Document>(Document) {
+    @observable _animateScalingTo = 0;
     private _downX: number = 0;
     private _downY: number = 0;
+    private _firstX: number = -1;
+    private _firstY: number = -1;
     private _lastTap: number = 0;
     private _doubleTap = false;
     private _mainCont = React.createRef<HTMLDivElement>();
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _showKPQuery: boolean = false;
     private _queries: string = "";
-    private _gestureEventDisposer?: GestureUtils.GestureEventDisposer;
     private _titleRef = React.createRef<EditableView>();
+    private _gestureEventDisposer?: GestureUtils.GestureEventDisposer;
+    private _holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
+    protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
-    protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
-    private holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
-
-    public get title() { return this.props.Document.title; }
     public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
     public get ContentDiv() { return this._mainCont.current; }
-    get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
+    private get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
     @computed get topMost() { return this.props.renderDepth === 0; }
     @computed get freezeDimensions() { return this.props.FreezeDimensions; }
     @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
@@ -136,9 +130,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     onClickFunc = () => this.onClickHandler;
     onDoubleClickFunc = () => this.onDoubleClickHandler;
 
-    private _firstX: number = -1;
-    private _firstY: number = -1;
-
     handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
         this.removeMoveListeners();
         this.removeEndListeners();
@@ -152,11 +143,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             this._firstX = pt.pageX;
             this._firstY = pt.pageY;
         }
-
     }
 
     handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
-
         const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
 
         if (this._firstX === -1 || this._firstY === -1) {
@@ -200,7 +189,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     componentDidMount() {
         this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document));
         this._mainCont.current && (this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this)));
-        this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this)));
+        this._mainCont.current && (this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this)));
         // this._mainCont.current && (this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this)));
 
         if (!this.props.dontRegisterView) {
@@ -212,13 +201,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     componentDidUpdate() {
         this._dropDisposer?.();
         this._gestureEventDisposer?.();
-        this.multiTouchDisposer?.();
-        this.holdDisposer?.();
+        this._multiTouchDisposer?.();
+        this._holdDisposer?.();
         if (this._mainCont.current) {
             this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document);
             this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this));
-            this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this));
-            this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this));
+            this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this));
+            this._holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this));
         }
     }
 
@@ -226,8 +215,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     componentWillUnmount() {
         this._dropDisposer?.();
         this._gestureEventDisposer?.();
-        this.multiTouchDisposer?.();
-        this.holdDisposer?.();
+        this._multiTouchDisposer?.();
+        this._holdDisposer?.();
         Doc.UnBrushDoc(this.props.Document);
         if (!this.props.dontRegisterView) {
             const index = DocumentManager.Instance.DocumentViews.indexOf(this);
@@ -242,7 +231,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             dragData.offset = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
             dragData.dropAction = dropAction;
             dragData.removeDocument = this.props.removeDocument;
-            dragData.moveDocument = this.props.moveDocument;//  this.layoutDoc.onDragStart ? undefined : this.props.moveDocument;
+            dragData.moveDocument = this.props.moveDocument;
             dragData.dragDivName = this.props.dragDivName;
             dragData.treeViewDoc = this.props.treeViewDoc;
             DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.layoutDoc.onDragStart });
@@ -254,7 +243,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const de = new DragManager.DocumentDragData([topDoc]);
         de.dragDivName = topDocView.props.dragDivName;
         de.moveDocument = topDocView.props.moveDocument;
-        undoBatch(action(() => topDoc.z = topDoc.z ? 0 : 1))();
         setTimeout(() => {
             const newDocView = DocumentManager.Instance.getDocumentView(topDoc);
             if (newDocView) {
@@ -263,6 +251,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 DragManager.StartDocumentDrag([contentDiv], de, x, y, { offsetX: x - xf.left, offsetY: y - xf.top, hideSource: true });
             }
         }, 0);
+        UndoManager.RunInBatch(action(() => topDoc.z = topDoc.z ? 0 : 1), "float");
     }
 
     onKeyDown = (e: React.KeyboardEvent) => {
@@ -301,41 +290,39 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                         const func = () => this.onDoubleClickHandler.script.run({
                             this: this.layoutDoc,
                             self: this.rootDoc,
-                            thisContainer: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey
+                            thisContainer: this.props.ContainingCollectionDoc,
+                            shiftKey: e.shiftKey
                         }, console.log);
                         func();
                     } else {
                         UndoManager.RunInBatch(() => {
+                            let fullScreenDoc = this.props.Document;
                             if (StrCast(this.props.Document.layoutKey) !== "layout_fullScreen" && this.props.Document.layout_fullScreen) {
-                                const fullScreenAlias = Doc.MakeAlias(this.props.Document);
-                                fullScreenAlias.layoutKey = "layout_fullScreen";
-                                this.props.addDocTab(fullScreenAlias, "inTab");
-                            } else {
-                                this.props.addDocTab(this.props.Document, "inTab");
+                                fullScreenDoc = Doc.MakeAlias(this.props.Document);
+                                fullScreenDoc.layoutKey = "layout_fullScreen";
                             }
+                            this.props.addDocTab(fullScreenDoc, "inTab");
                         }, "double tap");
                         SelectionManager.DeselectAll();
                         Doc.UnBrushDoc(this.props.Document);
                     }
                 }
             } else if (this.onClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
-                //SelectionManager.DeselectAll();
                 const func = () => this.onClickHandler.script.run({
                     this: this.layoutDoc,
                     self: this.rootDoc,
-                    thisContainer: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey
+                    thisContainer: this.props.ContainingCollectionDoc,
+                    shiftKey: e.shiftKey
                 }, console.log);
                 if (this.props.Document !== Doc.UserDoc()["dockedBtn-undo"] && this.props.Document !== Doc.UserDoc()["dockedBtn-redo"]) {
                     UndoManager.RunInBatch(func, "on click");
                 } else func();
             } else if (this.Document["onClick-rawScript"] && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) {// bcz: hack? don't edit a script if you're clicking on a scripting box itself
-                const alias = Doc.MakeAlias(this.props.Document);
-                DocUtils.makeCustomViewClicked(alias, undefined, "onClick");
-                this.props.addDocTab(alias, "onRight");
+                this.props.addDocTab(DocUtils.makeCustomViewClicked(Doc.MakeAlias(this.props.Document), undefined, "onClick"), "onRight");
             } else if (this.allLinks && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
                 this.allLinks.length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey);
             } else {
-                if ((this.layoutDoc.onDragStart || (this.props.Document.rootDocument)) && !(e.ctrlKey || e.button > 0)) {  // onDragStart implies a button doc that we don't want to select when clicking.   RootDocument & isTEmplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
+                if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) {  // onDragStart implies a button doc that we don't want to select when clicking.   RootDocument & isTemplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
                     stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
                 } else {
                     SelectionManager.SelectDoc(this, e.ctrlKey || e.shiftKey);
@@ -407,7 +394,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             }
             e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
             e.preventDefault();
-
         }
     }
 
@@ -445,12 +431,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const oldPoint2 = this.prevPoints.get(pt2.identifier);
         const pinching = InteractionUtils.Pinning(pt1, pt2, oldPoint1!, oldPoint2!);
         if (pinching !== 0 && oldPoint1 && oldPoint2) {
-            // let dX = (Math.min(pt1.clientX, pt2.clientX) - Math.min(oldPoint1.clientX, oldPoint2.clientX));
-            // let dY = (Math.min(pt1.clientY, pt2.clientY) - Math.min(oldPoint1.clientY, oldPoint2.clientY));
-            // let dX = Math.sign(Math.abs(pt1.clientX - oldPoint1.clientX) - Math.abs(pt2.clientX - oldPoint2.clientX));
-            // let dY = Math.sign(Math.abs(pt1.clientY - oldPoint1.clientY) - Math.abs(pt2.clientY - oldPoint2.clientY));
-            // let dW = -dX;
-            // let dH = -dY;
             const dW = (Math.abs(pt1.clientX - pt2.clientX) - Math.abs(oldPoint1.clientX - oldPoint2.clientX));
             const dH = (Math.abs(pt1.clientY - pt2.clientY) - Math.abs(oldPoint1.clientY - oldPoint2.clientY));
             const dX = -1 * Math.sign(dW);
@@ -533,7 +513,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     onPointerMove = (e: PointerEvent): void => {
-
         if ((e as any).formattedHandled) { e.stopPropagation(); return; }
         if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) return;
         if (e.cancelBubble && this.active) {
@@ -554,17 +533,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     onPointerUp = (e: PointerEvent): void => {
         this.cleanUpInteractions();
+        document.removeEventListener("pointermove", this.onPointerMove);
+        document.removeEventListener("pointerup", this.onPointerUp);
 
         if (this.onPointerUpHandler?.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
             this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log);
-            document.removeEventListener("pointerup", this.onPointerUp);
-            return;
+        } else {
+            this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
+            this._lastTap = Date.now();
         }
-
-        document.removeEventListener("pointermove", this.onPointerMove);
-        document.removeEventListener("pointerup", this.onPointerUp);
-        this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
-        this._lastTap = Date.now();
     }
 
     onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
@@ -586,54 +563,20 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
-    @undoBatch
-    togglePushpin = () => {
+    @undoBatch @action
+    toggleFollowLink = (location: Opt<string>, zoom: boolean, setPushpin: boolean): void => {
         this.Document.ignoreClick = false;
-        if (this.Document.isLinkButton || this.Document.isPushpin || this.onClickHandler || this.Document.ignoreClick) {
-            this.Document.isPushpin = this.Document.isLinkButton = false;
-            this.Document.onClick = this.layoutDoc.onClick = undefined;
+        this.Document.isLinkButton = !this.Document.isLinkButton;
+        setPushpin && (this.Document.isPushpin = this.Document.isLinkButton);
+        if (this.Document.isLinkButton && !this.onClickHandler) {
+            this.Document.followLinkZoom = zoom;
+            this.Document.followLinkLocation = location;
         } else {
-            this.Document.isPushpin = this.Document.isLinkButton = true;
-            this.Document.followLinkZoom = false;
-            this.Document.followLinkLocation = undefined;
-        }
-    }
-
-    @undoBatch
-    toggleLinkButtonBehavior = (): void => {
-        this.Document.ignoreClick = false;
-        if (this.Document.isLinkButton || this.onClickHandler || this.Document.ignoreClick) {
-            this.Document.isLinkButton = false;
             this.Document.onClick = this.layoutDoc.onClick = undefined;
-        } else {
-            this.Document.isLinkButton = true;
-            this.Document.followLinkZoom = false;
-            this.Document.followLinkLocation = undefined;
-        }
-    }
-
-    @undoBatch
-    toggleFollowInPlace = (): void => {
-        this.Document.ignoreClick = false;
-        this.Document.isLinkButton = !this.Document.isLinkButton;
-        if (this.Document.isLinkButton) {
-            this.Document.followLinkZoom = true;
-            this.Document.followLinkLocation = "inPlace";
         }
     }
 
-    @undoBatch
-    toggleFollowOnRight = (): void => {
-        this.Document.ignoreClick = false;
-        this.Document.isLinkButton = !this.Document.isLinkButton;
-        if (this.Document.isLinkButton) {
-            this.Document.followLinkZoom = false;
-            this.Document.followLinkLocation = "onRight";
-        }
-    }
-
-    @undoBatch
-    @action
+    @undoBatch @action
     drop = async (e: Event, de: DragManager.DropEvent) => {
         if (this.props.Document === Doc.UserDoc().activeWorkspace) {
             alert("linking to document tabs not yet supported.  Drop link on document content.");
@@ -663,11 +606,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
         if (de.complete.linkDragData) {
             e.stopPropagation();
-            if (de.complete.linkDragData.linkSourceDocument !== this.props.Document) {
-                const linkDoc = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument },
-                    { doc: this.props.Document }, `link`);
-                de.complete.linkDragData.linkSourceDocument !== this.props.Document &&
-                    (de.complete.linkDragData.linkDocument = linkDoc); // TODODO this is where in text links get passed
+            const linkSource = de.complete.linkDragData.linkSourceDocument;
+            if (linkSource !== this.props.Document) {
+                const linkDoc = DocUtils.MakeLink({ doc: linkSource }, { doc: this.props.Document }, `link`);
+                linkSource !== this.props.Document && (de.complete.linkDragData.linkDocument = linkDoc); // TODODO this is where in text links get passed
                 linkDoc && makeLink(linkDoc);
             }
 
@@ -680,6 +622,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.PanelWidth(), this.props.PanelHeight());
     }
 
+    @undoBatch
+    @action
+    toggleLockPosition = (): void => {
+        this.Document.lockedPosition = this.Document.lockedPosition ? undefined : true;
+    }
+
     @undoBatch
     @action
     makeIntoPortal = async () => {
@@ -694,9 +642,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     @undoBatch
     @action
-    toggleBackground = (temporary: boolean): void => {
-        this.Document._overflow = temporary ? "visible" : "hidden";
-        this.Document.isBackground = !temporary ? !this.Document.isBackground : (this.Document.isBackground ? undefined : true);
+    toggleBackground = () => {
+        this.Document.isBackground = (this.Document.isBackground ? undefined : true);
+        this.Document._overflow = this.Document.isBackground ? "visible" : undefined;
         if (this.Document.isBackground) {
             this.props.bringToFront(this.props.Document, true);
             this.props.Document[DataSym][Doc.LayoutFieldKey(this.Document) + "-nativeWidth"] = this.Document[WidthSym]();
@@ -704,41 +652,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
-    @undoBatch
-    @action
-    toggleLockPosition = (): void => {
-        this.Document.lockedPosition = this.Document.lockedPosition ? undefined : true;
-    }
-
-    @undoBatch
-    @action
-    setAcl = (acl: SharingPermissions) => {
-        this.dataDoc.ACL = this.props.Document.ACL = acl;
-        DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => {
-            if (d.author === Doc.CurrentUserEmail) d.ACL = acl;
-            const data = d[DataSym];
-            if (data && data.author === Doc.CurrentUserEmail) data.ACL = acl;
-        });
-    }
-
-    @undoBatch
-    @action
-    testAcl = (acl: SharingPermissions) => {
-        this.dataDoc.author = this.props.Document.author = "ADMIN";
-        this.dataDoc.ACL = this.props.Document.ACL = acl;
-        DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => {
-            if (d.author === Doc.CurrentUserEmail) {
-                d.author = "ADMIN";
-                d.ACL = acl;
-            }
-            const data = d[DataSym];
-            if (data && data.author === Doc.CurrentUserEmail) {
-                data.author = "ADMIN";
-                data.ACL = acl;
-            }
-        });
-    }
-
     @action
     onContextMenu = async (e: React.MouseEvent | Touch): Promise<void> => {
         // the touch onContextMenu is button 0, the pointer onContextMenu is button 2
@@ -785,10 +698,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
         onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.Document.layoutKey}")`), icon: "window-restore" });
         onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
-        onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: this.toggleFollowInPlace, icon: "concierge-bell" });
-        onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link on Right", event: this.toggleFollowOnRight, icon: "concierge-bell" });
-        onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: this.toggleLinkButtonBehavior, icon: "concierge-bell" });
-        onClicks.push({ description: (this.Document.isPushpin ? "Remove" : "Make") + " Pushpin", event: this.togglePushpin, icon: "snowflake" });
+        onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: () => this.toggleFollowLink("inPlace", true, false), icon: "concierge-bell" });
+        !this.Document.isLinkButton && onClicks.push({ description: "Follow Link on Right", event: () => this.toggleFollowLink("onRight", false, false), icon: "concierge-bell" });
+        onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: () => this.toggleFollowLink(undefined, false, false), icon: "concierge-bell" });
+        onClicks.push({ description: (this.Document.isPushpin ? "Remove" : "Make") + " Pushpin", event: () => this.toggleFollowLink(undefined, false, true), icon: "snowflake" });
         onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" });
         !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, addDivider: true, subitems: onClicks, icon: "hand-point-right" });
 
@@ -802,16 +715,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
         const more = cm.findByDescription("More...");
         const moreItems = more && "subitems" in more ? more.subitems : [];
-        moreItems.push({
-            description: "Download document", icon: "download", event: async () => {
-                Doc.Zip(this.props.Document);
-                // const a = document.createElement("a");
-                // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
-                // a.href = url;
-                // a.download = `DocExport-${this.props.Document[Id]}.zip`;
-                // a.click();
-            }
-        });
+        moreItems.push({ description: "Download document", icon: "download", event: async () => Doc.Zip(this.props.Document) });
         if (!Doc.UserDoc().noviceMode) {
             moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
             moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
@@ -836,179 +740,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" });
         cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" });
 
-        // const existingAcls = cm.findByDescription("Privacy...");
-        // const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : [];
-        // aclItems.push({ description: "Make Add Only", event: () => this.setAcl(SharingPermissions.Add), icon: "concierge-bell" });
-        // aclItems.push({ description: "Make Read Only", event: () => this.setAcl(SharingPermissions.View), icon: "concierge-bell" });
-        // aclItems.push({ description: "Make Private", event: () => this.setAcl(SharingPermissions.None), icon: "concierge-bell" });
-        // aclItems.push({ description: "Make Editable", event: () => this.setAcl(SharingPermissions.Edit), icon: "concierge-bell" });
-        // aclItems.push({ description: "Test Private", event: () => this.testAcl(SharingPermissions.None), icon: "concierge-bell" });
-        // aclItems.push({ description: "Test Readonly", event: () => this.testAcl(SharingPermissions.View), icon: "concierge-bell" });
-        // !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
-
-        // cm.addItem({ description: `${getPlaygroundMode() ? "Disable" : "Enable"} playground mode`, event: togglePlaygroundMode, icon: "concierge-bell" });
-
-        // const recommender_subitems: ContextMenuProps[] = [];
-
-        // recommender_subitems.push({
-        //     description: "Internal recommendations",
-        //     event: () => this.recommender(),
-        //     icon: "brain"
-        // });
-
-        // const ext_recommender_subitems: ContextMenuProps[] = [];
-
-        // ext_recommender_subitems.push({
-        //     description: "arXiv",
-        //     event: () => this.externalRecommendation("arxiv"),
-        //     icon: "brain"
-        // });
-        // ext_recommender_subitems.push({
-        //     description: "Bing",
-        //     event: () => this.externalRecommendation("bing"),
-        //     icon: "brain"
-        // });
-
-        // recommender_subitems.push({
-        //     description: "External recommendations",
-        //     subitems: ext_recommender_subitems,
-        //     icon: "brain"
-        // });
-
-
-        //moreItems.push({ description: "Recommender System", subitems: recommender_subitems, icon: "brain" });
-        //moreItems.push({ description: "Publish", event: () => DocUtils.Publish(this.props.Document, this.Document.title || "", this.props.addDocument, this.props.removeDocument), icon: "file" });
-        //moreItems.push({ description: "Undo Debug Test", event: () => UndoManager.TraceOpenBatches(), icon: "exclamation" });
-
-        // runInAction(() => {
-        //     const setWriteMode = (mode: DocServer.WriteMode) => {
-        //         DocServer.AclsMode = mode;
-        //         const mode1 = mode;
-        //         const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
-        //         DocServer.setFieldWriteMode("x", mode1);
-        //         DocServer.setFieldWriteMode("y", mode1);
-        //         DocServer.setFieldWriteMode("_width", mode1);
-        //         DocServer.setFieldWriteMode("_height", mode1);
-
-        //         DocServer.setFieldWriteMode("_panX", mode2);
-        //         DocServer.setFieldWriteMode("_panY", mode2);
-        //         DocServer.setFieldWriteMode("scale", mode2);
-        //         DocServer.setFieldWriteMode("_viewType", mode2);
-        //     };
-        //     const aclsMenu: ContextMenuProps[] = [];
-        //     aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" });
-        //     aclsMenu.push({ description: "Playground (write own/no read)", event: () => setWriteMode(DocServer.WriteMode.Playground), icon: DocServer.AclsMode === DocServer.WriteMode.Playground ? "check" : "exclamation" });
-        //     aclsMenu.push({ description: "Live Playground (write own/read others)", event: () => setWriteMode(DocServer.WriteMode.LivePlayground), icon: DocServer.AclsMode === DocServer.WriteMode.LivePlayground ? "check" : "exclamation" });
-        //     aclsMenu.push({ description: "Live Readonly (no write/read others)", event: () => setWriteMode(DocServer.WriteMode.LiveReadonly), icon: DocServer.AclsMode === DocServer.WriteMode.LiveReadonly ? "check" : "exclamation" });
-        //     cm.addItem({ description: "Collaboration ...", subitems: aclsMenu, icon: "share" });
-        // });
         runInAction(() => {
             if (!this.topMost && !(e instanceof Touch)) {
-                // DocumentViews should stop propagation of this event
-                e.stopPropagation();
+                e.stopPropagation(); // DocumentViews should stop propagation of this event
             }
             cm.displayMenu(e.pageX - 15, e.pageY - 15);
             !SelectionManager.IsSelected(this, true) && SelectionManager.SelectDoc(this, false);
         });
-        const path = this.props.LibraryPath.reduce((p: string, d: Doc) => p + "/" + (Doc.AreProtosEqual(d, (Doc.UserDoc()["tabs-button-library"] as Doc).sourcePanel as Doc) ? "" : d.title), "");
-        const item = ({
-            description: `path: ${path}`, event: () => {
-                if (this.props.LibraryPath !== emptyPath) {
-                    this.props.LibraryPath.map(lp => Doc.GetProto(lp).treeViewOpen = lp.treeViewOpen = true);
-                    Doc.linkFollowHighlight(this.props.Document);
-                } else {
-                    Doc.AddDocToList(Doc.GetProto(Doc.UserDoc().myCatalog as Doc), "data", this.props.Document[DataSym]);
-                }
-            }, icon: "check"
-        });
-        //cm.addItem(item);
-    }
-
-    recommender = async () => {
-        if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" });
-        const documents: Doc[] = [];
-        const allDocs = await SearchUtil.GetAllDocs();
-        // clears internal representation of documents as vectors
-        ClientRecommender.Instance.reset_docs();
-        //ClientRecommender.Instance.arxivrequest("electrons");
-        await Promise.all(allDocs.map((doc: Doc) => {
-            let isMainDoc: boolean = false;
-            const dataDoc = Doc.GetProto(doc);
-            if (doc.type === DocumentType.RTF) {
-                if (dataDoc === Doc.GetProto(this.props.Document)) {
-                    isMainDoc = true;
-                }
-                if (!documents.includes(dataDoc)) {
-                    documents.push(dataDoc);
-                    const extdoc = doc.data_ext as Doc;
-                    return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, true, "", isMainDoc);
-                }
-            }
-            if (doc.type === DocumentType.IMG) {
-                if (dataDoc === Doc.GetProto(this.props.Document)) {
-                    isMainDoc = true;
-                }
-                if (!documents.includes(dataDoc)) {
-                    documents.push(dataDoc);
-                    const extdoc = doc.data_ext as Doc;
-                    return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, true, "", isMainDoc, true);
-                }
-            }
-        }));
-        const doclist = ClientRecommender.Instance.computeSimilarities("cosine");
-        const recDocs: { preview: Doc, score: number }[] = [];
-        // tslint:disable-next-line: prefer-for-of
-        for (let i = 0; i < doclist.length; i++) {
-            recDocs.push({ preview: doclist[i].actualDoc, score: doclist[i].score });
-        }
-
-        const data = recDocs.map(unit => {
-            unit.preview.score = unit.score;
-            return unit.preview;
-        });
-
-        console.log(recDocs.map(doc => doc.score));
-
-        const title = `Showing ${data.length} recommendations for "${StrCast(this.props.Document.title)}"`;
-        const recommendations = Docs.Create.RecommendationsDocument(data, { title });
-        recommendations.documentIconHeight = 150;
-        recommendations.sourceDoc = this.props.Document;
-        recommendations.sourceDocContext = this.props.ContainingCollectionView!.props.Document;
-        this.props.addDocTab(recommendations, "onRight");
-
-        // RecommendationsBox.Instance.displayRecommendations(e.pageX + 100, e.pageY);
-    }
-
-    @action
-    externalRecommendation = async (api: string) => {
-        if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" });
-        ClientRecommender.Instance.reset_docs();
-        const doc = Doc.GetDataDoc(this.props.Document);
-        const extdoc = doc.data_ext as Doc;
-        const recs_and_kps = await ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, false, api);
-        let recs: any;
-        let kps: any;
-        if (recs_and_kps) {
-            recs = recs_and_kps.recs;
-            kps = recs_and_kps.keyterms;
-        }
-        else {
-            console.log("recommender system failed :(");
-            return;
-        }
-        console.log("ibm keyterms: ", kps.toString());
-        const headers = [new SchemaHeaderField("title"), new SchemaHeaderField("href")];
-        const bodies: Doc[] = [];
-        const titles = recs.title_vals;
-        const urls = recs.url_vals;
-        for (let i = 0; i < 5; i++) {
-            const body = Docs.Create.FreeformDocument([], { title: titles[i] });
-            body.href = urls[i];
-            bodies.push(body);
-        }
-        this.props.addDocTab(Docs.Create.SchemaDocument(headers, bodies, { title: `Showing External Recommendations for "${StrCast(doc.title)}"` }), "onRight");
-        this._showKPQuery = true;
-        this._queries = kps.toString();
     }
 
     // does Document set a layout prop
@@ -1088,7 +826,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     // used to decide whether a link anchor view should be created or not.
-    // if it's a tempoarl link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here.
+    // if it's a temporal link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here.
     // would be good to generalize this some way.
     isNonTemporalLink = (linkDoc: Doc) => {
         const anchor = Cast(Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1 : linkDoc.anchor2, Doc) as Doc;
@@ -1096,7 +834,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         return anchor.type === DocumentType.AUDIO && NumCast(ept) ? false : true;
     }
 
-
     @observable _link: Opt<Doc>;  // see DocumentButtonBar for explanation of how this works
     makeLink = () => this._link; // pass the link placeholde to child views so they can react to make a specialized anchor.  This is essentially a function call to the descendants since the value of the _link variable will immediately get set back to undefined.
 
@@ -1186,7 +923,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             DocUtils.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined);
         }
     }
-    @observable _animateScalingTo = 0;
+
     switchViews = action((custom: boolean, view: string) => {
         this._animateScalingTo = 0.1;  // shrink doc
         setTimeout(action(() => {
@@ -1200,7 +937,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         return (this.Document.isBackground !== undefined || this.isSelected(false)) &&
             ((this.Document.type === DocumentType.COL && this.Document._viewType !== CollectionViewType.Pile) || this.Document.type === DocumentType.IMG) &&
             this.props.renderDepth > 0 && !this.props.treeViewDoc ?
-            <div className="documentView-lock" onClick={() => this.toggleBackground(true)}>
+            <div className="documentView-lock" onClick={this.toggleBackground}>
                 <FontAwesomeIcon icon={this.Document.isBackground ? "unlock" : "lock"} style={{ color: this.Document.isBackground ? "red" : undefined }} size="lg" />
             </div>
             : (null);
@@ -1224,7 +961,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"];
         let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc._viewType !== CollectionViewType.Linear && this.props.Document.type !== DocumentType.INK;
         highlighting = highlighting && this.props.focus !== emptyFunction;  // bcz: hack to turn off highlighting onsidebar panel documents.  need to flag a document as not highlightable in a more direct way
-        return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
+        const topmost = this.topMost ? "-topmost" : "";
+        return <div className={`documentView-node${topmost}`}
             id={this.props.Document[Id]}
             ref={this._mainCont} onKeyDown={this.onKeyDown}
             onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
@@ -1266,7 +1004,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 this.innards}
             {this.renderLock()}
         </div>;
-        { this._showKPQuery ? <KeyphraseQueryView keyphrases={this._queries}></KeyphraseQueryView> : undefined; }
     }
 }
 
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 5f689624c..d668d332b 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -66,7 +66,7 @@ const uploadIcons = {
 
 @observer
 export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageDocument>(ImageDocument) {
-    protected multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
+    protected _multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); }
     private _imgRef: React.RefObject<HTMLImageElement> = React.createRef();
     private _dropDisposer?: DragManager.DragDropDisposer;
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index bc43cd473..1a5edc1d9 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -32,7 +32,7 @@ const ScriptingDocument = makeInterface(ScriptingSchema, documentSchema);
 export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps, ScriptingDocument>(ScriptingDocument) {
 
     private dropDisposer?: DragManager.DragDropDisposer;
-    protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer | undefined;
+    protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer | undefined;
     public static LayoutString(fieldStr: string) { return FieldView.LayoutString(ScriptingBox, fieldStr); }
     private _overlayDisposer?: () => void;
     private _caretPos = 0;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 87acb2ea7..267defa4b 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -587,6 +587,11 @@ export namespace Doc {
     }
 
     export async function Zip(doc: Doc) {
+        // const a = document.createElement("a");
+        // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
+        // a.href = url;
+        // a.download = `DocExport-${this.props.Document[Id]}.zip`;
+        // a.click();
         const { clone, map } = await Doc.MakeClone(doc, true);
         function replacer(key: any, value: any) {
             if (["cloneOf", "context", "cursors"].includes(key)) return undefined;
-- 
cgit v1.2.3-70-g09d2


From f1f370c43de775f27c08b52bf3b6de310c6d2cda Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 30 Jul 2020 11:04:58 -0400
Subject: added "float" option to the main toolbar for documentviews

---
 src/client/views/MainView.tsx                   |  4 ++--
 src/client/views/collections/CollectionMenu.tsx | 10 +++++++++-
 2 files changed, 11 insertions(+), 3 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 37d99fb16..bb73df75c 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,5 +1,5 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faHireAHelper } from '@fortawesome/free-brands-svg-icons';
+import { faHireAHelper, faBuffer } from '@fortawesome/free-brands-svg-icons';
 import * as fa from '@fortawesome/free-solid-svg-icons';
 import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -148,7 +148,7 @@ export class MainView extends React.Component {
             fa.faEye, fa.faArrowsAlt, fa.faQuoteLeft, fa.faSortAmountDown, fa.faAlignLeft, fa.faAlignCenter, fa.faAlignRight, fa.faHeading, fa.faRulerCombined,
             fa.faFillDrip, fa.faLink, fa.faUnlink, fa.faBold, fa.faItalic, fa.faChevronLeft, fa.faUnderline, fa.faStrikethrough, fa.faSuperscript, fa.faSubscript,
             fa.faIndent, fa.faEyeDropper, fa.faPaintRoller, fa.faBars, fa.faBrush, fa.faShapes, fa.faEllipsisH, fa.faHandPaper, fa.faMap, fa.faUser, faHireAHelper,
-            fa.faBezierCurve, fa.faCircle, fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight);
+            fa.faBezierCurve, fa.faCircle, fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight, faBuffer);
         this.initEventListeners();
         this.initAuthenticationRouters();
     }
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 8813861d2..da4c131cb 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -27,6 +27,7 @@ import { ColorState } from "react-color";
 import { ObjectField } from "../../../fields/ObjectField";
 import { ScriptField } from "../../../fields/ScriptField";
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { DocUtils } from "../../documents/Documents";
 
 @observer
 export default class CollectionMenu extends AntimodeMenu {
@@ -540,7 +541,14 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
                 <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
             </div>
 
-            {!this.props.isOverlay ? (null) :
+            {<button className={"antimodeMenu-button"} key="float"
+                style={{ backgroundColor: !this.props.docView.layoutDoc.isAnnotating ? "121212" : undefined, borderRight: "1px solid gray" }}
+                title="Toggle Overlay Layer"
+                onClick={() => DocumentView.FloatDoc(this.props.docView)}>
+                <FontAwesomeIcon icon={["fab", "buffer"]} size={"lg"} />
+            </button>}
+            
+            {!this.props.isOverlay || this.document.type !== DocumentType.WEB ? (null) :
                 <button className={"antimodeMenu-button"} key="hypothesis"
                     style={{ backgroundColor: !this.props.docView.layoutDoc.isAnnotating ? "121212" : undefined, borderRight: "1px solid gray" }}
                     title="Use Hypothesis"
-- 
cgit v1.2.3-70-g09d2

