diff options
Diffstat (limited to 'src/client/views/nodes/DocumentView.tsx')
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 192 |
1 files changed, 144 insertions, 48 deletions
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 4a5fca61a..373e3ee57 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -2,7 +2,7 @@ import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast } from "../../../fields/Doc"; +import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, StrListCast, WidthSym } from "../../../fields/Doc"; import { Document } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; @@ -37,7 +37,7 @@ import { DocComponent } from "../DocComponent"; import { EditableView } from '../EditableView'; import { InkingStroke } from "../InkingStroke"; import { LightboxView } from "../LightboxView"; -import { StyleLayers, StyleProp } from "../StyleProvider"; +import { StyleProp } from "../StyleProvider"; import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView"; import { DocumentContentsView } from "./DocumentContentsView"; import { DocumentLinksButton } from './DocumentLinksButton'; @@ -49,6 +49,8 @@ import { RadialMenu } from './RadialMenu'; import { ScriptingBox } from "./ScriptingBox"; import { PresBox } from './trails/PresBox'; import React = require("react"); +import { DocServer } from "../../DocServer"; +import { FieldViewProps } from "./FieldView"; const { Howl } = require('howler'); interface Window { @@ -79,6 +81,7 @@ export type DocAfterFocusFunc = (notFocused: boolean) => Promise<ViewAdjustment> export type DocFocusFunc = (doc: Doc, options?: DocFocusOptions) => void; export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => any; export interface DocComponentView { + updateIcon?: () => void; // updates the icon representation of the document getAnchor?: () => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box) scrollFocus?: (doc: Doc, smooth: boolean) => Opt<number>; // returns the duration of the focus setViewSpec?: (anchor: Doc, preview: boolean) => void; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document @@ -102,20 +105,27 @@ export interface DocComponentView { snapPt?: (pt: { X: number, Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number, Y: number }, distance: number }; search?: (str: string, bwd?: boolean, clear?: boolean) => boolean; } +// These props are passed to both FieldViews and DocumentViews export interface DocumentViewSharedProps { + fieldKey?: string; // only used by FieldViews but helpful here to allow styleProviders to access fieldKey of FieldViewProps. In priniciple, passing a fieldKey to a documentView could override or be the default fieldKey for fieldViews + DocumentView?: () => DocumentView; renderDepth: number; Document: Doc; DataDoc?: Doc; fitContentsToDoc?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitToBox property on a Document ContainingCollectionView: Opt<CollectionView>; ContainingCollectionDoc: Opt<Doc>; + suppressSetHeight?: boolean; thumbShown?: () => boolean; + isHovering?: () => boolean; setContentView?: (view: DocComponentView) => any; CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView; PanelWidth: () => number; PanelHeight: () => number; docViewPath: () => DocumentView[]; - layerProvider: undefined | ((doc: Doc, assign?: boolean) => boolean); + childHideDecorationTitle?: () => boolean; + childHideResizeHandles?: () => boolean; + dataTransition?: string; // specifies animation transition - used by collectionPile and potentially other layout engines when changing the size of documents so that the change won't be abrupt styleProvider: Opt<StyleProviderFunc>; focus: DocFocusFunc; fitWidth?: (doc: Doc) => boolean; @@ -140,11 +150,13 @@ export interface DocumentViewSharedProps { ignoreAutoHeight?: boolean; forceAutoHeight?: boolean; disableDocBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over. - pointerEvents?: string; + pointerEvents?: () => Opt<string>; scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document createNewFilterDoc?: () => void; updateFilterDoc?: (doc: Doc) => void; } + +// these props are specific to DocuentViews export interface DocumentViewProps extends DocumentViewSharedProps { // properties specific to DocumentViews but not to FieldView freezeDimensions?: boolean; @@ -158,6 +170,7 @@ export interface DocumentViewProps extends DocumentViewSharedProps { radialMenu?: String[]; LayoutTemplateString?: string; dontCenter?: "x" | "y" | "xy"; + dontScaleFilter?: (doc: Doc) => boolean; // decides whether a document can be scaled to fit its container vs native size with scrolling ContentScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NativeWidth?: () => number; NativeHeight?: () => number; @@ -167,8 +180,11 @@ export interface DocumentViewProps extends DocumentViewSharedProps { onDoubleClick?: () => ScriptField; onPointerDown?: () => ScriptField; onPointerUp?: () => ScriptField; + onBrowseClick?: () => (ScriptField | undefined); + onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => (boolean | undefined); } +// these props are only available in DocumentViewIntenral export interface DocumentViewInternalProps extends DocumentViewProps { NativeWidth: () => number; NativeHeight: () => number; @@ -181,6 +197,7 @@ export interface DocumentViewInternalProps extends DocumentViewProps { @observer export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps>() { public static SelectAfterContextMenu = true; // whether a document should be selected after it's contextmenu is triggered. + _animateScaleTime = 300; // milliseconds; @observable _animateScalingTo = 0; @observable _mediaState = 0; @observable _pendingDoubleClick = false; @@ -206,7 +223,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps @computed get ShowTitle() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ShowTitle) as (Opt<string>); } @computed get ContentScale() { return this.props.ContentScaling?.() || 1; } @computed get thumb() { return ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url.href.replace(".png", "_m.png"); } - @computed get hidden() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Hidden); } + @computed get hidden() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Hidden); } @computed get opacity() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Opacity); } @computed get boxShadow() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow); } @computed get borderRounding() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding); } @@ -220,11 +237,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps @computed get finalLayoutKey() { return StrCast(this.Document.layoutKey, "layout"); } @computed get nativeWidth() { return this.props.NativeWidth(); } @computed get nativeHeight() { return this.props.NativeHeight(); } - @computed get onClickHandler() { return this.props.onClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null)); } + @computed get onClickHandler() { return this.props.onClick?.() ?? (this.props.onBrowseClick?.() ?? 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); } + componentWillUnmount() { this.cleanupHandlers(true); } componentDidMount() { this.setupHandlers(); } //componentDidUpdate() { this.setupHandlers(); } @@ -236,6 +254,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps this._holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this)); } } + @action cleanupHandlers(unbrush: boolean) { this._dropDisposer?.(); this._multiTouchDisposer?.(); @@ -411,6 +430,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps const dragData = new DragManager.DocumentDragData([this.props.Document]); const [left, top] = this.props.ScreenToLocalTransform().scale(this.ContentScale).inverse().transformPoint(0, 0); dragData.offset = this.props.ScreenToLocalTransform().scale(this.ContentScale).transformDirection(x - left, y - top); + dragData.offset[0] = Math.min(this.rootDoc[WidthSym](), dragData.offset[0]); + dragData.offset[1] = Math.min(this.rootDoc[HeightSym](), dragData.offset[1]); dragData.dropAction = dropAction; dragData.treeViewDoc = this.props.treeViewDoc; dragData.removeDocument = this.props.removeDocument; @@ -446,7 +467,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps }); // 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, !options?.instant || !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) => @@ -459,10 +480,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) { let stopPropagate = true; let preventDefault = true; - !StrListCast(this.props.Document._layerTags).includes(StyleLayers.Background) && (this.rootDoc._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc); + (this.rootDoc._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc); if (this._doubleTap && (this.props.Document.type !== DocumentType.FONTICON || this.onDoubleClickHandler)) {// && !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._timeout) { clearTimeout(this._timeout); + this._pendingDoubleClick = false; this._timeout = undefined; } if (this.onDoubleClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name)) { // bcz: hack? don't execute script if you're clicking on a scripting box itself @@ -496,6 +518,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps }, console.log).result?.select === true ? this.props.select(false) : ""; const clickFunc = () => this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, "on click"); if (this.onDoubleClickHandler) { + runInAction(() => this._pendingDoubleClick = true); this._timeout = setTimeout(() => { this._timeout = undefined; clickFunc(); }, 350); } else clickFunc(); } else if (this.allLinks && this.Document.type !== DocumentType.LINK && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) { @@ -516,6 +539,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps }); onPointerDown = (e: React.PointerEvent): void => { + if (this.rootDoc.type === DocumentType.INK && CurrentUserUtils.SelectedTool === InkTool.Eraser) return; // continue if the event hasn't been canceled AND we are using a mouse or this has an onClick or onDragStart function (meaning it is a button document) if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool))) { if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) { @@ -531,6 +555,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps // if this is part of a template, let the event go up to the tempalte root unless right/ctrl clicking !(this.props.Document.rootDocument && !(e.ctrlKey || e.button > 0))) { if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && + !this.props.onBrowseClick?.() && !this.Document.ignoreClick && !e.ctrlKey && (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) && @@ -584,12 +609,16 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps } @undoBatch @action - toggleFollowLink = (location: Opt<string>, zoom: boolean, setPushpin: boolean): void => { + 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 (setPushpin) { + this.Document.isPushpin = !this.Document.isPushpin; + this.Document._isLinkButton = this.Document.isPushpin || this.Document._isLinkButton; + } else { + this.Document._isLinkButton = !this.Document._isLinkButton; + } if (this.Document._isLinkButton && !this.onClickHandler) { - this.Document.followLinkZoom = zoom; + zoom !== undefined && (this.Document.followLinkZoom = zoom); this.Document.followLinkLocation = location; } else if (this.Document._isLinkButton && this.onClickHandler) { this.Document._isLinkButton = false; @@ -644,7 +673,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps } if (de.complete.annoDragData || this.rootDoc !== linkdrag.linkSourceDoc.context) { const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.() ?? this.props.Document; - de.complete.linkDocument = DocUtils.MakeLink({ doc: linkdrag.linkSourceDoc }, { doc: dropDoc }, "link", undefined, undefined, undefined, [de.x, de.y]); + de.complete.linkDocument = DocUtils.MakeLink({ doc: linkdrag.linkSourceDoc }, { doc: dropDoc }, undefined, undefined, undefined, undefined, [de.x, de.y - 50]); } } } @@ -655,7 +684,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps 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), _fitWidth: true, title: StrCast(this.props.Document.title) + " [Portal]" }); - DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to"); + DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to:portal from"); } this.Document.followLinkLocation = "inPlace"; this.Document.followLinkZoom = true; @@ -720,7 +749,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps const alias = Doc.MakeAlias(this.rootDoc); alias.layout = FormattedTextBox.LayoutString("newfield"); alias.title = "newfield"; - alias._yMargin = 10; alias._height = 35; alias._width = 100; alias.syncLayoutFieldWithTitle = true; @@ -744,7 +772,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps zorderItems.push({ description: "Send to Back", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, true)), icon: "expand-arrows-alt" }); zorderItems.push({ description: this.rootDoc._raiseWhenDragged !== false ? "Keep ZIndex when dragged" : "Allow ZIndex to change when dragged", event: undoBatch(action(() => this.rootDoc._raiseWhenDragged = this.rootDoc._raiseWhenDragged === undefined ? false : undefined)), icon: "expand-arrows-alt" }); } - !zorders && cm.addItem({ description: "ZOrder...", subitems: zorderItems, icon: "compass" }); + !zorders && cm.addItem({ description: "ZOrder...", noexpand: true, subitems: zorderItems, icon: "compass" }); !Doc.UserDoc().noviceMode && onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" }); !Doc.UserDoc().noviceMode && onClicks.push({ description: "Toggle Detail", event: this.setToggleDetail, icon: "concierge-bell" }); @@ -824,11 +852,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin); contentScaling = () => this.ContentScale; onClickFunc = () => this.onClickHandler; - setHeight = (height: number) => { - if (this.props.renderDepth !== -1) { - this.layoutDoc._height = height; - } - } + setHeight = (height: number) => this.layoutDoc._height = height; setContentView = action((view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view); isContentActive = (outsideReaction?: boolean) => { return this.props.isContentActive() === false ? false : ( @@ -841,8 +865,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps this.props.isContentActive()) ? true : undefined; } @observable _retryThumb = 1; - thumbShown = () => !this.props.isSelected() && LightboxView.LightboxDoc !== this.rootDoc && this.thumb && - !this._componentView?.isAnyChildContentActive?.() ? true : false; + thumbShown = () => { + return !this.props.isSelected() && LightboxView.LightboxDoc !== this.rootDoc && this.thumb && + !Doc.AreProtosEqual(DocumentLinksButton.StartLink, this.rootDoc) && + !Doc.isBrushedHighlightedDegree(this.props.Document) && + !this._componentView?.isAnyChildContentActive?.() ? true : false; + } @computed get contents() { TraceMobx(); const audioView = !this.layoutDoc._showAudio ? (null) : @@ -851,13 +879,17 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps style={{ color: [DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._mediaState] }} icon={!DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "microphone" : "file-audio"} size="sm" /> </div>; + return <div className="documentView-contentsView" style={{ - pointerEvents: this.props.pointerEvents as any ? this.props.pointerEvents as any : (this.rootDoc.type !== DocumentType.INK && ((this.props.contentPointerEvents as any) || (this.isContentActive())) ? "all" : "none"), + pointerEvents: this.props.pointerEvents?.() as any ?? this.rootDoc.layoutKey === "layout_icon" ? "none" : + this.props.contentPointerEvents as any ? this.props.contentPointerEvents as any : + this.rootDoc.type !== DocumentType.INK && this.isContentActive() ? "all" : + "none", height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined, }}> {!this._retryThumb || !this.thumbShown() ? (null) : - <img style={{ background: "white", top: 0, position: "absolute" }} src={this.thumb} // + '?d=' + (new Date()).getTime()} + <img style={{ background: "white", top: 0, position: "relative" }} src={this.thumb} // + '?d=' + (new Date()).getTime()} width={this.props.PanelWidth()} height={this.props.PanelHeight()} onError={(e: any) => { setTimeout(action(() => this._retryThumb = 0), 0); @@ -867,10 +899,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps {...this.props} docViewPath={this.props.viewPath} thumbShown={this.thumbShown} + isHovering={this.isHovering} setContentView={this.setContentView} scaling={this.contentScaling} PanelHeight={this.panelHeight} - setHeight={this.setHeight} + setHeight={!this.props.suppressSetHeight ? this.setHeight : undefined} isContentActive={this.isContentActive} ScreenToLocalTransform={this.screenToLocal} rootSelected={this.rootSelected} @@ -878,10 +911,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps focus={this.focus} layoutKey={this.finalLayoutKey} /> {this.layoutDoc.hideAllLinks ? (null) : this.allLinkEndpoints} - {this.hideLinkButton || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() ? (null) : + {(!this.props.isSelected() && !this._isHovering) || this.hideLinkButton || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() ? (null) : <DocumentLinksButton View={this.props.DocumentView()} ContentScaling={this.props.ContentScaling} - Offset={[this.topMost ? 0 : -15, undefined, undefined, this.topMost ? 10 : -20]} /> + Offset={[this.topMost ? 0 : !this.props.isSelected() ? - 15 : -30, undefined, undefined, this.topMost ? 10 : !this.props.isSelected() ? - 15 : -30]} /> } {audioView} </div>; @@ -1021,7 +1054,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps /> </div>; const targetDoc = (showTitle?.startsWith("_") ? this.layoutDoc : this.rootDoc); - const background = StrCast(SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.sharingDoc.userColor, [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().userColor) : "rgba(0,0,0,0.4)"); + const background = StrCast(SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.sharingDoc.userColor, + Doc.UserDoc().showTitle && [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().userColor) : "rgba(0,0,0,0.4)"); const titleView = !showTitle ? (null) : <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} key="title" style={{ position: this.headerMargin ? "relative" : "absolute", @@ -1059,24 +1093,27 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps {captionView} </div>; } + isHovering = () => this._isHovering; + @observable _isHovering = false; @observable _: string = ""; @computed get renderDoc() { TraceMobx(); const thumb = ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url.href.replace(".png", "_m.png"); const isButton = this.props.Document.type === DocumentType.FONTICON; - if (!(this.props.Document instanceof Doc) || GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate || this.hidden) return null; + if (!(this.props.Document instanceof Doc) || GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate || (this.hidden && !this.props.treeViewDoc)) return null; return this.docContents ?? <div className={`documentView-node${this.topMost ? "-topmost" : ""}`} id={this.props.Document[Id]} + onPointerEnter={action(() => this._isHovering = true)} + onPointerLeave={action(() => this._isHovering = false)} style={{ background: isButton || thumb ? undefined : this.backgroundColor, opacity: this.opacity, color: StrCast(this.layoutDoc.color, "inherit"), fontFamily: StrCast(this.Document._fontFamily, "inherit"), fontSize: Cast(this.Document._fontSize, "string", null), - transformOrigin: this._animateScalingTo ? "center center" : undefined, transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined, - transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform 0.5s ease-${this._animateScalingTo < 1 ? "in" : "out"}`, + transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform ${this._animateScaleTime / 1000}s ease-${this._animateScalingTo < 1 ? "in" : "out"}`, }}> {this.innards} @@ -1087,9 +1124,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps render() { TraceMobx(); 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 = ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "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]; + const highlightColor = ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "orange", "lightBlue"][highlightIndex]; + const highlightStyle = ["solid", "dashed", "solid", "solid", "solid"][highlightIndex]; + const excludeTypes = !this.props.treeViewDoc && highlightIndex < 3 ? [DocumentType.FONTICON, DocumentType.INK] : [DocumentType.FONTICON]; let highlighting = !this.props.disableDocBrushing && highlightIndex && !excludeTypes.includes(this.layoutDoc.type as any) && this.layoutDoc._viewType !== CollectionViewType.Linear; highlighting = highlighting && this.props.focus !== emptyFunction && this.layoutDoc.title !== "[pres element template]"; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way @@ -1099,14 +1136,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps 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} + return <div className={`${DocumentView.ROOT_DIV} docView-hack`} ref={this._mainCont} onContextMenu={this.onContextMenu} onKeyDown={this.onKeyDown} onPointerDown={this.onPointerDown} onClick={this.onClick} - onPointerEnter={e => !SnappingManager.GetIsDragging() && Doc.BrushDoc(this.props.Document)} - onPointerLeave={e => !hasDescendantTarget(e.nativeEvent.x, e.nativeEvent.y, this.ContentDiv) && Doc.UnBrushDoc(this.props.Document)} + onPointerEnter={action(e => !SnappingManager.GetIsDragging() && Doc.BrushDoc(this.props.Document))} + onPointerLeave={action(e => !hasDescendantTarget(e.nativeEvent.x, e.nativeEvent.y, this.ContentDiv) && Doc.UnBrushDoc(this.props.Document))} style={{ + display: this.hidden ? "inline" : undefined, borderRadius: this.borderRounding, pointerEvents: this.pointerEvents, outline: highlighting && !this.borderRounding ? `${highlightColor} ${highlightStyle} ${highlightIndex}px` : "solid 0px", @@ -1144,6 +1182,21 @@ export class DocumentView extends React.Component<DocumentViewProps> { public ContentRef = React.createRef<HTMLDivElement>(); private _disposers: { [name: string]: IReactionDisposer } = {}; + public static showBackLinks(linkSource: Doc) { + const docid = Doc.CurrentUserEmail + Doc.GetProto(linkSource)[Id] + "-pivotish"; + DocServer.GetRefField(docid).then(docx => { + const rootAlias = () => { + const rootAlias = Doc.MakeAlias(linkSource); + rootAlias.x = rootAlias.y = 0; + return rootAlias; + }; + const linkCollection = ((docx instanceof Doc) && docx) || Docs.Create.StackingDocument([/*rootAlias()*/], { title: linkSource.title + "-pivot", _width: 500, _height: 500, }, docid); + linkCollection.linkSource = linkSource; + if (!linkCollection.reactionScript) linkCollection.reactionScript = ScriptField.MakeScript("updateLinkCollection(self)"); + LightboxView.SetLightboxDoc(linkCollection); + }); + } + @observable public docView: DocumentViewInternal | undefined | null; get Document() { return this.props.Document; } @@ -1167,7 +1220,11 @@ export class DocumentView extends React.Component<DocumentViewProps> { return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions)); } - @computed get shouldNotScale() { return (this.fitWidth && !this.nativeWidth) || this.props.treeViewDoc || [CollectionViewType.Docking].includes(this.Document._viewType as any); } + @computed get shouldNotScale() { + return (this.fitWidth && !this.nativeWidth) || + this.props.dontScaleFilter?.(this.Document) || + this.props.treeViewDoc || [CollectionViewType.Docking].includes(this.Document._viewType as any); + } @computed get effectiveNativeWidth() { return this.shouldNotScale ? 0 : (this.nativeWidth || NumCast(this.layoutDoc.width)); } @computed get effectiveNativeHeight() { return this.shouldNotScale ? 0 : (this.nativeHeight || NumCast(this.layoutDoc.height)); } @computed get nativeScaling() { @@ -1194,7 +1251,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight()); focus = (doc: Doc, options?: DocFocusOptions) => this.docView?.focus(doc, options); getBounds = () => { - if (!this.docView || !this.docView.ContentDiv || this.docView.props.renderDepth === 0 || this.docView.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) { + if (!this.docView || !this.docView.ContentDiv || this.docView.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) { return undefined; } const xf = (this.docView?.props.ScreenToLocalTransform().scale(this.nativeScaling)).inverse(); @@ -1206,15 +1263,17 @@ export class DocumentView extends React.Component<DocumentViewProps> { return { left, top, right, bottom, center: this.ComponentView?.getCenter?.(xf) }; } - public iconify() { + public iconify(finished?: () => void) { + this.ComponentView?.updateIcon?.(); const layoutKey = Cast(this.Document.layoutKey, "string", null); if (layoutKey !== "layout_icon") { - this.switchViews(true, "icon"); + this.switchViews(true, "icon", finished); if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") this.Document.deiconifyLayout = layoutKey.replace("layout_", ""); } else { const deiconifyLayout = Cast(this.Document.deiconifyLayout, "string", null); - this.switchViews(deiconifyLayout ? true : false, deiconifyLayout); + this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finished); this.Document.deiconifyLayout = undefined; + this.props.bringToFront(this.rootDoc); } } @undoBatch @@ -1223,13 +1282,16 @@ export class DocumentView extends React.Component<DocumentViewProps> { Doc.setNativeView(this.props.Document); custom && DocUtils.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined); } - switchViews = action((custom: boolean, view: string) => { + switchViews = action((custom: boolean, view: string, finished?: () => void) => { this.docView && (this.docView._animateScalingTo = 0.1); // shrink doc setTimeout(action(() => { this.setCustomView(custom, view); this.docView && (this.docView._animateScalingTo = 1); // expand it - setTimeout(action(() => this.docView && (this.docView._animateScalingTo = 0)), 400); - }), 400); + setTimeout(action(() => { + this.docView && (this.docView._animateScalingTo = 0); + finished?.(); + }), this.docView!._animateScaleTime - 10); + }), this.docView!._animateScaleTime - 10); }); startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this.docView?.startDragging(x, y, dropAction, hideSource); @@ -1247,6 +1309,10 @@ export class DocumentView extends React.Component<DocumentViewProps> { return this.props.ScreenToLocalTransform().translate(-this.centeringX, -this.centeringY).scale(1 / this.nativeScaling); } componentDidMount() { + this._disposers.reactionScript = reaction( + () => ScriptCast(this.rootDoc.reactionScript)?.script?.run({ this: this.props.Document, self: Cast(this.rootDoc, Doc, null) || this.props.Document }).result, + output => output + ); this._disposers.height = reaction( () => NumCast(this.layoutDoc._height), action(height => { @@ -1265,14 +1331,16 @@ 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 isPresTreeElement: boolean = this.props.treeViewDoc?.type === DocumentType.PRES; 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={{ + transition: this.props.dataTransition, position: this.props.Document.isInkMask ? "absolute" : undefined, transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`, - width: isButton ? "100%" : xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`, + width: isButton || isPresTreeElement ? "100%" : xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`, height: isButton || this.props.forceAutoHeight ? undefined : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` : `${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`), }}> @@ -1288,14 +1356,42 @@ export class DocumentView extends React.Component<DocumentViewProps> { ContentScaling={this.ContentScale} ScreenToLocalTransform={this.screenToLocalTransform} focus={this.props.focus || emptyFunction} - bringToFront={emptyFunction} ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))} /> </div>)} </div>); } } +export function deiconifyViewFunc(documentView: DocumentView) { + documentView.iconify(); + //StrCast(doc.layoutKey).split("_")[1] === "icon" && setNativeView(doc); +} +ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) { + documentView.iconify(); + documentView.select(false); +}); + ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) { if (dv.Document.layoutKey === "layout_" + detailLayoutKeySuffix) dv.switchViews(false, "layout"); else dv.switchViews(true, detailLayoutKeySuffix); +}); + +ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc) { + const linkSource = Cast(linkCollection.linkSource, Doc, null); + const collectedLinks = DocListCast(Doc.GetProto(linkCollection).data); + let wid = linkSource[WidthSym](); + const links = DocListCast(linkSource.links); + links.forEach(link => { + const other = LinkManager.getOppositeAnchor(link, linkSource); + const otherdoc = !other ? undefined : other.annotationOn ? Cast(other.annotationOn, Doc, null) : other; + if (otherdoc && !collectedLinks?.some(d => Doc.AreProtosEqual(d, otherdoc))) { + const alias = Doc.MakeAlias(otherdoc); + alias.x = wid; + alias.y = 0; + alias._lockedPosition = false; + wid += otherdoc[WidthSym](); + Doc.AddDocToList(Doc.GetProto(linkCollection), "data", alias); + } + }); + return links; });
\ No newline at end of file |