From c5c2c309cd88bbeb2f1b668cb040cffaac9c8470 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 4 Apr 2023 20:12:16 -0400
Subject: fixed using freeformview in sidebar of pdfs. fixed issues with
 dragging items out of schema and with selecting the schema view by clicking
 on headers.  Fixed a lot of errors caused by using OmitKeys which masks type
 checking.  fixed some pointerevent problems with treeview and freeformview

---
 src/client/views/nodes/DocumentView.tsx | 26 ++++----------------------
 1 file changed, 4 insertions(+), 22 deletions(-)

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

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 091a6b050..c7bf37a45 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -12,7 +12,7 @@ import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { AudioField } from '../../../fields/URLField';
 import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
-import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, OmitKeys, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils';
+import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils';
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { DocServer } from '../../DocServer';
 import { Docs, DocUtils } from '../../documents/Documents';
@@ -512,7 +512,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 this.props.isDocumentActive?.() &&
                 !this.props.onBrowseClick?.() &&
                 !this.Document.ignoreClick &&
-                !e.ctrlKey &&
                 e.button === 0 &&
                 this.pointerEvents !== 'none' &&
                 !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)
@@ -705,22 +704,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             const appearance = cm.findByDescription('UI Controls...');
             const appearanceItems: ContextMenuProps[] = appearance && 'subitems' in appearance ? appearance.subitems : [];
             !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template   ', event: () => this.props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' });
