diff options
Diffstat (limited to 'src/client/views/collections')
22 files changed, 345 insertions, 346 deletions
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index e2594b6ae..c83f4e689 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -15,6 +15,7 @@ import { RichTextField } from '../../../fields/RichTextField'; import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; +import { GestureUtils } from '../../../pen-gestures/GestureUtils'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; @@ -40,7 +41,7 @@ import { CollectionLinearView } from './collectionLinear'; import './CollectionMenu.scss'; import { COLLECTION_BORDER_WIDTH } from './CollectionView'; import { TabDocView } from './TabDocView'; -import { GestureUtils } from '../../../pen-gestures/GestureUtils'; +import { CollectionFreeFormView } from './collectionFreeForm'; interface CollectionMenuProps { panelHeight: () => number; @@ -733,7 +734,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView doc._currentFrame = 0; CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0); } - CollectionFreeFormDocumentView.updateKeyframe(undefined, childDocs, currentFrame || 0); + CollectionFreeFormView.updateKeyframe(undefined, [...childDocs, doc], currentFrame || 0); doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame); } } @@ -747,7 +748,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView this.document._currentFrame = 0; CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); } - this._keyTimer = CollectionFreeFormDocumentView.updateKeyframe(this._keyTimer, this.childDocs, currentFrame || 0); + this._keyTimer = CollectionFreeFormView.updateKeyframe(this._keyTimer, [...this.childDocs, this.document], currentFrame || 0); this.document._currentFrame = Math.max(0, (currentFrame || 0) + 1); this.document.lastFrame = Math.max(NumCast(this.document._currentFrame), NumCast(this.document.lastFrame)); }; @@ -759,7 +760,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView this.document._currentFrame = 0; CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); } - this._keyTimer = CollectionFreeFormDocumentView.gotoKeyframe(this._keyTimer, this.childDocs.slice()); + this._keyTimer = CollectionFreeFormView.gotoKeyframe(this._keyTimer, [...this.childDocs, this.document], 1000); this.document._currentFrame = Math.max(0, (currentFrame || 0) - 1); }; diff --git a/src/client/views/collections/CollectionNoteTakingView.scss b/src/client/views/collections/CollectionNoteTakingView.scss index 4d1f18e54..be1800d81 100644 --- a/src/client/views/collections/CollectionNoteTakingView.scss +++ b/src/client/views/collections/CollectionNoteTakingView.scss @@ -51,10 +51,6 @@ display: flex; } -.collectionNoteTakingViewFieldColumn { - display: flex; - overflow: auto; -} .collectionNoteTakingViewFieldColumn:hover { .collectionNoteTakingView-DocumentButtons { display: flex; @@ -98,6 +94,11 @@ position: relative; display: block; } + .collectionNoteTakingViewFieldColumn { + overflow: auto; + display: flex; + flex-direction: column; + } .collectionSchemaView-previewDoc { height: 100%; @@ -111,14 +112,19 @@ height: auto; } + .collectionNoteTakingView-columnStackContainer { + height: 100%; + overflow: auto; + width: 100%; + display: flex; + } .collectionNoteTakingView-columnStack { height: 100%; width: 100%; - display: inline-block; + display: table-cell; } .collectionNoteTakingView-Nodes { width: 100%; - height: 100%; position: absolute; display: flex; flex-direction: column; @@ -128,7 +134,6 @@ width: 100%; position: absolute; margin: auto; - width: max-content; height: max-content; position: relative; grid-auto-rows: 0px; @@ -165,6 +170,7 @@ // Documents in NoteTaking view .collectionNoteTakingView-columnDoc { display: flex; + width: 100%; // margin: auto; // Removed auto so that it is no longer center aligned - this could be something we change position: relative; diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 26c73438c..6035871cd 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -3,7 +3,7 @@ import { CursorProperty } from 'csstype'; import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { DataSym, Doc, Field, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; -import { Copy, Id } from '../../../fields/FieldSymbols'; +import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; @@ -11,7 +11,6 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Ty import { TraceMobx } from '../../../fields/util'; import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, smoothScroll, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; -import { DocumentType } from '../../documents/DocumentTypes'; import { DragManager, dropActionType } from '../../util/DragManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; @@ -19,7 +18,7 @@ import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { LightboxView } from '../LightboxView'; -import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, ViewAdjustment } from '../nodes/DocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { StyleProp } from '../StyleProvider'; @@ -55,11 +54,11 @@ export class CollectionNoteTakingView extends CollectionSubView() { const needsUnsetCategory = this.childDocs.some(d => !d[this.notetakingCategoryField] && !columnHeaders?.find(sh => sh.heading === 'unset')); if (needsUnsetCategory || columnHeaders === undefined || columnHeaders.length === 0) { setTimeout(() => { - const columnHeaders = Cast(this.dataDoc.columnHeaders, listSpec(SchemaHeaderField), null); + const columnHeaders = Array.from(Cast(this.dataDoc.columnHeaders, listSpec(SchemaHeaderField), null)); const needsUnsetCategory = this.childDocs.some(d => !d[this.notetakingCategoryField] && !columnHeaders?.find(sh => sh.heading === 'unset')); - if (needsUnsetCategory || columnHeaders === undefined || columnHeaders.length === 0) { - if (columnHeaders) columnHeaders.push(new SchemaHeaderField('unset', undefined, undefined, 1)); - else this.dataDoc.columnHeaders = new List<SchemaHeaderField>(); + if (needsUnsetCategory || columnHeaders.length === 0) { + columnHeaders.push(new SchemaHeaderField('unset', undefined, undefined, 1)); + this.resizeColumns(columnHeaders); } }); } @@ -187,20 +186,16 @@ export class CollectionNoteTakingView extends CollectionSubView() { // let's dive in and get the actual document we want to drag/move around focusDocument = (doc: Doc, options: DocFocusOptions) => { Doc.BrushDoc(doc); - let focusSpeed = 0; const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]); if (found) { const top = found.getBoundingClientRect().top; const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top); if (Math.floor(localTop[1]) !== 0) { - smoothScroll((focusSpeed = options.zoomTime ?? 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc); + let focusSpeed = options.zoomTime ?? 500; + smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc); + return focusSpeed; } } - const endFocus = async (moved: boolean) => (options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing); - this.props.focus(this.rootDoc, { - ...options, - afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)), - }); }; styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => { @@ -217,6 +212,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { isContentActive = () => this.props.isSelected() || this.props.isContentActive(); + blockPointerEventsWhenDragging = () => (this.docsDraggedRowCol.length ? 'none' : undefined); // getDisplayDoc returns the rules for displaying a document in this view (ie. DocumentView) getDisplayDoc(doc: Doc, width: () => number) { const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc; @@ -227,6 +223,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { <DocumentView ref={r => (dref = r || undefined)} Document={doc} + pointerEvents={this.blockPointerEventsWhenDragging} DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])} renderDepth={this.props.renderDepth + 1} PanelWidth={width} @@ -276,7 +273,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { // getDocTransform is used to get the coordinates of a document when we go from a view like freeform to columns getDocTransform(doc: Doc, dref?: DocumentView) { const y = this._scroll; // required for document decorations to update when the text box container is scrolled - const { scale, translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined); + const { translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined); // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.props.ScreenToLocalTransform().Scale); } @@ -319,18 +316,16 @@ export class CollectionNoteTakingView extends CollectionSubView() { // Removing example: column widths are [0.5, 0.30, 0.20] --> user deletes the final column --> column widths are [0.625, 0.375]. // Adding example: column widths are [0.6, 0.4] --> user adds column at end --> column widths are [0.4, 0.267, 0.33] @action - resizeColumns = (isAdd: boolean, colWidth: number, colIndex: number) => { - const n = this.columnHeaders.length; - if (n == 1) { - this.columnHeaders[0].setWidth(1); - return true; - } - const scaleFactor = isAdd ? 1 - colWidth : 1 / (1 - colWidth); - this.columnHeaders.forEach((h, i) => { - if (!(isAdd && i == colIndex)) { - h.width < 0 ? h.setWidth(1 / n) : h.setWidth(h.width * scaleFactor); - } - }); + resizeColumns = (headers: SchemaHeaderField[]) => { + const n = headers.length; + const curWidths = headers.reduce((sum, hdr) => sum + Math.abs(hdr.width), 0); + const scaleFactor = 1 / curWidths; + this.dataDoc.columnHeaders = new List<SchemaHeaderField>( + headers.map(h => { + h.setWidth(Math.abs(h.width) * scaleFactor); + return h; + }) + ); return true; }; @@ -533,7 +528,6 @@ export class CollectionNoteTakingView extends CollectionSubView() { dividerWidth={this.DividerWidth} maxColWidth={this.maxColWidth} availableWidth={this.availableWidth} - PanelWidth={this.PanelWidth} key={heading?.heading ?? 'unset'} headings={this.headings} heading={heading?.heading ?? 'unset'} @@ -555,16 +549,16 @@ export class CollectionNoteTakingView extends CollectionSubView() { addGroup = (value: string) => { if (this.columnHeaders) { for (const header of this.columnHeaders) { - if (header.heading == value) { + if (header.heading === value) { alert('You cannot use an existing column name. Please try a new column name'); return value; } } } - const columnHeaders = Cast(this.props.Document.columnHeaders, listSpec(SchemaHeaderField), null); + const columnHeaders = Array.from(Cast(this.props.Document.columnHeaders, listSpec(SchemaHeaderField), null)); const newColWidth = 1 / (this.numGroupColumns + 1); - value && columnHeaders?.push(new SchemaHeaderField(value, undefined, undefined, newColWidth)) && this.resizeColumns(true, newColWidth, this.columnHeaders.length - 1); - this.dataDoc.columnHeaders = new List<SchemaHeaderField>(columnHeaders.map(header => header[Copy]())); + columnHeaders.push(new SchemaHeaderField(value, undefined, undefined, newColWidth)); + value && this.resizeColumns(columnHeaders); return true; }; diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx index 84d1c0205..829d055e5 100644 --- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx +++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx @@ -44,8 +44,7 @@ interface CSVFieldColumnProps { observeHeight: (myref: any) => void; unobserveHeight: (myref: any) => void; editableViewProps: () => any; - resizeColumns: (isAdd: boolean, colWidth: number, colIndex: number) => boolean; - PanelWidth: number; + resizeColumns: (headers: SchemaHeaderField[]) => boolean; maxColWidth: number; dividerWidth: number; availableWidth: number; @@ -141,18 +140,11 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu @undoBatch @action deleteColumn = () => { - const acolumnHeaders = Cast(this.props.Document.columnHeaders, listSpec(SchemaHeaderField), null); - if (acolumnHeaders && this.props.headingObject) { - const index = acolumnHeaders.indexOf(this.props.headingObject); - const columnHeaders = acolumnHeaders; // new List<SchemaHeaderField>(acolumnHeaders.map(header => header[Copy]())); // needed for undo to work properly. otherwise we end up changing field values in the undo stack since they are shared by reference - const newColIndex = index > 0 ? index - 1 : 1; - const newColHeader = this.props.columnHeaders ? this.props.columnHeaders[newColIndex] : undefined; - const newHeading = newColHeader ? newColHeader.heading : 'unset'; - this.props.docList.forEach(d => (d[this.props.pivotField] = newHeading)); - const colWidth = this.props.columnHeaders ? this.props.columnHeaders[index].width : 0; - columnHeaders.splice(index, 1); - //Doc.GetProto(this.props.Document).columnHeaders = columnHeaders; - this.props.resizeColumns(false, colWidth, index); + const columnHeaders = Array.from(Cast(this.props.Document.columnHeaders, listSpec(SchemaHeaderField), null)); + if (this.props.headingObject) { + this.props.docList.forEach(d => (d[this.props.pivotField] = undefined)); + columnHeaders.splice(columnHeaders.indexOf(this.props.headingObject), 1); + this.props.resizeColumns(columnHeaders); } }; @@ -262,28 +254,30 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu return ( <> {headingView} - <div className="collectionNoteTakingView-columnStack"> - <div - key={`${heading}-stack`} - className={`collectionNoteTakingView-Nodes`} - style={{ - padding: `${columnYMargin}px ${0}px ${this.props.yMargin}px ${0}px`, - gridGap: this.props.gridGap, - gridTemplateColumns: templatecols, - }}> - {this.props.renderChildren(this.props.docList)} - </div> + <div className="collectionNoteTakingView-columnStackContainer"> + <div className="collectionNoteTakingView-columnStack"> + <div + key={`${heading}-stack`} + className={`collectionNoteTakingView-Nodes`} + style={{ + padding: `${columnYMargin}px ${0}px ${this.props.yMargin}px ${0}px`, + gridGap: this.props.gridGap, + gridTemplateColumns: templatecols, + }}> + {this.props.renderChildren(this.props.docList)} + </div> - {!this.props.chromeHidden && type !== DocumentType.PRES ? ( - <div className="collectionNoteTakingView-DocumentButtons" style={{ marginBottom: 10 }}> - <div key={`${heading}-add-document`} className="collectionNoteTakingView-addDocumentButton"> - <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.textCallback} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} /> - </div> - <div key={`${this.props.Document[Id]}-addGroup`} className="collectionNoteTakingView-addDocumentButton"> - <EditableView {...this.props.editableViewProps()} /> + {!this.props.chromeHidden && type !== DocumentType.PRES ? ( + <div className="collectionNoteTakingView-DocumentButtons" style={{ marginBottom: 10 }}> + <div key={`${heading}-add-document`} className="collectionNoteTakingView-addDocumentButton"> + <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.textCallback} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} /> + </div> + <div key={`${this.props.Document[Id]}-addGroup`} className="collectionNoteTakingView-addDocumentButton"> + <EditableView {...this.props.editableViewProps()} /> + </div> </div> - </div> - ) : null} + ) : null} + </div> </div> </> ); diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 28f08b6ce..572fbfec2 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -54,7 +54,7 @@ export enum TrimScope { @observer export class CollectionStackedTimeline extends CollectionSubView<CollectionStackedTimelineProps>() { @observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined; - @observable public static CurrentlyPlaying: Doc[]; + @observable public static CurrentlyPlaying: DocumentView[]; static RangeScript: ScriptField; static LabelScript: ScriptField; @@ -190,7 +190,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack CollectionStackedTimeline.SelectingRegion = this; } else { this._markerEnd = this.currentTime; - CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this._markerStart, this._markerEnd, undefined, true); + CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this._markerStart, this._markerEnd, undefined, true); this._markerEnd = undefined; CollectionStackedTimeline.SelectingRegion = undefined; } @@ -257,7 +257,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack this._markerEnd = tmp; } if (!isClick && Math.abs(movement[0]) > 15 && !this.IsTrimming) { - const anchor = CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this._markerStart, this._markerEnd, undefined, true); + const anchor = CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this._markerStart, this._markerEnd, undefined, true); setTimeout(() => DocumentManager.Instance.getDocumentView(anchor)?.select(false)); } (!isClick || !wasSelecting) && (this._markerEnd = undefined); @@ -273,7 +273,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack undefined, () => { if (shiftKey) { - CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this.currentTime, undefined, undefined, true); + CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.currentTime, undefined, undefined, true); } else { !wasPlaying && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width)); } @@ -388,8 +388,10 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack // creates marker on timeline @undoBatch @action - static createAnchor(rootDoc: Doc, dataDoc: Doc, fieldKey: string, startTag: string, endTag: string, anchorStartTime: Opt<number>, anchorEndTime: Opt<number>, docAnchor: Opt<Doc>, addAsAnnotation: boolean) { + static createAnchor(rootDoc: Doc, dataDoc: Doc, fieldKey: string, anchorStartTime: Opt<number>, anchorEndTime: Opt<number>, docAnchor: Opt<Doc>, addAsAnnotation: boolean) { if (anchorStartTime === undefined) return rootDoc; + const startTag = '_timecodeToShow'; + const endTag = '_timecodeToHide'; const anchor = docAnchor ?? Docs.Create.LabelDocument({ @@ -763,7 +765,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> // starting the drag event for anchor resizing onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => { - this.props._timeline?.setPointerCapture(e.pointerId); + //this.props._timeline?.setPointerCapture(e.pointerId); const newTime = (e: PointerEvent) => { const rect = (e.target as any).getBoundingClientRect(); return this.props.toTimeline(e.clientX - rect.x, rect.width); @@ -791,7 +793,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> }, e => { this.props.setTime(newTime(e)); - this.props._timeline?.releasePointerCapture(e.pointerId); + // this.props._timeline?.releasePointerCapture(e.pointerId); undo?.end(); }, emptyFunction diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 2f495d55c..4805a748b 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -22,7 +22,7 @@ import { ContextMenuProps } from '../ContextMenuItem'; import { EditableView } from '../EditableView'; import { LightboxView } from '../LightboxView'; import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; -import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, ViewAdjustment } from '../nodes/DocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { StyleProp } from '../StyleProvider'; @@ -129,6 +129,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection TraceMobx(); // appears that we are going to reset the _docXfs. TODO: what is Xfs? this._docXfs.length = 0; + this._renderCount < docs.length && setTimeout(action(() => (this._renderCount = Math.min(docs.length, this._renderCount + 5)))); return docs.map((d, i) => { const height = () => this.getDocHeight(d); const width = () => this.getDocWidth(d); @@ -139,7 +140,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection // So we're choosing whether we're going to render a column or a masonry doc return ( <div className={`collectionStackingView-${this.isStackingView ? 'columnDoc' : 'masonryDoc'}`} key={d[Id]} style={style}> - {this.getDisplayDoc(d, width)} + {this.getDisplayDoc(d, width, i)} </div> ); }); @@ -249,20 +250,17 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection focusDocument = (doc: Doc, options: DocFocusOptions) => { Doc.BrushDoc(doc); - let focusSpeed = 0; const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]); if (found) { const top = found.getBoundingClientRect().top; const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top); if (Math.floor(localTop[1]) !== 0) { - smoothScroll((focusSpeed = options.zoomTime ?? 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc); + let focusSpeed = options.zoomTime ?? 500; + smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc); + return focusSpeed; } } - const endFocus = async (moved: boolean) => options?.afterFocus?.(moved) ?? ViewAdjustment.doNothing; - this.props.focus(this.rootDoc, { - ...options, - afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)), - }); + return undefined; }; styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => { @@ -297,12 +295,13 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection }; isContentActive = () => (this.props.isSelected() || this.props.isContentActive() ? true : this.props.isSelected() === false || this.props.isContentActive() === false ? false : undefined); + @observable _renderCount = 5; isChildContentActive = () => this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined; isChildButtonContentActive = () => (this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined); // this is what renders the document that you see on the screen // called in Children: this actually adds a document to our children list - getDisplayDoc(doc: Doc, width: () => number) { + getDisplayDoc(doc: Doc, width: () => number, count: number) { const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc; const height = () => this.getDocHeight(doc); @@ -310,7 +309,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection const stackedDocTransform = () => this.getDocTransform(doc, dref); this._docXfs.push({ stackedDocTransform, width, height }); //DocumentView is how the node will be rendered - return ( + return count > this._renderCount ? null : ( <DocumentView ref={r => (dref = r || undefined)} Document={doc} diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 27ae3041f..c88ae314e 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -67,6 +67,7 @@ export function CollectionSubView<X>(moreProps?: X) { // to its children which may be templates. // If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey' @computed get dataField() { + if (this.layoutDoc[this.props.fieldKey]) return this.layoutDoc[this.props.fieldKey]; // sets the dataDoc's data field to an empty list if the data field is undefined - prevents issues with addonly // setTimeout changes it outside of the @computed section !this.dataDoc[this.props.fieldKey] && setTimeout(() => !this.dataDoc[this.props.fieldKey] && (this.dataDoc[this.props.fieldKey] = new List<Doc>())); diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 0ec21cc51..89b2fbfe3 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -7,7 +7,7 @@ import { ObjectField } from '../../../fields/ObjectField'; import { RichTextField } from '../../../fields/RichTextField'; import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; -import { Cast, NumCast, StrCast } from '../../../fields/Types'; +import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; import { emptyFunction, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; @@ -16,12 +16,13 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { EditableView } from '../EditableView'; -import { ViewSpecPrefix } from '../nodes/DocumentView'; import { computePivotLayout, computeTimelineLayout, ViewDefBounds } from './collectionFreeForm/CollectionFreeFormLayoutEngines'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; import { CollectionSubView } from './CollectionSubView'; import './CollectionTimeView.scss'; import React = require('react'); +import { DocFocusOptions, DocumentView } from '../nodes/DocumentView'; +import { PresBox } from '../nodes/trails'; @observer export class CollectionTimeView extends CollectionSubView() { @@ -32,18 +33,26 @@ export class CollectionTimeView extends CollectionSubView() { @observable _viewDefDivClick: Opt<ScriptField>; @observable _focusPivotField: Opt<string>; + async componentDidMount() { + this.props.setContentView?.(this); + //const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || DocUtils.findTemplate("detailView", StrCast(this.rootDoc.type), ""); + ///const childText = "const alias = getAlias(self); switchView(alias, detailView); alias.dropAction='alias'; useRightSplit(alias, shiftKey); "; + runInAction(() => { + this._childClickedScript = ScriptField.MakeScript('openInLightbox(self)', { this: Doc.name }); + this._viewDefDivClick = ScriptField.MakeScript('pivotColumnClick(this,payload)', { payload: 'any' }); + }); + } + + get pivotField() { + return this._focusPivotField || StrCast(this.layoutDoc._pivotField); + } + getAnchor = (addAsAnnotation: boolean) => { const anchor = Docs.Create.HTMLAnchorDocument([], { title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any, annotationOn: this.rootDoc, }); - - // 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<string>([]); - proto.docRangeFilters = ObjectField.MakeCopy(this.layoutDoc._docRangeFilters as ObjectField) || new List<string>([]); - proto[ViewSpecPrefix + '_viewType'] = this.layoutDoc._viewType; + PresBox.pinDocView(anchor, { pinData: { viewType: true, pivot: true, filters: true } }, this.rootDoc); if (addAsAnnotation) { // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered @@ -56,34 +65,13 @@ export class CollectionTimeView extends CollectionSubView() { return anchor; }; - async componentDidMount() { - this.props.setContentView?.(this); - //const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || DocUtils.findTemplate("detailView", StrCast(this.rootDoc.type), ""); - ///const childText = "const alias = getAlias(self); switchView(alias, detailView); alias.dropAction='alias'; useRightSplit(alias, shiftKey); "; - runInAction(() => { - this._childClickedScript = ScriptField.MakeScript('openInLightbox(self)', { this: Doc.name }); - this._viewDefDivClick = ScriptField.MakeScript('pivotColumnClick(this,payload)', { payload: 'any' }); - }); - } - - get pivotField() { - return this._focusPivotField || StrCast(this.layoutDoc._pivotField); - } @action - setViewSpec = (anchor: Doc, preview: boolean) => { - if (preview) { - // if in preview, then override document's fields with view spec - this._focusFilters = StrListCast(Doc.GetProto(anchor).docFilters); - this._focusRangeFilters = StrListCast(Doc.GetProto(anchor).docRangeFilters); - this._focusPivotField = StrCast(anchor.pivotField); - } 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<string>(StrListCast(anchor.docFilters)); - this.layoutDoc._docRangeFilters = new List<string>(StrListCast(anchor.docRangeFilters)); - } - return 0; + scrollPreview = (docView: DocumentView, anchor: Doc, focusSpeed: number, options: DocFocusOptions) => { + // if in preview, then override document's fields with view spec + this._focusFilters = StrListCast(anchor.presDocFilters); + this._focusRangeFilters = StrListCast(anchor.presPinDocRangeFilters); + this._focusPivotField = StrCast(anchor.presPivotField); + return undefined; }; layoutEngine = () => this._layoutEngine; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 1a265af4a..330e116d1 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -259,11 +259,13 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label })); }; headerFields = () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields); + @observable _renderCount = 1; @computed get treeViewElements() { TraceMobx(); const dropAction = StrCast(this.doc.childDropAction) as dropActionType; const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before); const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument?.(d, target, addDoc) || false; + if (this._renderCount < this.treeChildren.length) setTimeout(action(() => (this._renderCount = Math.min(this.treeChildren.length, this._renderCount + 20)))); return TreeView.GetChildElements( this.treeChildren, this, @@ -296,7 +298,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree //TODO: [AL] add these this.props.AddToMap, this.props.RemFromMap, - this.props.hierarchyIndex + this.props.hierarchyIndex, + this._renderCount ); } @computed get titleBar() { diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 55f46906e..065ce861a 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -193,7 +193,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab if (this.rootDoc.childClickedOpenTemplateView instanceof Doc) { optionItems.push({ description: 'View Child Detailed Layout', event: () => this.props.addDocTab(this.rootDoc.childClickedOpenTemplateView as Doc, OpenWhere.addRight), icon: 'project-diagram' }); } - !Doc.noviceMode && optionItems.push({ description: `${this.rootDoc.isInPlaceContainer ? 'Unset' : 'Set'} inPlace Container`, event: () => (this.rootDoc.isInPlaceContainer = !this.rootDoc.isInPlaceContainer), icon: 'project-diagram' }); + !Doc.noviceMode && optionItems.push({ description: `${this.rootDoc._isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => (this.rootDoc._isLightbox = !this.rootDoc._isLightbox), icon: 'project-diagram' }); if (!Doc.noviceMode && false) { optionItems.push({ @@ -258,7 +258,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab childHideDecorationTitle = () => this.props.childHideDecorationTitle?.() ?? BoolCast(this.Document.childHideDecorationTitle); childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.rootDoc.childLayoutTemplate, Doc, null); @computed get childLayoutString() { - return StrCast(this.rootDoc.childLayoutString); + return StrCast(this.rootDoc.childLayoutString, this.props.childLayoutString); } isContentActive = (outsideReaction?: boolean) => this.props.isContentActive(); diff --git a/src/client/views/collections/TabDocView.scss b/src/client/views/collections/TabDocView.scss index 0d045bada..58605c3f4 100644 --- a/src/client/views/collections/TabDocView.scss +++ b/src/client/views/collections/TabDocView.scss @@ -1,5 +1,9 @@ -@import "../global/globalCssVariables.scss"; +@import '../global/globalCssVariables.scss'; +.tabDocView-content { + height: 100%; + width: 100%; +} input.lm_title:focus, input.lm_title { diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 25fccd89c..99283996e 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -247,13 +247,13 @@ export class TabDocView extends React.Component<TabDocViewProps> { alert('Cannot pin presentation document to itself'); return; } - const anchorDoc = DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.getAnchor?.(false); - const pinDoc = Doc.MakeAlias(anchorDoc ?? doc); + const anchorDoc = DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.getAnchor?.(false, pinProps); + const pinDoc = Doc.MakeDelegate(anchorDoc && anchorDoc !== doc ? anchorDoc : doc); pinDoc.presentationTargetDoc = anchorDoc ?? doc; pinDoc.title = doc.title + ' - Slide'; pinDoc.data = new List<Doc>(); // the children of the alias' layout are the presentation slide children. the alias' data field might be children of a collection, PDF data, etc -- in any case we don't want the tree view to "see" this data pinDoc.presMovement = doc.type === DocumentType.SCRIPTING || pinProps?.pinDocLayout ? PresMovement.None : PresMovement.Zoom; - pinDoc.presDuration = 2000; + pinDoc.presDuration = pinDoc.presDuration ?? 1000; pinDoc.groupWithUp = false; pinDoc.context = curPres; // these should potentially all be props passed down by the CollectionTreeView to the TreeView elements. That way the PresBox could configure all of its children at render time @@ -274,7 +274,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { pinDoc.presStartTime = NumCast(doc.clipStart); pinDoc.presEndTime = NumCast(doc.clipEnd, duration); } - PresBox.pinDocView(pinDoc, pinProps.pinDocContent ? { ...pinProps, pinData: PresBox.pinDataTypes(doc) } : pinProps, pinDoc); + //PresBox.pinDocView(pinDoc, pinProps.pinDocContent ? { ...pinProps, pinData: PresBox.pinDataTypes(doc) } : pinProps, anchorDoc && anchorDoc !== doc && !anchorDoc.unrendered ? anchorDoc : doc); pinDoc.onClick = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)'); Doc.AddDocToList(curPres, 'data', pinDoc, presSelected); //save position @@ -305,7 +305,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { const docs = Cast(Doc.MyOverlayDocs.data, listSpec(Doc), []); if (docs.includes(curPres)) docs.splice(docs.indexOf(curPres), 1); CollectionDockingView.AddSplit(curPres, OpenWhereMod.right); - setTimeout(() => DocumentManager.Instance.jumpToDocument(docList.lastElement(), { willPan: true }, undefined, []), 100); // keeps the pinned doc in view since the sidebar shifts things + setTimeout(() => DocumentManager.Instance.showDocument(docList.lastElement(), { willPan: true }), 100); // keeps the pinned doc in view since the sidebar shifts things } setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs } @@ -350,7 +350,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { // replace[:{left,right,top,bottom,<any string>}] - e.g., "replace" will replace the current stack contents, // "replace:right" - will replace the stack on the right named "right" if it exists, or create a stack on the right with that name, // "replace:monkeys" - will replace any tab that has the label 'monkeys', or a tab with that label will be created by default on the right - // inPlace - will add the document to any collection along the path from the document to the docking view that has a field isInPlaceContainer. if none is found, inPlace adds a tab to current stack + // lightbox - will add the document to any collection along the path from the document to the docking view that has a field isLightbox. if none is found, it adds to the full screen lightbox addDocTab = (doc: Doc, location: OpenWhere) => { SelectionManager.DeselectAll(); const whereFields = doc._viewType === CollectionViewType.Docking ? [OpenWhere.dashboard] : location.split(':'); @@ -358,8 +358,16 @@ export class TabDocView extends React.Component<TabDocViewProps> { if (doc.dockingConfig) return DashboardView.openDashboard(doc); // prettier-ignore switch (whereFields[0]) { - case OpenWhere.inPlace: // fall through to lightbox - case OpenWhere.lightbox: return LightboxView.AddDocTab(doc, location, undefined, this.addDocTab); + case undefined: + case OpenWhere.lightbox: if (this.layoutDoc?._isLightbox) { + const lightboxView = !doc.annotationOn && DocCast(doc.context) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.context)) : undefined; + const data = lightboxView?.dataDoc[Doc.LayoutFieldKey(lightboxView.rootDoc)]; + if (lightboxView && (!data || data instanceof List)) { + lightboxView.layoutDoc[Doc.LayoutFieldKey(lightboxView.rootDoc)] = new List<Doc>([doc]); + return true; + } + } + return LightboxView.AddDocTab(doc, location); case OpenWhere.dashboard: return DashboardView.openDashboard(doc); case OpenWhere.fullScreen: return CollectionDockingView.OpenFullScreen(doc); case OpenWhere.close: return CollectionDockingView.CloseSplit(doc, whereMods); @@ -380,19 +388,17 @@ export class TabDocView extends React.Component<TabDocViewProps> { getCurrentFrame = () => { return NumCast(Cast(PresBox.Instance.childDocs[PresBox.Instance.itemIndex].presentationTargetDoc, Doc, null)._currentFrame); }; + static Activate = (tabDoc: Doc) => { + const tab = Array.from(CollectionDockingView.Instance?.tabMap!).find(tab => tab.DashDoc === tabDoc); + tab?.header.parent.setActiveContentItem(tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost) + return tab !== undefined; + }; @action focusFunc = (doc: Doc, options: DocFocusOptions) => { - const shrinkwrap = options?.originalTarget === this._document && this.view?.ComponentView?.shrinkWrap; - if (options?.willPanZoom !== false && shrinkwrap && this._document) { - const focusSpeed = options.zoomTime ?? 500; - shrinkwrap(); - this._view?.setViewTransition('transform', focusSpeed, () => options?.afterFocus?.(false)); - } else { - options?.afterFocus?.(false); - } if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) { this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost) } + return undefined; }; active = () => this._isActive; @observable _forceInvalidateScreenToLocal = 0; @@ -468,11 +474,9 @@ export class TabDocView extends React.Component<TabDocViewProps> { render() { return ( <div - className="collectionDockingView-content" + className="tabDocView-content" style={{ fontFamily: Doc.UserDoc().renderStyle === 'comic' ? 'Comic Sans MS' : undefined, - height: '100%', - width: '100%', }} ref={ref => { if ((this._mainCont = ref)) { diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index bd326f917..0d2968ba1 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -32,6 +32,7 @@ import { CollectionTreeView, TreeViewType } from './CollectionTreeView'; import { CollectionView } from './CollectionView'; import './TreeView.scss'; import React = require('react'); +import { render } from 'react-dom'; export interface TreeViewProps { treeView: CollectionTreeView; @@ -95,7 +96,7 @@ export class TreeView extends React.Component<TreeViewProps> { private _header: React.RefObject<HTMLDivElement> = React.createRef(); private _tref = React.createRef<HTMLDivElement>(); @observable _docRef: Opt<DocumentView>; - private _selDisposer: Opt<IReactionDisposer>; + private _disposers: { [name: string]: IReactionDisposer } = {}; private _editTitleScript: (() => ScriptField) | undefined; private _openScript: (() => ScriptField) | undefined; private _treedropDisposer?: DragManager.DragDropDisposer; @@ -212,14 +213,14 @@ export class TreeView extends React.Component<TreeViewProps> { }; @action setEditTitle = (docView?: DocumentView) => { - this._selDisposer?.(); + this._disposers.selection?.(); if (!docView) { this._editTitle = false; } else if (docView.isSelected()) { const doc = docView.Document; SelectionManager.SelectSchemaViewDoc(doc); this._editTitle = true; - this._selDisposer = reaction( + this._disposers.selection = reaction( () => SelectionManager.SelectedSchemaDoc(), seldoc => seldoc !== doc && this.setEditTitle(undefined) ); @@ -259,7 +260,8 @@ export class TreeView extends React.Component<TreeViewProps> { }; componentWillUnmount() { - this._selDisposer?.(); + this._renderTimer && clearTimeout(this._renderTimer); + Object.values(this._disposers).forEach(disposer => disposer?.()); this._treeEle && this.props.unobserveHeight(this._treeEle); document.removeEventListener('pointermove', this.onDragMove, true); document.removeEventListener('pointermove', this.onDragUp, true); @@ -268,6 +270,10 @@ export class TreeView extends React.Component<TreeViewProps> { } componentDidUpdate() { + this._disposers.opening = reaction( + () => this.treeViewOpen, + open => !open && (this._renderCount = 20) + ); this.props.hierarchyIndex !== undefined && this.props.AddToMap?.(this.doc, this.props.hierarchyIndex); } @@ -410,7 +416,7 @@ export class TreeView extends React.Component<TreeViewProps> { if (aspect) return this.embeddedPanelWidth() / (aspect || 1); return layoutDoc._fitWidth ? !Doc.NativeHeight(layoutDoc) - ? NumCast(layoutDoc._height) //this.props.containerCollection._height) + ? NumCast(layoutDoc._height) : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containerCollection._height))) : (this.embeddedPanelWidth() * layoutDoc[HeightSym]()) / layoutDoc[WidthSym](); })() @@ -512,6 +518,8 @@ export class TreeView extends React.Component<TreeViewProps> { return rows; } + _renderTimer: any; + @observable _renderCount = 1; @computed get renderContent() { TraceMobx(); const expandKey = this.treeViewExpandedView; @@ -543,6 +551,14 @@ export class TreeView extends React.Component<TreeViewProps> { const docs = expandKey === 'aliases' ? this.childAliases : expandKey === 'links' ? this.childLinks : expandKey === 'annotations' ? this.childAnnos : this.childDocs; let downX = 0, downY = 0; + if (docs?.length && this._renderCount < docs?.length) { + this._renderTimer && clearTimeout(this._renderTimer); + this._renderTimer = setTimeout( + action(() => { + this._renderCount = Math.min(docs!.length, this._renderCount + 20); + }) + ); + } return ( <> {!docs?.length || this.props.AddToMap /* hack to identify pres box trees */ ? null : ( @@ -599,7 +615,8 @@ export class TreeView extends React.Component<TreeViewProps> { // TODO: [AL] add these this.props.AddToMap, this.props.RemFromMap, - this.props.hierarchyIndex + this.props.hierarchyIndex, + this._renderCount )} </ul> </> @@ -651,7 +668,7 @@ export class TreeView extends React.Component<TreeViewProps> { return ( <div className={`bullet${this.props.treeView.outlineMode ? '-outline' : ''}`} - key={'bullet'} + key="bullet" title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : 'view fields'} onClick={this.bulletClick} style={ @@ -957,6 +974,7 @@ export class TreeView extends React.Component<TreeViewProps> { ); }; + fitWidthFilter = (doc: Doc) => (doc.type === DocumentType.IMG ? false : undefined); renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => { return ( <div style={{ height: this.embeddedPanelHeight(), width: this.embeddedPanelWidth() }}> @@ -965,6 +983,7 @@ export class TreeView extends React.Component<TreeViewProps> { ref={action((r: DocumentView | null) => (this._dref = r))} Document={this.doc} DataDoc={undefined} + fitWidth={this.fitWidthFilter} PanelWidth={this.embeddedPanelWidth} PanelHeight={this.embeddedPanelHeight} LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined} @@ -1129,7 +1148,8 @@ export class TreeView extends React.Component<TreeViewProps> { // TODO: [AL] add these AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[], RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[], - hierarchyIndex?: number[] + hierarchyIndex?: number[], + renderCount?: number ) { const viewSpecScript = Cast(containerCollection.viewSpecScript, ScriptField); if (viewSpecScript) { @@ -1142,6 +1162,7 @@ export class TreeView extends React.Component<TreeViewProps> { return docs .filter(child => child instanceof Doc) .map((child, i) => { + if (renderCount && i > renderCount) return null; const pair = Doc.GetLayoutDataDocPair(containerCollection, dataDoc, child); if (!pair.layout || pair.data instanceof Promise) { return null; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 7dd9cdb8b..fa0695fb2 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -14,6 +14,7 @@ export interface ViewDefBounds { x: number; y: number; z?: number; + rotation?: number; text?: string; zIndex?: number; width?: number; @@ -31,6 +32,7 @@ export interface PoolData { x: number; y: number; z?: number; + rotation?: number; zIndex?: number; width?: number; height?: number; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 9811c239b..7f1e15c2f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -136,6 +136,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo height = rect.height; var el = el.parentNode; while (el && el !== document.body) { + if (el.className === 'tabDocView-content') break; rect = el.getBoundingClientRect?.(); if (rect?.width) { if (top <= rect.bottom === false && getComputedStyle(el).overflow === 'hidden') return rect.bottom; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 7a7ae3f40..c6419885b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -33,7 +33,7 @@ } .collectionfreeformview-mask { mix-blend-mode: multiply; - background-color: rgba(0, 0, 0, 0.7); + background-color: rgba(0, 0, 0, 0.8); } .collectionfreeformview-viewdef { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 7aae5ed5f..63b566f28 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -4,11 +4,10 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import { DateField } from '../../../../fields/DateField'; -import { DataSym, Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from '../../../../fields/Doc'; +import { DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; -import { ObjectField } from '../../../../fields/ObjectField'; import { RichTextField } from '../../../../fields/RichTextField'; import { listSpec } from '../../../../fields/Schema'; import { ScriptField } from '../../../../fields/ScriptField'; @@ -22,7 +21,6 @@ import { Docs, DocUtils } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager, dropActionType } from '../../../util/DragManager'; -import { HistoryUtil } from '../../../util/History'; import { InteractionUtils } from '../../../util/InteractionUtils'; import { ReplayMovements } from '../../../util/ReplayMovements'; import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; @@ -34,15 +32,15 @@ import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { COLLECTION_BORDER_WIDTH } from '../../../views/global/globalCssVariables.scss'; import { Timeline } from '../../animationtimeline/Timeline'; import { ContextMenu } from '../../ContextMenu'; +import { DocumentDecorations } from '../../DocumentDecorations'; import { GestureOverlay } from '../../GestureOverlay'; import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke'; import { LightboxView } from '../../LightboxView'; import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView'; -import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, ViewAdjustment, ViewSpecPrefix } from '../../nodes/DocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../../nodes/DocumentView'; import { FieldViewProps } from '../../nodes/FieldView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; -import { PresBox } from '../../nodes/trails/PresBox'; -import { VideoBox } from '../../nodes/VideoBox'; +import { PinProps, PresBox } from '../../nodes/trails/PresBox'; import { CreateImage } from '../../nodes/WebBoxRenderer'; import { StyleProp } from '../../StyleProvider'; import { CollectionSubView } from '../CollectionSubView'; @@ -61,6 +59,7 @@ export type collectionFreeformViewProps = { scaleField?: string; noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale) engineProps?: any; + getScrollHeight?: () => number | undefined; dontScaleFilter?: (doc: Doc) => boolean; // whether this collection should scale documents to fit their panel vs just scrolling them dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are transparent or not. // However, this screws up interactions since only the top layer gets events. so we render the freeformview a 3rd time with all documents in order to get interaction events (eg., marquee) but we don't actually want to display the documents. @@ -148,7 +147,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection ? { x: cb[0], y: cb[1], r: cb[2], b: cb[3] } : this.props.contentBounds?.() ?? aggregateBounds( - this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), + this._layoutElements.filter(e => e.bounds && e.bounds.width && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10) ); @@ -183,6 +182,32 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } _keyTimer: NodeJS.Timeout | undefined; + + public static gotoKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], duration: number) { + if (timer) clearTimeout(timer); + return DocumentView.SetViewTransition(docs, 'all', duration, undefined, true); + } + public static updateKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], time: number) { + if (timer) clearTimeout(timer); + const newTimer = DocumentView.SetViewTransition(docs, 'all', 1000, undefined, true); + const timecode = Math.round(time); + docs.forEach(doc => { + CollectionFreeFormDocumentView.animFields.forEach(val => { + const findexed = Cast(doc[`${val.key}-indexed`], listSpec('number'), null); + findexed?.length <= timecode + 1 && findexed.push(undefined as any as number); + }); + CollectionFreeFormDocumentView.animStringFields.forEach(val => { + const findexed = Cast(doc[`${val}-indexed`], listSpec('string'), null); + findexed?.length <= timecode + 1 && findexed.push(undefined as any as string); + }); + CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => { + const findexed = Cast(doc[`${val}-indexed`], listSpec(InkField), null); + findexed?.length <= timecode + 1 && findexed.push(undefined as any); + }); + }); + return newTimer; + } + changeKeyFrame = (back = false) => { const currentFrame = Cast(this.Document._currentFrame, 'number', null); if (currentFrame === undefined) { @@ -190,10 +215,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); } if (back) { - this._keyTimer = CollectionFreeFormDocumentView.gotoKeyframe(this._keyTimer, this.childDocs.slice()); + this._keyTimer = CollectionFreeFormView.gotoKeyframe(this._keyTimer, [...this.childDocs, this.layoutDoc], 1000); this.Document._currentFrame = Math.max(0, (currentFrame || 0) - 1); } else { - this._keyTimer = CollectionFreeFormDocumentView.updateKeyframe(this._keyTimer, this.childDocs, currentFrame || 0); + this._keyTimer = CollectionFreeFormView.updateKeyframe(this._keyTimer, [...this.childDocs, this.layoutDoc], currentFrame || 0); this.Document._currentFrame = Math.max(0, (currentFrame || 0) + 1); this.Document.lastFrame = Math.max(NumCast(this.Document._currentFrame), NumCast(this.Document.lastFrame)); } @@ -272,6 +297,30 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return dispTime === -1 || curTime === -1 || (curTime - dispTime >= -1e-4 && curTime <= endTime); } + focus = (anchor: Doc, options: DocFocusOptions) => { + const xfToCollection = options?.docTransform ?? Transform.Identity(); + const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined }; + const cantTransform = this.fitContentsToBox || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc); + const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willZoomCentered) ? savedState : this.calculatePanIntoView(anchor, xfToCollection, options?.willZoomCentered ? options?.zoomScale || 0.75 : undefined); + + // focus on the document in the collection + const didMove = !cantTransform && !anchor.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== savedState.scale); + // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active... + if (didMove) { + const focusTime = options?.instant ? 0 : options.zoomTime ?? 500; + options.zoomScale && scale && (this.Document[this.scaleFieldKey] = scale); + this.setPan(panX, panY, focusTime, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow + return focusTime; + } + }; + + getView = async (doc: Doc): Promise<Opt<DocumentView>> => { + return new Promise<Opt<DocumentView>>(res => { + const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv)); + findDoc(dv => res(dv)); + }); + }; + @action internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number, yp: number) { if (!de.embedKey && !this.ChildDrag && this.rootDoc._isGroup) return false; @@ -363,10 +412,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const grouping = this.props.Document._useClusters ? NumCast(cd.cluster, -1) : NumCast(cd.group, -1); if (grouping !== -1) { const layoutDoc = Doc.Layout(cd); - const cx = NumCast(cd.x) - this._clusterDistance; - const cy = NumCast(cd.y) - this._clusterDistance; - const cw = NumCast(layoutDoc._width) + 2 * this._clusterDistance; - const ch = NumCast(layoutDoc._height) + 2 * this._clusterDistance; + const cx = NumCast(cd.x) - this._clusterDistance / 2; + const cy = NumCast(cd.y) - this._clusterDistance / 2; + const cw = NumCast(layoutDoc._width) + this._clusterDistance; + const ch = NumCast(layoutDoc._height) + this._clusterDistance; return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? grouping : cluster; } return cluster; @@ -494,6 +543,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } } break; + case StyleProp.FillColor: + if (doc && this.Document._currentFrame !== undefined) { + return CollectionFreeFormDocumentView.getStringValues(doc, NumCast(this.Document._currentFrame))?.fillColor; + } } return styleProp; }; @@ -545,7 +598,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection case InkTool.None: if (!(this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) { this._hitCluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)); - setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, false); + setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, this._hitCluster !== -1 ? true : false); } break; } @@ -771,7 +824,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } else if (!e.cancelBubble) { if (this.tryDragCluster(e, this._hitCluster)) { return true; - } else this.pan(e); + } + this.pan(e); } return false; }; @@ -1004,6 +1058,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection if (deltaScale * invTransform.Scale > 20) { deltaScale = 20 / invTransform.Scale; } + if (deltaScale < 1 && invTransform.Scale <= NumCast(this.rootDoc._viewScaleMin, 1) && this.isAnnotationOverlay) { + return; + } if (deltaScale * invTransform.Scale < NumCast(this.rootDoc._viewScaleMin, 1) && this.isAnnotationOverlay) { deltaScale = NumCast(this.rootDoc._viewScaleMin, 1) / invTransform.Scale; } @@ -1012,29 +1069,26 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection if (localTransform.Scale >= 0.05 || localTransform.Scale > this.zoomScaling()) { const safeScale = Math.min(Math.max(0.05, localTransform.Scale), 20); this.props.Document[this.scaleFieldKey] = Math.abs(safeScale); - this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale); + this.setPan(-localTransform.TranslateX / safeScale, NumCast(this.props.Document.scrollTop) * safeScale || -localTransform.TranslateY / safeScale); } }; @action onPointerWheel = (e: React.WheelEvent): void => { + if (this.Document._isGroup) return; // group style collections neither pan nor zoom PresBox.Instance?.pauseAutoPres(); if (this.layoutDoc._Transform || DocListCast(Doc.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return; e.stopPropagation(); e.preventDefault(); switch (!e.ctrlKey ? Doc.UserDoc().freeformScrollMode : freeformScrollMode.Pan) { case freeformScrollMode.Pan: - // if shift is selected then zoom + // if ctrl is selected then zoom if (e.ctrlKey) { - if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { - // things that can scroll vertically should do that instead of zooming - } else if (this.props.isContentActive(true) && !this.Document._isGroup) { + if (this.props.isContentActive(true)) { !this.props.isAnnotationOverlayScrollable && this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc? } - // otherwise pan - } else if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { - // things that can scroll vertically should do that instead of zooming - } else if (this.props.isContentActive(true) && !this.Document._isGroup) { + } // otherwise pan + else if (this.props.isContentActive(true)) { const dx = -e.deltaX; const dy = -e.deltaY; if (e.shiftKey) { @@ -1046,9 +1100,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection break; default: case freeformScrollMode.Zoom: - if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { - // things that can scroll vertically should do that instead of zooming - } else if (this.props.isContentActive(true) && !this.Document._isGroup) { + if (this.props.isContentActive(true)) { !this.props.isAnnotationOverlayScrollable && this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc? } break; @@ -1096,7 +1148,26 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const minPanX = NumCast(this.rootDoc._panXMin, 0); const minPanY = NumCast(this.rootDoc._panYMin, 0); const newPanX = Math.min(minPanX + (1 - minScale / scale) * NumCast(this.rootDoc._panXMax, this.nativeWidth), Math.max(minPanX, panX)); - const newPanY = Math.min(this.props.Document.scrollHeight !== undefined ? NumCast(this.Document.scrollHeight) : minPanY + (1 - minScale / scale) * NumCast(this.rootDoc._panYMax, this.nativeHeight), Math.max(minPanY, panY)); + const fitYscroll = (((this.nativeHeight / this.nativeWidth) * this.props.PanelWidth() - this.props.PanelHeight()) * this.props.ScreenToLocalTransform().Scale) / minScale; + const nativeHeight = (this.props.PanelHeight() / this.props.PanelWidth() / (this.nativeHeight / this.nativeWidth)) * this.nativeHeight; + const maxScrollTop = this.nativeHeight / this.props.ScreenToLocalTransform().Scale - this.props.PanelHeight(); + const maxPanY = + minPanY + // minPanY + scrolling introduced by view scaling + scrolling introduced by fitWidth + (1 - minScale / scale) * NumCast(this.rootDoc._panYMax, nativeHeight) + + (!this.props.getScrollHeight?.() ? fitYscroll : 0); // when not zoomed, scrolling is handled via a scrollbar, not panning + let newPanY = Math.max(minPanY, Math.min(maxPanY, panY)); + if (NumCast(this.rootDoc.scrollTop) && NumCast(this.rootDoc._viewScale, minScale) !== minScale) { + const relTop = NumCast(this.rootDoc.scrollTop) / maxScrollTop; + this.rootDoc.scrollTop = undefined; + newPanY = minPanY + relTop * (maxPanY - minPanY); + } else if (fitYscroll && this.rootDoc.scrollTop === undefined && NumCast(this.rootDoc._viewScale, minScale) === minScale) { + const maxPanY = minPanY + fitYscroll; + const relTop = (panY - minPanY) / (maxPanY - minPanY); + setTimeout(() => { + this.rootDoc.scrollTop = relTop * maxScrollTop; + }, 10); + newPanY = minPanY; + } !this.Document._verticalScroll && (this.Document._panX = this.isAnnotationOverlay ? newPanX : panX); !this.Document._horizontalScroll && (this.Document._panY = this.isAnnotationOverlay ? newPanY : panY); } @@ -1149,79 +1220,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return new Promise<number>(res => setTimeout(() => res(runInAction(() => (this._panZoomTransition = 0))), this._panZoomTransition)); // set transition to be smooth, then reset } - _focusCount = 0; - focusDocument = (doc: Doc, options: DocFocusOptions) => { - const state = HistoryUtil.getState(); - - // TODO This technically isn't correct if type !== "doc", as - // currently nothing is done, but we should probably push a new state - if (state.type === 'doc' && this.Document._panX !== undefined && this.Document._panY !== undefined) { - const init = state.initializers![this.Document[Id]]; - if (!init) { - state.initializers![this.Document[Id]] = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY) }; - HistoryUtil.pushState(state); - } else if (init.panX !== this.Document._panX || init.panY !== this.Document._panY) { - init.panX = NumCast(this.Document._panX); - init.panY = NumCast(this.Document._panY); - HistoryUtil.pushState(state); - } - } - // if (SelectionManager.Views().length !== 1 || SelectionManager.Views()[0].Document !== doc) { - // SelectionManager.DeselectAll(); - // } - if (this.props.Document.scrollHeight || this.props.Document.scrollTop !== undefined || this.props.Document.currentTimecode !== undefined || doc.type === DocumentType.MARKER) { - this.props.focus(doc, options); - } else { - const xfToCollection = options?.docTransform ?? Transform.Identity(); - const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: options?.willPanZoom ? this.Document[this.scaleFieldKey] : undefined }; - const newState = HistoryUtil.getState(); - const cantTransform = (this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc; - const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willPanZoom) ? savedState : this.calculatePanIntoView(doc, xfToCollection, options?.willPanZoom ? options?.zoomScale || 0.75 : undefined); - if (!cantTransform) { - // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection - newState.initializers![this.Document[Id]] = { panX, panY }; - HistoryUtil.pushState(newState); - } - // focus on the document in the collection - const didMove = !cantTransform && !doc.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== savedState.scale); - const focusSpeed = options?.instant ? 0 : didMove ? options.zoomTime ?? 500 : 0; - // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active... - if (didMove) { - options.zoomScale && scale && (this.Document[this.scaleFieldKey] = scale); - this.setPan(panX, panY, focusSpeed, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow - } - - const focusCount = ++this._focusCount; - const startTime = Date.now(); - // focus on this collection within its parent view. the parent view after focusing determines whether to reset the view change within the collection - const endFocus = async (moved: boolean) => { - doc.hidden && Doc.UnHighlightDoc(doc); - const resetView = options?.afterFocus ? await options?.afterFocus(moved) : ViewAdjustment.doNothing; - if (resetView) { - const restoreState = savedState; - if (typeof restoreState !== 'boolean') { - this.Document._panX = restoreState.panX; - this.Document._panY = restoreState.panY; - this.Document[this.scaleFieldKey] = restoreState.scale; - } - } - this._focusCount === focusCount && didMove && runInAction(() => (this._panZoomTransition = 0)); - return resetView; - }; - const xf = !cantTransform - ? Transform.Identity() - : this.props.isAnnotationOverlay - ? new Transform(NumCast(this.rootDoc.x), NumCast(this.rootDoc.y), this.rootDoc[WidthSym]() / Doc.NativeWidth(this.rootDoc)) - : new Transform(NumCast(this.rootDoc.x) + this.rootDoc[WidthSym]() / 2 - NumCast(this.rootDoc._panX), NumCast(this.rootDoc.y) + this.rootDoc[HeightSym]() / 2 - NumCast(this.rootDoc._panY), 1); - - this.props.focus(!cantTransform ? this.rootDoc : doc, { - ...options, - docTransform: xf, - afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didMove || didFocus)), Math.max(0, focusSpeed - (Date.now() - startTime)))), - }); - } - }; - calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => { const layoutdoc = Doc.Layout(doc); const pt = xf.transformPoint(NumCast(doc.x), NumCast(doc.y)); @@ -1286,7 +1284,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection pointerEvents = () => { const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine); const pointerEvents = - this.props.isContentActive() === false ? 'none' : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.()); + this.props.isContentActive() === false || DocumentDecorations.Instance.Interacting + ? 'none' + : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.()); return pointerEvents; }; getChildDocView(entry: PoolData) { @@ -1319,7 +1319,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection searchFilterDocs={this.searchFilterDocs} isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive} isContentActive={emptyFunction} - focus={this.focusDocument} + focus={this.isAnnotationOverlay ? this.props.focus : this.focus} addDocTab={this.addDocTab} addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} @@ -1337,12 +1337,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection dontScaleFilter={this.props.dontScaleFilter} dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView} pointerEvents={this.pointerEvents} - rotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0} + //rotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0} //fitContentsToBox={this.props.fitContentsToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this /> ); } addDocTab = action((doc: Doc, where: OpenWhere) => { + if (this.props.isAnnotationOverlay) return this.props.addDocTab(doc, where); switch (where) { case OpenWhere.inParent: return this.props.addDocument?.(doc) || false; @@ -1360,9 +1361,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection (!docContext || this.props.removeDocument?.(docContext))) || false ); - case OpenWhere.inPlace: - if (this.layoutDoc.isInPlaceContainer) { - this.dataDoc[this.props.fieldKey] = new List<Doc>(doc instanceof Doc ? [doc] : doc); + case undefined: + case OpenWhere.lightbox: + if (this.layoutDoc._isLightbox) { + // _isLightbox docs have a script that will unset this overlay onClick + this.layoutDoc[this.props.fieldKey] = new List<Doc>(doc instanceof Doc ? [doc] : doc); return true; } } @@ -1370,22 +1373,27 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }); getCalculatedPositions(params: { pair: { layout: Doc; data?: Doc }; index: number; collection: Doc }): PoolData { - const curFrame = Cast(this.Document._currentFrame, 'number'); const childDoc = params.pair.layout; const childDocLayout = Doc.Layout(childDoc); + const layoutFrameNumber = Cast(this.Document._currentFrame, 'number'); // frame number that container is at which determines layout frame values + const contentFrameNumber = Cast(childDocLayout._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed const { z, zIndex } = childDoc; - const { backgroundColor, color } = curFrame === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, curFrame); - const { x, y, opacity } = curFrame === undefined ? { x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() } : CollectionFreeFormDocumentView.getValues(childDoc, curFrame); + const { backgroundColor, color } = contentFrameNumber === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, contentFrameNumber); + const { x, y, _width, _height, opacity, _rotation } = + layoutFrameNumber === undefined + ? { _width: Cast(childDocLayout._width, 'number'), _height: Cast(childDocLayout._height, 'number'), _rotation: Cast(childDocLayout._rotation, 'number'), x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() } + : CollectionFreeFormDocumentView.getValues(childDoc, layoutFrameNumber); return { x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x), y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y), z: Cast(z, 'number'), + rotation: Cast(_rotation, 'number'), color: Cast(color, 'string') ? StrCast(color) : this.props.styleProvider?.(childDoc, this.props, StyleProp.Color), backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.getClusterColor(childDoc, this.props, StyleProp.BackgroundColor), opacity: this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity), zIndex: Cast(zIndex, 'number'), - width: Cast(childDocLayout._width, 'number'), - height: Cast(childDocLayout._height, 'number'), + width: _width, + height: _height, transition: StrCast(childDocLayout.dataTransition), pair: params.pair, replica: '', @@ -1506,6 +1514,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || + newPos.rotation !== lastPos.rotation || newPos.zIndex !== lastPos.zIndex || newPos.transition !== lastPos.transition ) { @@ -1521,13 +1530,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const elements = computedElementData.slice(); Array.from(newPool.entries()) .filter(entry => this.isCurrent(entry[1].pair.layout)) - .forEach((entry, i) => + .forEach((entry, i) => { + const childData: ViewDefBounds = this.childDataProvider(entry[1].pair.layout, entry[1].replica); + const childSize = this.childSizeProvider(entry[1].pair.layout, entry[1].replica); elements.push({ ele: this.getChildDocView(entry[1]), - bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica), + bounds: childData.opacity === 0 ? { ...childData, width: 0, height: 0 } : { ...childData, width: childSize.width, height: childSize.height }, inkMask: BoolCast(entry[1].pair.layout.isInkMask) ? NumCast(entry[1].pair.layout.opacity, 1) : -1, - }) - ); + }); + }); if (this.props.isAnnotationOverlay && this.props.Document[this.scaleFieldKey]) { // don't zoom out farther than 1-1 if it's a bounded item (image, video, pdf), otherwise don't allow zooming in closer than 1-1 if it's a text sidebar @@ -1539,47 +1550,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return elements; } - @action - setViewSpec = (anchor: Doc, preview: boolean) => { - if (preview) { - this._focusFilters = StrListCast(Doc.GetProto(anchor).docFilters); - this._focusRangeFilters = StrListCast(Doc.GetProto(anchor).docRangeFilters); - } else { - if (anchor.docFilters) { - this.layoutDoc._docFilters = new List<string>(StrListCast(anchor.docFilters)); - } - if (anchor.docRangeFilters) { - this.layoutDoc._docRangeFilters = new List<string>(StrListCast(anchor.docRangeFilters)); - } - } - return 0; - }; + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { + // create an anchor that saves information about the current state of the freeform view (pan, zoom, view type) + const anchor = Docs.Create.CollectionAnchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), unrendered: true, presTransition: 500, annotationOn: this.rootDoc }); + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: true, viewType: true, filters: true } }, this.rootDoc); - @action - scrollFocus = (anchor: Doc, options: DocFocusOptions) => { - const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500; - return PresBox.restoreTargetDocView( - this.props.DocumentView?.(), // - { pinDocLayout: BoolCast(anchor.presPinDocLayout) }, - anchor, - focusSpeed, - { - pannable: anchor.presPinData ? true : false, - } - ) - ? focusSpeed - : undefined; - }; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document - - getAnchor = (addAsAnnotation: boolean) => { - if (this.props.Document.annotationOn) { - return this.rootDoc; - } - const anchor = Docs.Create.TextanchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), presTransition: 500, annotationOn: this.rootDoc }); - PresBox.pinDocView(anchor, { pinData: { pannable: true } }, this.rootDoc); - const proto = Doc.GetProto(anchor); - proto[ViewSpecPrefix + '_viewType'] = this.layoutDoc._viewType; - proto.docFilters = ObjectField.MakeCopy(this.layoutDoc.docFilters as ObjectField) || new List<string>([]); if (addAsAnnotation) { if (Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), null) !== undefined) { Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), []).push(anchor); @@ -1706,7 +1681,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const nativeHeight = height; return CreateImage(Utils.prepend(''), document.styleSheets, htmlString, nativeWidth, (nativeWidth * panelHeight) / panelWidth, (scrollTop * panelHeight) / realNativeHeight) .then(async (data_url: any) => { - const returnedFilename = await VideoBox.convertDataUri(data_url, filename, noSuffix, replaceRootFilename); + const returnedFilename = await Utils.convertDataUri(data_url, filename, noSuffix, replaceRootFilename); cb(returnedFilename as string, nativeWidth, nativeHeight); }) .catch(function (error: any) { @@ -1966,7 +1941,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection zoomScaling={this.zoomScaling} presPaths={this.showPresPaths} presPinView={BoolCast(this.Document.presPinView)} - transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', null)} + transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.props.DocumentView?.()?.rootDoc._viewTransition, 'string', null))} viewDefDivClick={this.props.viewDefDivClick}> {this.children} </CollectionFreeFormViewPannableContents> @@ -2033,7 +2008,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection : (this.props.pointerEvents?.() as any), transform: `scale(${this.nativeDimScaling || 1})`, width: `${100 / (this.nativeDimScaling || 1)}%`, - height: this.isAnnotationOverlay && this.Document.scrollHeight ? NumCast(this.Document.scrollHeight) : `${100 / (this.nativeDimScaling || 1)}%`, // : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() + height: this.props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`, }}> {this._firstRender ? this.placeholder : this.marqueeView} {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />} @@ -2265,20 +2240,21 @@ class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFor } export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY: number) { + const browseTransitionTime = 500; SelectionManager.DeselectAll(); - dv.props.focus(dv.props.Document, { - willPanZoom: true, - afterFocus: async didMove => { - if (!didMove) { - const selfFfview = dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined; - const parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; - const ffview = selfFfview && selfFfview.rootDoc[selfFfview.props.scaleField || '_viewScale'] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview - await ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5); - } - return ViewAdjustment.doNothing; - }, - }); - Doc.linkFollowHighlight(dv?.props.Document, false); + if ( + dv.props.focus(dv.props.Document, { + willZoomCentered: true, + zoomScale: 0.8, + zoomTime: browseTransitionTime, + }) === undefined + ) { + const selfFfview = dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined; + const parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + const ffview = selfFfview && selfFfview.rootDoc[selfFfview.props.scaleField || '_viewScale'] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview + ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5, browseTransitionTime); + Doc.linkFollowHighlight(dv?.props.Document, false); + } } ScriptingGlobals.add(CollectionBrowseClick); ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 0b7854926..a73627165 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -19,7 +19,6 @@ import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { OpenWhere } from '../../nodes/DocumentView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; -import { VideoBox } from '../../nodes/VideoBox'; import { pasteImageBitmap } from '../../nodes/WebBoxRenderer'; import { PreviewCursor } from '../../PreviewCursor'; import { SubCollectionViewProps } from '../CollectionSubView'; @@ -167,7 +166,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque pasteImageBitmap((data: any, error: any) => { error && console.log(error); data && - VideoBox.convertDataUri(data, this.props.Document[Id] + '-thumb-frozen').then(returnedfilename => { + Utils.convertDataUri(data, this.props.Document[Id] + '-thumb-frozen').then(returnedfilename => { this.props.Document['thumb-frozen'] = new ImageField(returnedfilename); }); }) @@ -397,6 +396,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque newCollection.forceActive = makeGroup; newCollection.x = this.Bounds.left; newCollection.y = this.Bounds.top; + newCollection.fitwidth = true; selected.forEach(d => (d.context = newCollection)); this.hideMarquee(); return newCollection; @@ -431,8 +431,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque @undoBatch @action - collection = (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean) => { - const selected = this.marqueeSelect(false); + collection = (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean, selection?: Doc[]) => { + const selected = selection ?? this.marqueeSelect(false); const activeFrame = selected.reduce((v, d) => v ?? Cast(d._activeFrame, 'number', null), undefined as number | undefined); if (e instanceof KeyboardEvent ? 'cg'.includes(e.key) : true) { this.props.removeDocument?.(selected); diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index a2330c6b2..d54e8ce98 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -12,6 +12,7 @@ import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager, dropActionType } from '../../../util/DragManager'; import { Transform } from '../../../util/Transform'; import { Colors, Shadows } from '../../global/globalEnums'; +import { media_state } from '../../nodes/AudioBox'; import { DocumentLinksButton } from '../../nodes/DocumentLinksButton'; import { DocumentView } from '../../nodes/DocumentView'; import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup'; @@ -150,9 +151,13 @@ export class CollectionLinearView extends CollectionSubView() { <span className="bottomPopup-text"> Currently playing: {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => ( - <span className="audio-title" onPointerDown={() => DocumentManager.Instance.jumpToDocument(clip, { willPanZoom: true }, undefined, [])}> - {clip.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? '' : ',')} - </span> + <> + <span className="audio-title" onPointerDown={() => DocumentManager.Instance.showDocument(clip.rootDoc, { willZoomCentered: true })}> + {clip.rootDoc.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? ' ' : ',')} + </span> + <FontAwesomeIcon icon={!clip.ComponentView?.IsPlaying?.() ? 'play' : 'pause'} size="lg" onPointerDown={() => clip.ComponentView?.TogglePause?.()} />{' '} + <FontAwesomeIcon icon="times" size="lg" onPointerDown={() => clip.ComponentView?.Pause?.()} />{' '} + </> ))} </span> </span> diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index b88da5191..88d045fa7 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -234,7 +234,6 @@ export class CollectionMulticolumnView extends CollectionSubView() { onChildClickHandler = () => ScriptCast(this.Document.onChildClick); onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick); - focusDocument = (doc: Doc, options: DocFocusOptions) => this.props.focus(this.rootDoc, options); isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive(); isChildContentActive = () => (((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.isContentActive() ? true : false); getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => { @@ -260,7 +259,7 @@ export class CollectionMulticolumnView extends CollectionSubView() { hideResizeHandles={this.props.childHideResizeHandles?.()} hideDecorationTitle={this.props.childHideDecorationTitle?.()} fitContentsToBox={this.props.fitContentsToBox} - focus={this.focusDocument} + focus={this.props.focus} docFilters={this.childDocFilters} docRangeFilters={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index 407deaabd..f18917bef 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -234,7 +234,6 @@ export class CollectionMultirowView extends CollectionSubView() { onChildClickHandler = () => ScriptCast(this.Document.onChildClick); onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick); - focusDocument = (doc: Doc, options: DocFocusOptions) => this.props.focus(this.rootDoc, options); isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive(); isChildContentActive = () => (((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.isContentActive() ? true : false); getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => { diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx index 97a6c5c18..18ddd881b 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx @@ -214,7 +214,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> { const aliasdoc = await SearchUtil.GetAliasesOfDocument(this._rowDataDoc); const targetContext = aliasdoc.length <= 0 ? undefined : Cast(aliasdoc[0].context, Doc, null); // Jump to the this document - DocumentManager.Instance.jumpToDocument(this._rowDoc, { willPan: true }, emptyFunction, targetContext ? [targetContext] : [], () => this.props.setPreviewDoc(this._rowDoc)); + DocumentManager.Instance.showDocument(this._rowDoc, { willPan: true }, () => this.props.setPreviewDoc(this._rowDoc)); } }; |
