diff options
Diffstat (limited to 'src/client/views/nodes/DocumentView.tsx')
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 284 |
1 files changed, 156 insertions, 128 deletions
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index ee7bbbdba..5962cd09f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,11 +1,15 @@ +/* eslint-disable no-use-before-define */ +/* eslint-disable react/jsx-props-no-spreading */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { Howl } from 'howler'; import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Bounce, Fade, Flip, JackInTheBox, Roll, Rotate, Zoom } from 'react-awesome-reveal'; -import { DivWidth, Utils, emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick } from '../../../Utils'; -import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc'; +import { ClientUtils, DivWidth, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick } from '../../../ClientUtils'; +import { Utils, emptyFunction, emptyPath } from '../../../Utils'; +import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../fields/Doc'; import { AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; @@ -23,14 +27,14 @@ import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes' import { DocUtils, Docs } from '../../documents/Documents'; import { DictationManager } from '../../util/DictationManager'; import { DocumentManager } from '../../util/DocumentManager'; -import { DragManager, dropActionType } from '../../util/DragManager'; +import { DragManager } from '../../util/DragManager'; +import { dropActionType } from '../../util/DropActionTypes'; import { MakeTemplate, makeUserTemplateButton } from '../../util/DropConverter'; import { FollowLinkScript } from '../../util/LinkFollower'; -import { LinkManager } from '../../util/LinkManager'; +import { LinkManager, UPDATE_SERVER_CACHE } from '../../util/LinkManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SearchUtil } from '../../util/SearchUtil'; import { SelectionManager } from '../../util/SelectionManager'; -import { SettingsManager } from '../../util/SettingsManager'; import { SharingManager } from '../../util/SharingManager'; import { SnappingManager } from '../../util/SnappingManager'; import { UndoManager, undoBatch, undoable } from '../../util/UndoManager'; @@ -42,6 +46,7 @@ import { FieldsDropdown } from '../FieldsDropdown'; import { GestureOverlay } from '../GestureOverlay'; import { LightboxView } from '../LightboxView'; import { AudioAnnoState, StyleProp } from '../StyleProvider'; +import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView'; import { DocumentContentsView, ObserverJsxParser } from './DocumentContentsView'; import { DocumentLinksButton } from './DocumentLinksButton'; import './DocumentView.scss'; @@ -50,12 +55,6 @@ import { KeyValueBox } from './KeyValueBox'; import { LinkAnchorBox } from './LinkAnchorBox'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { PresEffect, PresEffectDirection } from './trails'; -interface Window { - MediaRecorder: MediaRecorder; -} -declare class MediaRecorder { - constructor(e: any); // whatever MediaRecorder has -} export enum OpenWhereMod { none = '', @@ -83,9 +82,6 @@ export enum OpenWhere { addRightKeyvalue = 'add:right:keyValue', } -export function returnEmptyDocViewList() { - return [] as DocumentView[]; -} export interface DocumentViewProps extends FieldViewSharedProps { hideDecorations?: boolean; // whether to suppress all DocumentDecorations when doc is selected hideResizeHandles?: boolean; // whether to suppress resized handles on doc decorations when this document is selected @@ -122,7 +118,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document * This function is filled in by MainView to allow non-viewBox views to add Docs as tabs without * needing to know about/reference MainView */ - public static addDocTabFunc: (doc: Doc, location: OpenWhere) => boolean = returnFalse; + public static addDocTabFunc: (doc: Doc | Doc[], location: OpenWhere) => boolean = returnFalse; private _disposers: { [name: string]: IReactionDisposer } = {}; private _doubleClickTimeout: NodeJS.Timeout | undefined; @@ -183,12 +179,12 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document @computed get disableClickScriptFunc() { const onScriptDisable = this._props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable; - // prettier-ignore return ( + // eslint-disable-next-line no-use-before-define DocumentView.LongPress || onScriptDisable === 'always' || (onScriptDisable !== 'never' && (this.rootSelected() || this._componentView?.isAnyChildContentActive?.())) - ); + ); // prettier-ignore } @computed get _rootSelected() { return this._props.isSelected() || BoolCast(this._props.TemplateDataDocument && this._props.rootSelected?.()); @@ -237,7 +233,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document } componentDidMount() { - runInAction(() => (this._mounted = true)); + runInAction(() => { + this._mounted = true; + }); this.setupHandlers(); this._disposers.contentActive = reaction( () => @@ -249,19 +247,23 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document : Doc.ActiveTool !== InkTool.None || SnappingManager.CanEmbed || this.rootSelected() || this.Document.forceActive || this._componentView?.isAnyChildContentActive?.() || this._props.isContentActive() ? true : undefined, - active => (this._isContentActive = active), + active => { + this._isContentActive = active; + }, { fireImmediately: true } ); this._disposers.pointerevents = reaction( () => this.style(this.Document, StyleProp.PointerEvents), - pointerevents => (this._pointerEvents = pointerevents), + pointerevents => { + this._pointerEvents = pointerevents; + }, { fireImmediately: true } ); } preDrop = (e: Event, de: DragManager.DropEvent, dropAction: dropActionType) => { const dragData = de.complete.docDragData; if (dragData && this.isContentActive() && !this.props.dontRegisterView) { - dragData.dropAction = dropAction ? dropAction : dragData.dropAction; + dragData.dropAction = dropAction || dragData.dropAction; e.stopPropagation(); } }; @@ -291,7 +293,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document dragData.removeDocument = this._props.removeDocument; dragData.moveDocument = this._props.moveDocument; dragData.draggedViews = [docView]; - dragData.canEmbed = this.Document.dragAction ?? this._props.dragAction ? true : false; + dragData.canEmbed = !!(this.Document.dragAction ?? this._props.dragAction); (this._props.dragConfig ?? this._componentView?.dragConfig)?.(dragData); DragManager.StartDocumentDrag( selected.map(dv => dv.ContentDiv!), @@ -311,7 +313,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document onClick = action((e: React.MouseEvent | React.PointerEvent) => { if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return; const documentView = this._docView; - if (documentView && !this.Document.ignoreClick && this._props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) { + if (documentView && !this.Document.ignoreClick && this._props.renderDepth >= 0 && ClientUtils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) { let stopPropagate = true; let preventDefault = true; !this.layoutDoc._keepZWhenDragged && this._props.bringToFront?.(this.Document); @@ -368,6 +370,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document if ((clickFunc && waitFordblclick !== 'never') || waitFordblclick === 'always') { this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout); this._doubleClickTimeout = setTimeout(this._singleClickFunc, 300); + // eslint-disable-next-line no-use-before-define } else if (!DocumentView.LongPress) { this._singleClickFunc(); this._singleClickFunc = undefined; @@ -380,6 +383,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document onPointerDown = (e: React.PointerEvent): void => { if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return; + // eslint-disable-next-line no-use-before-define this._longPressSelector = setTimeout(() => DocumentView.LongPress && this._props.select(false), 1000); if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this._docView; @@ -412,7 +416,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document onPointerMove = (e: PointerEvent): void => { if (e.buttons !== 1 || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) return; - if (!Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) { + if (!ClientUtils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) { this.cleanupPointerEvents(); this._longPressSelector && clearTimeout(this._longPressSelector); this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && dropActionType.embed) || ((this.Document.dragAction || this._props.dragAction || undefined) as dropActionType)); @@ -430,14 +434,15 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document if (this.onPointerUpHandler?.script) { this.onPointerUpHandler.script.run({ this: this.Document }, console.log); - } else if (e.button === 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) { - this._doubleTap = (this.onDoubleClickHandler?.script || this.Document.defaultDoubleClick !== 'ignore') && Date.now() - this._lastTap < Utils.CLICK_TIME; + } else if (e.button === 0 && ClientUtils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) { + this._doubleTap = (this.onDoubleClickHandler?.script || this.Document.defaultDoubleClick !== 'ignore') && Date.now() - this._lastTap < ClientUtils.CLICK_TIME; if (!this.isContentActive()) this._lastTap = Date.now(); // don't want to process the start of a double tap if the doucment is selected } + // eslint-disable-next-line no-use-before-define if (DocumentView.LongPress) e.preventDefault(); }; - toggleFollowLink = undoable((zoom?: boolean, setTargetToggle?: boolean): void => { + toggleFollowLink = undoable((): void => { const hadOnClick = this.Document.onClick; this.noOnClick(); this.Document.onClick = hadOnClick ? undefined : FollowLinkScript(); @@ -458,16 +463,14 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document }, 'default on click'); deleteClicked = undoable(() => this._props.removeDocument?.(this.Document), 'delete doc'); - setToggleDetail = undoable( - (scriptFieldKey: 'onClick') => - (this.Document[scriptFieldKey] = ScriptField.MakeScript( - `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey) - .replace('layout_', '') - .replace(/^layout$/, 'detail')}")`, - { documentView: 'any' } - )), - 'set toggle detail' - ); + setToggleDetail = undoable((scriptFieldKey: 'onClick') => { + this.Document[scriptFieldKey] = ScriptField.MakeScript( + `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey) + .replace('layout_', '') + .replace(/^layout$/, 'detail')}")`, + { documentView: 'any' } + ); + }, 'set toggle detail'); drop = undoable((e: Event, de: DragManager.DropEvent) => { if (this._props.dontRegisterView || this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return false; @@ -505,7 +508,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document const input = document.createElement('input'); input.type = 'file'; input.accept = '.zip'; - input.onchange = _e => { + input.onchange = () => { if (input.files) { const batch = UndoManager.StartBatch('importing'); Doc.importDocument(input.files[0]).then(doc => { @@ -523,7 +526,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document if (e && this.layoutDoc.layout_hideContextMenu && Doc.noviceMode) { e.preventDefault(); e.stopPropagation(); - //!this._props.isSelected(true) && SelectionManager.SelectView(this.DocumentView(), false); + // !this._props.isSelected(true) && SelectionManager.SelectView(this.DocumentView(), false); } // the touch onContextMenu is button 0, the pointer onContextMenu is button 2 if (e) { @@ -535,7 +538,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document e.stopPropagation(); e.persist(); - if (!navigator.userAgent.includes('Mozilla') && (Math.abs(this._downX - e?.clientX) > 3 || Math.abs(this._downY - e?.clientY) > 3)) { + if (!navigator.userAgent.includes('Mozilla') && (Math.abs(this._downX - (e?.clientX ?? 0)) > 3 || Math.abs(this._downY - (e?.clientY ?? 0)) > 3)) { return; } } @@ -587,7 +590,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document zorderItems.push({ description: 'Send to Back', event: () => SelectionManager.Views.forEach(dv => dv._props.bringToFront?.(dv.Document, true)), icon: 'arrow-down' }); zorderItems.push({ description: !this.layoutDoc._keepZDragged ? 'Keep ZIndex when dragged' : 'Allow ZIndex to change when dragged', - event: undoBatch(action(() => (this.layoutDoc._keepZWhenDragged = !this.layoutDoc._keepZWhenDragged))), + event: undoBatch( + action(() => { + this.layoutDoc._keepZWhenDragged = !this.layoutDoc._keepZWhenDragged; + }) + ), icon: 'hand-point-up', }); !zorders && cm.addItem({ description: 'Z Order...', addDivider: true, noexpand: true, subitems: zorderItems, icon: 'layer-group' }); @@ -597,7 +604,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: undoable(e => DocUtils.makeIntoPortal(this.Document, this.layoutDoc, this._allLinks), 'make into portal'), icon: 'window-restore' }); + onClicks.push({ description: 'Enter Portal', event: undoable(() => 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) { @@ -613,9 +620,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document const funcs: ContextMenuProps[] = []; if (!Doc.noviceMode && this.layoutDoc.onDragStart) { - funcs.push({ description: 'Drag an Embedding', icon: 'edit', event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getEmbedding(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) }); + funcs.push({ description: 'Drag an Embedding', icon: 'edit', event: () => { this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getEmbedding(this.dragFactory)')); } }); // prettier-ignore + funcs.push({ description: 'Drag a Copy', icon: 'edit', event: () => { this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')); } }); // prettier-ignore + funcs.push({ description: 'Drag Document', icon: 'edit', event: () => { this.layoutDoc.onDragStart = undefined; } }); // prettier-ignore cm.addItem({ description: 'OnDrag...', noexpand: true, subitems: funcs, icon: 'asterisk' }); } @@ -624,14 +631,14 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document if (!Doc.IsSystem(this.Document)) { if (!Doc.noviceMode) { moreItems.push({ description: 'Make View of Metadata Field', event: () => Doc.MakeMetadataFieldTemplate(this.Document, this._props.TemplateDataDocument), icon: 'concierge-bell' }); - moreItems.push({ description: `${this.Document._chromeHidden ? 'Show' : 'Hide'} Chrome`, event: () => (this.Document._chromeHidden = !this.Document._chromeHidden), icon: 'project-diagram' }); + moreItems.push({ description: `${this.Document._chromeHidden ? 'Show' : 'Hide'} Chrome`, event: () => { this.Document._chromeHidden = !this.Document._chromeHidden; }, icon: 'project-diagram' }); // prettier-ignore if (Cast(Doc.GetProto(this.Document).data, listSpec(Doc))) { moreItems.push({ description: 'Export to Google Photos Album', event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.Document }).then(console.log), icon: 'caret-square-right' }); moreItems.push({ description: 'Tag Child Images via Google Photos', event: () => GooglePhotos.Query.TagChildImages(this.Document), icon: 'caret-square-right' }); moreItems.push({ description: 'Write Back Link to Album', event: () => GooglePhotos.Transactions.AddTextEnrichment(this.Document), icon: 'caret-square-right' }); } - moreItems.push({ description: 'Copy ID', event: () => Utils.CopyText(Doc.globalServerPath(this.Document)), icon: 'fingerprint' }); + moreItems.push({ description: 'Copy ID', event: () => ClientUtils.CopyText(Doc.globalServerPath(this.Document)), icon: 'fingerprint' }); } } @@ -639,7 +646,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document } const constantItems: ContextMenuProps[] = []; if (!Doc.IsSystem(this.Document) && this.Document._type_collection !== CollectionViewType.Docking) { - constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => Doc.Zip(this.Document) }); + constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => DocUtils.Zip(this.Document) }); (this.Document._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this._docView), icon: 'users' }); if (this._props.removeDocument && Doc.ActiveDashboard !== this.Document) { // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions) @@ -655,8 +662,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document !Doc.noviceMode && helpItems.push({ description: 'Print Document in Console', event: () => console.log(this.Document), icon: 'hand-point-right' }); !Doc.noviceMode && helpItems.push({ description: 'Print DataDoc in Console', event: () => console.log(this.dataDoc), icon: 'hand-point-right' }); - let documentationDescription: string | undefined = undefined; - let documentationLink: string | undefined = undefined; + let documentationDescription: string | undefined; + let documentationLink: string | undefined; switch (this.Document.type) { case DocumentType.COL: documentationDescription = 'See collection documentation'; @@ -690,6 +697,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document documentationDescription = 'See DataViz node documentation'; documentationLink = 'https://brown-dash.github.io/Dash-Documentation/documents/dataViz/'; break; + default: } // Add link to help documentation (unless the doc contents have been overriden in which case the documentation isn't relevant) if (!this.docContents && documentationDescription && documentationLink) { @@ -710,8 +718,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document panelHeight = () => this._props.PanelHeight() - this.headerMargin; screenToLocalContent = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin); onClickFunc = this.disableClickScriptFunc ? undefined : () => this.onClickHandler; - setHeight = (height: number) => !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height)); - setContentView = action((view: ViewBoxInterface) => (this._componentView = view)); + setHeight = (height: number) => { !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height)); } // prettier-ignore + setContentView = action((view: ViewBoxInterface) => { this._componentView = view; }); // prettier-ignore isContentActive = (): boolean | undefined => this._isContentActive; childFilters = () => [...this._props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)]; @@ -729,11 +737,13 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document const filtered = DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines); return filtered.some(link => link._link_displayArrow) ? 0 : undefined; } + default: } return this._props.styleProvider?.(doc, props, property); }; - removeLinkByHiding = (link: Doc) => () => (link.link_displayLine = false); + // eslint-disable-next-line no-return-assign + removeLinkByHiding = (link: Doc) => () => link.link_displayLine = false; // prettier-ignore @computed get allLinkEndpoints() { // the small blue dots that mark the endpoints of links if (this._componentView instanceof KeyValueBox || this._props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this._props.dontRegisterView || this.layoutDoc.layout_unrendered) return null; @@ -748,8 +758,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document PanelHeight={this.anchorPanelHeight} dontRegisterView={false} layout_showTitle={returnEmptyString} - hideCaptions={true} - hideLinkAnchors={true} + hideCaptions + hideLinkAnchors layout_fitWidth={returnTrue} removeDocument={this.removeLinkByHiding(link)} styleProvider={this.anchorStyleProvider} @@ -792,32 +802,30 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document } captionStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption'); - fieldsDropdown = (placeholder: string) => { - return ( - <div - ref={action((r: any) => r && (this._titleDropDownInnerWidth = DivWidth(r)))} - onPointerDown={action(e => (this._changingTitleField = true))} - style={{ width: 'max-content', background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}> - <FieldsDropdown - Document={this.Document} - placeholder={placeholder} - selectFunc={action((field: string | number) => { - if (this.layoutDoc.layout_showTitle) { - this.layoutDoc._layout_showTitle = field; - } else if (!this._props.layout_showTitle) { - Doc.UserDoc().layout_showTitle = field; - } - this._changingTitleField = false; - })} - menuClose={action(() => (this._changingTitleField = false))} - /> - </div> - ); - }; + fieldsDropdown = (placeholder: string) => ( + <div + ref={action((r: any) => { r && (this._titleDropDownInnerWidth = DivWidth(r));} )} // prettier-ignore + onPointerDown={action(() => { this._changingTitleField = true; })} // prettier-ignore + style={{ width: 'max-content', background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor, transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}> + <FieldsDropdown + Document={this.Document} + placeholder={placeholder} + selectFunc={action((field: string | number) => { + if (this.layoutDoc.layout_showTitle) { + this.layoutDoc._layout_showTitle = field; + } else if (!this._props.layout_showTitle) { + Doc.UserDoc().layout_showTitle = field; + } + this._changingTitleField = false; + })} + menuClose={action(() => { this._changingTitleField = false; })} // prettier-ignore + /> + </div> + ); /** * displays a 'title' at the top of a document. The title contents default to the 'title' field, but can be changed to one or more fields by * setting layout_showTitle using the format: field1[:hover] - **/ + * */ @computed get titleView() { const showTitle = this.layout_showTitle?.split(':')[0]; const showTitleHover = this.layout_showTitle?.includes(':hover'); @@ -825,7 +833,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document const targetDoc = showTitle?.startsWith('_') ? this.layoutDoc : this.Document; const background = StrCast( this.layoutDoc.layout_headingColor, - StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(Doc.SharingDoc().headingColor, SettingsManager.userBackgroundColor)) + StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(Doc.SharingDoc().headingColor, SnappingManager.userBackgroundColor)) ); const dropdownWidth = this._titleRef.current?._editing || this._changingTitleField ? Math.max(10, (this._titleDropDownInnerWidth * this.titleHeight) / 30) : 0; const sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', ''); @@ -839,7 +847,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document position: this.headerMargin ? 'relative' : 'absolute', height: this.titleHeight, width: 100 - sidebarWidthPercent + '%', - color: background === 'transparent' ? SettingsManager.userColor : lightOrDark(background), + color: background === 'transparent' ? SnappingManager.userColor : lightOrDark(background), background, pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined, }}> @@ -860,11 +868,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document contents={ showTitle .split(';') - .map(field => Field.toJavascriptString(this.Document[field] as Field)) + .map(field => Field.toJavascriptString(this.Document[field] as FieldType)) .join(' \\ ') || '-unset-' } display="block" - oneLine={true} + oneLine fontSize={(this.titleHeight / 15) * 10} GetValue={() => showTitle @@ -905,10 +913,10 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document xPadding={10} fieldKey={this.layout_showCaption} styleProvider={this.captionStyleProvider} - dontRegisterView={true} + dontRegisterView rootSelected={this.rootSelected} - noSidebar={true} - dontScale={true} + noSidebar + dontScale renderDepth={this._props.renderDepth} isContentActive={this.isContentActive} /> @@ -952,8 +960,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document render() { TraceMobx(); - const highlighting = this.highlighting; - const borderPath = this.borderPath; + const { highlighting, borderPath } = this; const boxShadow = !highlighting ? this.boxShadow : highlighting && this.borderRounding && highlighting.highlightStyle !== 'dashed' @@ -968,23 +975,22 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document }); return ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events <div className={`${DocumentView.ROOT_DIV} docView-hack`} ref={this._mainCont} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} - onPointerEnter={e => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)} - onPointerOver={e => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)} + onPointerEnter={() => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)} + onPointerOver={() => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)} onPointerLeave={e => !isParentOf(this._contentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.Document)} style={{ borderRadius: this.borderRounding, pointerEvents: this._pointerEvents === 'visiblePainted' ? 'none' : this._pointerEvents, // visible painted means that the underlying doc contents are irregular and will process their own pointer events (otherwise, the contents are expected to fill the entire doc view box so we can handle pointer events here) }}> - <> - {this._componentView instanceof KeyValueBox ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation], this.Document)} - {borderPath?.jsx} - </> + {this._componentView instanceof KeyValueBox ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation])} + {borderPath?.jsx} </div> ); } @@ -994,7 +1000,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document * @param presEffectDoc presentation effects document that specifies the animation effect parameters * @returns a function that will wrap a JSX animation element wrapping any JSX element */ - public static AnimationEffect(renderDoc: JSX.Element, presEffectDoc: Opt<Doc>, root: Doc) { + public static AnimationEffect(renderDoc: JSX.Element, presEffectDoc: Opt<Doc> /* , root: Doc */) { const dir = presEffectDoc?.presentation_effectDirection ?? presEffectDoc?.followLinkAnimDirection; const effectProps = { left: dir === PresEffectDirection.Left, @@ -1005,10 +1011,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document delay: 0, duration: Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null)), }; - //prettier-ignore + // prettier-ignore switch (StrCast(presEffectDoc?.presentation_effect, StrCast(presEffectDoc?.followLinkAnimEffect))) { - default: - case PresEffect.None: return renderDoc; case PresEffect.Zoom: return <Zoom {...effectProps}>{renderDoc}</Zoom>; case PresEffect.Fade: return <Fade {...effectProps}>{renderDoc}</Fade>; case PresEffect.Flip: return <Flip {...effectProps}>{renderDoc}</Flip>; @@ -1016,17 +1020,19 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document case PresEffect.Bounce: return <Bounce {...effectProps}>{renderDoc}</Bounce>; case PresEffect.Roll: return <Roll {...effectProps}>{renderDoc}</Roll>; case PresEffect.Lightspeed: return <JackInTheBox {...effectProps}>{renderDoc}</JackInTheBox>; + case PresEffect.None: + default: return renderDoc; } } public static recordAudioAnnotation(dataDoc: Doc, field: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) { let gumStream: any; let recorder: any; - navigator.mediaDevices.getUserMedia({ audio: true }).then(function (stream) { + navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => { let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null); if (audioTextAnnos) audioTextAnnos.push(''); else audioTextAnnos = dataDoc[field + '_audioAnnotations_text'] = new List<string>(['']); DictationManager.Controls.listen({ - interimHandler: value => (audioTextAnnos[audioTextAnnos.length - 1] = value), + interimHandler: value => { audioTextAnnos[audioTextAnnos.length - 1] = value; }, // prettier-ignore continuous: { indefinite: false }, }).then(results => { if (results && [DictationManager.Controls.Infringed].includes(results)) { @@ -1060,9 +1066,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document } @observer -export class DocumentView extends DocComponent<DocumentViewProps>() { +export class DocumentView extends DocComponent<DocumentViewProps & { CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView }>() { public static ROOT_DIV = 'documentView-effectsWrapper'; - public get displayName() { return 'DocumentView(' + this.Document?.title + ')'; } // prettier-ignore + public get displayName() { return 'DocumentView(' + (this.Document?.title??"") + ')'; } // prettier-ignore public ContentRef = React.createRef<HTMLDivElement>(); private _htmlOverlayEffect: Opt<Doc>; private _disposers: { [name: string]: IReactionDisposer } = {}; @@ -1084,7 +1090,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { return () => (SnappingManager.ExploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined); } - constructor(props: DocumentViewProps) { + constructor(props: DocumentViewProps & { CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView }) { super(props); makeObservable(this); } @@ -1161,7 +1167,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.RemoveView(this); } - public set IsSelected(val) { runInAction(() => (this._selected = val)); } // prettier-ignore + public set IsSelected(val) { runInAction(() => { this._selected = val; }); } // prettier-ignore public get IsSelected() { return this._selected; } // prettier-ignore public get topMost() { return this._props.renderDepth === 0; } // prettier-ignore public get ContentDiv() { return this._docViewInternal?._contentDiv; } // prettier-ignore @@ -1176,7 +1182,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { return this._props.layout_fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth; } @computed get anchorViewDoc() { - return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document['link_anchor_2']) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document['link_anchor_1']) : undefined; + return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document.link_anchor_2) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document.link_anchor_1) : undefined; } @computed get getBounds(): Opt<{ left: number; top: number; right: number; bottom: number; transition?: string }> { @@ -1213,6 +1219,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { public get containerViewPath() { return this._props.containerViewPath; } // prettier-ignore public get CollectionFreeFormView() { return this.CollectionFreeFormDocumentView?.CollectionFreeFormView; } // prettier-ignore public get CollectionFreeFormDocumentView() { return this._props.CollectionFreeFormDocumentView?.(); } // prettier-ignore + public get LocalRotation() { return this._props.LocalRotation?.(); } // prettier-ignore public clearViewTransition = () => { this._viewTimer && clearTimeout(this._viewTimer); @@ -1231,18 +1238,18 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { public iconify(finished?: () => void, animateTime?: number) { this.ComponentView?.updateIcon?.(); const animTime = this._docViewInternal?.animateScaleTime(); - runInAction(() => this._docViewInternal && animateTime !== undefined && (this._docViewInternal._animateScaleTime = animateTime)); + runInAction(() => { this._docViewInternal && animateTime !== undefined && (this._docViewInternal._animateScaleTime = animateTime); }); // prettier-ignore const finalFinished = action(() => { finished?.(); this._docViewInternal && (this._docViewInternal._animateScaleTime = animTime); }); - const layout_fieldKey = Cast(this.Document.layout_fieldKey, 'string', null); - if (layout_fieldKey !== 'layout_icon') { + const layoutFieldKey = Cast(this.Document.layout_fieldKey, 'string', null); + if (layoutFieldKey !== 'layout_icon') { this.switchViews(true, 'icon', finalFinished); - if (layout_fieldKey && layout_fieldKey !== 'layout' && layout_fieldKey !== 'layout_icon') this.Document.deiconifyLayout = layout_fieldKey.replace('layout_', ''); + if (layoutFieldKey && layoutFieldKey !== 'layout' && layoutFieldKey !== 'layout_icon') this.Document.deiconifyLayout = layoutFieldKey.replace('layout_', ''); } else { const deiconifyLayout = Cast(this.Document.deiconifyLayout, 'string', null); - this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finalFinished, true); + this.switchViews(!!deiconifyLayout, deiconifyLayout, finalFinished, true); this.Document.deiconifyLayout = undefined; this._props.bringToFront?.(this.Document); } @@ -1262,7 +1269,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { autoplay: true, loop: false, volume: 0.5, - onend: action(() => (self.dataDoc.audioAnnoState = AudioAnnoState.stopped)), + onend: action(() => { self.dataDoc.audioAnnoState = AudioAnnoState.stopped; }), // prettier-ignore }); this.dataDoc.audioAnnoState = AudioAnnoState.playing; break; @@ -1270,6 +1277,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { this.dataDoc[AudioPlay]?.stop(); this.dataDoc.audioAnnoState = AudioAnnoState.stopped; break; + default: } } }; @@ -1284,10 +1292,10 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { this._docViewInternal._animateScaleTime = time; } }); - public setAnimEffect = (presEffect: Doc, timeInMs: number, afterTrans?: () => void) => { + public setAnimEffect = (presEffect: Doc, timeInMs: number /* , afterTrans?: () => void */) => { this._animEffectTimer && clearTimeout(this._animEffectTimer); this.Document[Animation] = presEffect; - this._animEffectTimer = setTimeout(() => (this.Document[Animation] = undefined), timeInMs); + this._animEffectTimer = setTimeout(() => { this.Document[Animation] = undefined; }, timeInMs); // prettier-ignore }; public setViewTransition = (transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) => { this._viewTimer = DocumentView.SetViewTransition([this.layoutDoc], transProp, timeInMs, this._viewTimer, afterTrans, dataTrans); @@ -1304,7 +1312,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { } const view = SelectionManager.Views[0]?._props.renderDepth > 0 ? SelectionManager.Views[0] : undefined; undoable(() => { - var tempDoc: Opt<Doc>; + let tempDoc: Opt<Doc>; if (view) { if (!view.layoutDoc.isTemplateDoc) { tempDoc = view.Document; @@ -1322,6 +1330,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { } Doc.UserDoc().defaultTextLayout = tempDoc ? new PrefetchProxy(tempDoc) : undefined; }, 'set default template')(); + return undefined; } /** @@ -1335,12 +1344,13 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { const curLayout = StrCast(this.Document.layout_fieldKey).replace('layout_', '').replace('layout', ''); if (!this.Document.layout_default && curLayout !== detailLayoutKeySuffix) this.Document.layout_default = curLayout; const defaultLayout = StrCast(this.Document.layout_default); - if (this.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) this.switchViews(defaultLayout ? true : false, defaultLayout, undefined, true); + if (this.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) this.switchViews(!!defaultLayout, defaultLayout, undefined, true); else this.switchViews(true, detailLayoutKeySuffix, undefined, true); }; public switchViews = (custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => { const batch = UndoManager.StartBatch('switchView:' + view); - runInAction(() => this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1)); // shrink doc + // shrink doc first.. + runInAction(() => { this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1); }); // prettier-ignore setTimeout( action(() => { if (useExistingLayout && custom && this.Document['layout_' + view]) { @@ -1348,7 +1358,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { } else { this.setCustomView(custom, view); } - this._docViewInternal && (this._docViewInternal._animateScalingTo = 1); // expand it + this._docViewInternal && (this._docViewInternal._animateScalingTo = 1); // now expand it setTimeout( action(() => { this._docViewInternal && (this._docViewInternal._animateScalingTo = 0); @@ -1366,7 +1376,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { */ public docViewPath = () => (this.containerViewPath ? [...this.containerViewPath(), this] : [this]); - layout_fitWidthFunc = (doc: Doc) => BoolCast(this.layout_fitWidth); + layout_fitWidthFunc = (/* doc: Doc */) => BoolCast(this.layout_fitWidth); screenToLocalScale = () => this._props.ScreenToLocalTransform().Scale; isSelected = () => this.IsSelected; select = (extendSelection: boolean, focusSelection?: boolean) => { @@ -1390,7 +1400,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { PanelWidth = () => this.panelWidth; PanelHeight = () => this.panelHeight; NativeDimScaling = () => this.nativeScaling; - hideLinkCount = () => (this.hideLinkButton ? true : false); + hideLinkCount = () => !!this.hideLinkButton; selfView = () => this; /** * @returns Transform to the document view (in the coordinate system of whatever contains the DocumentView) @@ -1413,17 +1423,16 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { ref={r => { const val = r?.style.display !== 'none'; // if the outer overlay has been displayed, trigger the innner div to start it's opacity fade in transition if (r && val !== this._enableHtmlOverlayTransitions) { - setTimeout(action(() => (this._enableHtmlOverlayTransitions = val))); + setTimeout(action(() => { this._enableHtmlOverlayTransitions = val; })); // prettier-ignore } }} style={{ display: !this._htmlOverlayText ? 'none' : undefined }}> <div className="documentView-htmlOverlayInner" style={{ transition: `all 500ms`, opacity: this._enableHtmlOverlayTransitions ? 0.9 : 0 }}> {DocumentViewInternal.AnimationEffect( <div className="webBox-textHighlight"> - <ObserverJsxParser autoCloseVoidElements={true} key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this._htmlOverlayText)} /> + <ObserverJsxParser autoCloseVoidElements key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this._htmlOverlayText)} /> </div>, - { ...(this._htmlOverlayEffect ?? {}), presentation_effect: effect ?? PresEffect.Zoom } as any as Doc, - this.Document + { ...(this._htmlOverlayEffect ?? {}), presentation_effect: effect ?? PresEffect.Zoom } as any as Doc )} </div> </div> @@ -1436,7 +1445,15 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { const yshift = Math.abs(this.Yshift) <= 0.001 ? this._props.PanelHeight() : undefined; return ( - <div id={this.ViewGuid} className="contentFittingDocumentView" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))}> + <div + id={this.ViewGuid} + className="contentFittingDocumentView" + onPointerEnter={action(() => { + this._isHovering = true; + })} + onPointerLeave={action(() => { + this._isHovering = false; + })}> {!this.Document || !this._props.PanelWidth() ? null : ( <div className="contentFittingDocumentView-previewDoc" @@ -1462,14 +1479,16 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { layout_fitWidth={this.layout_fitWidthFunc} ScreenToLocalTransform={this.screenToContentsTransform} focus={this._props.focus || emptyFunction} - ref={action((r: DocumentViewInternal | null) => r && (this._docViewInternal = r))} + ref={action((r: DocumentViewInternal | null) => { + r && (this._docViewInternal = r); + })} /> {this.htmlOverlay()} {this.ComponentView?.infoUI?.()} </div> )} {/* display link count button */} - <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.screenToLocalScale} OnHover={true} Bottom={this.topMost} ShowCount={true} /> + <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.screenToLocalScale} OnHover Bottom={this.topMost} ShowCount /> </div> ); } @@ -1493,7 +1512,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { // shows a stacking view collection (by default, but the user can change) of all documents linked to the source public static showBackLinks(linkAnchor: Doc) { - const docId = Doc.CurrentUserEmail + Doc.GetProto(linkAnchor)[Id] + '-pivotish'; + const docId = ClientUtils.CurrentUserEmail() + Doc.GetProto(linkAnchor)[Id] + '-pivotish'; // prettier-ignore DocServer.GetRefField(docId).then(docx => LightboxView.Instance.SetLightboxDoc( @@ -1504,19 +1523,27 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { } } +export function returnEmptyDocViewList() { + return emptyPath; +} + +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) { documentView.iconify(); documentView.select(false); }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function deiconifyViewToLightbox(documentView: DocumentView) { - LightboxView.Instance.AddDocTab(documentView.Document, OpenWhere.lightbox, 'layout'); //, 0); + LightboxView.Instance.AddDocTab(documentView.Document, OpenWhere.lightbox, 'layout'); // , 0); }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) { dv.toggleDetail(detailLayoutKeySuffix); }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSource: Doc) { const collectedLinks = DocListCast(linkCollection[DocData].data); let wid = NumCast(linkSource._width); @@ -1534,9 +1561,10 @@ ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSour Doc.AddDocToList(Doc.GetProto(linkCollection), 'data', embedding); } }); - embedding && DocServer.UPDATE_SERVER_CACHE(); // if a new embedding was made, update the client's server cache so that it will not come back as a promise + embedding && UPDATE_SERVER_CACHE(); // if a new embedding was made, update the client's server cache so that it will not come back as a promise return links; }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function updateTagsCollection(collection: Doc) { const tag = StrCast(collection.title).split('-->')[1]; const matchedTags = Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, tag, false, ['tags']).keys()); |