-            !Doc.noviceMode &&
-                appearanceItems.push({
-                    description: 'Add a Field',
-                    event: () => {
-                        const alias = Doc.MakeAlias(this.rootDoc);
-                        alias.layout = FormattedTextBox.LayoutString('newfield');
-                        alias.title = 'newfield';
-                        alias._height = 35;
-                        alias._width = 100;
-                        alias.syncLayoutFieldWithTitle = true;
-                        alias.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc.width);
-                        alias.y = NumCast(this.rootDoc.y);
-                        this.props.addDocument?.(alias);
-                    },
-                    icon: 'eye',
-                });
             LinkManager.Links(this.Document).length &&
                 appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? 'Show' : 'Hide'} Link Button`, event: action(() => (this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton)), icon: 'eye' });
             !appearance && cm.addItem({ description: 'UI Controls...', subitems: appearanceItems, icon: 'compass' });
@@ -863,7 +846,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler);
     setHeight = (height: number) => (this.layoutDoc._height = height);
     setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view));
-    isContentActive = (outsideReaction?: boolean) => {
+    isContentActive = (outsideReaction?: boolean): boolean | undefined => {
         return this.props.isContentActive() === false
             ? false
             : Doc.ActiveTool !== InkTool.None ||
@@ -1064,7 +1047,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
     };
 
-    captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps>, property: string) => this.props?.styleProvider?.(doc, props, property + ':caption');
+    captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => this.props?.styleProvider?.(doc, props, property + ':caption');
     @computed get innards() {
         TraceMobx();
         const ffscale = () => this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.ScreenToLocalTransform().Scale || 1;
@@ -1079,7 +1062,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     maxHeight: `max(100%, ${20 * ffscale()}px)`,
                 }}>
                 <FormattedTextBox
-                    {...OmitKeys(this.props, ['children']).omit}
+                    {...this.props}
                     yPadding={10}
                     xPadding={10}
                     fieldKey={this.showCaption}
@@ -1090,7 +1073,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     dontScale={true}
                     renderDepth={this.props.renderDepth}
                     isContentActive={this.isContentActive}
-                    onClick={this.onClickFunc}
                 />
             </div>
         );
-- 
cgit v1.2.3-70-g09d2


From e4b76f6c1dee63c49408c48d7b2bc98e62dc813a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 5 Apr 2023 12:19:01 -0400
Subject: adjusted general context menu items to always appear last.  cleaned
 up makeLink api,

---
 src/client/documents/Documents.ts                  | 32 +++++-----
 src/client/util/DragManager.ts                     |  1 -
 src/client/util/LinkFollower.ts                    |  2 -
 src/client/views/ContextMenu.tsx                   | 12 ++--
 src/client/views/DocumentButtonBar.tsx             |  4 +-
 src/client/views/MainView.tsx                      |  1 +
 src/client/views/SidebarAnnos.tsx                  |  2 +-
 .../views/collections/CollectionNoteTakingView.tsx |  2 +-
 .../views/collections/CollectionStackingView.tsx   | 28 ++++-----
 src/client/views/collections/CollectionSubView.tsx |  2 +-
 src/client/views/collections/TreeView.tsx          |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 26 ++------
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 70 +++++++++++++++-------
 src/client/views/nodes/ImageBox.tsx                |  2 +-
 src/client/views/nodes/PDFBox.tsx                  | 31 +++++-----
 src/client/views/nodes/VideoBox.tsx                |  6 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  4 +-
 .../views/nodes/formattedText/RichTextRules.ts     |  2 +-
 src/client/views/search/SearchBox.tsx              |  2 +-
 21 files changed, 117 insertions(+), 118 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 9cb480c4a..5623ca218 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -470,7 +470,7 @@ export namespace Docs {
                 DocumentType.AUDIO,
                 {
                     layout: { view: AudioBox, dataField: defaultDataKey },
-                    options: { _height: 100, forceReflow: true, nativeDimModifiable: true },
+                    options: { _height: 100, fitWidth: true, forceReflow: true, nativeDimModifiable: true },
                 },
             ],
             [
@@ -934,13 +934,13 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey);
         }
 
-        export function LinkDocument(source: { doc: Doc; ctx?: Doc }, target: { doc: Doc; ctx?: Doc }, options: DocumentOptions = {}, id?: string) {
+        export function LinkDocument(source: Doc, target: Doc, options: DocumentOptions = {}, id?: string) {
             const linkDoc = InstanceFromProto(
                 Prototypes.get(DocumentType.LINK),
                 undefined,
                 {
-                    anchor1: source.doc,
-                    anchor2: target.doc,
+                    anchor1: source,
+                    anchor2: target,
                     ...options,
                 },
                 id
@@ -1294,16 +1294,14 @@ export namespace DocUtils {
         broadcastEvent && runInAction(() => (DocumentManager.Instance.RecordingEvent = DocumentManager.Instance.RecordingEvent + 1));
         return DocUtils.ActiveRecordings.map(audio => {
             const sourceDoc = getSourceDoc();
-            const link = sourceDoc && DocUtils.MakeLink({ doc: sourceDoc }, { doc: audio.getAnchor(true) || audio.props.Document }, 'recording annotation:linked recording', 'recording timeline');
-            link && (link.followLinkLocation = OpenWhere.addRight);
-            return link;
+            return sourceDoc && DocUtils.MakeLink(sourceDoc, audio.getAnchor(true) || audio.props.Document, { linkDisplay: false, linkRelationship: 'recording annotation:linked recording', description: 'recording timeline' });
         });
     }
 
-    export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = '', description: string = '', id?: string, allowParCollectionLink?: boolean, showPopup?: number[]) {
-        if (!linkRelationship) linkRelationship = target.doc.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link';
-        const sv = DocumentManager.Instance.getDocumentView(source.doc);
-        if (!allowParCollectionLink && sv?.props.ContainingCollectionDoc === target.doc) return;
+    export function MakeLink(source: Doc, target: Doc, linkSettings: { linkRelationship?: string; description?: string; linkDisplay?: boolean }, id?: string, allowParCollectionLink?: boolean, showPopup?: number[]) {
+        if (!linkSettings.linkRelationship) linkSettings.linkRelationship = target.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link';
+        const sv = DocumentManager.Instance.getDocumentView(source);
+        if (!allowParCollectionLink && sv?.props.ContainingCollectionDoc === target) return;
         if (target.doc === Doc.UserDoc()) return undefined;
 
         const makeLink = action((linkDoc: Doc, showPopup?: number[]) => {
@@ -1343,16 +1341,16 @@ export namespace DocUtils {
                 target,
                 {
                     title: ComputedField.MakeFunction('generateLinkTitle(self)') as any,
-                    'anchor1-useLinkSmallAnchor': source.doc.useLinkSmallAnchor ? true : undefined,
-                    'anchor2-useLinkSmallAnchor': target.doc.useLinkSmallAnchor ? true : undefined,
+                    'anchor1-useLinkSmallAnchor': source.useLinkSmallAnchor ? true : undefined,
+                    'anchor2-useLinkSmallAnchor': target.useLinkSmallAnchor ? true : undefined,
                     'acl-Public': SharingPermissions.Augment,
                     '_acl-Public': SharingPermissions.Augment,
-                    linkDisplay: true,
+                    linkDisplay: linkSettings.linkDisplay,
                     _linkAutoMove: true,
-                    linkRelationship,
+                    linkRelationship: linkSettings.linkRelationship,
                     _showCaption: 'description',
                     _showTitle: 'linkRelationship',
-                    description,
+                    description: linkSettings.description,
                 },
                 id
             ),
@@ -1700,7 +1698,7 @@ export namespace DocUtils {
                 _timecodeToShow: Cast(doc._timecodeToShow, 'number', null),
             });
             Doc.AddDocToList(context, annotationField, pushpin);
-            const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, 'pushpin', '');
+            const pushpinLink = DocUtils.MakeLink(pushpin, doc, { linkRelationship: 'pushpin' }, '');
             doc._timecodeToShow = undefined;
             return pushpin;
         }
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index cc116fb46..7d2aa813f 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -168,7 +168,6 @@ export namespace DragManager {
             this.dropDocCreator = dropDocCreator;
             this.offset = [0, 0];
         }
-        linkSourceDoc?: Doc;
         dropDocCreator: (annotationOn: Doc | undefined) => Doc;
         dropDocument?: Doc;
         offset: number[];
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 785018990..df61ecece 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -9,8 +9,6 @@ import { DocumentManager } from './DocumentManager';
 import { LinkManager } from './LinkManager';
 import { SelectionManager } from './SelectionManager';
 import { UndoManager } from './UndoManager';
-
-type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void;
 /*
  * link doc:
  * - anchor1: doc
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 6a530e3ae..e4c3e864b 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -102,13 +102,11 @@ export class ContextMenu extends React.Component {
         }
     }
     @action
-    moveAfter(item: ContextMenuProps, after: ContextMenuProps) {
-        if (after && this.findByDescription(after.description)) {
-            const curInd = this._items.findIndex(i => i.description === item.description);
-            this._items.splice(curInd, 1);
-            const afterInd = this._items.findIndex(i => i.description === after.description);
-            this._items.splice(afterInd + 1, 0, item);
-        }
+    moveAfter(item: ContextMenuProps, after?: ContextMenuProps) {
+        const curInd = this._items.findIndex(i => i.description === item.description);
+        this._items.splice(curInd, 1);
+        const afterInd = after && this.findByDescription(after.description) ? this._items.findIndex(i => i.description === after.description) : this._items.length;
+        this._items.splice(afterInd, 0, item);
     }
     @action
     setDefaultItem(prefix: string, item: (name: string) => void) {
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 9389fed01..3e1a0f265 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -416,7 +416,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         const targetDoc = this.view0?.props.Document;
         return !targetDoc ? null : (
             <Tooltip title={<div className="dash-tooltip">{`Open Context Menu`}</div>}>
-                <div className="documentButtonBar-icon" style={{ color: 'white', cursor: 'pointer' }} onClick={e => this.openContextMenu(e)}>
+                <div className="documentButtonBar-icon" style={{ color: 'white', cursor: 'pointer' }} onClick={this.openContextMenu}>
                     <FontAwesomeIcon className="documentdecorations-icon" icon="bars" />
                 </div>
             </Tooltip>
@@ -569,7 +569,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
             const anchor = rootView.ComponentView?.getAnchor?.(false) ?? rootDoc;
             const trail = DocCast(anchor.presTrail) ?? Doc.MakeCopy(DocCast(Doc.UserDoc().emptyTrail), true);
             if (trail !== anchor.presTrail) {
-                DocUtils.MakeLink({ doc: anchor }, { doc: trail }, 'link trail');
+                DocUtils.MakeLink(anchor, trail, { linkRelationship: 'link trail' });
                 anchor.presTrail = trail;
             }
             Doc.ActivePresentation = trail;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index befb0200d..b0888df68 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -270,6 +270,7 @@ export class MainView extends React.Component {
                 fa.faLaptopCode,
                 fa.faMale,
                 fa.faCopy,
+                fa.faHome,
                 fa.faHandPointLeft,
                 fa.faHandPointRight,
                 fa.faCompass,
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 48c0150e6..2d2b0f83e 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -74,7 +74,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
         });
         FormattedTextBox.SelectOnLoad = target[Id];
         FormattedTextBox.DontSelectInitialText = true;
-        const link = DocUtils.MakeLink({ doc: anchor }, { doc: target }, 'inline comment:comment on');
+        const link = DocUtils.MakeLink(anchor, target, { linkRelationship: 'inline comment:comment on' });
         link && (link.linkDisplay = false);
 
         const taggedContent = this.docFilters()
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 121260680..cb5be990d 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -441,7 +441,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         } else if (de.complete.linkDragData?.dragDocument.context === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) {
             const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _fitWidth: true, title: 'dropped annotation' });
             this.props.addDocument?.(source);
-            de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceGetAnchor() }, 'doc annotation', ''); // TODODO this is where in text links get passed
+            de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { linkRelationship: 'doc annotation' }); // TODODO this is where in text links get passed
             e.stopPropagation();
         } else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData);
         return false;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 64ec419ec..1e02fc9d4 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -3,14 +3,14 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { CursorProperty } from 'csstype';
 import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
+import { DataSym, Doc, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
-import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { CollectionViewType } from '../../documents/DocumentTypes';
 import { DragManager, dropActionType } from '../../util/DragManager';
@@ -460,7 +460,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         } else if (de.complete.linkDragData?.dragDocument.context === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) {
             const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _fitWidth: true, title: 'dropped annotation' });
             this.props.addDocument?.(source);
-            de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceGetAnchor() }, 'doc annotation', ''); // TODODO this is where in text links get passed
+            de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { linkRelationship: 'doc annotation' }); // TODODO this is where in text links get passed
             e.stopPropagation();
         } else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData);
         return false;
@@ -623,11 +623,13 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     onContextMenu = (e: React.MouseEvent): void => {
         // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
         if (!e.isPropagationStopped()) {
-            const subItems: ContextMenuProps[] = [];
-            subItems.push({ description: `${this.layoutDoc._columnsFill ? 'Variable Size' : 'Autosize'} Column`, event: () => (this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill), icon: 'plus' });
-            subItems.push({ description: `${this.layoutDoc._autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: 'plus' });
-            subItems.push({ description: 'Clear All', event: () => (this.dataDoc.data = new List([])), icon: 'times' });
-            ContextMenu.Instance.addItem({ description: 'Options...', subitems: subItems, icon: 'eye' });
+            const cm = ContextMenu.Instance;
+            const options = cm.findByDescription('Options...');
+            const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
+            optionItems.push({ description: `${this.layoutDoc._columnsFill ? 'Variable Size' : 'Autosize'} Column`, event: () => (this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill), icon: 'plus' });
+            optionItems.push({ description: `${this.layoutDoc._autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: 'plus' });
+            optionItems.push({ description: 'Clear All', event: () => (this.dataDoc[this.fieldKey ?? 'data'] = new List([])), icon: 'times' });
+            !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
         }
     };
 
@@ -735,14 +737,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                                 <EditableView {...editableViewProps} />
                             </div>
                         )}
-                        {/* {this.chromeHidden || !this.props.isSelected() ? (null) :
-                            <Switch
-                                onChange={this.onToggle}
-                                onClick={this.onToggle}
-                                defaultChecked={true}
-                                checkedChildren="edit"
-                                unCheckedChildren="view"
-                            />} */}
                     </div>
                 </div>
             </>
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 5100d8d67..132ed6fb6 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -335,7 +335,7 @@ export function CollectionSubView<X>(moreProps?: X) {
                                 const focusNode = iframe?.contentDocument?.getSelection()?.focusNode as any;
                                 if (focusNode) {
                                     const anchor = srcWeb?.ComponentView?.getAnchor?.(true);
-                                    anchor && DocUtils.MakeLink({ doc: htmlDoc }, { doc: anchor });
+                                    anchor && DocUtils.MakeLink(htmlDoc, anchor, {});
                                 }
                             }
                         }
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 99b7549c0..75e76019e 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -359,7 +359,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         if (de.complete.linkDragData) {
             const sourceDoc = de.complete.linkDragData.linkSourceGetAnchor();
             const destDoc = this.doc;
-            DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, 'tree link', '');
+            DocUtils.MakeLink(sourceDoc, destDoc, { linkRelationship: 'tree link' });
             e.stopPropagation();
         }
         const docDragData = de.complete.docDragData;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d39668a5d..fb0bd2a19 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -397,7 +397,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 // if the source doc view's context isn't this same freeformcollectionlinkDragData.dragDocument.context === this.props.Document
                 const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, x: xp, y: yp, title: 'dropped annotation' });
                 this.props.addDocument?.(source);
-                de.complete.linkDocument = DocUtils.MakeLink({ doc: linkDragData.linkSourceGetAnchor() }, { doc: source }, 'annotated by:annotation of', ''); // TODODO this is where in text links get passed
+                de.complete.linkDocument = DocUtils.MakeLink(linkDragData.linkSourceGetAnchor(), source, { linkRelationship: 'annotated by:annotation of' }); // TODODO this is where in text links get passed
             }
             e.stopPropagation(); //  do nothing if link is dropped into any freeform view parent of dragged document
             return true;
@@ -1693,6 +1693,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             },
             icon: 'compress-arrows-alt',
         });
+        if (this.props.setContentView === emptyFunction) {
+            !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
+            return;
+        }
         !Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: 'Reset default note style', event: () => (Doc.UserDoc().defaultTextLayout = undefined), icon: 'eye' });
         appearanceItems.push({
             description: `${this.fitContentsToBox ? 'Make Zoomable' : 'Scale to Window'}`,
@@ -1700,7 +1704,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             icon: !this.fitContentsToBox ? 'expand-arrows-alt' : 'compress-arrows-alt',
         });
         appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, { pinViewport: MarqueeView.CurViewBounds(this.rootDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' });
-        //appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" });
+        !Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: 'compress-arrows-alt' });
         appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
 
         this.props.Document._isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: () => this.transcribeStrokes(false), icon: 'font' });
@@ -1730,27 +1734,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         !options && ContextMenu.Instance.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
         const mores = ContextMenu.Instance.findByDescription('More...');
         const moreItems = mores && 'subitems' in mores ? mores.subitems : [];
-        moreItems.push({ description: 'Import exported collection', icon: 'upload', event: ({ x, y }) => this.importDocument(e.clientX, e.clientY) });
         !mores && ContextMenu.Instance.addItem({ description: 'More...', subitems: moreItems, icon: 'eye' });
     };
 
-    importDocument = (x: number, y: number) => {
-        const input = document.createElement('input');
-        input.type = 'file';
-        input.accept = '.zip';
-        input.onchange = _e => {
-            input.files &&
-                Doc.importDocument(input.files[0]).then(doc => {
-                    if (doc instanceof Doc) {
-                        const [xx, yy] = this.getTransform().transformPoint(x, y);
-                        (doc.x = xx), (doc.y = yy);
-                        this.props.addDocument?.(doc);
-                    }
-                });
-        };
-        input.click();
-    };
-
     @undoBatch
     @action
     transcribeStrokes = (math: boolean) => {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 043fe0bcf..0b4f76fa1 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -527,7 +527,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         });
         const summary = Docs.Create.TextDocument('', { backgroundColor: '#e2ad32', x: this.Bounds.left, y: this.Bounds.top, followLinkToggle: true, _width: 200, _height: 200, _fitContentsToBox: true, _showSidebar: true, title: 'overview' });
         const portal = Docs.Create.FreeformDocument(selected, { x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' });
-        DocUtils.MakeLink({ doc: summary }, { doc: portal }, 'summary of:summarized by', '');
+        DocUtils.MakeLink(summary, portal, { linkRelationship: 'summary of:summarized by' });
 
         portal.hidden = true;
         this.props.addDocument?.(portal);
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index d6679b46d..df3299eef 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -142,7 +142,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             } else if (startLink !== endLink) {
                 endLink = endLinkView?.docView?._componentView?.getAnchor?.(true, pinProps) || endLink;
                 startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.(true) || startLink;
-                const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink }, DocumentLinksButton.AnnotationId ? 'hypothes.is annotation' : undefined, undefined, undefined, true);
+                const linkDoc = DocUtils.MakeLink(startLink, endLink, { linkRelationship: DocumentLinksButton.AnnotationId ? 'hypothes.is annotation' : undefined }, undefined, true);
 
                 LinkManager.currentLink = linkDoc;
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c7bf37a45..159aa026a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -617,22 +617,24 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     @undoBatch
     @action
-    drop = async (e: Event, de: DragManager.DropEvent) => {
+    drop = (e: Event, de: DragManager.DropEvent) => {
         if (this.props.dontRegisterView || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return;
         if (this.props.Document === Doc.ActiveDashboard) {
             alert((e.target as any)?.closest?.('*.lm_content') ? "You can't perform this move most likely because you don't have permission to modify the destination." : 'Linking to document tabs not yet supported. Drop link on document content.');
             return;
         }
         const linkdrag = de.complete.annoDragData ?? de.complete.linkDragData;
-        if (linkdrag) linkdrag.linkSourceDoc = linkdrag.linkSourceGetAnchor();
-        if (linkdrag?.linkSourceDoc) {
-            e.stopPropagation();
-            if (de.complete.annoDragData && !de.complete.annoDragData.dropDocument) {
-                de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator(undefined);
-            }
-            if (de.complete.annoDragData || this.rootDoc !== linkdrag.linkSourceDoc.context) {
-                const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.(true) ?? this.props.Document;
-                de.complete.linkDocument = DocUtils.MakeLink({ doc: linkdrag.linkSourceDoc }, { doc: dropDoc }, undefined, undefined, undefined, undefined, [de.x, de.y - 50]);
+        if (linkdrag) {
+            linkdrag.linkSourceDoc = linkdrag.linkSourceGetAnchor();
+            if (linkdrag.linkSourceDoc) {
+                e.stopPropagation();
+                if (de.complete.annoDragData && !de.complete.annoDragData.dropDocument) {
+                    de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator(undefined);
+                }
+                if (de.complete.annoDragData || this.rootDoc !== linkdrag.linkSourceDoc.context) {
+                    const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.(true) ?? this.rootDoc;
+                    de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, undefined, [de.x, de.y - 50]);
+                }
             }
         }
     };
@@ -643,9 +645,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         const portalLink = this.allLinks.find(d => d.anchor1 === this.props.Document && d.linkRelationship === 'portal to:portal from');
         if (!portalLink) {
             DocUtils.MakeLink(
-                { doc: this.props.Document },
-                { doc: Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _isLightbox: true, _fitWidth: true, title: StrCast(this.props.Document.title) + ' [Portal]' }) },
-                'portal to:portal from'
+                this.props.Document,
+                Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _isLightbox: true, _fitWidth: true, title: StrCast(this.props.Document.title) + ' [Portal]' }),
+                { linkRelationship: 'portal to:portal from' }
             );
         }
         this.Document.followLinkLocation = OpenWhere.lightbox;
@@ -653,6 +655,24 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         this.Document._isLinkButton = true;
     };
 
+    importDocument = () => {
+        const input = document.createElement('input');
+        input.type = 'file';
+        input.accept = '.zip';
+        input.onchange = _e => {
+            if (input.files) {
+                const batch = UndoManager.StartBatch('importing');
+                Doc.importDocument(input.files[0]).then(doc => {
+                    if (doc instanceof Doc) {
+                        this.props.addDocTab(doc, OpenWhere.addRight);
+                        batch.end();
+                    }
+                });
+            }
+        };
+        input.click();
+    };
+
     @action
     onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => {
         if (e && this.rootDoc._hideContextMenu && Doc.noviceMode) {
@@ -704,9 +724,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             const appearance = cm.findByDescription('UI Controls...');
             const appearanceItems: ContextMenuProps[] = appearance && 'subitems' in appearance ? appearance.subitems : [];
             !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template   ', event: () => this.props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' });
-            LinkManager.Links(this.Document).length &&
-                appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? 'Show' : 'Hide'} Link Button`, event: action(() => (this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton)), icon: 'eye' });
-            !appearance && cm.addItem({ description: 'UI Controls...', subitems: appearanceItems, icon: 'compass' });
+            !appearance && appearanceItems.length && cm.addItem({ description: 'UI Controls...', subitems: appearanceItems, icon: 'compass' });
 
             if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._viewType !== CollectionViewType.Docking && this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Tree) {
                 const existingOnClick = cm.findByDescription('OnClick...');
@@ -774,16 +792,21 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     }
                     moreItems.push({ description: 'Copy ID', event: () => Utils.CopyText(Doc.globalServerPath(this.props.Document)), icon: 'fingerprint' });
                 }
