From 4a9d6d1409327fd99c5f554caebd917a316db32b Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 12 Feb 2021 11:00:29 -0500 Subject: changed lightbox to navigate within frame if next target is alreay there, otherwise create it. changed focus default to call afterFocus(). fixed bug of zooming on target doc to compute scale correctly. --- src/client/views/collections/CollectionSchemaView.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/client/views/collections/CollectionSchemaView.tsx') diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 66064e354..d2ed5427b 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -25,6 +25,7 @@ import { DefaultStyleProvider } from "../StyleProvider"; import "./CollectionSchemaView.scss"; import { CollectionSubView } from "./CollectionSubView"; import { SchemaTable } from "./SchemaTable"; +import { DocUtils } from "../../documents/Documents"; // bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657 export enum ColumnType { @@ -404,7 +405,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { fitContentsToDoc={true} freezeDimensions={true} dontCenter={"y"} - focus={emptyFunction} + focus={DocUtils.DefaultFocus} renderDepth={this.props.renderDepth} rootSelected={this.rootSelected} PanelWidth={this.previewWidth} -- cgit v1.2.3-70-g09d2 From d9ec489c2f549c4aef0b3f458abfda5bb11e7188 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 12 Feb 2021 22:25:40 -0500 Subject: changed audio video annotation to use only timecodeToShow/timecodeToHide. distinguish timeline annotation from video frame annotations with _timelineLabel field. Lightbox improvements to restore original camera values and to use fitContentsToDoc for collections --- src/client/documents/Documents.ts | 1 + src/client/views/LightboxView.tsx | 36 +++++++++++++++++----- .../views/collections/CollectionSchemaView.tsx | 4 +-- .../collections/CollectionStackedTimeline.tsx | 7 ++--- .../views/collections/CollectionTimeView.tsx | 4 +-- src/client/views/collections/TabDocView.tsx | 4 +-- src/client/views/collections/TreeView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 32 ++++++++++--------- src/client/views/nodes/AudioBox.tsx | 6 ++-- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/ImageBox.tsx | 5 ++- src/client/views/nodes/PresBox.tsx | 4 +-- src/client/views/nodes/VideoBox.tsx | 14 ++++----- .../views/nodes/formattedText/FormattedTextBox.tsx | 6 ++-- src/fields/documentSchemas.ts | 1 + 15 files changed, 77 insertions(+), 51 deletions(-) (limited to 'src/client/views/collections/CollectionSchemaView.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index aafa58028..d72efb1b4 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -155,6 +155,7 @@ export interface DocumentOptions { _currentFrame?: number; // the current frame of a frame-based collection (e.g., progressive slide) _timecodeToShow?: number; // the time that a document should be displayed (e.g., when an annotation shows up as a video plays) _timecodeToHide?: number; // the time that a document should be hidden + _timelineLabel?: boolean; // whether the document exists on a timeline lastFrame?: number; // the last frame of a frame-based collection (e.g., progressive slide) activeFrame?: number; // the active frame of a document in a frame base collection appearFrame?: number; // the frame in which the document appears diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 4c6592e58..0957fc94d 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -13,6 +13,7 @@ import { DocUtils } from '../documents/Documents'; import { DocumentManager } from '../util/DocumentManager'; import { SelectionManager } from '../util/SelectionManager'; import { TabDocView } from './collections/TabDocView'; +import { Cast } from '../../fields/Types'; interface LightboxViewProps { PanelWidth: number; @@ -22,14 +23,31 @@ interface LightboxViewProps { @observer export class LightboxView extends React.Component { + public static SavedState: Opt<{ panX: Opt, panY: Opt, scale: Opt, transition: Opt }>; @observable static LightboxDoc: Opt; + @observable static LightboxDocTarget: Opt; @action public static SetLightboxDoc(doc: Opt, future?: Doc[]) { - LightboxView.LightboxDoc = doc; if (!doc) { + if (this.LightboxDoc) { + this.LightboxDoc._panX = this.SavedState?.panX; + this.LightboxDoc._panY = this.SavedState?.panY; + this.LightboxDoc._viewScale = this.SavedState?.scale; + this.LightboxDoc._viewTransition = this.SavedState?.transition; + } LightboxView.LightboxFuture = LightboxView.LightboxHistory = []; - } else if (future) { + } else { + LightboxView.SavedState = { + panX: Cast(doc._panX, "number", null), + panY: Cast(doc._panY, "number", null), + scale: Cast(doc._viewScale, "number", null), + transition: Cast(doc._viewTransition, "string", null) + }; + } + if (future) { LightboxView.LightboxFuture = future.slice(); } + LightboxView.LightboxDoc = LightboxView.LightboxDocTarget = doc; + return true; } public static IsLightboxDocView(path: DocumentView[]) { return path.includes(LightboxView.LightboxDocView.current!); } @@ -58,6 +76,7 @@ export class LightboxView extends React.Component { return LightboxView.SetLightboxDoc(doc); } + fitToBox = () => LightboxView.LightboxDocTarget === LightboxView.LightboxDoc; render() { if (LightboxView.LightboxHistory.lastElement() !== LightboxView.LightboxDoc) LightboxView.LightboxHistory.push(LightboxView.LightboxDoc); let downx = 0, downy = 0; @@ -79,6 +98,7 @@ export class LightboxView extends React.Component { Document={LightboxView.LightboxDoc} DataDoc={undefined} addDocument={undefined} + fitContentsToDoc={this.fitToBox} addDocTab={this.addDocTab} pinToPres={TabDocView.PinDoc} rootSelected={returnTrue} @@ -102,10 +122,10 @@ export class LightboxView extends React.Component { {this.navBtn(undefined, "chevron-left", () => LightboxView.LightboxDoc && LightboxView.LightboxHistory.length ? "" : "none", - e => { + action(e => { e.stopPropagation(); const previous = LightboxView.LightboxHistory.pop(); - const target = LightboxView.LightboxHistory.lastElement(); + const target = LightboxView.LightboxDocTarget = LightboxView.LightboxHistory.lastElement(); const docView = target && DocumentManager.Instance.getLightboxDocumentView(target); if (docView && target) { if (LightboxView.LightboxFuture.lastElement() !== previous) LightboxView.LightboxFuture.push(previous); @@ -113,12 +133,12 @@ export class LightboxView extends React.Component { } else { LightboxView.SetLightboxDoc(target); } - })} + }))} {this.navBtn(this.props.PanelWidth - Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]), "chevron-right", () => LightboxView.LightboxDoc && LightboxView.LightboxFuture.length ? "" : "none", - e => { + action(e => { e.stopPropagation(); - const target = LightboxView.LightboxFuture.pop(); + const target = LightboxView.LightboxDocTarget = LightboxView.LightboxFuture.pop(); const docView = target && DocumentManager.Instance.getLightboxDocumentView(target); if (docView && target) { docView.focus(target, true, 0.9); @@ -126,7 +146,7 @@ export class LightboxView extends React.Component { } else { LightboxView.SetLightboxDoc(target); } - })} + }))} ; } diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index d2ed5427b..528cdc8b7 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -11,7 +11,7 @@ import { listSpec } from "../../../fields/Schema"; import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField"; import { Cast, NumCast } from "../../../fields/Types"; import { TraceMobx } from "../../../fields/util"; -import { emptyFunction, emptyPath, returnFalse, setupMoveUpEvents, returnEmptyDoclist } from "../../../Utils"; +import { emptyFunction, emptyPath, returnFalse, setupMoveUpEvents, returnEmptyDoclist, returnTrue } from "../../../Utils"; import { SelectionManager } from "../../util/SelectionManager"; import { SnappingManager } from "../../util/SnappingManager"; import { Transform } from "../../util/Transform"; @@ -402,7 +402,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { boolean; startTag: string; endTag: string; - fieldKeySuffix?: string; }; @observer @@ -47,7 +46,6 @@ export class CollectionStackedTimeline extends CollectionSubView ({ level: this.getLevel(anchor, overlaps), anchor })); + const drawAnchors = this.childDocs.map(anchor => ({ level: this.getLevel(anchor, overlaps), anchor })); const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2; const isActive = this.props.isChildActive() || this.props.isSelected(false); return
this._timeline = timeline} diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index cc625e12e..869e01fea 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -6,7 +6,7 @@ import { ObjectField } from "../../../fields/ObjectField"; import { RichTextField } from "../../../fields/RichTextField"; import { ComputedField, ScriptField } from "../../../fields/ScriptField"; import { NumCast, StrCast, BoolCast, Cast } from "../../../fields/Types"; -import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../Utils"; +import { emptyFunction, returnFalse, setupMoveUpEvents, returnTrue } from "../../../Utils"; import { Scripting } from "../../util/Scripting"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; @@ -86,7 +86,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { @computed get contents() { return
{ case "close": return CollectionDockingView.CloseSplit(doc, locationParams); case "fullScreen": return CollectionDockingView.OpenFullScreen(doc); case "replace": return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack); - case "lightbox": return LightboxView.SetLightboxDoc(doc); + case "lightbox": return LightboxView.SetLightboxDoc(doc, DocListCast(doc[Doc.LayoutFieldKey(doc) + "-annotations"]).sort((a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow))); case "inPlace": case "add": default: @@ -342,7 +342,7 @@ export class TabDocView extends React.Component { docFilters={CollectionDockingView.Instance.docFilters} docRangeFilters={CollectionDockingView.Instance.docRangeFilters} searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs} - fitContentsToDoc={true} + fitContentsToDoc={returnTrue} />
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 14075db1f..d7198ee7c 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -632,7 +632,7 @@ export class TreeView extends React.Component { PanelHeight={panelHeight} NativeWidth={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfWidth : undefined} NativeHeight={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfHeight : undefined} - fitContentsToDoc={true} + fitContentsToDoc={returnTrue} hideTitle={asText} LayoutTemplateString={asText ? FormattedTextBox.LayoutString("text") : undefined} focus={asText ? this.refocus : returnFalse} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index eb99702a1..1f65cf1ea 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -118,7 +118,7 @@ export class CollectionFreeFormView extends CollectionSubView e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); } @computed get nativeWidth() { return this.fitToContent ? 0 : Doc.NativeWidth(this.Document); } @@ -864,8 +864,8 @@ export class CollectionFreeFormView extends CollectionSubView { - return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, "audioStart", "audioEnd", this._ele?.currentTime || Cast(this.props.Document._currentTimecode, "number", null) || (this.audioState === "recording" ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined)) || this.rootDoc; + return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, "_timecodeToShow" /* audioStart */, "_timecodeToHide" /* audioEnd */, this._ele?.currentTime || Cast(this.props.Document._currentTimecode, "number", null) || (this.audioState === "recording" ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined)) || this.rootDoc; } componentWillUnmount() { @@ -354,8 +354,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitToBox property on a Document ContainingCollectionView: Opt; ContainingCollectionDoc: Opt; setContentView?: (view: DocComponentView) => any; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 67397dc51..441d6232a 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -151,6 +151,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes; @undoBatch rotate = action(() => { @@ -170,6 +172,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" }); funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" }); @@ -223,7 +226,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent if (audio) { audio.mediaStart = "manual"; audio.mediaStop = "manual"; - audio.presStartTime = NumCast(doc.audioStart, NumCast(doc.videoStart)); - audio.presEndTime = NumCast(doc.audioEnd, NumCast(doc.videoEnd)); + audio.presStartTime = NumCast(doc._timecodeToShow /* audioStart */, NumCast(doc._timecodeToShow /* videoStart */)); + audio.presEndTime = NumCast(doc._timecodeToHide /* audioEnd */, NumCast(doc._timecodeToHide /* videoEnd */)); audio.presDuration = audio.presStartTime - audio.presEndTime; TabDocView.PinDoc(audio, { audioRange: true }); setTimeout(() => this.removeDocument(doc), 0); diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index a99853aac..6d0a200cb 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -4,7 +4,7 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction, import { observer } from "mobx-react"; import * as rp from 'request-promise'; import { Dictionary } from "typescript-collections"; -import { Doc, DocListCast } from "../../../fields/Doc"; +import { Doc, DocListCast, StrListCast } from "../../../fields/Doc"; import { documentSchema } from "../../../fields/documentSchemas"; import { InkTool } from "../../../fields/InkField"; import { makeInterface } from "../../../fields/Schema"; @@ -71,7 +71,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent { const timecode = Cast(this.layoutDoc._currentTimecode, "number", null); - return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey + "-timeline", "videoStart", "videoEnd", timecode ? timecode : undefined) || this.rootDoc; + const anchor = CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, "_timecodeToShow"/* videoStart */, "_timecodeToHide" /* videoEnd */, timecode ? timecode : undefined) || this.rootDoc; + return anchor; } choosePath(url: string) { @@ -202,7 +203,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent this.props.isSelected(), selected => { if (!selected) { @@ -494,9 +494,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent (this.props.scaling?.() || 1) * this.heightPercent / 100; marqueeOffset = () => [this.panelWidth() / 2 * (1 - this.heightPercent / 100) / (this.heightPercent / 100), 0]; - + timelineDocFilter = () => ["_timelineLabel:true:x"]; render() { const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding); const borderRadius = borderRad?.includes("px") ? `${Number(borderRad.split("px")[0]) / this.scaling()}px` : borderRad; @@ -558,6 +557,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent { const anchor = (l.anchor1 as Doc).annotationOn ? l.anchor1 as Doc : (l.anchor2 as Doc).annotationOn ? (l.anchor2 as Doc) : undefined; if (anchor && (anchor.annotationOn as Doc).audioState === "recording") { - linkTime = NumCast(anchor.audioStart); + linkTime = NumCast(anchor._timecodeToShow /* audioStart */); linkAnchor = anchor; } }); @@ -1669,8 +1669,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } sidebarContentScaling = () => (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); + fitToBox = () => this.props.Document._fitToBox; @computed get sidebarCollection() { - const fitToBox = this.props.Document._fitToBox; const collectionProps: SubCollectionViewProps & collectionFreeformViewProps = { ...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit, NativeWidth: returnZero, @@ -1683,7 +1683,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp scaleField: this.SidebarKey + "-scale", isAnnotationOverlay: true, fieldKey: this.SidebarKey, - fitContentsToDoc: fitToBox, + fitContentsToDoc: this.fitToBox, select: emptyFunction, active: this.annotationsActive, scaling: this.sidebarContentScaling, diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index 056243953..bdc498c97 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -20,6 +20,7 @@ export const documentSchema = createSchema({ activeFrame: "number", // the active frame of a frame based animated document _currentTimecode: "number", // current play back time of a temporal document (video / audio) _timecodeToShow: "number", // the time that a document should be displayed (e.g., time an annotation should be displayed on a video) + _timecodeToHIde: "number", // the time that a document should be hidden isLabel: "boolean", // whether the document is a label or not (video / audio) markers: listSpec(Doc), // list of markers for audio / video x: "number", // x coordinate when in a freeform view -- cgit v1.2.3-70-g09d2 From 3dae4d3d8ac10f6f3c9f937282dbfa5882584aad Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 16 Feb 2021 14:39:41 -0500 Subject: added getAnchor() to freeformView to capture viewtype & filters. fixed setPanIntoView bug. added setViewSpec componentView method to allow anchor to set view specs. --- .../views/collections/CollectionSchemaCells.tsx | 2 +- .../views/collections/CollectionSchemaView.tsx | 4 +- .../views/collections/CollectionTimeView.tsx | 65 ++++++++++++---------- .../collectionFreeForm/CollectionFreeFormView.tsx | 38 ++++++++++++- src/client/views/nodes/DocumentView.tsx | 17 ++++-- src/client/views/nodes/LinkDocPreview.tsx | 6 +- 6 files changed, 91 insertions(+), 41 deletions(-) (limited to 'src/client/views/collections/CollectionSchemaView.tsx') diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index 904b4c761..2e6186680 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -216,7 +216,7 @@ export class CollectionSchemaCell extends React.Component { const placeholder = type === "number" ? "0" : contents === "" ? "--" : "undefined"; return (
+ ref={dragRef} onPointerDown={this.onPointerDown} onClick={action(e => this._isEditing = true)} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave}>
{!this.props.Document._searchDoc ? diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 528cdc8b7..504cf32a0 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -103,7 +103,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @action setHeaderIsEditing = (isEditing: boolean) => this._headerIsEditing = isEditing; @undoBatch - setColumnType = (columnField: SchemaHeaderField, type: ColumnType): void => { + setColumnType = action((columnField: SchemaHeaderField, type: ColumnType): void => { this._openTypes = false; if (columnTypes.get(columnField.heading)) return; @@ -114,7 +114,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { columns[index] = columnField; this.columns = columns; } - } + }); @undoBatch setColumnColor = (columnField: SchemaHeaderField, color: string): void => { diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 8c3e53dfd..426eced1d 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -1,16 +1,21 @@ +import { toUpper } from "lodash"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, Opt, DocCastAsync, StrListCast } from "../../../fields/Doc"; +import { Doc, DocCastAsync, Opt, StrListCast } from "../../../fields/Doc"; import { List } from "../../../fields/List"; import { ObjectField } from "../../../fields/ObjectField"; import { RichTextField } from "../../../fields/RichTextField"; +import { listSpec } from "../../../fields/Schema"; import { ComputedField, ScriptField } from "../../../fields/ScriptField"; -import { NumCast, StrCast, BoolCast, Cast } from "../../../fields/Types"; -import { emptyFunction, returnFalse, setupMoveUpEvents, returnTrue } from "../../../Utils"; +import { Cast, NumCast, StrCast } from "../../../fields/Types"; +import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from "../../../Utils"; +import { Docs, DocUtils } from "../../documents/Documents"; +import { DocumentManager } from "../../util/DocumentManager"; import { Scripting } from "../../util/Scripting"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; import { EditableView } from "../EditableView"; +import { ViewSpecPrefix } from "../nodes/DocumentView"; import { ViewDefBounds } from "./collectionFreeForm/CollectionFreeFormLayoutEngines"; import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; import { CollectionSubView } from "./CollectionSubView"; @@ -19,12 +24,6 @@ const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; import React = require("react"); -import { DocUtils, Docs } from "../../documents/Documents"; -import { toUpper } from "lodash"; -import { DocumentManager } from "../../util/DocumentManager"; -import { listSpec } from "../../../fields/Schema"; -import { LinkManager } from "../../util/LinkManager"; -import { LinkDocPreview } from "../nodes/LinkDocPreview"; @observer export class CollectionTimeView extends CollectionSubView(doc => doc) { @@ -33,6 +32,9 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { @observable _collapsed: boolean = false; @observable _childClickedScript: Opt; @observable _viewDefDivClick: Opt; + @observable _focusDocFilters: Opt; // fields that get overridden by a focus anchor + @observable _focusPivotField: Opt; + @observable _focusRangeFilters: Opt; getAnchor = () => { const anchor = Docs.Create.TextanchorDocument({ @@ -41,9 +43,15 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { hideLinkButton: true, annotationOn: this.rootDoc }); - Doc.GetProto(anchor).pivotField = this.pivotField; - Doc.GetProto(anchor).docFilters = ObjectField.MakeCopy(this.layoutDoc._docFilters as ObjectField); - Doc.GetProto(anchor)._docRangeFilters = ObjectField.MakeCopy(this.layoutDoc._docRangeFilters as ObjectField); + + // save view spec information for anchor + const proto = Doc.GetProto(anchor); + proto.pivotField = this.pivotField; + proto.docFilters = ObjectField.MakeCopy(this.layoutDoc._docFilters as ObjectField) || new List([]); + proto.docRangeFilters = ObjectField.MakeCopy(this.layoutDoc._docRangeFilters as ObjectField) || new List([]); + proto[ViewSpecPrefix + "_viewType"] = this.layoutDoc._viewType; + + // store anchor in annotations list of document (not technically needed since these anchors are never drawn) if (Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), null) !== undefined) { Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), []).push(anchor); } else { @@ -63,26 +71,24 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { }); } - @observable _scrollFilters: Opt; - @observable _scrollPivotField: Opt; - timeDocFilters = () => this._scrollFilters || ([] as string[]); - get pivotField() { return this._scrollPivotField || StrCast(this.layoutDoc._pivotField); } + get pivotField() { return this._focusPivotField || StrCast(this.layoutDoc._pivotField); } @action - scrollFocus = (anchor: Doc, smooth: boolean) => { - if (!LinkDocPreview.LinkInfo) { - if (anchor.pivotField !== undefined) { - this.layoutDoc._prevFilterIndex = 1; - this.layoutDoc._pivotField = StrCast(anchor.pivotField); - this.layoutDoc._docFilters = ObjectField.MakeCopy(Doc.GetProto(anchor).docFilters as ObjectField); - this.layoutDoc._docRangeFilters = ObjectField.MakeCopy(Doc.GetProto(anchor).docRangeFilters as ObjectField); - } - } else { - this._scrollPivotField = StrCast(anchor.pivotField); - this._scrollFilters = StrListCast(Doc.GetProto(anchor).docFilters); + setViewSpec = (anchor: Doc, preview: boolean) => { + if (preview) { // if in preview, then override document's fields with view spec + this._focusPivotField = StrCast(anchor.pivotField); + this._focusDocFilters = StrListCast(anchor.docFilters); + this._focusRangeFilters = StrListCast(anchor.docRangeFilters); + } else if (anchor.pivotField !== undefined) { // otherwise set document's fields based on anchor view spec + this.layoutDoc._prevFilterIndex = 1; + this.layoutDoc._pivotField = StrCast(anchor.pivotField); + this.layoutDoc._docFilters = new List(this.pivotDocFilters()); + this.layoutDoc._docRangeFilters = new List(this.pivotDocRangeFilters()); } return 0; } + pivotDocFilters = () => this._focusDocFilters || this.props.docFilters(); + pivotDocRangeFilters = () => this._focusRangeFilters || this.props.docRangeFilters(); layoutEngine = () => this._layoutEngine; toggleVisibility = action(() => this._collapsed = !this._collapsed); @@ -136,9 +142,10 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { return
(); @observable _marqueeRef = React.createRef(); + @observable _focusFilters: Opt; // fields that get overriden by focus anchor @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && (this.props.ContainingCollectionView?.active() || this.props.active()); } @computed get fitToContentVals() { @@ -985,7 +987,8 @@ export class CollectionFreeFormView extends CollectionSubView this._focusFilters || this.docFilters(); + @action + setViewSpec = (anchor: Doc, preview: boolean) => { + if (preview) { + this._focusFilters = StrListCast(Doc.GetProto(anchor).docFilters); + } else if (anchor.pivotField !== undefined) { + this.layoutDoc._docFilters = ObjectField.MakeCopy(Doc.GetProto(anchor).docFilters as ObjectField); + } + return 0; + } + + getAnchor = () => { + const anchor = Docs.Create.TextanchorDocument({ + title: StrCast(this.layoutDoc._viewType), + useLinkSmallAnchor: true, + hideLinkButton: true, + annotationOn: this.rootDoc + }); + const proto = Doc.GetProto(anchor); + proto[ViewSpecPrefix + "_viewType"] = this.layoutDoc._viewType; + proto[ViewSpecPrefix + "_docFilters"] = ObjectField.MakeCopy(this.layoutDoc.docFilters as ObjectField) || new List([]); + if (Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), null) !== undefined) { + Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), []).push(anchor); + } else { + this.dataDoc[this.props.fieldKey + "-annotations"] = new List([anchor]); + } + return anchor; + } freeformData = (force?: boolean) => this.fitToContent || force ? this.fitToContentVals : undefined; @action componentDidMount() { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 4aa424af3..e52791901 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -45,6 +45,7 @@ import { LinkDocPreview } from "./LinkDocPreview"; import { PresBox } from './PresBox'; import { RadialMenu } from './RadialMenu'; import React = require("react"); +import { ObjectField } from "../../../fields/ObjectField"; const { Howl } = require('howler'); interface Window { @@ -56,12 +57,13 @@ declare class MediaRecorder { constructor(e: any); } - export enum ViewAdjustment { resetView = 1, 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 interface DocFocusOptions { originalTarget?: Doc; // set in JumpToDocument, used by TabDocView to determine whether to fit contents to tab willZoom?: boolean; // determines whether to zoom in on target document @@ -75,6 +77,7 @@ export type StyleProviderFunc = (doc: Opt, props: Opt Doc; scrollFocus?: (doc: Doc, smooth: boolean) => Opt; // returns the duration of the focus + setViewSpec?: (anchor: Doc, preview: boolean) => void; back?: () => boolean; forward?: () => boolean; url?: () => string; @@ -405,10 +408,14 @@ export class DocumentViewInternal extends DocComponent { - const focusSpeed = this._componentView?.scrollFocus?.(doc, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here + focus = (anchor: Doc, options?: DocFocusOptions) => { + // 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 endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : ViewAdjustment.doNothing; - this.props.focus(options?.docTransform ? doc : this.rootDoc, { + this.props.focus(options?.docTransform ? anchor : this.rootDoc, { ...options, afterFocus: (didFocus: boolean) => new Promise(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : ViewAdjustment.doNothing), focusSpeed ?? 0)) }); diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 7bd5d14d2..716810536 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -45,7 +45,11 @@ export class LinkDocPreview extends React.Component { if (anchor1 && anchor2) { linkTarget = Doc.AreProtosEqual(anchor1, this._linkSrc) || Doc.AreProtosEqual(anchor1?.annotationOn as Doc, this._linkSrc) ? anchor2 : anchor1; } - linkTarget && DocCastAsync(linkTarget.annotationOn).then(action(anno => this._targetDoc = anno)); + if (linkTarget?.annotationOn) { + linkTarget && DocCastAsync(linkTarget.annotationOn).then(action(anno => this._targetDoc = anno)); + } else { + this._targetDoc = linkTarget; + } this._toolTipText = ""; } componentDidUpdate(props: any) { -- cgit v1.2.3-70-g09d2