aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/DocumentView.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/DocumentView.tsx')
-rw-r--r--src/client/views/nodes/DocumentView.tsx318
1 files changed, 183 insertions, 135 deletions
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 42f5fb946..c0d530160 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -3,7 +3,7 @@ import * as fa from '@fortawesome/free-solid-svg-icons';
import { action, computed, runInAction, trace, observable } from "mobx";
import { observer } from "mobx-react";
import * as rp from "request-promise";
-import { Doc, DocListCast, Opt } from "../../../new_fields/Doc";
+import { Doc, DocListCast, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc";
import { Document, PositionDocument } from '../../../new_fields/documentSchemas';
import { Id } from '../../../new_fields/FieldSymbols';
import { InkTool } from '../../../new_fields/InkField';
@@ -14,7 +14,7 @@ import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
import { AudioField, ImageField, PdfField, VideoField } from '../../../new_fields/URLField';
import { TraceMobx } from '../../../new_fields/util';
import { GestureUtils } from '../../../pen-gestures/GestureUtils';
-import { emptyFunction, returnOne, returnTransparent, returnTrue, Utils, OmitKeys } from "../../../Utils";
+import { emptyFunction, returnOne, returnTransparent, returnTrue, Utils, OmitKeys, returnZero } from "../../../Utils";
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
import { DocServer } from "../../DocServer";
import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents";
@@ -47,25 +47,31 @@ import { ClientRecommender } from '../../ClientRecommender';
import { SearchUtil } from '../../util/SearchUtil';
import { RadialMenu } from './RadialMenu';
import { KeyphraseQueryView } from '../KeyphraseQueryView';
+import { undo } from 'prosemirror-history';
library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight,
fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale,
fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone);
+export type DocFocusFunc = () => boolean;
export interface DocumentViewProps {
ContainingCollectionView: Opt<CollectionView>;
ContainingCollectionDoc: Opt<Doc>;
+ FreezeDimensions?: boolean;
+ NativeWidth: () => number;
+ NativeHeight: () => number;
Document: Doc;
DataDoc?: Doc;
LayoutDoc?: () => Opt<Doc>;
LibraryPath: Doc[];
fitToBox?: boolean;
- rootSelected: () => boolean; // whether the root of a template has been selected
+ rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected
onClick?: ScriptField;
onPointerDown?: ScriptField;
onPointerUp?: ScriptField;
dropAction?: dropActionType;
dragDivName?: string;
+ nudge?: (x: number, y: number) => void;
addDocument?: (doc: Doc) => boolean;
removeDocument?: (doc: Doc) => boolean;
moveDocument?: (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
@@ -74,16 +80,14 @@ export interface DocumentViewProps {
ContentScaling: () => number;
PanelWidth: () => number;
PanelHeight: () => number;
- focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => void;
+ focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: DocFocusFunc) => void;
parentActive: (outsideReaction: boolean) => boolean;
whenActiveChanged: (isActive: boolean) => void;
bringToFront: (doc: Doc, sendToBack?: boolean) => void;
addDocTab: (doc: Doc, where: string, libraryPath?: Doc[]) => boolean;
pinToPres: (document: Doc) => void;
- zoomToScale: (scale: number) => void;
backgroundHalo?: () => boolean;
backgroundColor?: (doc: Doc) => string | undefined;
- getScale: () => number;
ChromeHeight?: () => number;
dontRegisterView?: boolean;
layoutKey?: string;
@@ -108,19 +112,20 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
public get ContentDiv() { return this._mainCont.current; }
- @computed get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
+ get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
@computed get topMost() { return this.props.renderDepth === 0; }
- @computed get nativeWidth() { return this.layoutDoc._nativeWidth || 0; }
- @computed get nativeHeight() { return this.layoutDoc._nativeHeight || 0; }
- @computed get onClickHandler() { return this.props.onClick || this.layoutDoc.onClick || this.Document.onClick; }
+ @computed get freezeDimensions() { return this.props.FreezeDimensions; }
+ @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
+ @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight, this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); }
+ @computed get onClickHandler() { return this.props.onClick || Cast(this.layoutDoc.onClick, ScriptField, null) || this.Document.onClick; }
@computed get onPointerDownHandler() { return this.props.onPointerDown ? this.props.onPointerDown : this.Document.onPointerDown; }
@computed get onPointerUpHandler() { return this.props.onPointerUp ? this.props.onPointerUp : this.Document.onPointerUp; }
+ NativeWidth = () => this.nativeWidth;
+ NativeHeight = () => this.nativeHeight;
private _firstX: number = -1;
private _firstY: number = -1;
-
-
handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
this.removeMoveListeners();
this.removeEndListeners();
@@ -193,27 +198,31 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this)));
// this._mainCont.current && (this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this)));
- !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.push(this);
+ if (!this.props.dontRegisterView) {
+ DocumentManager.Instance.DocumentViews.push(this);
+ }
}
@action
componentDidUpdate() {
- this._dropDisposer && this._dropDisposer();
- this._gestureEventDisposer && this._gestureEventDisposer();
- this.multiTouchDisposer && this.multiTouchDisposer();
- this.holdDisposer && this.holdDisposer();
- this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this)));
- this._mainCont.current && (this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this)));
- this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this)));
- this._mainCont.current && (this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this)));
+ this._dropDisposer?.();
+ this._gestureEventDisposer?.();
+ this.multiTouchDisposer?.();
+ this.holdDisposer?.();
+ if (this._mainCont.current) {
+ this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this));
+ this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this));
+ this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this));
+ this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this));
+ }
}
@action
componentWillUnmount() {
- this._dropDisposer && this._dropDisposer();
- this._gestureEventDisposer && this._gestureEventDisposer();
- this.multiTouchDisposer && this.multiTouchDisposer();
- this.holdDisposer && this.holdDisposer();
+ this._dropDisposer?.();
+ this._gestureEventDisposer?.();
+ this.multiTouchDisposer?.();
+ this.holdDisposer?.();
Doc.UnBrushDoc(this.props.Document);
if (!this.props.dontRegisterView) {
const index = DocumentManager.Instance.DocumentViews.indexOf(this);
@@ -273,38 +282,42 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
- dontDecorateSelection: any = false;
onClick = (e: React.MouseEvent | React.PointerEvent) => {
- this.dontDecorateSelection = this.props.Document.dontDecorateSelection && (!e.ctrlKey || e.button < 2);
if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick &&
(Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
let stopPropagate = true;
let preventDefault = true;
- this.props.bringToFront(this.props.Document);
+ this.props.Document.isBackground === undefined && this.props.bringToFront(this.props.Document);
if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
- const fullScreenAlias = Doc.MakeAlias(this.props.Document);
- if (StrCast(fullScreenAlias.layoutKey) !== "layout_fullScreen" && fullScreenAlias.layout_fullScreen) {
- fullScreenAlias.layoutKey = "layout_fullScreen";
+ if (!(e.nativeEvent as any).formattedHandled) {
+ const fullScreenAlias = Doc.MakeAlias(this.props.Document);
+ if (StrCast(fullScreenAlias.layoutKey) !== "layout_fullScreen" && fullScreenAlias.layout_fullScreen) {
+ fullScreenAlias.layoutKey = "layout_fullScreen";
+ }
+ UndoManager.RunInBatch(() => this.props.addDocTab(fullScreenAlias, "inTab"), "double tap");
+ SelectionManager.DeselectAll();
+ Doc.UnBrushDoc(this.props.Document);
}
- UndoManager.RunInBatch(() => this.props.addDocTab(fullScreenAlias, "inTab"), "double tap");
- SelectionManager.DeselectAll();
- Doc.UnBrushDoc(this.props.Document);
- } else if (this.onClickHandler?.script) {
- SelectionManager.DeselectAll();
- UndoManager.RunInBatch(() => this.onClickHandler!.script.run({
- this: this.props.Document,
- self: Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document,
- containingCollection: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey
- }, console.log) && !this.props.Document.dontDecorateSelection && !this.props.Document.isButton && this.select(false), "on click");
- } else if (this.Document.type === DocumentType.BUTTON) {
- UndoManager.RunInBatch(() => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY), "on button click");
- } else if (this.Document.isButton) {
- SelectionManager.SelectDoc(this, e.ctrlKey); // don't think this should happen if a button action is actually triggered.
- UndoManager.RunInBatch(() => this.buttonClick(e.altKey, e.ctrlKey), "on link button follow");
+ } else if (this.onClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
+ //SelectionManager.DeselectAll();
+ const func = () => this.onClickHandler.script.run({
+ this: this.layoutDoc,
+ self: this.rootDoc,
+ thisContainer: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey
+ }, console.log);
+ if (this.props.Document !== Doc.UserDoc().undoBtn && this.props.Document !== Doc.UserDoc().redoBtn) {
+ UndoManager.RunInBatch(func, "on click");
+ } else func();
+ } else if (this.Document["onClick-rawScript"] && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) {// bcz: hack? don't edit a script if you're clicking on a scripting box itself
+ UndoManager.RunInBatch(() => DocumentView.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick");
+ //ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY), "on button click");
+ } else if (this.Document.isLinkButton) {
+ DocListCast(this.props.Document.links).length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey);
} else {
- if (this.props.Document.isTemplateForField && !(e.ctrlKey || e.button > 0)) {
- stopPropagate = false;
+ if ((this.props.Document.onDragStart || (this.props.Document.rootDocument && this.props.Document.isTemplateForField)) && !(e.ctrlKey || e.button > 0)) { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTEmplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
+ stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
} else {
+ this.props.focus(this.props.Document, false);
SelectionManager.SelectDoc(this, e.ctrlKey);
}
preventDefault = false;
@@ -314,14 +327,26 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
- buttonClick = async (altKey: boolean, ctrlKey: boolean) => {
- const linkDocs = DocListCast(this.props.Document.links);
- if (linkDocs.length) {
- DocumentManager.Instance.FollowLink(undefined, this.props.Document,
- // open up target if it's not already in view ... by zooming into the button document first and setting flag to reset zoom afterwards
- (doc: Doc, maxLocation: string) => this.props.focus(this.props.Document, true, 1, () => this.props.addDocTab(doc, maxLocation)),
- ctrlKey, altKey, this.props.ContainingCollectionDoc);
- }
+ // follows a link - if the target is on screen, it highlights/pans to it.
+ // if the target isn't onscreen, then it will open up the target in a tab, on the right, or in place
+ // depending on the followLinkLocation property of the source (or the link itself as a fallback);
+ followLinkClick = async (altKey: boolean, ctrlKey: boolean, shiftKey: 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 targetFocusAfterDocFocus = () => {
+ const where = StrCast(this.Document.followLinkLocation) || followLoc;
+ const hackToCallFinishAfterFocus = () => {
+ finished && setTimeout(finished, 0); // finished() needs to be called right after hackToCallFinishAfterFocus(), but there's no callback for that so we use the hacky timeout.
+ return false; // we must return false here so that the zoom to the document is not reversed. If it weren't for needing to call finished(), we wouldn't need this function at all since not having it is equivalent to returning false
+ };
+ this.props.addDocTab(doc, where) && this.props.focus(doc, true, undefined, hackToCallFinishAfterFocus); // add the target and focus on it.
+ return where !== "inPlace"; // return true to reset the initial focus&zoom (return false for 'inPlace' since resetting the initial focus&zoom will negate the zoom into the target)
+ };
+ // first focus & zoom onto this (the clicked document). Then execute the function to focus on the target
+ this.props.focus(this.props.Document, true, 1, targetFocusAfterDocFocus);
+ };
+ await DocumentManager.Instance.FollowLink(undefined, this.props.Document, createViewFunc, shiftKey, this.props.ContainingCollectionDoc, batch.end, altKey ? true : undefined);
}
handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
@@ -492,8 +517,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
onPointerUp = (e: PointerEvent): void => {
this.cleanUpInteractions();
- if (this.onPointerUpHandler && this.onPointerUpHandler.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
- this.onPointerUpHandler.script.run({ this: this.Document.isTemplateForField && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log);
+ if (this.onPointerUpHandler?.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
+ this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log);
document.removeEventListener("pointerup", this.onPointerUp);
return;
}
@@ -517,7 +542,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument?.(this.props.Document); }
// applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView)
- static makeCustomViewClicked = (doc: Doc, creator: (documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc, templateSignature: string = "custom", docLayoutTemplate?: Doc) => {
+ static makeCustomViewClicked = (doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) => {
const batch = UndoManager.StartBatch("makeCustomViewClicked");
runInAction(() => {
doc.layoutKey = "layout_" + templateSignature;
@@ -527,16 +552,22 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
});
batch.end();
}
- static createCustomView = (doc: Doc, creator: (documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc, templateSignature: string = "custom", docLayoutTemplate?: Doc) => {
+ static findTemplate(templateName: string, type: string, signature: string) {
+ let docLayoutTemplate: Opt<Doc>;
const iconViews = DocListCast(Cast(Doc.UserDoc().iconViews, Doc, null)?.data);
const templBtns = DocListCast(Cast(Doc.UserDoc().templateButtons, Doc, null)?.data);
const noteTypes = DocListCast(Cast(Doc.UserDoc().noteTypes, Doc, null)?.data);
- const allTemplates = iconViews.concat(templBtns).concat(noteTypes).map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc).filter(doc => doc.isTemplateDoc);
- const templateName = templateSignature.replace(/\(.*\)/, "");
+ const clickFuncs = DocListCast(Cast(Doc.UserDoc().clickFuncs, Doc, null)?.data);
+ const allTemplates = iconViews.concat(templBtns).concat(noteTypes).concat(clickFuncs).map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc).filter(doc => doc.isTemplateDoc);
// bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document. but type is not reliable and there could be other types of template searches so this should be generalized
// first try to find a template that matches the specific document type (<typeName>_<templateName>). otherwise, fallback to a general match on <templateName>
- !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === doc.type + "_" + templateName && (docLayoutTemplate = tempDoc));
+ !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === type + "_" + templateName && (docLayoutTemplate = tempDoc));
!docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc));
+ return docLayoutTemplate;
+ }
+ static createCustomView = (doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) => {
+ const templateName = templateSignature.replace(/\(.*\)/, "");
+ docLayoutTemplate = docLayoutTemplate || DocumentView.findTemplate(templateName, StrCast(doc.type), templateSignature);
const customName = "layout_" + templateSignature;
const _width = NumCast(doc._width);
@@ -555,20 +586,31 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
} else if (doc.data instanceof ImageField) {
fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options);
}
- const docTemplate = docLayoutTemplate || creator(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) });
+ const docTemplate = docLayoutTemplate || creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) });
- fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(docTemplate));
- Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined);
+ fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate);
+ docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined);
}
@undoBatch
- toggleButtonBehavior = (): void => {
- if (this.Document.isButton || this.Document.onClick || this.Document.ignoreClick) {
- this.Document.isButton = false;
+ toggleLinkButtonBehavior = (): void => {
+ if (this.Document.isLinkButton || this.Document.onClick || this.Document.ignoreClick) {
+ this.Document.isLinkButton = false;
this.Document.ignoreClick = false;
this.Document.onClick = undefined;
} else {
- this.Document.isButton = true;
+ this.Document.isLinkButton = true;
+ this.Document.followLinkLocation = undefined;
+ }
+ }
+
+ @undoBatch
+ toggleFollowInPlace = (): void => {
+ if (this.Document.isLinkButton) {
+ this.Document.isLinkButton = false;
+ } else {
+ this.Document.isLinkButton = true;
+ this.Document.followLinkLocation = "inPlace";
}
}
@@ -613,10 +655,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
makeIntoPortal = async () => {
const portalLink = DocListCast(this.Document.links).find(d => d.anchor1 === this.props.Document);
if (!portalLink) {
- const portal = Docs.Create.FreeformDocument([], { _width: (this.layoutDoc._width || 0) + 10, _height: this.layoutDoc._height || 0, title: StrCast(this.props.Document.title) + ".portal" });
+ const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), title: StrCast(this.props.Document.title) + ".portal" });
DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to");
}
- this.Document.isButton = true;
+ this.Document.isLinkButton = true;
}
@undoBatch
@@ -630,8 +672,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
@action
- makeBackground = (): void => {
- this.Document.isBackground = !this.Document.isBackground;
+ toggleBackground = (temporary: boolean): void => {
+ this.Document.overflow = temporary ? "visible" : "hidden";
+ this.Document.isBackground = !temporary ? !this.Document.isBackground : (this.Document.isBackground ? undefined : true);
this.Document.isBackground && this.props.bringToFront(this.Document, true);
}
@@ -648,27 +691,30 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
@action
- onContextMenu = async (e: React.MouseEvent): Promise<void> => {
+ onContextMenu = async (e: React.MouseEvent | Touch): Promise<void> => {
// the touch onContextMenu is button 0, the pointer onContextMenu is button 2
- if (e.button === 0 && !e.ctrlKey) {
- e.preventDefault();
- return;
- }
- e.persist();
- e?.stopPropagation();
- if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3 ||
- e.isDefaultPrevented()) {
+ if (!(e instanceof Touch)) {
+ if (e.button === 0 && !e.ctrlKey) {
+ e.preventDefault();
+ return;
+ }
+ e.persist();
+ e?.stopPropagation();
+
+ if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3 ||
+ e.isDefaultPrevented()) {
+ e.preventDefault();
+ return;
+ }
e.preventDefault();
- return;
}
- e.preventDefault();
const cm = ContextMenu.Instance;
const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
const existing = cm.findByDescription("Layout...");
const layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : [];
- layoutItems.push({ description: this.Document.isBackground ? "As Foreground" : "As Background", event: this.makeBackground, icon: this.Document.lockedPosition ? "unlock" : "lock" });
+ layoutItems.push({ description: this.Document.isBackground ? "As Foreground" : "As Background", event: (e) => this.toggleBackground(false), icon: this.Document.lockedPosition ? "unlock" : "lock" });
layoutItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
layoutItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
@@ -698,8 +744,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(this, "${this.props.Document.layoutKey}")`), icon: "window-restore" });
onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
- onClicks.push({ description: this.Document.isButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.toggleButtonBehavior, icon: "concierge-bell" });
- onClicks.push({ description: "Edit onClick Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", obj.x, obj.y) });
+ onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: this.toggleFollowInPlace, icon: "concierge-bell" });
+ onClicks.push({ description: this.Document.isLinkButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.toggleLinkButtonBehavior, icon: "concierge-bell" });
+ onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocumentView.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" });
!existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
const funcs: ContextMenuProps[] = [];
@@ -741,7 +788,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
recommender_subitems.push({
description: "Internal recommendations",
- event: () => this.recommender(e),
+ event: () => this.recommender(),
icon: "brain"
});
@@ -802,7 +849,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
icon: "external-link-alt"
});
- if (!this.topMost) {
+ if (!this.topMost && !(e instanceof Touch)) {
// DocumentViews should stop propagation of this event
e.stopPropagation();
}
@@ -820,7 +867,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
});
}
- recommender = async (e: React.MouseEvent) => {
+ recommender = async () => {
if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" });
const documents: Doc[] = [];
const allDocs = await SearchUtil.GetAllDocs();
@@ -831,7 +878,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
await Promise.all(allDocs.map((doc: Doc) => {
let isMainDoc: boolean = false;
const dataDoc = Doc.GetProto(doc);
- if (doc.type === DocumentType.TEXT) {
+ if (doc.type === DocumentType.RTF) {
if (dataDoc === Doc.GetProto(this.props.Document)) {
isMainDoc = true;
}
@@ -934,37 +981,41 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const fallback = Cast(this.props.Document.layoutKey, "string");
return typeof fallback === "string" ? fallback : "layout";
}
- rootSelected = () => {
- return this.isSelected(false) || (this.props.Document.forceActive && this.props.rootSelected?.() ? true : false);
+ rootSelected = (outsideReaction?: boolean) => {
+ return this.isSelected(outsideReaction) || (this.rootDoc && this.props.rootSelected?.(outsideReaction));
}
childScaling = () => (this.layoutDoc._fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling());
+ panelWidth = () => this.props.PanelWidth();
+ panelHeight = () => this.props.PanelHeight();
+ screenToLocalTransform = () => this.props.ScreenToLocalTransform();
@computed get contents() {
TraceMobx();
return (<DocumentContentsView ContainingCollectionView={this.props.ContainingCollectionView}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+ NativeWidth={this.NativeWidth}
+ NativeHeight={this.NativeHeight}
Document={this.props.Document}
DataDoc={this.props.DataDoc}
LayoutDoc={this.props.LayoutDoc}
makeLink={this.makeLink}
rootSelected={this.rootSelected}
+ dontRegisterView={this.props.dontRegisterView}
fitToBox={this.props.fitToBox}
LibraryPath={this.props.LibraryPath}
addDocument={this.props.addDocument}
removeDocument={this.props.removeDocument}
moveDocument={this.props.moveDocument}
- ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+ ScreenToLocalTransform={this.screenToLocalTransform}
renderDepth={this.props.renderDepth}
- PanelWidth={this.props.PanelWidth}
- PanelHeight={this.props.PanelHeight}
+ PanelWidth={this.panelWidth}
+ PanelHeight={this.panelHeight}
focus={this.props.focus}
parentActive={this.props.parentActive}
whenActiveChanged={this.props.whenActiveChanged}
bringToFront={this.props.bringToFront}
addDocTab={this.props.addDocTab}
pinToPres={this.props.pinToPres}
- zoomToScale={this.props.zoomToScale}
backgroundColor={this.props.backgroundColor}
- getScale={this.props.getScale}
ContentScaling={this.childScaling}
ChromeHeight={this.chromeHeight}
isSelected={this.isSelected}
@@ -974,7 +1025,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
linkEndpoint = (linkDoc: Doc) => Doc.LinkEndpoint(linkDoc, this.props.Document);
- // used to decide whether a link document should be created or not.
+ // used to decide whether a link anchor view should be created or not.
// if it's a tempoarl link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here.
// would be good to generalize this some way.
isNonTemporalLink = (linkDoc: Doc) => {
@@ -983,36 +1034,44 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
return anchor.type === DocumentType.AUDIO && NumCast(ept) ? false : true;
}
- @observable _link: Opt<Doc>;
- makeLink = () => {
- return this._link;
- }
+ @observable _link: Opt<Doc>; // see DocumentButtonBar for explanation of how this works
+ makeLink = () => this._link; // pass the link placeholde to child views so they can react to make a specialized anchor. This is essentially a function call to the descendants since the value of the _link variable will immediately get set back to undefined.
+
+ @undoBatch
+ hideLinkAnchor = (doc: Doc) => doc.hidden = true
+ anchorPanelWidth = () => this.props.PanelWidth() || 1;
+ anchorPanelHeight = () => this.props.PanelHeight() || 1;
+ @computed get anchors() {
+ TraceMobx();
+ return this.layoutDoc.presBox ? (null) : DocListCast(this.Document.links).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
+ <div className="documentView-linkAnchorBoxWrapper" key={d[Id]}>
+ <DocumentView {...this.props}
+ Document={d}
+ ContainingCollectionView={this.props.ContainingCollectionView}
+ ContainingCollectionDoc={this.props.Document} // bcz: hack this.props.Document is not a collection Need a better prop for passing the containing document to the LinkAnchorBox
+ PanelWidth={this.anchorPanelWidth}
+ PanelHeight={this.anchorPanelHeight}
+ layoutKey={this.linkEndpoint(d)}
+ ContentScaling={returnOne}
+ backgroundColor={returnTransparent}
+ removeDocument={this.hideLinkAnchor}
+ LayoutDoc={undefined}
+ />
+ </div>);
+ }
@computed get innards() {
TraceMobx();
- if (!this.props.PanelWidth()) {
- return <div style={{ display: "flex", overflow: "hidden" }}>
+ if (!this.props.PanelWidth()) { // this happens when the document is a tree view label
+ return <div className="documentView-linkAnchorBoxAnchor" >
{StrCast(this.props.Document.title)}
- {this.Document.links && DocListCast(this.Document.links).filter(d => !d.hidden).filter(this.isNonTemporalLink).map((d, i) =>
- <div className="documentView-docuLinkWrapper" style={{ position: "absolute", top: 0, left: 0 }} key={`${d[Id]}`}>
- <DocumentView {...this.props}
- Document={d}
- ContainingCollectionDoc={this.props.Document}
- PanelWidth={returnOne} PanelHeight={returnOne}
- layoutKey={this.linkEndpoint(d)} ContentScaling={returnOne}
- backgroundColor={returnTransparent}
- removeDocument={undoBatch(doc => doc.hidden = true)} />
- </div>)}
+ {this.anchors}
</div>;
}
const showTitle = StrCast(this.layoutDoc._showTitle);
const showTitleHover = StrCast(this.layoutDoc._showTitleHover);
const showCaption = StrCast(this.layoutDoc._showCaption);
const showTextTitle = showTitle && (StrCast(this.layoutDoc.layout).indexOf("PresBox") !== -1 || StrCast(this.layoutDoc.layout).indexOf("FormattedTextBox") !== -1) ? showTitle : undefined;
- const searchHighlight = (!this.Document.searchFields ? (null) :
- <div className="documentView-searchHighlight">
- {this.Document.searchFields}
- </div>);
const captionView = (!showCaption ? (null) :
<div className="documentView-captionWrapper">
<DocumentContentsView {...OmitKeys(this.props, ['children']).omit}
@@ -1039,32 +1098,21 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
/>
</div>);
return <>
- {this.Document.links && DocListCast(this.Document.links).filter(d => !d.hidden).filter(this.isNonTemporalLink).map((d, i) =>
- <div className="documentView-docuLinkWrapper" key={`${d[Id]}`}>
- <DocumentView {...this.props} ContentScaling={returnOne} ContainingCollectionDoc={this.props.Document} Document={d} layoutKey={this.linkEndpoint(d)} backgroundColor={returnTransparent} removeDocument={undoBatch(doc => doc.hidden = true)} />
- </div>)}
+ {this.anchors}
{!showTitle && !showCaption ?
- this.Document.searchFields ?
- (<div className="documentView-searchWrapper">
- {this.contents}
- {searchHighlight}
- </div>)
- :
- this.contents
- :
+ this.contents :
<div className="documentView-styleWrapper" >
<div className="documentView-styleContentWrapper" style={{ height: showTextTitle ? `calc(100% - ${this.chromeHeight()}px)` : "100%", top: showTextTitle ? this.chromeHeight() : undefined }}>
{this.contents}
</div>
{titleView}
{captionView}
- {searchHighlight}
</div>
}
</>;
}
@computed get ignorePointerEvents() {
- return (this.Document.isBackground && !this.isSelected()) || this.props.layoutKey?.includes("layout_key") || (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
+ return (this.Document.isBackground && !this.isSelected() && !SelectionManager.GetIsDragging()) || this.props.layoutKey?.includes("layout_key") || (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
}
@observable _animate = 0;
@@ -1110,12 +1158,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
background: finalColor,
opacity: this.Document.opacity
}}>
- {this.Document.isBackground ? <div className="documentView-lock"> <FontAwesomeIcon icon="unlock" size="lg" /> </div> : (null)}
{this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <>
{this.innards}
<div className="documentView-contentBlocker" />
</> :
this.innards}
+ {(this.Document.isBackground !== undefined || this.isSelected(false)) && this.props.renderDepth > 0 ? <div className="documentView-lock" onClick={() => this.toggleBackground(true)}> <FontAwesomeIcon icon={this.Document.isBackground ? "unlock" : "lock"} size="lg" /> </div> : (null)}
</div>;
{ this._showKPQuery ? <KeyphraseQueryView keyphrases={this._queries}></KeyphraseQueryView> : undefined; }
}