-                moreItems.push({ description: 'Export collection', icon: 'download', event: async () => Doc.Zip(this.props.Document) });
-
-                (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.noviceMode) && moreItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: 'users' });
             }
 
-            if (this.props.removeDocument && !Doc.IsSystem(this.rootDoc) && Doc.ActiveDashboard !== this.props.Document) {
+            !more && moreItems.length && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'compass' });
+        }
+        const constantItems: ContextMenuProps[] = [];
+
+        if (!Doc.IsSystem(this.rootDoc)) {
+            constantItems.push({ description: 'Export as Zip file', icon: 'download', event: async () => Doc.Zip(this.props.Document) });
+            constantItems.push({ description: 'Import Zipped file', icon: 'upload', event: ({ x, y }) => this.importDocument() });
+            (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: 'users' });
+            if (this.props.removeDocument && Doc.ActiveDashboard !== this.props.Document) {
                 // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
-                moreItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' });
+                constantItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' });
             }
-            !more && moreItems.length && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'compass' });
+            cm.addItem({ description: 'General...', noexpand: false, subitems: constantItems, icon: 'question' });
         }
         const help = cm.findByDescription('Help...');
         const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : [];
@@ -834,7 +857,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 icon: 'book',
             });
         }
-        cm.addItem({ description: 'Help...', noexpand: true, subitems: helpItems, icon: 'question' });
+        if (!help) cm.addItem({ description: 'Help...', noexpand: true, subitems: helpItems, icon: 'question' });
+        else cm.moveAfter(help);
 
         e?.stopPropagation(); // DocumentViews should stop propagation of this event
         cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index b5193cd20..f38ebba27 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -222,7 +222,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         croppingProto.panYMin = anchy / viewScale;
         croppingProto.panYMax = anchh / viewScale;
         if (addCrop) {
-            DocUtils.MakeLink({ doc: region }, { doc: cropping }, 'cropped image', '');
+            DocUtils.MakeLink(region, cropping, { linkRelationship: 'cropped image' });
             cropping.x = NumCast(this.rootDoc.x) + this.rootDoc[WidthSym]();
             cropping.y = NumCast(this.rootDoc.y);
             this.props.addDocTab(cropping, OpenWhere.inParent);
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index a254edd87..e38d7e2a8 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -130,7 +130,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         croppingProto['data-nativeWidth'] = anchw;
         croppingProto['data-nativeHeight'] = anchh;
         if (addCrop) {
-            DocUtils.MakeLink({ doc: region }, { doc: cropping }, 'cropped image', '');
+            DocUtils.MakeLink(region, cropping, { linkRelationship: 'cropped image' });
         }
         this.props.bringToFront(cropping);
 
@@ -344,7 +344,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             },
             (e, movement, isClick) => !isClick && batch.end(),
             () => {
-                this.toggleSidebar();
+                onButton && this.toggleSidebar();
                 batch.end();
             }
         );
