diff options
Diffstat (limited to 'src/client/views/nodes/DocumentView.tsx')
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 577 |
1 files changed, 182 insertions, 395 deletions
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 09eeaee36..15cf9556b 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,35 +1,33 @@ -import { library } from '@fortawesome/fontawesome-svg-core'; -import * as fa from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import * as rp from "request-promise"; -import { Doc, DocListCast, HeightSym, Opt, WidthSym, DataSym, AclSym, AclReadonly, AclPrivate } from "../../../fields/Doc"; +import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; import { Document } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { listSpec } from "../../../fields/Schema"; import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types"; -import { TraceMobx } from '../../../fields/util'; +import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types"; +import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util'; +import { MobileInterface } from '../../../mobile/MobileInterface'; import { GestureUtils } from '../../../pen-gestures/GestureUtils'; -import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils, emptyPath } from "../../../Utils"; +import { emptyFunction, emptyPath, OmitKeys, returnOne, returnTransparent, Utils } from "../../../Utils"; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { ClientRecommender } from '../../ClientRecommender'; import { Docs, DocUtils } from "../../documents/Documents"; import { DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from "../../util/DocumentManager"; -import { SnappingManager } from '../../util/SnappingManager'; import { DragManager, dropActionType } from "../../util/DragManager"; import { InteractionUtils } from '../../util/InteractionUtils'; +import { LinkManager } from '../../util/LinkManager'; import { Scripting } from '../../util/Scripting'; import { SearchUtil } from '../../util/SearchUtil'; import { SelectionManager } from "../../util/SelectionManager"; import SharingManager from '../../util/SharingManager'; +import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from "../../util/Transform"; import { undoBatch, UndoManager } from "../../util/UndoManager"; -import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionView, CollectionViewType } from '../collections/CollectionView'; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from '../ContextMenuItem'; @@ -37,15 +35,13 @@ import { DocComponent } from "../DocComponent"; import { EditableView } from '../EditableView'; import { KeyphraseQueryView } from '../KeyphraseQueryView'; import { DocumentContentsView } from "./DocumentContentsView"; +import { DocumentLinksButton } from './DocumentLinksButton'; import "./DocumentView.scss"; import { LinkAnchorBox } from './LinkAnchorBox'; +import { LinkDescriptionPopup } from './LinkDescriptionPopup'; import { RadialMenu } from './RadialMenu'; +import { TaskCompletionBox } from './TaskCompletedBox'; import React = require("react"); -import { DocumentLinksButton } from './DocumentLinksButton'; - -library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight, - fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale, - fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone, fa.faKeyboard, fa.faQuestion); export type DocFocusFunc = () => boolean; @@ -65,10 +61,10 @@ export interface DocumentViewProps { ignoreAutoHeight?: boolean; contextMenuItems?: () => { script: ScriptField, label: string }[]; rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected - onClick?: ScriptField; - onDoubleClick?: ScriptField; - onPointerDown?: ScriptField; - onPointerUp?: ScriptField; + onClick?: () => ScriptField; + onDoubleClick?: () => ScriptField; + onPointerDown?: () => ScriptField; + onPointerUp?: () => ScriptField; treeViewDoc?: Doc; dropAction?: dropActionType; dragDivName?: string; @@ -102,36 +98,37 @@ export interface DocumentViewProps { @observer export class DocumentView extends DocComponent<DocumentViewProps, Document>(Document) { + @observable _animateScalingTo = 0; private _downX: number = 0; private _downY: number = 0; + private _firstX: number = -1; + private _firstY: number = -1; private _lastTap: number = 0; private _doubleTap = false; private _mainCont = React.createRef<HTMLDivElement>(); private _dropDisposer?: DragManager.DragDropDisposer; private _showKPQuery: boolean = false; private _queries: string = ""; - private _gestureEventDisposer?: GestureUtils.GestureEventDisposer; private _titleRef = React.createRef<EditableView>(); - - protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; - private holdDisposer?: InteractionUtils.MultiTouchEventDisposer; + private _gestureEventDisposer?: GestureUtils.GestureEventDisposer; + private _holdDisposer?: InteractionUtils.MultiTouchEventDisposer; + protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive public get ContentDiv() { return this._mainCont.current; } - get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); } + private get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); } @computed get topMost() { return this.props.renderDepth === 0; } @computed get freezeDimensions() { return this.props.FreezeDimensions; } @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); } @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.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null)); } - @computed get onDoubleClickHandler() { return this.props.onDoubleClick || Cast(this.layoutDoc.onDoubleClick, ScriptField, null) || this.Document.onDoubleClick; } - @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; } + @computed get onClickHandler() { return this.props.onClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null)); } + @computed get onDoubleClickHandler() { return this.props.onDoubleClick?.() ?? (Cast(this.layoutDoc.onDoubleClick, ScriptField, null) ?? this.Document.onDoubleClick); } + @computed get onPointerDownHandler() { return this.props.onPointerDown?.() ?? ScriptCast(this.Document.onPointerDown); } + @computed get onPointerUpHandler() { return this.props.onPointerUp?.() ?? ScriptCast(this.Document.onPointerUp); } NativeWidth = () => this.nativeWidth; NativeHeight = () => this.nativeHeight; - - private _firstX: number = -1; - private _firstY: number = -1; + onClickFunc = () => this.onClickHandler; + onDoubleClickFunc = () => this.onDoubleClickHandler; handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => { this.removeMoveListeners(); @@ -146,11 +143,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu this._firstX = pt.pageX; this._firstY = pt.pageY; } - } handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => { - const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1]; if (this._firstX === -1 || this._firstY === -1) { @@ -181,10 +176,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1]; RadialMenu.Instance.openMenu(pt.pageX - 15, pt.pageY - 15); - RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "map-pin", selected: -1 }); - RadialMenu.Instance.addItem({ description: "Delete this document", event: () => { this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu(); }, icon: "layer-group", selected: -1 }); - RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "onRight"), icon: "trash", selected: -1 }); - RadialMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "folder", selected: -1 }); + // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "map-pin", selected: -1 }); + RadialMenu.Instance.addItem({ description: "Delete", event: () => { this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu(); }, icon: "external-link-square-alt", selected: -1 }); + // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "onRight"), icon: "trash", selected: -1 }); + RadialMenu.Instance.addItem({ description: "Pin", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 }); + RadialMenu.Instance.addItem({ description: "Open", event: () => MobileInterface.Instance.handleClick(this.props.Document), icon: "trash", selected: -1 }); SelectionManager.DeselectAll(); } @@ -193,7 +189,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu componentDidMount() { this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document)); this._mainCont.current && (this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this))); - this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this))); + this._mainCont.current && (this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this))); // this._mainCont.current && (this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this))); if (!this.props.dontRegisterView) { @@ -205,13 +201,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu componentDidUpdate() { this._dropDisposer?.(); this._gestureEventDisposer?.(); - this.multiTouchDisposer?.(); - this.holdDisposer?.(); + this._multiTouchDisposer?.(); + this._holdDisposer?.(); if (this._mainCont.current) { this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document); this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this)); - this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this)); - this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this)); + this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this)); + this._holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this)); } } @@ -219,8 +215,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu componentWillUnmount() { this._dropDisposer?.(); this._gestureEventDisposer?.(); - this.multiTouchDisposer?.(); - this.holdDisposer?.(); + this._multiTouchDisposer?.(); + this._holdDisposer?.(); Doc.UnBrushDoc(this.props.Document); if (!this.props.dontRegisterView) { const index = DocumentManager.Instance.DocumentViews.indexOf(this); @@ -235,27 +231,35 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu dragData.offset = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top); dragData.dropAction = dropAction; dragData.removeDocument = this.props.removeDocument; - dragData.moveDocument = this.props.moveDocument;// this.layoutDoc.onDragStart ? undefined : this.props.moveDocument; + dragData.moveDocument = this.props.moveDocument; dragData.dragDivName = this.props.dragDivName; dragData.treeViewDoc = this.props.treeViewDoc; DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.layoutDoc.onDragStart }); } } - public static FloatDoc(topDocView: DocumentView, x: number, y: number) { + @undoBatch @action + public static FloatDoc(topDocView: DocumentView, x?: number, y?: number) { const topDoc = topDocView.props.Document; - const de = new DragManager.DocumentDragData([topDoc]); - de.dragDivName = topDocView.props.dragDivName; - de.moveDocument = topDocView.props.moveDocument; - undoBatch(action(() => topDoc.z = topDoc.z ? 0 : 1))(); - setTimeout(() => { - const newDocView = DocumentManager.Instance.getDocumentView(topDoc); - if (newDocView) { - const contentDiv = newDocView.ContentDiv!; - const xf = contentDiv.getBoundingClientRect(); - DragManager.StartDocumentDrag([contentDiv], de, x, y, { offsetX: x - xf.left, offsetY: y - xf.top, hideSource: true }); + const container = topDocView.props.ContainingCollectionView; + if (container) { + SelectionManager.DeselectAll(); + if (topDoc.z && (x === undefined && y === undefined)) { + const spt = container.screenToLocalTransform().inverse().transformPoint(NumCast(topDoc.x), NumCast(topDoc.y)); + topDoc.z = 0; + topDoc.x = spt[0]; + topDoc.y = spt[1]; + topDocView.props.removeDocument?.(topDoc); + topDocView.props.addDocTab(topDoc, "inParent"); + } else { + const spt = topDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); + const fpt = container.screenToLocalTransform().transformPoint(x !== undefined ? x : spt[0], y !== undefined ? y : spt[1]); + topDoc.z = 1; + topDoc.x = fpt[0]; + topDoc.y = fpt[1]; } - }, 0); + setTimeout(() => SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(topDoc, container)!, false), 0); + } } onKeyDown = (e: React.KeyboardEvent) => { @@ -288,47 +292,45 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu let stopPropagate = true; let preventDefault = true; !this.props.Document.isBackground && this.props.bringToFront(this.props.Document); - if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click + if (this._doubleTap && this.props.renderDepth) {// && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click if (!(e.nativeEvent as any).formattedHandled) { if (this.onDoubleClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) { // bcz: hack? don't execute script if you're clicking on a scripting box itself const func = () => this.onDoubleClickHandler.script.run({ this: this.layoutDoc, self: this.rootDoc, - thisContainer: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey + thisContainer: this.props.ContainingCollectionDoc, + shiftKey: e.shiftKey }, console.log); func(); } else { UndoManager.RunInBatch(() => { + let fullScreenDoc = this.props.Document; if (StrCast(this.props.Document.layoutKey) !== "layout_fullScreen" && this.props.Document.layout_fullScreen) { - const fullScreenAlias = Doc.MakeAlias(this.props.Document); - fullScreenAlias.layoutKey = "layout_fullScreen"; - this.props.addDocTab(fullScreenAlias, "inTab"); - } else { - this.props.addDocTab(this.props.Document, "inTab"); + fullScreenDoc = Doc.MakeAlias(this.props.Document); + fullScreenDoc.layoutKey = "layout_fullScreen"; } + this.props.addDocTab(fullScreenDoc, "inTab"); }, "double tap"); SelectionManager.DeselectAll(); Doc.UnBrushDoc(this.props.Document); } } } else if (this.onClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) { // bcz: hack? don't execute script if you're clicking on a scripting box itself - //SelectionManager.DeselectAll(); const func = () => this.onClickHandler.script.run({ this: this.layoutDoc, self: this.rootDoc, - thisContainer: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey + thisContainer: this.props.ContainingCollectionDoc, + shiftKey: e.shiftKey }, console.log); if (this.props.Document !== Doc.UserDoc()["dockedBtn-undo"] && this.props.Document !== Doc.UserDoc()["dockedBtn-redo"]) { UndoManager.RunInBatch(func, "on click"); } else func(); } else if (this.Document["onClick-rawScript"] && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) {// bcz: hack? don't edit a script if you're clicking on a scripting box itself - const alias = Doc.MakeAlias(this.props.Document); - DocUtils.makeCustomViewClicked(alias, undefined, "onClick"); - this.props.addDocTab(alias, "onRight"); - } else if (this.props.Document.links && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) { - DocListCast(this.props.Document.links).length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey); + this.props.addDocTab(DocUtils.makeCustomViewClicked(Doc.MakeAlias(this.props.Document), undefined, "onClick"), "onRight"); + } else if (this.allLinks && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) { + this.allLinks.length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey); } else { - if ((this.layoutDoc.onDragStart || (this.props.Document.rootDocument)) && !(e.ctrlKey || e.button > 0)) { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTEmplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part + if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template } else { SelectionManager.SelectDoc(this, e.ctrlKey || e.shiftKey); @@ -345,12 +347,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu // 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 ... + // 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. + 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, BoolCast(this.Document.followLinkZoom, true), undefined, hackToCallFinishAfterFocus); // add the target and focus on it. @@ -400,7 +402,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers e.preventDefault(); - } } @@ -438,12 +439,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const oldPoint2 = this.prevPoints.get(pt2.identifier); const pinching = InteractionUtils.Pinning(pt1, pt2, oldPoint1!, oldPoint2!); if (pinching !== 0 && oldPoint1 && oldPoint2) { - // let dX = (Math.min(pt1.clientX, pt2.clientX) - Math.min(oldPoint1.clientX, oldPoint2.clientX)); - // let dY = (Math.min(pt1.clientY, pt2.clientY) - Math.min(oldPoint1.clientY, oldPoint2.clientY)); - // let dX = Math.sign(Math.abs(pt1.clientX - oldPoint1.clientX) - Math.abs(pt2.clientX - oldPoint2.clientX)); - // let dY = Math.sign(Math.abs(pt1.clientY - oldPoint1.clientY) - Math.abs(pt2.clientY - oldPoint2.clientY)); - // let dW = -dX; - // let dH = -dY; const dW = (Math.abs(pt1.clientX - pt2.clientX) - Math.abs(oldPoint1.clientX - oldPoint2.clientX)); const dH = (Math.abs(pt1.clientY - pt2.clientY) - Math.abs(oldPoint1.clientY - oldPoint2.clientY)); const dX = -1 * Math.sign(dW); @@ -526,7 +521,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } onPointerMove = (e: PointerEvent): void => { - if ((e as any).formattedHandled) { e.stopPropagation(); return; } if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) return; if (e.cancelBubble && this.active) { @@ -547,17 +541,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu onPointerUp = (e: PointerEvent): void => { this.cleanUpInteractions(); + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); if (this.onPointerUpHandler?.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) { this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log); - document.removeEventListener("pointerup", this.onPointerUp); - return; + } else { + this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2); + this._lastTap = Date.now(); } - - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2); - this._lastTap = Date.now(); } onGesture = (e: Event, ge: GestureUtils.GestureEvent) => { @@ -579,75 +571,56 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } } - - @undoBatch - toggleLinkButtonBehavior = (): void => { - if (this.Document.isLinkButton || this.onClickHandler || this.Document.ignoreClick) { - this.Document.isLinkButton = false; - const first = DocListCast(this.Document.links).find(d => d instanceof Doc); - first && (first.hidden = false); - this.Document.ignoreClick = false; - this.Document.onClick = this.layoutDoc.onClick = undefined; - } else { - this.Document.isLinkButton = true; - const first = DocListCast(this.Document.links).find(d => d instanceof Doc); - first && (first.hidden = true); - this.Document.followLinkZoom = false; - this.Document.followLinkLocation = undefined; - } - } - - @undoBatch - toggleFollowInPlace = (): void => { - if (this.Document.isLinkButton) { - this.Document.isLinkButton = false; - const first = DocListCast(this.Document.links).find(d => d instanceof Doc); - first && (first.hidden = false); + @undoBatch @action + toggleFollowLink = (location: Opt<string>, zoom: boolean, setPushpin: boolean): void => { + this.Document.ignoreClick = false; + this.Document.isLinkButton = !this.Document.isLinkButton; + setPushpin && (this.Document.isPushpin = this.Document.isLinkButton); + if (this.Document.isLinkButton && !this.onClickHandler) { + this.Document.followLinkZoom = zoom; + this.Document.followLinkLocation = location; } else { - this.Document.isLinkButton = true; - const first = DocListCast(this.Document.links).find(d => d instanceof Doc); - first && (first.hidden = true); - this.Document.followLinkZoom = true; - this.Document.followLinkLocation = "inPlace"; - } - } - - @undoBatch - toggleFollowOnRight = (): void => { - if (this.Document.isLinkButton) { - this.Document.isLinkButton = false; - const first = DocListCast(this.Document.links).find(d => d instanceof Doc); - first && (first.hidden = false); - } else { - this.Document.isLinkButton = true; - this.Document.followLinkZoom = false; - const first = DocListCast(this.Document.links).find(d => d instanceof Doc); - first && (first.hidden = true); - this.Document.followLinkLocation = "onRight"; + this.Document.onClick = this.layoutDoc.onClick = undefined; } } - @undoBatch - @action + @undoBatch @action drop = async (e: Event, de: DragManager.DropEvent) => { if (this.props.Document === Doc.UserDoc().activeWorkspace) { alert("linking to document tabs not yet supported. Drop link on document content."); return; } + const makeLink = action((linkDoc: Doc) => { + LinkManager.currentLink = linkDoc; + + TaskCompletionBox.textDisplayed = "Link Created"; + TaskCompletionBox.popupX = de.x; + TaskCompletionBox.popupY = de.y - 33; + TaskCompletionBox.taskCompleted = true; + + LinkDescriptionPopup.popupX = de.x; + LinkDescriptionPopup.popupY = de.y; + LinkDescriptionPopup.descriptionPopup = true; + + setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2500); + }); if (de.complete.annoDragData) { /// this whole section for handling PDF annotations looks weird. Need to rethink this to make it cleaner e.stopPropagation(); de.complete.annoDragData.linkedToDoc = true; - DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document }, "link"); + const linkDoc = DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document }, "link"); + linkDoc && makeLink(linkDoc); } if (de.complete.linkDragData) { e.stopPropagation(); - // const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true); - // const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView); - de.complete.linkDragData.linkSourceDocument !== this.props.Document && - (de.complete.linkDragData.linkDocument = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument }, - { doc: this.props.Document }, `link`)); // TODODO this is where in text links get passed + const linkSource = de.complete.linkDragData.linkSourceDocument; + if (linkSource !== this.props.Document) { + const linkDoc = DocUtils.MakeLink({ doc: linkSource }, { doc: this.props.Document }, `link`); + linkSource !== this.props.Document && (de.complete.linkDragData.linkDocument = linkDoc); // TODODO this is where in text links get passed + linkDoc && makeLink(linkDoc); + } + } } @@ -659,8 +632,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu @undoBatch @action + toggleLockPosition = (): void => { + this.Document.lockedPosition = this.Document.lockedPosition ? undefined : true; + } + + @undoBatch + @action makeIntoPortal = async () => { - const portalLink = DocListCast(this.Document.links).find(d => d.anchor1 === this.props.Document); + const portalLink = this.allLinks.find(d => d.anchor1 === this.props.Document); if (!portalLink) { 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"); @@ -671,9 +650,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu @undoBatch @action - toggleBackground = (temporary: boolean): void => { - this.Document._overflow = temporary ? "visible" : "hidden"; - this.Document.isBackground = !temporary ? !this.Document.isBackground : (this.Document.isBackground ? undefined : true); + toggleBackground = () => { + this.Document.isBackground = (this.Document.isBackground ? undefined : true); + this.Document._overflow = this.Document.isBackground ? "visible" : undefined; if (this.Document.isBackground) { this.props.bringToFront(this.props.Document, true); this.props.Document[DataSym][Doc.LayoutFieldKey(this.Document) + "-nativeWidth"] = this.Document[WidthSym](); @@ -681,40 +660,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } } - @undoBatch - @action - toggleLockPosition = (): void => { - this.Document.lockedPosition = this.Document.lockedPosition ? undefined : true; - } - - @undoBatch - @action - setAcl = (acl: "readOnly" | "addOnly" | "ownerOnly" | "write") => { - this.dataDoc.ACL = this.props.Document.ACL = acl; - DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => { - if (d.author === Doc.CurrentUserEmail) d.ACL = acl; - const data = d[DataSym]; - if (data && data.author === Doc.CurrentUserEmail) data.ACL = acl; - }); - } - @undoBatch - @action - testAcl = (acl: "readOnly" | "addOnly" | "ownerOnly" | "write") => { - this.dataDoc.author = this.props.Document.author = "ADMIN"; - this.dataDoc.ACL = this.props.Document.ACL = acl; - DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => { - if (d.author === Doc.CurrentUserEmail) { - d.author = "ADMIN"; - d.ACL = acl; - } - const data = d[DataSym]; - if (data && data.author === Doc.CurrentUserEmail) { - data.author = "ADMIN"; - data.ACL = acl; - } - }); - } - @action onContextMenu = async (e: React.MouseEvent | Touch): Promise<void> => { // the touch onContextMenu is button 0, the pointer onContextMenu is button 2 @@ -724,17 +669,18 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu return; } e.persist(); - e?.stopPropagation(); + e.stopPropagation(); - if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3 || - e.isDefaultPrevented()) { - e.preventDefault(); + if (Math.abs(this._downX - e?.clientX) > 3 || Math.abs(this._downY - e?.clientY) > 3 || + e?.isDefaultPrevented()) { + e?.preventDefault(); return; } e.preventDefault(); } const cm = ContextMenu.Instance; + if (!cm) return; const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []); Cast(this.props.Document.contextMenuLabels, listSpec("string"), []).forEach((label, i) => @@ -742,34 +688,42 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu this.props.contextMenuItems?.().forEach(item => cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" })); + const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null); + const appearance = cm.findByDescription("UI Controls..."); + const appearanceItems: ContextMenuProps[] = appearance && "subitems" in appearance ? appearance.subitems : []; + templateDoc && appearanceItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" }); + //DocListCast(this.Document.links).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" }); + const options = cm.findByDescription("Options..."); const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : []; - const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null); - templateDoc && optionItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" }); - optionItems.push({ description: "Toggle Show Each Link Dot", event: () => this.layoutDoc.showLinks = !this.layoutDoc.showLinks, icon: "eye" }); + optionItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" }); + const existingOnClick = cm.findByDescription("OnClick..."); const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" }); onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.Document.layoutKey}")`), icon: "window-restore" }); onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" }); - onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: this.toggleFollowInPlace, icon: "concierge-bell" }); - onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link on Right", event: this.toggleFollowOnRight, icon: "concierge-bell" }); - onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: this.toggleLinkButtonBehavior, icon: "concierge-bell" }); + onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: () => this.toggleFollowLink("inPlace", true, false), icon: "concierge-bell" }); + !this.Document.isLinkButton && onClicks.push({ description: "Follow Link on Right", event: () => this.toggleFollowLink("onRight", false, false), icon: "concierge-bell" }); + onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: () => this.toggleFollowLink(undefined, false, false), icon: "concierge-bell" }); + onClicks.push({ description: (this.Document.isPushpin ? "Remove" : "Make") + " Pushpin", event: () => this.toggleFollowLink(undefined, false, true), icon: "snowflake" }); onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" }); - !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); + !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, addDivider: true, subitems: onClicks, icon: "hand-point-right" }); const funcs: ContextMenuProps[] = []; if (this.layoutDoc.onDragStart) { funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) }); funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) }); funcs.push({ description: "Drag Document", icon: "edit", event: () => this.layoutDoc.onDragStart = undefined }); - cm.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" }); + cm.addItem({ description: "OnDrag...", noexpand: true, subitems: funcs, icon: "asterisk" }); } const more = cm.findByDescription("More..."); const moreItems = more && "subitems" in more ? more.subitems : []; + moreItems.push({ description: "Download document", icon: "download", event: async () => Doc.Zip(this.props.Document) }); if (!Doc.UserDoc().noviceMode) { moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" }); moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); @@ -779,209 +733,32 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" }); moreItems.push({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" }); } - moreItems.push({ - description: "Download document", icon: "download", event: async () => { - const response = await rp.get(Utils.CorsProxy("http://localhost:8983/solr/dash/select"), { - qs: { q: 'world', fq: 'NOT baseProto_b:true AND NOT deleted:true', start: '0', rows: '100', hl: true, 'hl.fl': '*' } - }); - console.log(response ? JSON.parse(response) : undefined); - } - // const a = document.createElement("a"); - // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); - // a.href = url; - // a.download = `DocExport-${this.props.Document[Id]}.zip`; - // a.click(); - }); + moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" }); } - moreItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); - moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" }); - moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" }); + const effectiveAcl = GetEffectiveAcl(this.props.Document); + (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) && moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" }); moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "external-link-alt" }); !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" }); cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!); const help = cm.findByDescription("Help..."); const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : []; - helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("http://localhost:1050/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "onRight"), icon: "keyboard" }); + helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }), "onRight"), icon: "keyboard" }); helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" }); - cm.addItem({ description: "Help...", subitems: helpItems, icon: "question" }); - - const existingAcls = cm.findByDescription("Privacy..."); - const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : []; - aclItems.push({ description: "Make Add Only", event: () => this.setAcl("addOnly"), icon: "concierge-bell" }); - aclItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" }); - aclItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" }); - aclItems.push({ description: "Make Editable", event: () => this.setAcl("write"), icon: "concierge-bell" }); - aclItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" }); - aclItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" }); - !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" }); - - // const recommender_subitems: ContextMenuProps[] = []; - - // recommender_subitems.push({ - // description: "Internal recommendations", - // event: () => this.recommender(), - // icon: "brain" - // }); - - // const ext_recommender_subitems: ContextMenuProps[] = []; - - // ext_recommender_subitems.push({ - // description: "arXiv", - // event: () => this.externalRecommendation("arxiv"), - // icon: "brain" - // }); - // ext_recommender_subitems.push({ - // description: "Bing", - // event: () => this.externalRecommendation("bing"), - // icon: "brain" - // }); - - // recommender_subitems.push({ - // description: "External recommendations", - // subitems: ext_recommender_subitems, - // icon: "brain" - // }); - - - //moreItems.push({ description: "Recommender System", subitems: recommender_subitems, icon: "brain" }); - //moreItems.push({ description: "Publish", event: () => DocUtils.Publish(this.props.Document, this.Document.title || "", this.props.addDocument, this.props.removeDocument), icon: "file" }); - //moreItems.push({ description: "Undo Debug Test", event: () => UndoManager.TraceOpenBatches(), icon: "exclamation" }); - - // runInAction(() => { - // const setWriteMode = (mode: DocServer.WriteMode) => { - // DocServer.AclsMode = mode; - // const mode1 = mode; - // const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground; - // DocServer.setFieldWriteMode("x", mode1); - // DocServer.setFieldWriteMode("y", mode1); - // DocServer.setFieldWriteMode("_width", mode1); - // DocServer.setFieldWriteMode("_height", mode1); - - // DocServer.setFieldWriteMode("_panX", mode2); - // DocServer.setFieldWriteMode("_panY", mode2); - // DocServer.setFieldWriteMode("scale", mode2); - // DocServer.setFieldWriteMode("_viewType", mode2); - // }; - // const aclsMenu: ContextMenuProps[] = []; - // aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" }); - // aclsMenu.push({ description: "Playground (write own/no read)", event: () => setWriteMode(DocServer.WriteMode.Playground), icon: DocServer.AclsMode === DocServer.WriteMode.Playground ? "check" : "exclamation" }); - // aclsMenu.push({ description: "Live Playground (write own/read others)", event: () => setWriteMode(DocServer.WriteMode.LivePlayground), icon: DocServer.AclsMode === DocServer.WriteMode.LivePlayground ? "check" : "exclamation" }); - // aclsMenu.push({ description: "Live Readonly (no write/read others)", event: () => setWriteMode(DocServer.WriteMode.LiveReadonly), icon: DocServer.AclsMode === DocServer.WriteMode.LiveReadonly ? "check" : "exclamation" }); - // cm.addItem({ description: "Collaboration ...", subitems: aclsMenu, icon: "share" }); - // }); + helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" }); + cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" }); + runInAction(() => { if (!this.topMost && !(e instanceof Touch)) { - // DocumentViews should stop propagation of this event - e.stopPropagation(); + e.stopPropagation(); // DocumentViews should stop propagation of this event } - ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); + cm.displayMenu(e.pageX - 15, e.pageY - 15); !SelectionManager.IsSelected(this, true) && SelectionManager.SelectDoc(this, false); }); - const path = this.props.LibraryPath.reduce((p: string, d: Doc) => p + "/" + (Doc.AreProtosEqual(d, (Doc.UserDoc()["tabs-button-library"] as Doc).sourcePanel as Doc) ? "" : d.title), ""); - const item = ({ - description: `path: ${path}`, event: () => { - if (this.props.LibraryPath !== emptyPath) { - this.props.LibraryPath.map(lp => Doc.GetProto(lp).treeViewOpen = lp.treeViewOpen = true); - Doc.linkFollowHighlight(this.props.Document); - } else { - Doc.AddDocToList(Doc.GetProto(Doc.UserDoc().myCatalog as Doc), "data", this.props.Document[DataSym]); - } - }, icon: "check" - }); - //cm.addItem(item); - } - - recommender = async () => { - if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" }); - const documents: Doc[] = []; - const allDocs = await SearchUtil.GetAllDocs(); - // allDocs.forEach(doc => console.log(doc.title)); - // clears internal representation of documents as vectors - ClientRecommender.Instance.reset_docs(); - //ClientRecommender.Instance.arxivrequest("electrons"); - await Promise.all(allDocs.map((doc: Doc) => { - let isMainDoc: boolean = false; - const dataDoc = Doc.GetProto(doc); - if (doc.type === DocumentType.RTF) { - if (dataDoc === Doc.GetProto(this.props.Document)) { - isMainDoc = true; - } - if (!documents.includes(dataDoc)) { - documents.push(dataDoc); - const extdoc = doc.data_ext as Doc; - return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, true, "", isMainDoc); - } - } - if (doc.type === DocumentType.IMG) { - if (dataDoc === Doc.GetProto(this.props.Document)) { - isMainDoc = true; - } - if (!documents.includes(dataDoc)) { - documents.push(dataDoc); - const extdoc = doc.data_ext as Doc; - return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, true, "", isMainDoc, true); - } - } - })); - const doclist = ClientRecommender.Instance.computeSimilarities("cosine"); - const recDocs: { preview: Doc, score: number }[] = []; - // tslint:disable-next-line: prefer-for-of - for (let i = 0; i < doclist.length; i++) { - recDocs.push({ preview: doclist[i].actualDoc, score: doclist[i].score }); - } - - const data = recDocs.map(unit => { - unit.preview.score = unit.score; - return unit.preview; - }); - - console.log(recDocs.map(doc => doc.score)); - - const title = `Showing ${data.length} recommendations for "${StrCast(this.props.Document.title)}"`; - const recommendations = Docs.Create.RecommendationsDocument(data, { title }); - recommendations.documentIconHeight = 150; - recommendations.sourceDoc = this.props.Document; - recommendations.sourceDocContext = this.props.ContainingCollectionView!.props.Document; - CollectionDockingView.AddRightSplit(recommendations, undefined); - - // RecommendationsBox.Instance.displayRecommendations(e.pageX + 100, e.pageY); - } - - @action - externalRecommendation = async (api: string) => { - if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" }); - ClientRecommender.Instance.reset_docs(); - const doc = Doc.GetDataDoc(this.props.Document); - const extdoc = doc.data_ext as Doc; - const recs_and_kps = await ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, false, api); - let recs: any; - let kps: any; - if (recs_and_kps) { - recs = recs_and_kps.recs; - kps = recs_and_kps.keyterms; - } - else { - console.log("recommender system failed :("); - return; - } - console.log("ibm keyterms: ", kps.toString()); - const headers = [new SchemaHeaderField("title"), new SchemaHeaderField("href")]; - const bodies: Doc[] = []; - const titles = recs.title_vals; - const urls = recs.url_vals; - for (let i = 0; i < 5; i++) { - const body = Docs.Create.FreeformDocument([], { title: titles[i] }); - body.href = urls[i]; - bodies.push(body); - } - CollectionDockingView.AddRightSplit(Docs.Create.SchemaDocument(headers, bodies, { title: `Showing External Recommendations for "${StrCast(doc.title)}"` }), undefined); - this._showKPQuery = true; - this._queries = kps.toString(); } // does Document set a layout prop - // does Document set a layout prop + // does Document set a layout prop setsLayoutProp = (prop: string) => this.props.Document[prop] !== this.props.Document["default" + prop[0].toUpperCase() + prop.slice(1)] && this.props.Document["default" + prop[0].toUpperCase() + prop.slice(1)]; // get the a layout prop by first choosing the prop from Document, then falling back to the layout doc otherwise. getLayoutPropStr = (prop: string) => StrCast(this.setsLayoutProp(prop) ? this.props.Document[prop] : this.layoutDoc[prop]); @@ -1007,6 +784,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu return this.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false; } childScaling = () => (this.layoutDoc._fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling()); + @computed.struct get linkOffset() { return [-15, 0]; } @computed get contents() { TraceMobx(); return (<div style={{ position: "absolute", width: "100%", height: "100%" }}> @@ -1045,16 +823,18 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu ChromeHeight={this.chromeHeight} isSelected={this.isSelected} select={this.select} - onClick={this.onClickHandler} + onClick={this.onClickFunc} layoutKey={this.finalLayoutKey} /> - {this.layoutDoc.showLinks ? this.anchors : (null)} - {this.props.forcedBackgroundColor?.(this.Document) === "transparent" || this.props.dontRegisterView ? (null) : <DocumentLinksButton View={this} Offset={[-15, 0]} />} + {this.layoutDoc.hideAllLinks ? (null) : this.allAnchors} + {/* {this.allAnchors} */} + {this.props.forcedBackgroundColor?.(this.Document) === "transparent" || this.layoutDoc.isLinkButton || this.layoutDoc.hideLinkButton || this.props.dontRegisterView ? (null) : + <DocumentLinksButton View={this} links={this.allLinks} Offset={this.linkOffset} />} </div> ); } // used to decide whether a link anchor view should be created or not. - // if it's a tempoarl link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here. + // if it's a temporal link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here. // would be good to generalize this some way. isNonTemporalLink = (linkDoc: Doc) => { const anchor = Cast(Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1 : linkDoc.anchor2, Doc) as Doc; @@ -1062,7 +842,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu return anchor.type === DocumentType.AUDIO && NumCast(ept) ? false : true; } - @observable _link: Opt<Doc>; // see DocumentButtonBar for explanation of how this works makeLink = () => this._link; // pass the link placeholde to child views so they can react to make a specialized anchor. This is essentially a function call to the descendants since the value of the _link variable will immediately get set back to undefined. @@ -1070,12 +849,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu hideLinkAnchor = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && (doc.hidden = true), true) anchorPanelWidth = () => this.props.PanelWidth() || 1; anchorPanelHeight = () => this.props.PanelHeight() || 1; - @computed get anchors() { + + @computed.struct get directLinks() { return LinkManager.Instance.getAllDirectLinks(this.Document); } + @computed.struct get allLinks() { return DocListCast(this.Document.links); } + @computed.struct get allAnchors() { TraceMobx(); + if (this.props.LayoutTemplateString?.includes("LinkAnchorBox")) return null; return (this.props.treeViewDoc && this.props.LayoutTemplateString) || // render nothing for: tree view anchor dots this.layoutDoc.presBox || // presentationbox nodes + this.rootDoc.type === DocumentType.LINK || this.props.dontRegisterView ? (null) : // view that are not registered - DocListCast(this.Document.links).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) => + DocUtils.FilterDocs(this.directLinks, this.props.docFilters(), []).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) => <DocumentView {...this.props} key={i + 1} Document={d} ContainingCollectionView={this.props.ContainingCollectionView} @@ -1093,10 +877,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } @computed get innards() { TraceMobx(); - if (this.props.treeViewDoc && !this.props.LayoutTemplateString) { // this happens when the document is a tree view label (but not an anchor dot) + if (this.props.treeViewDoc && !this.props.LayoutTemplateString?.includes("LinkAnchorBox")) { // this happens when the document is a tree view label (but not an anchor dot) return <div className="documentView-treeView" style={{ maxWidth: this.props.PanelWidth() || undefined }}> {StrCast(this.props.Document.title)} - {this.anchors} + {this.allAnchors} </div>; } @@ -1113,7 +897,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu ChromeHeight={this.chromeHeight} isSelected={this.isSelected} select={this.select} - onClick={this.onClickHandler} + onClick={this.onClickFunc} layoutKey={this.finalLayoutKey} /> </div>); const titleView = (!showTitle ? (null) : @@ -1148,7 +932,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu DocUtils.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined); } } - @observable _animateScalingTo = 0; + switchViews = action((custom: boolean, view: string) => { this._animateScalingTo = 0.1; // shrink doc setTimeout(action(() => { @@ -1162,7 +946,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu return (this.Document.isBackground !== undefined || this.isSelected(false)) && ((this.Document.type === DocumentType.COL && this.Document._viewType !== CollectionViewType.Pile) || this.Document.type === DocumentType.IMG) && this.props.renderDepth > 0 && !this.props.treeViewDoc ? - <div className="documentView-lock" onClick={() => this.toggleBackground(true)}> + <div className="documentView-lock" onClick={this.toggleBackground}> <FontAwesomeIcon icon={this.Document.isBackground ? "unlock" : "lock"} style={{ color: this.Document.isBackground ? "red" : undefined }} size="lg" /> </div> : (null); @@ -1170,7 +954,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu render() { if (!(this.props.Document instanceof Doc)) return (null); - if (this.props.Document[AclSym] && this.props.Document[AclSym] === AclPrivate) return (null); + if (GetEffectiveAcl(this.props.Document) === AclPrivate) return (null); if (this.props.Document.hidden) return (null); const backgroundColor = Doc.UserDoc().renderStyle === "comic" ? undefined : this.props.forcedBackgroundColor?.(this.Document) || StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document); const opacity = Cast(this.layoutDoc._opacity, "number", Cast(this.layoutDoc.opacity, "number", Cast(this.Document.opacity, "number", null))); @@ -1186,11 +970,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"]; let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc._viewType !== CollectionViewType.Linear && this.props.Document.type !== DocumentType.INK; highlighting = highlighting && this.props.focus !== emptyFunction; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way - return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`} + const topmost = this.topMost ? "-topmost" : ""; + return <div className={`documentView-node${topmost}`} id={this.props.Document[Id]} ref={this._mainCont} onKeyDown={this.onKeyDown} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} - onPointerEnter={action(() => Doc.BrushDoc(this.props.Document))} + onPointerEnter={action(() => { Doc.BrushDoc(this.props.Document); })} onPointerLeave={action((e: React.PointerEvent<HTMLDivElement>) => { let entered = false; const target = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y); @@ -1199,7 +984,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu entered = true; } } + // if (this.props.Document !== DocumentLinksButton.StartLink?.Document) { !entered && Doc.UnBrushDoc(this.props.Document); + //} + })} style={{ transformOrigin: this._animateScalingTo ? "center center" : undefined, @@ -1216,7 +1004,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu background: finalColor, opacity: finalOpacity, fontFamily: StrCast(this.Document._fontFamily, "inherit"), - fontSize: Cast(this.Document._fontSize, "number", null) + fontSize: Cast(this.Document._fontSize, "string", null) }}> {this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <> {this.innards} @@ -1225,7 +1013,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu this.innards} {this.renderLock()} </div>; - { this._showKPQuery ? <KeyphraseQueryView keyphrases={this._queries}></KeyphraseQueryView> : undefined; } } } @@ -1233,4 +1020,4 @@ Scripting.addGlobal(function toggleDetail(doc: any, layoutKey: string, otherKey: const dv = DocumentManager.Instance.getDocumentView(doc); if (dv?.props.Document.layoutKey === layoutKey) dv?.switchViews(otherKey !== "layout", otherKey.replace("layout_", "")); else dv?.switchViews(true, layoutKey.replace("layout_", "")); -});
\ No newline at end of file +}); |