diff options
Diffstat (limited to 'src/client/views/nodes/DocumentView.tsx')
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 66 |
1 files changed, 38 insertions, 28 deletions
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 23166335e..2bd79df64 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -13,7 +13,7 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Ty import { AudioField } from "../../../fields/URLField"; import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util'; import { MobileInterface } from '../../../mobile/MobileInterface'; -import { emptyFunction, hasDescendantTarget, OmitKeys, returnVal, Utils, returnTrue } from "../../../Utils"; +import { emptyFunction, hasDescendantTarget, OmitKeys, returnTrue, returnVal, Utils } from "../../../Utils"; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { Docs, DocUtils } from "../../documents/Documents"; import { DocumentType } from '../../documents/DocumentTypes'; @@ -25,6 +25,7 @@ import { InteractionUtils } from '../../util/InteractionUtils'; import { LinkManager } from '../../util/LinkManager'; import { Scripting } from '../../util/Scripting'; import { SelectionManager } from "../../util/SelectionManager"; +import { ColorScheme } from "../../util/SettingsManager"; import { SharingManager } from '../../util/SharingManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from "../../util/Transform"; @@ -41,13 +42,13 @@ import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView import { DocumentContentsView } from "./DocumentContentsView"; import { DocumentLinksButton } from './DocumentLinksButton'; import "./DocumentView.scss"; +import { FormattedTextBox } from "./formattedText/FormattedTextBox"; import { LinkAnchorBox } from './LinkAnchorBox'; import { LinkDocPreview } from "./LinkDocPreview"; -import { PresBox } from './trails/PresBox'; import { RadialMenu } from './RadialMenu'; -import React = require("react"); import { ScriptingBox } from "./ScriptingBox"; -import { FormattedTextBox } from "./formattedText/FormattedTextBox"; +import { PresBox } from './trails/PresBox'; +import React = require("react"); const { Howl } = require('howler'); interface Window { @@ -64,7 +65,7 @@ export enum ViewAdjustment { doNothing = 0 } -export const ViewSpecPrefix = "_VIEW"; // field prefix for anchor fields that are immediately copied over to the target document when link is followed. Other anchor properties will be copied over in the specific setViewSpec() method on their view (which allows for seting preview values instead of writing to the document) +export const ViewSpecPrefix = "viewSpec"; // field prefix for anchor fields that are immediately copied over to the target document when link is followed. Other anchor properties will be copied over in the specific setViewSpec() method on their view (which allows for seting preview values instead of writing to the document) export interface DocFocusOptions { originalTarget?: Doc; // set in JumpToDocument, used by TabDocView to determine whether to fit contents to tab @@ -88,6 +89,8 @@ export interface DocComponentView { setKeyFrameEditing?: (set: boolean) => void; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown) playFrom?: (time: number, endTime?: number) => void; setFocus?: () => void; + fieldKey?: string; + annotationKey?: string; getTitle?: () => string; } export interface DocumentViewSharedProps { @@ -133,7 +136,7 @@ export interface DocumentViewSharedProps { export interface DocumentViewProps extends DocumentViewSharedProps { // properties specific to DocumentViews but not to FieldView freezeDimensions?: boolean; - hideResizeHandles?: boolean; // whether to suppress DocumentDecorations when this document is selected + hideResizeHandles?: boolean; // whether to suppress DocumentDecorations when this document is selected hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings hideDecorationTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings treeViewDoc?: Doc; @@ -183,7 +186,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; _componentView: Opt<DocComponentView>; // needs to be accessed from DocumentView wrapper class - private get topMost() { return this.props.renderDepth === 0; } + private get topMost() { return this.props.renderDepth === 0 && !LightboxView.LightboxDoc; } public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive public get ContentDiv() { return this._mainCont.current; } public get LayoutFieldKey() { return Doc.LayoutFieldKey(this.layoutDoc); } @@ -421,11 +424,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps focus = (anchor: Doc, options?: DocFocusOptions) => { LightboxView.SetCookie(StrCast(anchor["cookies-set"])); - // copying over _VIEW fields immediately allows the view type to switch to create the right _componentView - Array.from(Object.keys(Doc.GetProto(anchor))).filter(key => key.startsWith(ViewSpecPrefix)).forEach(spec => this.layoutDoc[spec.replace(ViewSpecPrefix, "")] = ((field) => field instanceof ObjectField ? ObjectField.MakeCopy(field) : field)(anchor[spec])); + // copying over VIEW fields immediately allows the view type to switch to create the right _componentView + Array.from(Object.keys(Doc.GetProto(anchor))).filter(key => key.startsWith(ViewSpecPrefix)).forEach(spec => { + this.layoutDoc[spec.replace(ViewSpecPrefix, "")] = ((field) => field instanceof ObjectField ? ObjectField.MakeCopy(field) : field)(anchor[spec]); + }); // after a timeout, the right _componentView should have been created, so call it to update its view spec values setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false)); - const focusSpeed = this._componentView?.scrollFocus?.(anchor, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here + const focusSpeed = this._componentView?.scrollFocus?.(anchor, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : ViewAdjustment.doNothing; this.props.focus(options?.docTransform ? anchor : this.rootDoc, { ...options, afterFocus: (didFocus: boolean) => @@ -663,7 +668,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps if (!cm || (e as any)?.nativeEvent?.SchemaHandled) return; const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []); - Cast(this.props.Document.contextMenuLabels, listSpec("string"), []).forEach((label, i) => + StrListCast(this.Document.contextMenuLabels).forEach((label, i) => cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: "sticky-note" })); this.props.contextMenuItems?.().forEach(item => item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: "sticky-note" })); @@ -747,7 +752,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps 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: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" }); + moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Doc.globalServerPath(this.props.Document)), icon: "fingerprint" }); } } @@ -755,16 +760,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps moreItems.push({ description: "Close", event: this.deleteClicked, icon: "times" }); } - !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 : []; !Doc.UserDoc().novice && helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" }); - helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" }); + helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" }); !Doc.UserDoc().novice && helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" }); + !Doc.UserDoc().novice && helpItems.push({ description: "Print DataDoc in Console", event: () => console.log(this.props.Document[DataSym]), icon: "hand-point-right" }); cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" }); } + if (!this.topMost) e?.stopPropagation(); // DocumentViews should stop propagation of this event cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15); DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected(true) && setTimeout(() => SelectionManager.SelectView(this.props.DocumentView(), false), 300); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear. @@ -777,7 +781,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps onClickFunc = () => this.onClickHandler; setHeight = (height: number) => this.layoutDoc._height = height; setContentView = (view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view; - isContentActive = (outsideReaction?: boolean) => this.props.isContentActive() ? true : false; + isContentActive = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || this.props.Document.forceActive || this.props.isContentActive() ? true : false; @computed get contents() { TraceMobx(); const audioView = !this.layoutDoc._showAudio ? (null) : @@ -792,7 +796,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps </div>; return <div className="documentView-contentsView" style={{ - pointerEvents: this.props.contentPointerEvents as any, + pointerEvents: (this.props.contentPointerEvents as any),// || (CurrentUserUtils.SelectedTool !== InkTool.None || SnappingManager.GetIsDragging() || this.isContentActive() ? "all" : "none"), height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined, }}> <DocumentContentsView key={1} {...this.props} @@ -809,7 +813,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps layoutKey={this.finalLayoutKey} /> {this.layoutDoc.hideAllLinks ? (null) : this.allLinkEndpoints} {this.hideLinkButton ? (null) : - <DocumentLinksButton View={this.props.DocumentView()} Offset={[this.topMost ? 0 : -15, undefined, undefined, this.topMost ? 10 : -20]} />} + <div style={{ transformOrigin: "top left", transform: `scale(${Math.min(1, this.props.ScreenToLocalTransform().scale(this.props.ContentScaling?.() || 1).Scale)})` }}> + <DocumentLinksButton View={this.props.DocumentView()} Offset={[this.topMost ? 0 : -15, undefined, undefined, this.topMost ? 10 : -20]} /> + </div>} {audioView} </div>; @@ -839,7 +845,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps if (this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return null; if (this.layoutDoc.presBox || this.rootDoc.type === DocumentType.LINK || this.props.dontRegisterView) return (null); // need to use allLinks for RTF since embedded linked text anchors are not rendered with DocumentViews. All other documents render their anchors with nested DocumentViews so we just need to render the directLinks here - const filtered = DocUtils.FilterDocs(this.rootDoc.type === DocumentType.RTF ? this.allLinks : this.directLinks, this.props.docFilters(), []).filter(d => !d.hidden); + const filtered = DocUtils.FilterDocs(this.rootDoc.type === DocumentType.RTF ? this.allLinks : this.directLinks, this.props.docFilters?.() ?? [], []).filter(d => !d.hidden); return filtered.map((link, i) => <div className="documentView-anchorCont" key={i + 1}> <DocumentView {...this.props} @@ -886,7 +892,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps recorder.ondataavailable = async (e: any) => { const [{ result }] = await Networking.UploadFilesToServer(e.data); if (!(result instanceof Error)) { - const audioDoc = Docs.Create.AudioDocument(Utils.prepend(result.accessPaths.agnostic.client), { title: "audio test", _width: 200, _height: 32 }); + const audioDoc = Docs.Create.AudioDocument(result.accessPaths.agnostic.client, { title: "audio test", _width: 200, _height: 32 }); audioDoc.treeViewExpandedView = "layout"; const audioAnnos = Cast(self.dataDoc[self.LayoutFieldKey + "-audioAnnotations"], listSpec(Doc)); if (audioAnnos === undefined) { @@ -949,12 +955,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps } @computed get renderDoc() { TraceMobx(); + const isButton: boolean = this.props.Document.type === DocumentType.FONTICON; if (!(this.props.Document instanceof Doc) || GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate || this.hidden) return null; return this.docContents ?? <div className={`documentView-node${this.topMost ? "-topmost" : ""}`} id={this.props.Document[Id]} style={{ - background: this.backgroundColor, + background: isButton ? undefined : this.backgroundColor, opacity: this.opacity, color: StrCast(this.layoutDoc.color, "inherit"), fontFamily: StrCast(this.Document._fontFamily, "inherit"), @@ -970,9 +977,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps } render() { const highlightIndex = this.props.LayoutTemplateString ? (Doc.IsHighlighted(this.props.Document) ? 6 : 0) : Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString - const highlightColor = (CurrentUserUtils.ActiveDashboard?.darkScheme ? + const highlightColor = (Doc.UserDoc().colorScheme === ColorScheme.Dark ? ["transparent", "#65350c", "#65350c", "yellow", "magenta", "cyan", "orange"] : - ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"])[highlightIndex]; + ["transparent", "#4476F7", "#4476F7", "yellow", "magenta", "cyan", "orange"])[highlightIndex]; const highlightStyle = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"][highlightIndex]; const excludeTypes = !this.props.treeViewDoc ? [DocumentType.FONTICON, DocumentType.INK] : [DocumentType.FONTICON]; let highlighting = !this.props.disableDocBrushing && highlightIndex && !excludeTypes.includes(this.layoutDoc.type as any) && this.layoutDoc._viewType !== CollectionViewType.Linear; @@ -980,8 +987,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps const borderPath = this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BorderPath) || { path: undefined }; const internal = PresBox.EffectsProvider(this.layoutDoc, this.renderDoc) || this.renderDoc; - const boxShadow = highlighting && this.borderRounding && highlightStyle !== "dashed" ? `0 0 0 ${highlightIndex}px ${highlightColor}` : + const boxShadow = this.props.treeViewDoc ? null : highlighting && this.borderRounding && highlightStyle !== "dashed" ? `0 0 0 ${highlightIndex}px ${highlightColor}` : this.boxShadow || (this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined); + + // Return surrounding highlight return <div className={DocumentView.ROOT_DIV} ref={this._mainCont} onContextMenu={this.onContextMenu} onKeyDown={this.onKeyDown} @@ -1142,14 +1151,15 @@ export class DocumentView extends React.Component<DocumentViewProps> { TraceMobx(); const xshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined); const yshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined); + const isButton: boolean = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear; return (<div className="contentFittingDocumentView"> {!this.props.Document || !this.props.PanelWidth() ? (null) : ( <div className="contentFittingDocumentView-previewDoc" ref={this.ContentRef} style={{ position: this.props.Document.isInkMask ? "absolute" : undefined, - transform: `translate(${this.centeringX}px, ${this.centeringY}px)`, - width: xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`, - height: yshift() ?? (this.fitWidth ? `${this.panelHeight}px` : + transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`, + width: isButton ? "100%" : xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`, + height: isButton ? undefined : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` : `${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`), }}> <DocumentViewInternal {...this.props} |