@@ -440,17 +440,20 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const nativeDiff = NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc);
         return PDFBox.sidebarResizerWidth + nativeDiff * (this.props.NativeDimScaling?.() || 1);
     };
+    @undoBatch
+    toggleSidebarType = () => (this.rootDoc.sidebarViewType = this.rootDoc.sidebarViewType === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform);
     specificContextMenu = (e: React.MouseEvent): void => {
-        const funcs: ContextMenuProps[] = [];
-        funcs.push({
-            description: 'Toggle Sidebar Type',
-            event: () => (this.rootDoc.sidebarViewType = this.rootDoc.sidebarViewType === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform),
-            icon: 'expand-arrows-alt',
-        });
-        funcs.push({ description: 'Copy path', event: () => this.pdfUrl && Utils.CopyText(Utils.prepend('') + this.pdfUrl.url.pathname), icon: 'expand-arrows-alt' });
-        funcs.push({ description: 'update icon', event: () => this.pdfUrl && this.updateIcon(), icon: 'expand-arrows-alt' });
-        //funcs.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" });
-        ContextMenu.Instance.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' });
+        const cm = ContextMenu.Instance;
+        const options = cm.findByDescription('Options...');
+        const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
+        optionItems.push({ description: 'Toggle Sidebar Type', event: this.toggleSidebarType, icon: 'expand-arrows-alt' });
+        !Doc.noviceMode && optionItems.push({ description: 'update icon', event: () => this.pdfUrl && this.updateIcon(), icon: 'expand-arrows-alt' });
+        //optionItems.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" });
+        !options && ContextMenu.Instance.addItem({ description: 'Options...', subitems: optionItems, icon: 'asterisk' });
+        const help = cm.findByDescription('Help...');
+        const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : [];
+        helpItems.push({ description: 'Copy path', event: () => this.pdfUrl && Utils.CopyText(Utils.prepend('') + this.pdfUrl.url.pathname), icon: 'expand-arrows-alt' });
+        !help && ContextMenu.Instance.addItem({ description: 'Help...', noexpand: true, subitems: helpItems, icon: 'asterisk' });
     };
 
     @computed get renderTitleBox() {
@@ -523,8 +526,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     sidebarAddDocument={this.sidebarAddDocument}
                     moveDocument={this.moveDocument}
                     removeDocument={this.removeDocument}
-                    //PanelWidth={this.sidebarWidth}
-                    //ScreenToLocalTransform={this.sidebarScreenToLocal}
                 />
             ) : (
                 <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}>
