diff options
-rw-r--r-- | src/client/documents/Documents.ts | 23 | ||||
-rw-r--r-- | src/client/util/CurrentUserUtils.ts | 12 | ||||
-rw-r--r-- | src/client/util/ScrollBox.tsx | 24 | ||||
-rw-r--r-- | src/client/views/TouchScrollableMenu.tsx | 63 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 116 |
6 files changed, 66 insertions, 174 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index a0870ba43..ee3bf0d82 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -59,6 +59,7 @@ import { WebBox } from '../views/nodes/WebBox'; import { SearchBox } from '../views/search/SearchBox'; import { CollectionViewType, DocumentType } from './DocumentTypes'; import { CalendarBox } from '../views/nodes/calendarBox/CalendarBox'; +import { OpenWhere } from '../views/nodes/DocumentView'; const { default: { DFLT_IMAGE_NATIVE_DIM } } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', '')); @@ -407,7 +408,7 @@ export class DocumentOptions { onChildClick?: ScriptField; // script given to children of a collection to execute when they are clicked onChildDoubleClick?: ScriptField; // script given to children of a collection to execute when they are double clicked onClickScriptDisable?: STRt = new StrInfo('"always" disable click script, "never" disable click script, or default'); - defaultDoubleClick?: 'ignore' | 'default'; // ignore double clicks, or deafult (undefined) means open document full screen + defaultDoubleClick?: 'ignore' | 'default'; // ignore double clicks, or default (undefined) means open document full screen waitForDoubleClickToClick?: 'always' | 'never' | 'default'; // whether a click function wait for double click to expire. 'default' undefined = wait only if there's a click handler, "never" = never wait, "always" = alway wait onPointerDown?: ScriptField; onPointerUp?: ScriptField; @@ -1102,7 +1103,7 @@ export namespace Docs { I.stroke_isInkMask = isInkMask; I.text_align = 'center'; I.rotation = 0; - I.defaultDoubleClick = 'click'; + I.defaultDoubleClick = 'ignore'; I.author_date = new DateField(); I['acl-Guest'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View; //I['acl-Override'] = SharingPermissions.Unset; @@ -1796,6 +1797,24 @@ export namespace DocUtils { return newCollection; } } + export function makeIntoPortal(doc: Doc, layoutDoc: Doc, allLinks: Doc[]) { + const portalLink = allLinks.find(d => d.link_anchor_1 === doc && d.link_relationship === 'portal to:portal from'); + if (!portalLink) { + DocUtils.MakeLink( + doc, + Docs.Create.FreeformDocument([], { + _width: NumCast(layoutDoc._width) + 10, + _height: Math.max(NumCast(layoutDoc._height), NumCast(layoutDoc._width) + 10), + _isLightbox: true, + _layout_fitWidth: true, + title: StrCast(doc.title) + ' [Portal]', + }), + { link_relationship: 'portal to:portal from' } + ); + } + doc.followLinkLocation = OpenWhere.lightbox; + doc.onClick = FollowLinkScript(); + } export function LeavePushpin(doc: Doc, annotationField: string) { if (doc.followLinkToggle) return undefined; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 07ee777cd..6ddb65941 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -604,12 +604,12 @@ export class CurrentUserUtils { CurrentUserUtils.createToolButton(opts), scripts, funcs); const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet - { scripts: { onClick: "undo()"}, opts: { title: "Undo", icon: "undo-alt", toolTip: "Undo ⌘Z" }}, - { scripts: { onClick: "redo()"}, opts: { title: "Redo", icon: "redo-alt", toolTip: "Redo ⌘⇧Z" }}, - { scripts: { }, opts: { title: "undoStack", layout: "<UndoStack>", toolTip: "Undo/Redo Stack"}}, // note: layout fields are hacks -- they don't actually run through the JSX parser (yet) - { scripts: { }, opts: { title: "linker", layout: "<LinkingUI>", toolTip: "link started"}}, - { scripts: { }, opts: { title: "currently playing", layout: "<CurrentlyPlayingUI>", toolTip: "currently playing media"}}, - { scripts: { }, opts: { title: "Branching", layout: "<Branching>", toolTip: "Branch, baby!"}} + { scripts: { onClick: "undo()"}, opts: { title: "Undo", icon: "undo-alt", toolTip: "Undo ⌘Z" }}, + { scripts: { onClick: "redo()"}, opts: { title: "Redo", icon: "redo-alt", toolTip: "Redo ⌘⇧Z" }}, + { scripts: { }, opts: { title: "undoStack", layout: "<UndoStack>", toolTip: "Undo/Redo Stack"}}, // note: layout fields are hacks -- they don't actually run through the JSX parser (yet) + { scripts: { }, opts: { title: "linker", layout: "<LinkingUI>", toolTip: "link started"}}, + { scripts: { }, opts: { title: "currently playing", layout: "<CurrentlyPlayingUI>", toolTip: "currently playing media"}}, + { scripts: { }, opts: { title: "Branching", layout: "<Branching>", toolTip: "Branch, baby!"}} ]; const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, defaultDoubleClick: 'ignore', undoIgnoreFields: new List<string>(['opacity']), _dragOnlyWithinContainer: true, ...desc.opts}, desc.scripts)); const dockBtnsReqdOpts:DocumentOptions = { diff --git a/src/client/util/ScrollBox.tsx b/src/client/util/ScrollBox.tsx deleted file mode 100644 index 785526ab3..000000000 --- a/src/client/util/ScrollBox.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import * as React from 'react'; - -export class ScrollBox extends React.Component<React.PropsWithChildren<{}>> { - onWheel = (e: React.WheelEvent) => { - if (e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { - // If the element has a scroll bar, then we don't want the containing collection to zoom - e.stopPropagation(); - } - }; - - render() { - return ( - <div - style={{ - overflow: 'auto', - width: '100%', - height: '100%', - }} - onWheel={this.onWheel}> - {this.props.children} - </div> - ); - } -} diff --git a/src/client/views/TouchScrollableMenu.tsx b/src/client/views/TouchScrollableMenu.tsx deleted file mode 100644 index 530c693a7..000000000 --- a/src/client/views/TouchScrollableMenu.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import * as React from 'react'; -import { computed } from 'mobx'; -import { observer } from 'mobx-react'; - -export interface TouchScrollableMenuProps { - options: JSX.Element[]; - bounds: { - right: number; - left: number; - bottom: number; - top: number; - width: number; - height: number; - }; - selectedIndex: number; - x: number; - y: number; -} - -export interface TouchScrollableMenuItemProps { - text: string; - onClick: () => any; -} - -@observer -export default class TouchScrollableMenu extends React.Component<TouchScrollableMenuProps> { - @computed - private get possibilities() { - return this.props.options; - } - - @computed - private get selectedIndex() { - return this.props.selectedIndex; - } - - render() { - return ( - <div - className="inkToTextDoc-cont" - style={{ - transform: `translate(${this.props.x}px, ${this.props.y}px)`, - width: 300, - height: this.possibilities.length * 25, - }}> - <div className="inkToTextDoc-scroller" style={{ transform: `translate(0, ${-this.selectedIndex * 25}px)` }}> - {this.possibilities} - </div> - <div className="shadow" style={{ height: `calc(100% - 25px - ${this.selectedIndex * 25}px)` }}></div> - </div> - ); - } -} - -export class TouchScrollableMenuItem extends React.Component<TouchScrollableMenuItemProps> { - render() { - return ( - <div className="menuItem-cont" onClick={this.props.onClick}> - {this.props.text} - </div> - ); - } -} diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index c2f8232c6..a9910c2a8 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -219,7 +219,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps const scrollMode = e.altKey ? (Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan ? freeformScrollMode.Zoom : freeformScrollMode.Pan) : Doc.UserDoc().freeformScrollMode; // allow marquee if right drag/meta drag, or pan mode - if (e.button === 2 || e.metaKey || scrollMode === freeformScrollMode.Pan) { + if (e.button === 2 || e.metaKey || (this._props.isContentActive() && scrollMode === freeformScrollMode.Pan)) { this.setPreviewCursor(e.clientX, e.clientY, true, false, this._props.Document); e.preventDefault(); } else PreviewCursor.Instance.Visible = false; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 8eb354e1e..a82580ddb 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -236,16 +236,15 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document runInAction(() => (this._mounted = true)); this.setupHandlers(); this._disposers.contentActive = reaction( - () => { + () => // true - if the document has been activated directly or indirectly (by having its children selected) // false - if its pointer events are explicitly turned off or if it's container tells it that it's inactive // undefined - it is not active, but it should be responsive to actions that might activate it or its contents (eg clicking) - return this._props.isContentActive() === false || this._props.pointerEvents?.() === 'none' + this._props.isContentActive() === false || this._props.pointerEvents?.() === 'none' ? false : Doc.ActiveTool !== InkTool.None || SnappingManager.CanEmbed || this.rootSelected() || this.Document.forceActive || this._componentView?.isAnyChildContentActive?.() || this._props.isContentActive() ? true - : undefined; - }, + : undefined, active => (this._isContentActive = active), { fireImmediately: true } ); @@ -312,20 +311,23 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document let stopPropagate = true; let preventDefault = true; !this.layoutDoc._keepZWhenDragged && this._props.bringToFront?.(this.Document); + const scriptProps = { + this: this.Document, + _readOnly_: false, + scriptContext: this._props.scriptContext, + documentView, + clientX: e.clientX, + clientY: e.clientY, + shiftKey: e.shiftKey, + altKey: e.altKey, + metaKey: e.metaKey, + value: undefined, + }; if (this._doubleTap) { const defaultDblclick = this._props.defaultDoubleClick?.() || this.Document.defaultDoubleClick; if (this.onDoubleClickHandler?.script) { - const { clientX, clientY, shiftKey, altKey, ctrlKey } = e; // or we could call e.persist() to capture variables - // prettier-ignore - const func = () => this.onDoubleClickHandler.script.run( { - this: this.Document, - scriptContext: this._props.scriptContext, - documentView, - clientX, clientY, altKey, shiftKey, ctrlKey, - value: undefined, - }, console.log ); - UndoManager.RunInBatch(() => (func().result?.select === true ? this._props.select(false) : ''), 'on double click'); - } else if (!Doc.IsSystem(this.Document) && (defaultDblclick === undefined || defaultDblclick === 'default')) { + UndoManager.RunInBatch(() => this.onDoubleClickHandler.script.run(scriptProps, console.log).result?.select && this._props.select(false), 'on double click: ' + this.Document.title); + } else if (!Doc.IsSystem(this.Document) && defaultDblclick !== 'ignore') { UndoManager.RunInBatch(() => LightboxView.Instance.AddDocTab(this.Document, OpenWhere.lightbox), 'double tap'); SelectionManager.DeselectAll(); Doc.UnBrushDoc(this.Document); @@ -338,33 +340,14 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document } else { let clickFunc: undefined | (() => any); if (!this.disableClickScriptFunc && this.onClickHandler?.script) { - const { clientX, clientY, shiftKey, altKey, metaKey } = e; - const func = () => { - // replace default add doc func with this view's add doc func. - // to allow override behaviors for how to display links to undisplayed documents. - // e.g., if this document is part of a labeled 'lightbox' container, then documents will be shown in place - // instead of in the global lightbox + clickFunc = undoable(() => { + // use this view's add doc func to override method for following links to undisplayed documents. + // e.g., if this document is part of a labeled 'lightbox' container, then documents will be shown in this container of in the global lightbox const oldFunc = DocumentViewInternal.addDocTabFunc; DocumentViewInternal.addDocTabFunc = this._props.addDocTab; - this.onClickHandler?.script.run( - { - this: this.Document, - _readOnly_: false, - scriptContext: this._props.scriptContext, - documentView, - clientX, - clientY, - shiftKey, - altKey, - metaKey, - }, - console.log - ).result?.select === true - ? this._props.select(false) - : ''; + this.onClickHandler?.script.run(scriptProps, console.log).result?.select && this._props.select(false); DocumentViewInternal.addDocTabFunc = oldFunc; - }; - clickFunc = () => UndoManager.RunInBatch(func, 'click ' + this.Document.title); + }, 'click ' + this.Document.title); } else { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField 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.TemplateDataDocument) && !(e.ctrlKey || e.button > 0)) { @@ -412,10 +395,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document !Doc.IsInMyOverlay(this.layoutDoc) ) { e.stopPropagation(); - // don't preventDefault anymore. Goldenlayout, PDF text selection and RTF text selection all need it to go though - //if (this._props.isSelected(true) && this.Document.type !== DocumentType.PDF && this.layoutDoc._type_collection !== CollectionViewType.Docking) e.preventDefault(); + // don't preventDefault. Goldenlayout, PDF text selection and RTF text selection all need it to go though - // listen to move events if document content isn't active or document is draggable + // listen to move events when document content isn't active or document is always draggable if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || BoolCast(this.layoutDoc._dragWhenActive, this._props.dragWhenActive))) { document.addEventListener('pointermove', this.onPointerMove); } @@ -489,26 +471,24 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document if (this.Document === Doc.ActiveDashboard) { e.stopPropagation(); e.preventDefault(); - alert( - (e.target as any)?.closest?.('*.lm_content') - ? "You can't perform this move most likely because you didn't drag the document's title bar to enable embedding in a different document." - : 'Linking to document tabs not yet supported. Drop link on document content.' - ); + alert((e.target as any)?.closest?.('*.lm_content') ? "You can't perform this move most likely because you didn't drag the document's title bar to enable embedding in a different document." : 'Linking to document tabs not yet supported.'); return true; } - const linkdrag = de.complete.annoDragData ?? de.complete.linkDragData; + const annoData = de.complete.annoDragData; + const linkdrag = annoData ?? de.complete.linkDragData; if (linkdrag) { linkdrag.linkSourceDoc = linkdrag.linkSourceGetAnchor(); if (linkdrag.linkSourceDoc && linkdrag.linkSourceDoc !== this.Document) { - if (de.complete.annoDragData && !de.complete.annoDragData.dropDocument) { - de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator(undefined); + if (annoData && !annoData.dropDocument) { + annoData.dropDocument = annoData.dropDocCreator(undefined); } - if (de.complete.annoDragData || this.Document !== linkdrag.linkSourceDoc.embedContainer) { - const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.(true) ?? this.Document; - de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, [de.x, de.y - 50]); - if (de.complete.linkDocument) { - de.complete.linkDocument.layout_isSvg = true; - this._docView?.CollectionFreeFormView?.addDocument(de.complete.linkDocument); + if (annoData || this.Document !== linkdrag.linkSourceDoc.embedContainer) { + const dropDoc = annoData?.dropDocument ?? this._componentView?.getAnchor?.(true) ?? this.Document; + const linkDoc = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, [de.x, de.y - 50]); + if (linkDoc) { + de.complete.linkDocument = linkDoc; + linkDoc.layout_isSvg = true; + this._docView?.CollectionFreeFormView?.addDocument(linkDoc); } } e.stopPropagation(); @@ -518,25 +498,6 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document return false; }, 'drop doc'); - makeIntoPortal = undoable(() => { - const portalLink = this._allLinks.find(d => d.link_anchor_1 === this.Document && d.link_relationship === 'portal to:portal from'); - if (!portalLink) { - DocUtils.MakeLink( - this.Document, - Docs.Create.FreeformDocument([], { - _width: NumCast(this.layoutDoc._width) + 10, - _height: Math.max(NumCast(this.layoutDoc._height), NumCast(this.layoutDoc._width) + 10), - _isLightbox: true, - _layout_fitWidth: true, - title: StrCast(this.Document.title) + ' [Portal]', - }), - { link_relationship: 'portal to:portal from' } - ); - } - this.Document.followLinkLocation = OpenWhere.lightbox; - this.Document.onClick = FollowLinkScript(); - }, 'make into portal'); - importDocument = () => { const input = document.createElement('input'); input.type = 'file'; @@ -627,7 +588,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document 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: 'Enter Portal', event: undoable(e => DocUtils.makeIntoPortal(this.Document, this.layoutDoc, this._allLinks), 'make into portal'), icon: 'window-restore' }); !Doc.noviceMode && onClicks.push({ description: 'Toggle Detail', event: this.setToggleDetail, icon: 'concierge-bell' }); if (!this.Document.annotationOn) { @@ -818,7 +779,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document rootSelected={this.rootSelected} onClickScript={this.onClickFunc} setTitleFocus={this.setTitleFocus} - hideClickBehaviors={BoolCast(this.layoutDoc.hideClickBehaviors)} + hideClickBehaviors={BoolCast(this.Document.hideClickBehaviors)} /> {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints()} </div> @@ -1254,7 +1215,6 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { this.layoutDoc._viewTransition = undefined; }; public noOnClick = () => this._docViewInternal?.noOnClick(); - public makeIntoPortal = () => this._docViewInternal?.makeIntoPortal(); public toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => this._docViewInternal?.toggleFollowLink(zoom, setTargetToggle); public setToggleDetail = () => this._docViewInternal?.setToggleDetail(); public onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => this._docViewInternal?.onContextMenu?.(e, pageX, pageY); |