aboutsummaryrefslogtreecommitdiff
path: root/src/client/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util')
-rw-r--r--src/client/util/CurrentUserUtils.ts28
-rw-r--r--src/client/util/DocumentManager.ts232
-rw-r--r--src/client/util/Import & Export/DirectoryImportBox.tsx2
-rw-r--r--src/client/util/InteractionUtils.tsx9
-rw-r--r--src/client/util/LinkFollower.ts56
-rw-r--r--src/client/util/LinkManager.ts2
-rw-r--r--src/client/util/SettingsManager.tsx12
-rw-r--r--src/client/util/SharingManager.tsx32
8 files changed, 137 insertions, 236 deletions
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 5f183cf91..a7b2c3d04 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -183,7 +183,7 @@ export class CurrentUserUtils {
const allopts = {system: true, ...opts};
return DocUtils.AssignScripts( (curIcon?.iconTemplate === opts.iconTemplate ?
DocUtils.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[iconFieldName] = MakeTemplate(creator(allopts), true, iconFieldName, templateField))),
- {onClick:"deiconifyView(documentView)"});
+ {onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView"});
};
const labelBox = (opts: DocumentOptions, data?:string) => Docs.Create.LabelDocument({
textTransform: "unset", letterSpacing: "unset", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts
@@ -258,7 +258,7 @@ export class CurrentUserUtils {
}[] = [
{key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _autoHeight: true }},
{key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200 }},
- {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100 }},
+ {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100, _fitWidth: true }},
{key: "Equation", creator: opts => Docs.Create.EquationDocument(opts), opts: { _width: 300, _height: 35, _backgroundGridShow: true, }},
{key: "Webpage", creator: opts => Docs.Create.WebDocument("",opts), opts: { _width: 400, _height: 512, _nativeWidth: 850, useCors: true, }},
{key: "Comparison", creator: Docs.Create.ComparisonDocument, opts: { _width: 300, _height: 300 }},
@@ -589,20 +589,15 @@ export class CurrentUserUtils {
{ scripts: { onClick: "undo()"}, opts: { title: "undo", icon: "undo-alt", toolTip: "Click to undo" }},
{ scripts: { onClick: "redo()"}, opts: { title: "redo", icon: "redo-alt", toolTip: "Click to redo" }},
{ scripts: { }, opts: { title: "linker", icon: "linkui", toolTip: "link started"}},
- { scripts: { }, opts: { title: "currently playing", icon: "currentlyplayingui", toolTip: "currently playing audio"}},
- // { scripts: { onClick: 'prevKeyFrame(_readOnly_)'}, opts: { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, width: 20}},
- // { scripts: { onClick:""}, opts: { title: "Num", icon: "", toolTip: "Frame Number", btnType: ButtonType.TextButton, width: 20}, funcs: { buttonText: 'selectedDocs()?.lastElement()?.currentFrame.toString()'}},
- // { scripts: { onClick: 'nextKeyFrame(_readOnly_)'}, opts:{title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, width: 20,} },
- ];
+ { scripts: { }, opts: { title: "currently playing", icon: "currentlyplayingui", toolTip: "currently playing media"}},
+ ];
const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, dontUndo: true, _stayInCollection: true, ...desc.opts}, desc.scripts));
const dockBtnsReqdOpts:DocumentOptions = {
title: "docked buttons", _height: 40, flexGap: 0, boxShadow: "standard", childDropAction: 'alias',
childDontRegisterViews: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true
};
reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true });
- reaction(() => UndoManager.undoStack.slice(), () => {
- Doc.GetProto(btns.find(btn => btn.title === "undo")!).opacity = UndoManager.CanUndo() ? 1 : 0.4;
- }, { fireImmediately: true });
+ reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "undo")!).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true });
return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns);
}
@@ -612,6 +607,7 @@ export class CurrentUserUtils {
btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) },
{ title: "Size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, ignoreClick: true, scripts: {script: '{ return setFontSize(value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions },
{ title: "Color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, scripts: {script: '{ return setFontColor(value, _readOnly_);}'}},
+ { title: "Highlight",toolTip:"Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", ignoreClick: true, scripts: {script: '{ return setFontHighlight(value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} },
{ title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", scripts: {onClick: '{ return toggleBold(_readOnly_); }'} },
{ title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", scripts: {onClick: '{ return toggleItalic(_readOnly_);}'} },
{ title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", scripts: {onClick: '{ return toggleUnderline(_readOnly_);}'} },
@@ -635,9 +631,9 @@ export class CurrentUserUtils {
{ title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", scripts: {onClick:'{ return setActiveTool("write", false, _readOnly_);}'} },
{ title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", scripts: {onClick:'{ return setActiveTool("eraser", false, _readOnly_);}' }},
// { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", scripts:{onClick: 'setActiveTool("highlighter")'} },
- { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", true, _readOnly_);}`} },
- { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", true, _readOnly_);}`} },
- { title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", true, _readOnly_);}`} },
+ { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", true, _readOnly_);}`} },
+ { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", true, _readOnly_);}`} },
+ { title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", true, _readOnly_);}`} },
{ title: "Mask", toolTip: "Mask", btnType: ButtonType.ToggleButton, icon: "user-circle", scripts: {onClick:'{ return setIsInkMask(_readOnly_);}'} },
{ title: "Fill", toolTip: "Fill color", btnType: ButtonType.ColorButton, icon: "fill-drip",ignoreClick: true, scripts: {script: '{ return setFillColor(value, _readOnly_);}'} },
{ title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, ignoreClick: true, scripts: {script: '{ return setStrokeWidth(value, _readOnly_);}'}, numBtnType: NumButtonType.Slider, numBtnMin: 1},
@@ -668,7 +664,7 @@ export class CurrentUserUtils {
title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}},
{ title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "tab")'}, width: 20, scripts: { onClick: 'pinWithView(_readOnly_, altKey)'}},
{ title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
- { title: "Num",icon: "",toolTip: "Frame Number (click to toggle edit mode)",btnType: ButtonType.TextButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { script: '{ return curKeyFrame(_readOnly_);}'}},
+ { title: "Num",icon: "",toolTip: "Frame Number (click to toggle edit mode)",btnType: ButtonType.TextButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
{ title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
{ title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, funcs: {hidden: '!SelectionManager_selectedDocType()'}, ignoreClick: true, width: 20, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected
{ title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, funcs: {hidden: '!SelectionManager_selectedDocType()'}, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}},
@@ -803,7 +799,9 @@ export class CurrentUserUtils {
doc._showLabel ?? (doc._showLabel = true);
doc.textAlign ?? (doc.textAlign = "left");
doc.activeTool = InkTool.None;
- doc.activeInkColor ?? (doc.activeInkColor = "rgb(0, 0, 0)");;
+ doc.openInkInLightbox ?? (doc.openInkInLightbox = false);
+ doc.activeInkHideTextLabels ?? (doc.activeInkHideTextLabels = false);
+ doc.activeInkColor ?? (doc.activeInkColor = "rgb(0, 0, 0)");
doc.activeInkWidth ?? (doc.activeInkWidth = 1);
doc.activeInkBezier ?? (doc.activeInkBezier = "0");
doc.activeFillColor ?? (doc.activeFillColor = "");
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 088f2429d..c670d0ab8 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,17 +1,22 @@
+import { loadAsync } from 'jszip';
import { action, observable, ObservableSet, runInAction } from 'mobx';
import { AnimationSym, Doc, Opt } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { listSpec } from '../../fields/Schema';
import { Cast, DocCast, StrCast } from '../../fields/Types';
import { AudioField } from '../../fields/URLField';
-import { returnFalse } from '../../Utils';
-import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
+import { emptyFunction } from '../../Utils';
+import { CollectionViewType } from '../documents/DocumentTypes';
import { CollectionDockingView } from '../views/collections/CollectionDockingView';
import { CollectionFreeFormView } from '../views/collections/collectionFreeForm';
import { CollectionView } from '../views/collections/CollectionView';
+import { TabDocView } from '../views/collections/TabDocView';
import { LightboxView } from '../views/LightboxView';
-import { DocFocusOptions, DocumentView, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView';
+import { MainView } from '../views/MainView';
+import { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
+import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox';
import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
+import { PresBox } from '../views/nodes/trails';
import { ScriptingGlobals } from './ScriptingGlobals';
import { SelectionManager } from './SelectionManager';
const { Howl } = require('howler');
@@ -165,12 +170,12 @@ export class DocumentManager {
public getLightboxDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
const views: DocumentView[] = [];
Array.from(DocumentManager.Instance.DocumentViews).map(view => LightboxView.IsLightboxDocView(view.docViewPath) && Doc.AreProtosEqual(view.rootDoc, toFind) && views.push(view));
- return views?.find(view => view.ContentDiv?.getBoundingClientRect().width && view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse) || (views.length ? views[0] : undefined);
+ return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /*&& view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse*/) || (views.length ? views[0] : undefined);
};
public getFirstDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
if (LightboxView.LightboxDoc) return DocumentManager.Instance.getLightboxDocumentView(toFind, originatingDoc);
- const views = this.getDocumentViews(toFind).filter(view => view.rootDoc !== originatingDoc);
- return views?.find(view => view.ContentDiv?.getBoundingClientRect().width && view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse) || (views.length ? views[0] : undefined);
+ const views = this.getDocumentViews(toFind); //.filter(view => view.rootDoc !== originatingDoc);
+ return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /*&& view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse*/) || (views.length ? views[0] : undefined);
};
public getDocumentViews(toFindIn: Doc): DocumentView[] {
const toFind =
@@ -198,8 +203,8 @@ export class DocumentManager {
static GetContextPath(doc: Opt<Doc>, includeExistingViews?: boolean) {
if (!doc) return [];
- const srcContext = Cast(doc.context, Doc, null) ?? Cast(Cast(doc.annotationOn, Doc, null)?.context, Doc, null);
- var containerDocContext = srcContext ? [srcContext] : [];
+ const srcContext = Cast(doc.context, Doc, null) ?? Cast(doc.annotationOn, Doc, null);
+ var containerDocContext = srcContext ? [srcContext, doc] : [doc];
while (
containerDocContext.length &&
containerDocContext[0]?.context &&
@@ -235,164 +240,91 @@ export class DocumentManager {
CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
finished?.();
};
- public jumpToDocument = (
+
+ // shows a documentView by:
+ // traverses down through the viewPath of contexts to the view:
+ // focusing on each context
+ public showDocumentView = async (targetDocView: DocumentView, options: DocFocusOptions) => {
+ const docViewPath = targetDocView.docViewPath.slice();
+ let rootContextView = docViewPath.shift();
+ return rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined }));
+ };
+
+ // shows a document by first:
+ // traversing down through the contexts that contain target until an existing view is found
+ // if no container view is found, create one by: opening an existing tab that has the top-level view, or showing the top-level context in the lightbox.
+ // once a containing view is found, it then traverses back down through the contexts to the target document by:
+ // focusing on each context
+ // and finally restoring the targetDoc to the viewSpec specified by the last document which may either be the targetDoc, or a viewSpec that describes the targetDoc configuration
+ public showDocument = async (
targetDoc: Doc, // document to display
options: DocFocusOptions, // options for how to navigate to target
- createViewFunc = DocumentManager.addView, // how to create a view of the doc if it doesn't exist
- docContextPath: Doc[], // context to load that should contain the target
finished?: () => void
- ): void => {
- const originalTarget = options.originalTarget ?? targetDoc;
- const docView = this.getFirstDocumentView(targetDoc, options.originatingDoc);
- const annotatedDoc = Cast(targetDoc.annotationOn, Doc, null);
- const resolvedTarget = targetDoc.type === DocumentType.MARKER ? annotatedDoc ?? docView?.rootDoc ?? targetDoc : docView?.rootDoc ?? targetDoc; // if target is a marker, then focus toggling should apply to the document it's on since the marker itself doesn't have a hidden field
- var wasHidden = resolvedTarget.hidden;
- if (wasHidden) {
- runInAction(() => {
- resolvedTarget.hidden = false; // if the target is hidden, un-hide it here.
- docView?.props.bringToFront(resolvedTarget);
- });
- }
- const focusAndFinish = action((didFocus: boolean) => {
- const finalTargetDoc = resolvedTarget;
- if (options.toggleTarget) {
- if (!didFocus && !wasHidden) {
- // don't toggle the hidden state if the doc was already un-hidden as part of this document traversal
- finalTargetDoc.hidden = !finalTargetDoc.hidden;
- }
- } else {
- finalTargetDoc.hidden && (finalTargetDoc.hidden = undefined);
- !options.noSelect && docView?.select(false);
- }
- if (targetDoc.textHtml && options.zoomTextSelections) {
- const containerView = DocumentManager.Instance.getFirstDocumentView(finalTargetDoc);
- if (containerView) {
- containerView.htmlOverlayEffect = StrCast(options?.effect?.presEffect, StrCast(options?.effect?.followLinkAnimEffect));
- containerView.textHtmlOverlay = StrCast(targetDoc.textHtml);
- DocumentManager._overlayViews.add(containerView);
- if (Doc.UnhighlightTimer) {
- Doc.AddUnHighlightWatcher(() => {
- DocumentManager.removeOverlayViews();
- containerView.htmlOverlayEffect = '';
- });
- } else setTimeout(() => (containerView.htmlOverlayEffect = ''));
- }
- }
- finished?.();
+ ) => {
+ const docContextPath = DocumentManager.GetContextPath(targetDoc, true);
+ let rootContextView = await new Promise<DocumentView>(res => {
+ const viewIndex = docContextPath.findIndex(doc => this.getDocumentView(doc));
+ if (viewIndex !== -1) return res(this.getDocumentView(docContextPath[viewIndex])!);
+ docContextPath.some(doc => TabDocView.Activate(doc)) || MainView.addDocTabFunc(docContextPath[0], options.openLocation as OpenWhere);
+ this.AddViewRenderedCb(docContextPath[0], dv => res(dv));
});
- const annoContainerView = (!wasHidden || resolvedTarget !== annotatedDoc) && annotatedDoc && this.getFirstDocumentView(annotatedDoc);
- if (annoContainerView) {
- if (annoContainerView.props.Document.layoutKey === 'layout_icon') {
- return annoContainerView.iconify(() => DocumentManager.Instance.AddViewRenderedCb(targetDoc, () => this.jumpToDocument(targetDoc, { ...options, originalTarget, toggleTarget: false }, createViewFunc, docContextPath, finished)), 30);
- }
- if (!docView && targetDoc.type !== DocumentType.MARKER) {
- annoContainerView.focus(targetDoc, {}); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below
- }
- }
- const contextDoc = docContextPath.length ? docContextPath[0] : undefined;
- const remainingDocContext = docContextPath.length ? docContextPath.slice(1) : [];
- const targetDocContext = contextDoc || annotatedDoc;
- const targetDocContextView = (targetDocContext && this.getFirstDocumentView(targetDocContext)) || (wasHidden && annoContainerView); // if we have an annotation container and the target was hidden, then try again because we just un-hid the document above
- const focusView = !docView && targetDoc.type === DocumentType.MARKER && annoContainerView ? annoContainerView : docView;
- if (focusView) {
- if (focusView.rootDoc === originalTarget) {
- if (!options.noSelect) Doc.linkFollowHighlight(focusView.rootDoc, undefined, options.effect); //TODO:glr make this a setting in PresBox
- else {
- focusView.rootDoc[AnimationSym] = options.effect;
- if (Doc.UnhighlightTimer) {
- Doc.AddUnHighlightWatcher(action(() => (focusView.rootDoc[AnimationSym] = undefined)));
- }
- }
- }
- if (options.playAudio) DocumentManager.playAudioAnno(focusView.rootDoc);
- const doFocus = (forceDidFocus: boolean) =>
- focusView.focus(originalTarget, {
- ...options,
- originalTarget,
- afterFocus: (didFocus: boolean) =>
- new Promise<ViewAdjustment>(res => {
- focusAndFinish(forceDidFocus || didFocus);
- res(ViewAdjustment.doNothing);
- }),
- });
- if (focusView.props.Document.layoutKey === 'layout_icon' && focusView.rootDoc.type !== DocumentType.SCRIPTING) {
- focusView.iconify(() => doFocus(true));
- } else {
- doFocus(false);
- }
- } else {
- if (!targetDocContext) {
- // we don't have a view and there's no context specified ... create a new view of the target using the dockFunc or default
- createViewFunc(Doc.BrushDoc(targetDoc), () => focusAndFinish(true)); // bcz: should we use this?: Doc.MakeAlias(targetDoc)));
- } else {
- // otherwise try to get a view of the context of the target
- if (targetDocContextView) {
- // we found a context view and aren't forced to create a new one ... focus on the context first..
- wasHidden = wasHidden || targetDocContextView.rootDoc.hidden;
- targetDocContextView.rootDoc.hidden = false; // make sure context isn't hidden
+ docContextPath.shift();
+ const childViewIterator = async (docView: DocumentView) => {
+ const innerDoc = docContextPath.shift();
+ return { viewSpec: innerDoc, childDocView: innerDoc && !innerDoc.unrendered ? (await docView.ComponentView?.getView?.(innerDoc)) ?? this.getDocumentView(innerDoc) : undefined };
+ };
+ const target = await this.focusViewsInPath(rootContextView, options, childViewIterator);
+ this.restoreDocView(target.viewSpec, target.docView, options, target.contextView ?? target.docView, targetDoc);
- if (targetDocContext.layoutKey === 'layout_icon') {
- return targetDocContextView.iconify(
- () => DocumentManager.Instance.AddViewRenderedCb(targetDoc, () => this.jumpToDocument(resolvedTarget ?? targetDoc, { ...options /* originalTarget - needed? */ }, createViewFunc, docContextPath, finished)),
- 30
- );
- }
+ finished?.();
+ };
- const contextFocusTime = options.zoomTime ? options.zoomTime / 2 : 500;
- const remainingFocustime = options.zoomTime ? options.zoomTime - contextFocusTime : undefined;
- targetDocContextView.setViewTransition('transform', contextFocusTime);
- // this makes focusing on contexts run in parallel -- jutmp to document below makes them run sequentially
- this.AddViewRenderedCb(targetDoc, () => this.jumpToDocument(targetDoc, { ...options, zoomTime: remainingFocustime }, createViewFunc, remainingDocContext, finished));
- targetDocContextView.props.focus(targetDocContextView.rootDoc, {
- ...options,
- zoomTime: contextFocusTime,
- // originalTarget, // needed?
- afterFocus: async () => {
- // now find the target document within the context
- if (targetDoc._timecodeToShow) {
- // if the target has a timecode, it should show up once the (presumed) video context scrubs to the display timecode;
- targetDocContext._currentTimecode = targetDoc.anchorTimecodeToShow;
- finished?.();
- } else {
- // otherwise, just look for the target document in this context view now that we've focused the context view
- if (this.getFirstDocumentView(resolvedTarget)) {
- // test again for the target view snce we presumably created the context above by focusing on it
- this.jumpToDocument(targetDoc, { ...options, zoomTime: remainingFocustime }, createViewFunc, remainingDocContext, finished);
- } else if (targetDoc.layout) {
- // there will no layout for a TEXTANCHOR type document
- createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target
- }
- }
- return ViewAdjustment.doNothing;
- },
- });
- } else {
- if (docContextPath.length && docContextPath[0]?.layoutKey === 'layout_icon') {
- Doc.deiconifyView(docContextPath[0]);
- this.jumpToDocument(targetDoc, options, createViewFunc, docContextPath, finished);
- } else {
- // there's no context view so we need to create one first and try again when that finishes
- createViewFunc(
- targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target
- () => this.jumpToDocument(targetDoc, { ...options }, (doc: Doc, finished?: () => void) => doc !== targetDocContext && createViewFunc(doc, finished), remainingDocContext, finished)
- );
- }
- }
- }
+ focusViewsInPath = async (docView: DocumentView, options: DocFocusOptions, iterator: (docView: DocumentView) => Promise<{ viewSpec: Opt<Doc>; childDocView: Opt<DocumentView> }>) => {
+ let contextView: DocumentView | undefined; // view containing context that contains target
+ while (true) {
+ docView.rootDoc.layoutKey === 'layout_icon' ? await new Promise<void>(res => docView.iconify(res)) : undefined;
+ docView.props.focus(docView.rootDoc, options); // focus the view within its container
+ const { childDocView, viewSpec } = await iterator(docView);
+ if (!childDocView) return { viewSpec: viewSpec ?? docView.rootDoc, docView, contextView };
+ contextView = docView;
+ docView = childDocView;
}
};
+
+ @action
+ restoreDocView(viewSpec: Opt<Doc>, docView: DocumentView, options: DocFocusOptions, contextView: Opt<DocumentView>, targetDoc: Doc) {
+ if (viewSpec && docView) {
+ if (docView.ComponentView instanceof FormattedTextBox) docView.ComponentView?.focus(viewSpec, options);
+ PresBox.restoreTargetDocView(docView, viewSpec, options.zoomTime ?? 500);
+ Doc.linkFollowHighlight(docView.rootDoc, undefined, options.effect);
+ if (options.playAudio) DocumentManager.playAudioAnno(docView.rootDoc);
+ if (options.toggleTarget) docView.rootDoc.hidden = !docView.rootDoc.hidden;
+ if (options.effect) docView.rootDoc[AnimationSym] = options.effect;
+
+ if (options.zoomTextSelections && Doc.UnhighlightTimer && contextView && viewSpec.textHtml) {
+ // if the docView is a text anchor, the contextView is the PDF/Web/Text doc
+ contextView.htmlOverlayEffect = StrCast(options?.effect?.presEffect, StrCast(options?.effect?.followLinkAnimEffect));
+ contextView.textHtmlOverlay = StrCast(targetDoc.textHtml);
+ DocumentManager._overlayViews.add(contextView);
+ }
+ Doc.AddUnHighlightWatcher(() => {
+ docView.rootDoc[AnimationSym] = undefined;
+ DocumentManager.removeOverlayViews();
+ contextView && (contextView.htmlOverlayEffect = '');
+ });
+ }
+ }
}
export function DocFocusOrOpen(doc: Doc, collectionDoc?: Doc) {
const cv = collectionDoc && DocumentManager.Instance.getDocumentView(collectionDoc);
const dv = DocumentManager.Instance.getDocumentView(doc, (cv?.ComponentView as CollectionFreeFormView)?.props.CollectionView);
- if (dv && Doc.AreProtosEqual(dv.props.Document, doc)) {
- dv.props.focus(dv.props.Document, { willPanZoom: true });
- Doc.linkFollowHighlight(dv?.props.Document, false);
+ if (dv) {
+ DocumentManager.Instance.showDocumentView(dv, { willZoomCentered: true });
} else {
const context = doc.context !== Doc.MyFilesystem && Cast(doc.context, Doc, null);
const showDoc = context || doc;
- CollectionDockingView.AddSplit(Doc.BestAlias(showDoc), OpenWhereMod.right) && context && setTimeout(() => DocumentManager.Instance.getDocumentView(Doc.GetProto(doc))?.focus(doc, {}));
+ DocumentManager.Instance.showDocument(Doc.BestAlias(showDoc), { openLocation: OpenWhere.addRight }, () => DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }));
}
}
ScriptingGlobals.add(DocFocusOrOpen);
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 7f0c8a3e8..559958c2b 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx
@@ -168,7 +168,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
await GooglePhotos.Export.CollectionToAlbum({ collection: importContainer });
Doc.AddDocToList(Doc.GetProto(parent.props.Document), 'data', importContainer);
!this.persistent && this.props.removeDocument && this.props.removeDocument(doc);
- DocumentManager.Instance.jumpToDocument(importContainer, { willPanZoom: true }, undefined, []);
+ DocumentManager.Instance.showDocument(importContainer, { willZoomCentered: true });
}
runInAction(() => {
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 3cdf4dbd2..9591dbed3 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -107,7 +107,8 @@ export namespace InteractionUtils {
pevents: string,
opacity: number,
nodefs: boolean,
- downHdlr?: (e: React.PointerEvent) => void
+ downHdlr?: (e: React.PointerEvent) => void,
+ mask?: boolean
) {
const pts = shape ? makePolygon(shape, points) : points;
@@ -133,6 +134,11 @@ export namespace InteractionUtils {
{/* setting the svg fill sets the arrowStart fill */}
{nodefs ? null : (
<defs>
+ {!mask ? null : (
+ <filter id={`mask${defGuid}`} x="-1" y="-1" width="500%" height="500%">
+ <feGaussianBlur result="blurOut" in="offOut" stdDeviation="5"></feGaussianBlur>
+ </filter>
+ )}
{arrowStart !== 'dot' && arrowEnd !== 'dot' ? null : (
<marker id={`dot${defGuid}`} orient="auto" markerUnits="userSpaceOnUse" refX={0} refY="0" overflow="visible">
<circle r={strokeWidth * arrowWidthFactor} fill="context-stroke" />
@@ -168,6 +174,7 @@ export namespace InteractionUtils {
style={{
// filter: drawHalo ? "url(#inkSelectionHalo)" : undefined,
fill: fill && fill !== 'transparent' ? fill : 'none',
+ filter: mask ? `url(#mask${defGuid})` : undefined,
opacity: 1.0,
// opacity: strokeWidth !== width ? 0.5 : undefined,
pointerEvents: pevents as any,
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index bbfd516da..c9a178db7 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -1,14 +1,11 @@
import { action, runInAction } from 'mobx';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
-import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
-import { CollectionDockingView } from '../views/collections/CollectionDockingView';
+import { DocumentType } from '../documents/DocumentTypes';
import { DocumentDecorations } from '../views/DocumentDecorations';
-import { LightboxView } from '../views/LightboxView';
-import { DocFocusOptions, DocumentViewSharedProps, OpenWhere, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView';
+import { DocFocusOptions, DocumentViewSharedProps, OpenWhere } from '../views/nodes/DocumentView';
import { PresBox } from '../views/nodes/trails';
import { DocumentManager } from './DocumentManager';
-import { LinkManager } from './LinkManager';
import { SelectionManager } from './SelectionManager';
import { UndoManager } from './UndoManager';
@@ -31,44 +28,10 @@ export class LinkFollower {
// depending on the followLinkLocation property of the source (or the link itself as a fallback);
public static FollowLink = (linkDoc: Opt<Doc>, sourceDoc: Doc, docViewProps: DocumentViewSharedProps, altKey: boolean) => {
const batch = UndoManager.StartBatch('follow link click');
- // open up target if it's not already in view ...
- const createViewFunc = (doc: Doc, followLoc: string, finished?: Opt<() => void>) => {
- const createTabForTarget = (didFocus: boolean) =>
- new Promise<ViewAdjustment>(res => {
- const where = LightboxView.LightboxDoc ? OpenWhere.inPlace : (StrCast(sourceDoc.followLinkLocation, followLoc) as OpenWhere);
- docViewProps.addDocTab(doc, where);
- setTimeout(() => {
- const targDocView = DocumentManager.Instance.getFirstDocumentView(doc); // get first document view available within the lightbox if that's open, or anywhere otherwise.
- if (targDocView) {
- targDocView.props.focus(doc, {
- willPan: true,
- willPanZoom: BoolCast(sourceDoc.followLinkZoom, false),
- afterFocus: (didFocus: boolean) => {
- finished?.();
- res(ViewAdjustment.resetView);
- return new Promise<ViewAdjustment>(res2 => res2(ViewAdjustment.doNothing));
- },
- });
- } else {
- finished?.();
- res(where !== OpenWhere.inPlace || BoolCast(sourceDoc.followLinkZoom) ? ViewAdjustment.resetView : ViewAdjustment.doNothing); // for 'inPlace' resetting the initial focus&zoom would negate the zoom into the target
- }
- }, 100);
- });
-
- if (!sourceDoc.followLinkZoom) {
- createTabForTarget(false);
- } else {
- // first focus & zoom onto this (the clicked document). Then execute the function to focus on the target
- docViewProps.focus(sourceDoc, { willPan: true, willPanZoom: BoolCast(sourceDoc.followLinkZoom, true), zoomTime: 1000, zoomScale: 1, afterFocus: createTabForTarget });
- }
- };
runInAction(() => (DocumentDecorations.Instance.overrideBounds = true)); // turn off decoration bounds while following links since animations may occur, and DocDecorations is based on screenToLocal which is not always an observable value
LinkFollower.traverseLink(
linkDoc,
sourceDoc,
- createViewFunc,
- docViewProps.ContainingCollectionDoc,
action(() => {
batch.end();
Doc.AddUnHighlightWatcher(action(() => (DocumentDecorations.Instance.overrideBounds = false)));
@@ -77,7 +40,7 @@ export class LinkFollower {
);
};
- public static traverseLink(link: Opt<Doc>, sourceDoc: Doc, createViewFunc: CreateViewFunc, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) {
+ public static traverseLink(link: Opt<Doc>, sourceDoc: Doc, finished?: () => void, traverseBacklink?: boolean) {
const linkDocs = link ? [link] : DocListCast(sourceDoc.links);
const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor1
const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor2
@@ -101,14 +64,17 @@ export class LinkFollower {
) as Doc;
if (target) {
const doFollow = (canToggle?: boolean) => {
+ const toggleTarget = canToggle && BoolCast(sourceDoc.followLinkToggle);
const options: DocFocusOptions = {
playAudio: BoolCast(sourceDoc.followLinkAudio),
- toggleTarget: canToggle && BoolCast(sourceDoc.followLinkToggle),
+ toggleTarget,
+ noSelect: true,
willPan: true,
- willPanZoom: BoolCast(LinkManager.getOppositeAnchor(linkDoc, target)?.followLinkZoom, false),
- zoomTime: NumCast(LinkManager.getOppositeAnchor(linkDoc, target)?.followLinkTransitionTime, 500),
+ willZoomCentered: BoolCast(sourceDoc.followLinkZoom, false),
+ zoomTime: NumCast(sourceDoc.followLinkTransitionTime, 500),
zoomScale: Cast(sourceDoc.followLinkZoomScale, 'number', null),
easeFunc: StrCast(sourceDoc.followLinkEase, 'ease') as any,
+ openLocation: StrCast(sourceDoc.followLinkLocation, OpenWhere.lightbox),
effect: sourceDoc,
originatingDoc: sourceDoc,
zoomTextSelections: BoolCast(sourceDoc.followLinkZoomText),
@@ -121,9 +87,7 @@ export class LinkFollower {
}
finished?.();
} else {
- const containerDocContext = DocumentManager.GetContextPath(target);
- const targetContexts = !sourceDoc.followLinkToOuterContext && containerDocContext.length ? [containerDocContext.lastElement()] : containerDocContext;
- DocumentManager.Instance.jumpToDocument(target, options, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, OpenWhere.inPlace), finished), targetContexts, allFinished);
+ DocumentManager.Instance.showDocument(target, options, allFinished);
}
};
let movedTarget = false;
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 7da16ca78..46d44fea4 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -48,7 +48,7 @@ export class LinkManager {
const a2 = DocCast(linkdata[1]);
a1 &&
a2 &&
- Promise.all([a1.proto, a2.proto]).then(
+ Promise.all([Doc.GetProto(a1), Doc.GetProto(a2)]).then(
action(protos => {
(protos[0] as Doc)?.[DirectLinksSym].add(link);
(protos[1] as Doc)?.[DirectLinksSym].add(link);
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index a3d76591f..6c823e80a 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -192,8 +192,16 @@ export class SettingsManager extends React.Component<{}> {
<div className="preferences-check">Show button labels</div>
</div>
<div>
- <input type="checkbox" onChange={e => FontIconBox.SetRecognizeGesturs(!FontIconBox.GetRecognizeGestures())} checked={FontIconBox.GetRecognizeGestures()} />
- <div className="preferences-check">Recognize ink Gesturs</div>
+ <input type="checkbox" onChange={e => FontIconBox.SetRecognizeGestures(!FontIconBox.GetRecognizeGestures())} checked={FontIconBox.GetRecognizeGestures()} />
+ <div className="preferences-check">Recognize ink Gestures</div>
+ </div>
+ <div>
+ <input type="checkbox" onChange={e => (Doc.UserDoc().activeInkHideTextLabels = !Doc.UserDoc().activeInkHideTextLabels)} checked={BoolCast(Doc.UserDoc().activeInkHideTextLabels)} />
+ <div className="preferences-check">Hide Labels In Ink Shapes</div>
+ </div>
+ <div>
+ <input type="checkbox" onChange={e => (Doc.UserDoc().openInkInLightbox = !Doc.UserDoc().openInkInLightbox)} checked={BoolCast(Doc.UserDoc().openInkInLightbox)} />
+ <div className="preferences-check">Open Ink Docs in Lightbox</div>
</div>
</div>
);
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 00ae85d12..3c05af4bb 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -130,29 +130,21 @@ export class SharingManager extends React.Component<{}> {
if (!this.populating && Doc.UserDoc()[Id] !== '__guest__') {
this.populating = true;
const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
- const raw = JSON.parse(userList) as User[];
- const sharingDocs: ValidatedUser[] = [];
- const evaluating = raw.map(async user => {
- const isCandidate = user.email !== Doc.CurrentUserEmail;
- if (isCandidate) {
- const sharingDoc = await DocServer.GetRefField(user.sharingDocumentId);
- const linkDatabase = await DocServer.GetRefField(user.linkDatabaseId);
+ const raw = (JSON.parse(userList) as User[]).filter(user => user.email !== 'guest' && user.email !== Doc.CurrentUserEmail);
+ const docs = await DocServer.GetRefFields(raw.reduce((list, user) => [...list, user.sharingDocumentId, user.linkDatabaseId], [] as string[]));
+ raw.map(
+ action((newUser: User) => {
+ const sharingDoc = docs[newUser.sharingDocumentId];
+ const linkDatabase = docs[newUser.linkDatabaseId];
if (sharingDoc instanceof Doc && linkDatabase instanceof Doc) {
- sharingDocs.push({ user, sharingDoc, linkDatabase, userColor: StrCast(sharingDoc.userColor) });
- }
- }
- });
- return Promise.all(evaluating).then(() => {
- runInAction(async () => {
- for (const sharer of sharingDocs) {
- if (!this.users.find(user => user.user.email === sharer.user.email)) {
- this.users.push(sharer);
+ if (!this.users.find(user => user.user.email === newUser.email)) {
+ this.users.push({ user: newUser, sharingDoc, linkDatabase, userColor: StrCast(sharingDoc.userColor) });
// LinkManager.addLinkDB(sharer.linkDatabase);
}
}
- });
- this.populating = false;
- });
+ })
+ );
+ this.populating = false;
}
};
@@ -383,7 +375,7 @@ export class SharingManager extends React.Component<{}> {
onClick={() => {
let context: Opt<CollectionView>;
if (this.targetDoc && this.targetDocView && docs.length === 1 && (context = this.targetDocView.props.ContainingCollectionView)) {
- DocumentManager.Instance.jumpToDocument(this.targetDoc, { willPanZoom: true }, undefined, [context.props.Document]);
+ DocumentManager.Instance.showDocument(this.targetDoc, { willZoomCentered: true });
}
}}
onPointerEnter={action(() => {