@@ -568,7 +569,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const scale = previewScale * (this.props.NativeDimScaling?.() || 1);
         return !this._pdf ? null : (
             <div
-                className={'pdfBox'}
+                className="pdfBox"
                 onContextMenu={this.specificContextMenu}
                 style={{
                     display: this.props.thumbShown?.() ? 'none' : undefined,
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 0570c7fcb..e14ad4b05 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -327,7 +327,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 _isLinkButton: true,
             });
             this.props.addDocument?.(b);
-            DocUtils.MakeLink({ doc: b }, { doc: this.rootDoc }, 'video snapshot');
+            DocUtils.MakeLink(b, this.rootDoc, { linkRelationship: 'video snapshot' });
             Networking.PostToServer('/youtubeScreenshot', {
                 id: this.youtubeVideoId,
                 timecode: this.layoutDoc._currentTimecode,
@@ -376,7 +376,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         Doc.SetNativeWidth(Doc.GetProto(imageSnapshot), Doc.NativeWidth(this.layoutDoc));
         Doc.SetNativeHeight(Doc.GetProto(imageSnapshot), Doc.NativeHeight(this.layoutDoc));
         this.props.addDocument?.(imageSnapshot);
-        const link = DocUtils.MakeLink({ doc: imageSnapshot }, { doc: this.getAnchor(true) }, 'video snapshot');
+        const link = DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { linkRelationship: 'video snapshot' });
         link && (Doc.GetProto(link.anchor2 as Doc).timecodeToHide = NumCast((link.anchor2 as Doc).timecodeToShow) + 3);
         setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, 'move', true));
     };
@@ -1031,7 +1031,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         croppingProto.panYMin = anchy / viewScale;
         croppingProto.panYMax = anchh / viewScale;
         if (addCrop) {
-            DocUtils.MakeLink({ doc: region }, { doc: cropping }, 'cropped image', '');
+            DocUtils.MakeLink(region, cropping, { linkRelationship: 'cropped image' });
         }
         this.props.bringToFront(cropping);
         return cropping;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index d8afc3227..361e000f9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -454,7 +454,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                             alink =
                                 alink ??
                                 (LinkManager.Links(this.Document).find(link => Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) ||
-                                    DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, LinkManager.AutoKeywords)!);
+                                    DocUtils.MakeLink(this.props.Document, target, { linkRelationship: LinkManager.AutoKeywords })!);
                             newAutoLinks.add(alink);
                             const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }];
                             allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? []));
@@ -1273,7 +1273,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         ]),
                     ]);
 
-                    const link = DocUtils.MakeLink({ doc: pdfAnchor }, { doc: this.rootDoc }, 'PDF pasted');
+                    const link = DocUtils.MakeLink(pdfAnchor, this.rootDoc, { linkRelationship: 'PDF pasted' });
                     if (link) {
                         view.dispatch(view.state.tr.replaceSelectionWith(dashField, false).scrollIntoView().setMeta('paste', true).setMeta('uiEvent', 'paste'));
                     }
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 9e9b61db3..e691869cc 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -259,7 +259,7 @@ export class RichTextRules {
                                 this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3))));
                             }
                             const target = (docx instanceof Doc && docx) || Docs.Create.FreeformDocument([], { title: docId, _width: 500, _height: 500 }, docId);
-                            DocUtils.MakeLink({ doc: this.TextBox.getAnchor(true) }, { doc: target }, 'portal to:portal from', undefined);
+                            DocUtils.MakeLink(this.TextBox.getAnchor(true), target, { linkRelationship: 'portal to:portal from' });
 
                             const fstate = this.TextBox.EditorView?.state;
                             if (fstate && selection) {
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index fe9b13fbe..8f93f1150 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -117,7 +117,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
     makeLink = action((linkTo: Doc) => {
         const linkFrom = this.props.linkCreateAnchor?.();
         if (linkFrom) {
-            const link = DocUtils.MakeLink({ doc: linkFrom }, { doc: linkTo });
+            const link = DocUtils.MakeLink(linkFrom, linkTo, {});
             link && this.props.linkCreated?.(link);
         }
     });
-- 
cgit v1.2.3-70-g09d2


From 1edbb801fdd1cfecbd0838f382149fc2b38a4df1 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 5 Apr 2023 12:44:50 -0400
Subject: fixed dragging groups

---
 src/client/documents/Documents.ts                               | 2 +-
 src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 1 +
 src/client/views/nodes/DocumentView.tsx                         | 2 +-
 3 files changed, 3 insertions(+), 2 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 5623ca218..5039be2f6 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -166,6 +166,7 @@ export class DocumentOptions {
     _chromeHidden?: boolean; // whether the editing chrome for a document is hidden
     _searchDoc?: boolean; // is this a search document (used to change UI for search results in schema view)
     _forceActive?: boolean; // flag to handle pointer events when not selected (or otherwise active)
+    enableDragWhenActive?: boolean; // allow dragging even if document contentts are active (e.g., tree, groups)
     _stayInCollection?: boolean; // whether the document should remain in its collection when someone tries to drag and drop it elsewhere
     _raiseWhenDragged?: boolean; // whether a document is brought to front when dragged.
     _hideContextMenu?: boolean; // whether the context menu can be shown
@@ -232,7 +233,6 @@ export class DocumentOptions {
     contextMenuIcons?: List<string>;
     defaultDoubleClick?: 'ignore' | 'default'; // ignore double clicks, or deafult (undefined) means open document full screen
     waitForDoubleClickToClick?: 'always' | 'never' | 'default'; // whether a click function wait for double click to expire.  'default' undefined = wait only if there's a click handler, "never" = never wait, "always" = alway wait
-    enableDragWhenActive?: boolean;
     dontUndo?: boolean; // whether button clicks should be undoable (this is set to true for Undo/Redo/and sidebar buttons that open the siebar panel)
     description?: string; // added for links
     layout?: string | Doc; // default layout string for a document
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 0b4f76fa1..d443df0f3 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -396,6 +396,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         newCollection._height = this.Bounds.height;
         newCollection._isGroup = makeGroup;
         newCollection.forceActive = makeGroup;
+        newCollection.enableDragWhenActive = makeGroup;
         newCollection.x = this.Bounds.left;
         newCollection.y = this.Bounds.top;
         newCollection.fitWidth = true;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 159aa026a..0e0e22b84 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -876,7 +876,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             : Doc.ActiveTool !== InkTool.None ||
               SnappingManager.GetIsDragging() ||
               this.rootSelected() ||
-              this.props.Document.forceActive ||
+              this.rootDoc.forceActive ||
               this.props.isSelected(outsideReaction) ||
               this._componentView?.isAnyChildContentActive?.() ||
               this.props.isContentActive()
-- 
cgit v1.2.3-70-g09d2


From 99fba6d6e99da0154d40d2e3e87e120d8e6f2810 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 5 Apr 2023 22:44:38 -0400
Subject: fixed up dataviz to work again.  fixed point selection, tooltips,
 making and following links

---
 src/client/documents/Documents.ts                  |  4 +-
 src/client/util/CurrentUserUtils.ts                |  2 +-
 src/client/views/nodes/DataVizBox/ChartBox.tsx     | 62 ++++-----------------
 .../views/nodes/DataVizBox/ChartInterface.ts       |  6 +--
 src/client/views/nodes/DataVizBox/DataVizBox.scss  |  4 ++
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   | 63 ++++++++++------------
 .../views/nodes/DataVizBox/components/Chart.scss   |  9 +++-
 .../nodes/DataVizBox/components/LineChart.tsx      | 41 ++++++++------
 src/client/views/nodes/DocumentView.tsx            |  1 +
 src/client/views/nodes/trails/PresBox.tsx          |  9 +++-
 src/server/DataVizUtils.ts                         | 22 ++++----
 11 files changed, 98 insertions(+), 125 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index c4014a752..1f09cdd3d 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1148,8 +1148,8 @@ export namespace Docs {
             return Prototypes.get(DocumentType.PRESELEMENT);
         }
 
-        export function DataVizDocument(url: string, options?: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: 'Data Viz', ...options });
+        export function DataVizDocument(url: string, options?: DocumentOptions, overwriteDoc?: Doc) {
+            return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: 'Data Viz', ...options }, undefined, undefined, undefined, overwriteDoc);
         }
 
         export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 9aceed366..623aaf357 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -273,7 +273,7 @@ export class CurrentUserUtils {
             {key: "WebCam",     creator: opts => Docs.Create.WebCamDocument("", opts),      opts: { _width: 400, _height: 200, recording:true, system: true, cloneFieldFilter: new List<string>(["system"]) }},
             {key: "Button",     creator: Docs.Create.ButtonDocument,                        opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, _isLinkButton: true }},
             {key: "Script",     creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
-            // {key: "DataViz",    creator: opts => Docs.Create.DataVizDocument(opts),         opts: { _width: 300, _height: 300 }},
+            {key: "DataViz",    creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts),   opts: { _width: 300, _height: 300 }},
             {key: "Header",     creator: headerTemplate,                                    opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, treeViewHideUnrendered: true}},
             {key: "Trail",      creator: Docs.Create.PresDocument,                          opts: { _width: 400, _height: 30, _viewType: CollectionViewType.Stacking, targetDropAction: "alias" as any, treeViewHideTitle: true, _fitWidth:true, _chromeHidden: true, boxShadow: "0 0" }},
             {key: "Tab",        creator: opts => Docs.Create.FreeformDocument([], opts),    opts: { _width: 500, _height: 800, _fitWidth: true, _backgroundGridShow: true, }},
diff --git a/src/client/views/nodes/DataVizBox/ChartBox.tsx b/src/client/views/nodes/DataVizBox/ChartBox.tsx
index c229e5471..ee577b9fd 100644
--- a/src/client/views/nodes/DataVizBox/ChartBox.tsx
+++ b/src/client/views/nodes/DataVizBox/ChartBox.tsx
@@ -5,10 +5,12 @@ import { action, computed, observable } from 'mobx';
 import { NumCast, StrCast } from '../../../../fields/Types';
 import { LineChart } from './components/LineChart';
 import { Chart, ChartData } from './ChartInterface';
+import { PinProps } from '../trails';
 
 export interface ChartBoxProps {
     rootDoc: Doc;
     dataDoc: Doc;
+    height: number;
     pairs: { x: number; y: number }[];
     setChartBox: (chartBox: ChartBox) => void;
     getAnchor: () => Doc;
@@ -71,11 +73,7 @@ export class ChartBox extends React.Component<ChartBoxProps> {
 
     componentDidMount = () => {
         this.generateChartData();
-        // setting timeout to allow rerender cycle of the actual chart tof inish
-        // setTimeout(() => {
         this.props.setChartBox(this);
-        // });
-        // this.props.setChartBox(this);
     };
 
     @action
@@ -86,59 +84,17 @@ export class ChartBox extends React.Component<ChartBoxProps> {
         this.props.rootDoc._currChartView = e.currentTarget.value.toLowerCase();
     };
 
-    scrollFocus(doc: Doc, smooth: boolean): number | undefined {
-        // if x and y exist on this
-        // then highlight/focus the x and y position (datapoint)
-        // highlight for a few seconds and then remove (set a timer to unhighlight after a period of time,
-        //  to be consistent, use 5 seconds and highlight color is orange)
-        // if x and y don't exist => link to whole doc => dash will take care of focusing on the entire doc
-        // return value is how long this action takes
-        //  undefined => immediately, 0 => introduces a timeout
-        //  number in ms => won't do any of the focus or call the automated highlighting thing until this timeout expires
-        //      in this case probably number in ms doesn't matter
+    getAnchor = (pinProps?: PinProps) => this.currChart?._getAnchor(pinProps);
 
-        // for now could just update the currSelected in linechart to be what doc.x and doc.y
-
-        if (this.currChart && doc.x && doc.y) {
-            // update curr selected
-            this.currChart.setCurrSelected(Number(doc.x), Number(doc.y));
-        }
-        return 0;
-    }
-
-    _getAnchor() {
-        return this.currChart?._getAnchor();
-    }
+    restoreView = (data: Doc) => this.currChart?.restoreView(data);
 
     render() {
         if (this.props.pairs && this._chartData) {
-            let width = NumCast(this.props.rootDoc._width);
-            width = width * 0.7;
-            let height = NumCast(this.props.rootDoc._height);
-            height = height * 0.7;
-            console.log(width, height);
-            const margin = { top: 50, right: 50, bottom: 50, left: 50 };
-            return (
-                <div>
-                    <div>
-                        {/*{this.props.rootDoc._currChartView == 'line' ? (
-                            <Line ref={this._chartRef} options={this.options} data={this._chartJsData} onClick={e => this.onClick(e)} />
-                        ) : (
-                            <Bar ref={this._chartRef} options={this.options} data={this._chartJsData} onClick={e => this.onClick(e)} />
-                        )} */}
-                        {/* {this.reLineChart} */}
-                        <LineChart margin={margin} width={width} height={height} chartData={this._chartData} setCurrChart={this.setCurrChart} dataDoc={this.props.dataDoc} fieldKey={'data'} getAnchor={this.props.getAnchor} />
-                    </div>
-                    <button onClick={e => this.onClickChangeChart(e)} value="line">
-                        Line
-                    </button>
-                    <button onClick={e => this.onClickChangeChart(e)} value="bar">
-                        Bar
-                    </button>
-                </div>
-            );
-        } else {
-            return <div></div>;
+            const width = NumCast(this.props.rootDoc._width) * 0.9;
+            const height = this.props.height * 0.9;
+            const margin = { top: 10, right: 50, bottom: 50, left: 50 };
+            return <LineChart margin={margin} width={width} height={height} chartData={this._chartData} setCurrChart={this.setCurrChart} dataDoc={this.props.dataDoc} fieldKey={'data'} getAnchor={this.props.getAnchor} />;
         }
+        return <div />;
     }
 }
diff --git a/src/client/views/nodes/DataVizBox/ChartInterface.ts b/src/client/views/nodes/DataVizBox/ChartInterface.ts
index 494242ac5..8ebcc2a5f 100644
--- a/src/client/views/nodes/DataVizBox/ChartInterface.ts
+++ b/src/client/views/nodes/DataVizBox/ChartInterface.ts
@@ -1,15 +1,15 @@
 import { Doc } from '../../../../fields/Doc';
+import { PinProps } from '../trails';
 import { DataPoint } from './ChartBox';
-import { LineChart } from './components/LineChart';
 
 export interface Chart {
     tooltipContent: (data: DataPoint) => string;
     drawChart: () => void;
+    restoreView: (data: any) => boolean;
     height: number;
     width: number;
     // TODO: nda - probably want to get rid of this to keep charts more generic
-    _getAnchor: () => Doc;
-    setCurrSelected: (x: number, y: number) => void;
+    _getAnchor: (pinProps?: PinProps) => Doc;
 }
 
 export interface ChartProps {
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss
index e69de29bb..cd500e9ae 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.scss
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss
@@ -0,0 +1,4 @@
+.dataviz {
+    overflow: auto;
+    height: 100%;
+}
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index db677ff61..68766e8dc 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -2,7 +2,7 @@ import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
-import { Cast, StrCast } from '../../../../fields/Types';
+import { BoolCast, Cast, StrCast } from '../../../../fields/Types';
 import { CsvField } from '../../../../fields/URLField';
 import { Docs } from '../../../documents/Documents';
 import { ViewBoxAnnotatableComponent } from '../../DocComponent';
@@ -10,6 +10,7 @@ import { FieldViewProps, FieldView } from '../FieldView';
 import { ChartBox } from './ChartBox';
 import './DataVizBox.scss';
 import { TableBox } from './components/TableBox';
+import { PinProps } from '../trails';
 
 enum DataVizView {
     TABLE = 'table',
@@ -54,40 +55,23 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     };
 
     @computed get currView() {
-        if (this.rootDoc._dataVizView) {
-            return StrCast(this.rootDoc._dataVizView);
-        } else {
-            return 'table';
-        }
+        return StrCast(this.rootDoc._dataVizView, 'table');
     }
 
-    scrollFocus = (doc: Doc, smooth: boolean) => {
-        // reconstruct the view based on the anchor ->
-        // keep track of the datavizbox view and the type of chart we're rendering
-
-        // use dataview to restore part of it and then pass on the rest to the chartbox
-
-        // pseudocode
-        return this._chartBox?.scrollFocus(doc, smooth);
+    @action
+    restoreView = (data: Doc) => {
+        const changed = this.currView !== data.dataView && (this.rootDoc._dataVizView = data.dataView);
+        const func = () => this._chartBox?.restoreView(data);
+        if (changed) {
+            setTimeout(func);
+            return changed ? true : false;
+        }
+        return func() ?? false;
     };
 
-    getAnchor = () => {
-        // TODO: nda - look into DocumentButtonBar and DocumentLinksButton
-
-        /*
-        if nothing selected:
-            return this.rootDoc
-        this part does not specify view type (doesn't change type when following the link)
-
-            // this slightly diff than the stuff below: 
-                below part returns an anchor that specifies the viewtype for the whole doc and nothing else
-                
-        */
-
-        // store whatever information would allow me to reselect the same thing (store parameters I would pass to get the exact same element)
+    getAnchor = (addAsAnnotation?: boolean, pinProps?: PinProps) => {
         const anchor =
-            // this._COMPONENTNAME._getAnchor() ??
-            this._chartBox?._getAnchor() ??
+            this._chartBox?.getAnchor(pinProps) ??
             Docs.Create.TextanchorDocument({
                 // when we clear selection -> we should have it so chartBox getAnchor returns undefined
                 // this is for when we want the whole doc (so when the chartBox getAnchor returns without a marker)
@@ -98,7 +82,6 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
         this.addDocument(anchor);
         return anchor;
-        // have some other function in code that
     };
     constructor(props: any) {
         super(props);
@@ -117,7 +100,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             case 'table':
                 return <TableBox pairs={this.pairs} />;
             case 'histogram':
-                return <ChartBox getAnchor={this.getAnchor} setChartBox={this.setChartBox} rootDoc={this.rootDoc} pairs={this.pairs} dataDoc={this.dataDoc} />;
+                return <ChartBox height={this.props.PanelHeight() - 32 /* height of 'change view' button */} getAnchor={this.getAnchor} setChartBox={this.setChartBox} rootDoc={this.rootDoc} pairs={this.pairs} dataDoc={this.dataDoc} />;
             // case "histogram":
             //     return (<HistogramBox rootDoc={this.rootDoc} pairs={this.pairs}/>)
         }
@@ -152,7 +135,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @action changeViewHandler(e: React.MouseEvent<HTMLButtonElement>) {
         e.preventDefault();
         e.stopPropagation();
-        this.rootDoc._dataVizView = this.currView == 'table' ? 'histogram' : 'table';
+        this.rootDoc._dataVizView = this.currView === 'table' ? 'histogram' : 'table';
     }
 
     render() {
@@ -161,7 +144,19 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         }
 
         return (
-            <div className="dataViz">
+            <div
+                className="dataViz"
+                onWheel={e => e.stopPropagation()}
+                ref={r =>
+                    r?.addEventListener(
+                        'wheel', // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
+                        (e: WheelEvent) => {
+                            if (!r.scrollTop && e.deltaY <= 0) e.preventDefault();
+                            e.stopPropagation();
+                        },
+                        { passive: false }
+                    )
+                }>
                 <button onClick={e => this.changeViewHandler(e)}>Change View</button>
                 {this.selectView}
             </div>
diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss
index 7792a2758..2d6c5f0f2 100644
--- a/src/client/views/nodes/DataVizBox/components/Chart.scss
+++ b/src/client/views/nodes/DataVizBox/components/Chart.scss
@@ -1,10 +1,15 @@
 .tooltip {
     // make the height width bigger
-    width: 50px;
-    height: 50px;
+    width: fit-content;
+    height: fit-content;
 }
 
 .highlight {
     // change the color of the circle element to be red
     fill: red;
 }
+.chart-container {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+}
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index d893b3e12..e5f7dc4f4 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -9,6 +9,10 @@ import { Docs } from '../../../../documents/Documents';
 import { Doc, DocListCast } from '../../../../../fields/Doc';
 import { Chart, ChartProps } from '../ChartInterface';
 import './Chart.scss';
+import { List } from '../../../../../fields/List';
+import { PinProps, PresBox } from '../../trails';
+import { listSpec } from '../../../../../fields/Schema';
+import { Cast } from '../../../../../fields/Types';
 
 type minMaxRange = {
     xMin: number | undefined;
@@ -103,21 +107,25 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
         // loop through and remove any annotations that no longer exist
     }
 
-    _getAnchor() {
+    restoreView = (data: Doc) => {
+        const coords = Cast(data.presDataViz, listSpec('number'), null);
+        if ((coords && this._currSelected?.x !== coords[0]) || this._currSelected?.y !== coords[1]) {
+            this.setCurrSelected(coords[0], coords[1]);
+            return true;
+        }
+        return false;
+    };
+    _getAnchor = (pinProps?: PinProps) => {
         // store whatever information would allow me to reselect the same thing (store parameters I would pass to get the exact same element)
 
-        // TODO: nda - look at pdfviewer get anchor for args
-        const doc = Docs.Create.TextanchorDocument({
+        const anchor = Docs.Create.TextanchorDocument({
             /*put in some options*/
             title: 'line doc selection' + this._currSelected?.x,
         });
-        // access curr selected from the charts
-        doc.x = this._currSelected?.x;
-        doc.y = this._currSelected?.y;
-        doc.chartType = 'line';
-        return doc;
+        PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), dataviz: this._currSelected ? [this._currSelected.x, this._currSelected.y] : undefined } }, this.props.dataDoc);
+        return anchor;
         // have some other function in code that
-    }
+    };
 
     componentWillUnmount() {
         if (this._dataReactionDisposer) {
@@ -132,7 +140,7 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
     }
 
     tooltipContent(data: DataPoint) {
-        return `<b>x: ${data.x} y: ${data.y}</b>`;
+        return `<b>(${data.x},${data.y})</b>`;
     }
 
     @computed get height(): number {
@@ -235,7 +243,7 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
         const mousemove = action((e: any) => {
             const bisect = d3.bisector((d: DataPoint) => d.x).left;
             const xPos = d3.pointer(e)[0];
-            const x0 = bisect(data, xScale.invert(xPos));
+            const x0 = bisect(data, xScale.invert(xPos - 25));
             const d0 = data[x0];
             this._x = d0.x;
             this._y = d0.y;
@@ -243,23 +251,22 @@ export class LineChart extends React.Component<ChartProps> implements Chart {
             // TODO: nda - implement tooltips
             tooltip.transition().duration(300).style('opacity', 0.9);
             // TODO: nda - updating the inner html could be deadly cause injection attacks!
-            tooltip.html(() => this.tooltipContent(d0)).style('transform', `translate(${xScale(d0.x) - (this.width + margin.left + margin.right) + 30}px,${yScale(d0.y) + 30}px)`);
+            tooltip
+                .html(() => this.tooltipContent(d0))
+                .style('pointer-events', 'none')
+                .style('transform', `translate(${xScale(d0.x) - this.width / 2 + 12}px,${yScale(d0.y) + 30}px)`);
         });
 
         const onPointClick = action((e: any) => {
             const bisect = d3.bisector((d: DataPoint) => d.x).left;
             const xPos = d3.pointer(e)[0];
-            const x0 = bisect(data, xScale.invert(xPos));
+            const x0 = bisect(data, xScale.invert(xPos - 25));
             const d0 = data[x0];
             this._x = d0.x;
             this._y = d0.y;
             // find .circle-d1 with data-x = d0.x and data-y = d0.y
             const selected = svg.selectAll('.datapoint').filter((d: any) => d['data-x'] === d0.x && d['data-y'] === d0.y);
             this._currSelected = { x: d0.x, y: d0.y, elem: selected };
-            console.log('Getting here');
-            // this.drawAnnotations(this._x, this._y);
-            // this.props.getAnchor();
-            console.log(this._currSelected);
         });
 
         this._chartSvg
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0e0e22b84..e24bf35f5 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -108,6 +108,7 @@ export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, p
 export interface DocComponentView {
     updateIcon?: () => void; // updates the icon representation of the document
     getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box)
+    restoreView?: (viewSpec: Doc) => boolean;
     scrollPreview?: (docView: DocumentView, doc: Doc, focusSpeed: number, options: DocFocusOptions) => Opt<number>; // returns the duration of the focus
     brushView?: (view: { width: number; height: number; panX: number; panY: number }) => void;
     getView?: (doc: Doc) => Promise<Opt<DocumentView>>; // returns a nested DocumentView for the specified doc or undefined
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index bfaae8069..0e22ea3b1 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -3,7 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
 import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { AnimationSym, Doc, DocListCast, FieldResult, Opt, StrListCast } from '../../../../fields/Doc';
+import { AnimationSym, Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../../fields/Doc';
 import { Copy, Id } from '../../../../fields/FieldSymbols';
 import { InkField, InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
@@ -39,6 +39,7 @@ const { Howl } = require('howler');
 
 export interface pinDataTypes {
     scrollable?: boolean;
+    dataviz?: number[];
     pannable?: boolean;
     viewType?: boolean;
     inkable?: boolean;
@@ -491,6 +492,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 changed = true;
             }
         }
+        if (!pinDataTypes && activeItem.presDataViz !== undefined) {
+            if (bestTargetView?.ComponentView?.restoreView?.(activeItem)) {
+                changed = true;
+            }
+        }
 
         if (pinDataTypes?.scrollable || (!pinDataTypes && activeItem.presViewScroll !== undefined)) {
             if (bestTarget._scrollTop !== activeItem.presViewScroll) {
@@ -648,6 +654,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             if (pinProps.pinData.viewType) pinDoc.presViewType = targetDoc._viewType;
             if (pinProps.pinData.filters) pinDoc.presDocFilters = ObjectField.MakeCopy(targetDoc.docFilters as ObjectField);
             if (pinProps.pinData.pivot) pinDoc.presPivotField = targetDoc._pivotField;
+            if (pinProps.pinData.dataviz) pinDoc.presDataViz = new List<number>(pinProps.pinData.dataviz);
             if (pinProps.pinData.pannable) {
                 pinDoc.presPanX = NumCast(targetDoc._panX);
                 pinDoc.presPanY = NumCast(targetDoc._panY);
diff --git a/src/server/DataVizUtils.ts b/src/server/DataVizUtils.ts
index 2528fb1ab..15f03b319 100644
--- a/src/server/DataVizUtils.ts
+++ b/src/server/DataVizUtils.ts
@@ -1,19 +1,17 @@
-import { readFileSync } from "fs";
+import { readFileSync } from 'fs';
 
 export function csvParser(csv: string) {
-    const lines = csv.split("\n");
-    const headers = lines[0].split(",");
-    const data = lines.slice(1).map(line => {
-        const values = line.split(",");
-        const obj: any = {};
-        for (let i = 0; i < headers.length; i++) {
-            obj[headers[i]] = values[i];
-        }
-        return obj;
-    });
+    const lines = csv.split('\n');
+    const headers = lines[0].split(',').map(header => header.trim());
+    const data = lines.slice(1).map(line =>
+        line.split(',').reduce((last, value, i) => {
+            last[headers[i]] = value.trim();
+            return last;
+        }, {} as any)
+    );
     return data;
 }
 
 export function csvToString(path: string) {
     return readFileSync(path, 'utf8');
-}
\ No newline at end of file
+}
-- 
cgit v1.2.3-70-g09d2

