diff options
| author | mehekj <mehek.jethani@gmail.com> | 2022-10-12 13:21:07 -0400 |
|---|---|---|
| committer | mehekj <mehek.jethani@gmail.com> | 2022-10-12 13:21:07 -0400 |
| commit | 0b3a83acd4f75b7f6ff4b9bb7daf4377dede51a1 (patch) | |
| tree | 438789f7e7f50e5eb9829e1f301b4d043d8d4906 /src/client/views/collections | |
| parent | 69ca9baca6ff1da272a5191187542351bd242ccc (diff) | |
| parent | eb5f75785fd28acb50f1b30434e89223fff00185 (diff) | |
Merge branch 'master' into schema-mehek
Diffstat (limited to 'src/client/views/collections')
21 files changed, 530 insertions, 397 deletions
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index 091ba8e74..ac3541f2c 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -154,7 +154,15 @@ background: $white; } + .lm_controls { + height: 27px; + display: flex; + align-content: center; + justify-content: center; + } + .lm_controls > li { + height: 27px !important; opacity: 1; transform: scale(1); } diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 6d70cc0d2..e9b41de25 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -6,7 +6,7 @@ import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; -import { Cast, NumCast, StrCast } from '../../../fields/Types'; +import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { inheritParentAcls } from '../../../fields/util'; import { emptyFunction, incrementTitleCopy } from '../../../Utils'; @@ -56,11 +56,12 @@ export class CollectionDockingView extends CollectionSubView() { constructor(props: SubCollectionViewProps) { super(props); - runInAction(() => (CollectionDockingView.Instance = this)); + if (this.props.renderDepth < 0) runInAction(() => (CollectionDockingView.Instance = this)); //Why is this here? (window as any).React = React; (window as any).ReactDOM = ReactDOM; DragManager.StartWindowDrag = this.StartOtherDrag; + this.rootDoc.myTrails; // this is equivalent to having a prefetchProxy for myTrails which is needed for the My Trails button in the UI which assumes that Doc.ActiveDashboard.myTrails is legit... } /** @@ -73,7 +74,7 @@ export class CollectionDockingView extends CollectionSubView() { public StartOtherDrag = (e: { pageX: number; pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => { this._flush = this._flush ?? UndoManager.StartBatch('golden layout drag'); const config = dragDocs.length === 1 ? CollectionDockingView.makeDocumentConfig(dragDocs[0]) : { type: 'row', content: dragDocs.map(doc => CollectionDockingView.makeDocumentConfig(doc)) }; - const dragSource = this._goldenLayout.createDragSource(document.createElement('div'), config); + const dragSource = CollectionDockingView.Instance?._goldenLayout.createDragSource(document.createElement('div'), config); this.tabDragStart(dragSource, finishDrag); dragSource._dragListener.onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 }); }; @@ -280,12 +281,13 @@ export class CollectionDockingView extends CollectionSubView() { this.stateChanged(); return true; } - setupGoldenLayout = async () => { + //const config = StrCast(this.props.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this.props.Document))); const config = StrCast(this.props.Document.dockingConfig); if (config) { const matches = config.match(/\"documentId\":\"[a-z0-9-]+\"/g); const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')) ?? []; + await Promise.all(docids.map(id => DocServer.GetRefField(id))); if (this._goldenLayout) { @@ -311,6 +313,8 @@ export class CollectionDockingView extends CollectionSubView() { glay.root.layoutManager.on('itemDropped', this.tabItemDropped); glay.root.layoutManager.on('dragStart', this.tabDragStart); glay.root.layoutManager.on('activeContentItemChanged', this.stateChanged); + } else { + console.log('ERROR: no config for dashboard!!'); } }; @@ -395,10 +399,10 @@ export class CollectionDockingView extends CollectionSubView() { const _height = Number(getComputedStyle(content).height.replace('px', '')); return CollectionFreeFormView.UpdateIcon(this.layoutDoc[Id] + '-icon' + new Date().getTime(), content, _width, _height, _width, _height, 0, 1, true, this.layoutDoc[Id] + '-icon', (iconFile, _nativeWidth, _nativeHeight) => { const img = Docs.Create.ImageDocument(new ImageField(iconFile), { title: this.rootDoc.title + '-icon', _width, _height, _nativeWidth, _nativeHeight }); - const proto = Cast(img.proto, Doc, null)!; - proto['data-nativeWidth'] = _width; - proto['data-nativeHeight'] = _height; - this.dataDoc.thumb = img; + const proto = this.dataDoc; // Cast(img.proto, Doc, null)!; + proto['thumb-nativeWidth'] = _width; + proto['thumb-nativeHeight'] = _height; + this.dataDoc.thumb = new ImageField(iconFile); }); } } @@ -429,7 +433,8 @@ export class CollectionDockingView extends CollectionSubView() { return newtab; }); const copy = Docs.Create.DockDocument(newtabs, json, { title: incrementTitleCopy(StrCast(doc.title)) }); - return DashboardView.openDashboard(await copy); + DashboardView.SetupDashboardTrails(copy); + return DashboardView.openDashboard(copy); } @action @@ -455,7 +460,7 @@ export class CollectionDockingView extends CollectionSubView() { }; tabDestroyed = (tab: any) => { - if (tab.DashDoc?.type !== DocumentType.KVP) { + if (![DocumentType.KVP, DocumentType.PRES].includes(tab.DashDoc?.type)) { Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc); Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true); } @@ -475,6 +480,7 @@ export class CollectionDockingView extends CollectionSubView() { }; stackCreated = (stack: any) => { + stack = stack.header ? stack : stack.origin; stack.header?.element.on('mousedown', (e: any) => { const dashboard = Doc.ActiveDashboard; if (dashboard && e.target === stack.header?.element[0] && e.button === 2) { @@ -497,7 +503,7 @@ export class CollectionDockingView extends CollectionSubView() { .click( action(() => { //if (confirm('really close this?')) { - if (!stack.parent.parent.isRoot || stack.parent.contentItems.length > 1) { + if ((!stack.parent.isRoot && !stack.parent.parent.isRoot) || stack.parent.contentItems.length > 1) { stack.remove(); } else { alert('cant delete the last stack'); @@ -532,7 +538,23 @@ export class CollectionDockingView extends CollectionSubView() { }; render() { - return <div className="collectiondockingview-container" onPointerDown={this.onPointerDown} ref={this._containerRef} />; + const href = ImageCast(this.rootDoc.thumb)?.url.href; + return this.props.renderDepth > -1 ? ( + <div> + {href ? ( + <img + style={{ background: 'white', top: 0, position: 'absolute' }} + src={href} // + '?d=' + (new Date()).getTime()} + width={this.props.PanelWidth()} + height={this.props.PanelHeight()} + /> + ) : ( + <p>nested dashboards has no thumbnail</p> + )} + </div> + ) : ( + <div className="collectiondockingview-container" onPointerDown={this.onPointerDown} ref={this._containerRef} /> + ); } } diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 0dc30e0fd..46e8494ab 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -34,7 +34,6 @@ import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocum import { DocumentView } from '../nodes/DocumentView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; -import { PresBox } from '../nodes/trails/PresBox'; import { DefaultStyleProvider } from '../StyleProvider'; import { CollectionDockingView } from './CollectionDockingView'; import { CollectionLinearView } from './collectionLinear'; @@ -726,18 +725,16 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView } public static gotoKeyFrame(doc: Doc, newFrame: number) { - if (!doc) { - return; - } - const dataField = doc[Doc.LayoutFieldKey(doc)]; - const childDocs = DocListCast(dataField); - const currentFrame = Cast(doc._currentFrame, 'number', null); - if (currentFrame === undefined) { - doc._currentFrame = 0; - CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0); + if (doc) { + const childDocs = DocListCast(doc[Doc.LayoutFieldKey(doc)]); + const currentFrame = Cast(doc._currentFrame, 'number', null); + if (currentFrame === undefined) { + doc._currentFrame = 0; + CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0); + } + CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0); + doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame); } - CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0); - doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame); } @undoBatch diff --git a/src/client/views/collections/CollectionNoteTakingView.scss b/src/client/views/collections/CollectionNoteTakingView.scss index fe98f307e..4d1f18e54 100644 --- a/src/client/views/collections/CollectionNoteTakingView.scss +++ b/src/client/views/collections/CollectionNoteTakingView.scss @@ -1,7 +1,7 @@ @import '../global/globalCssVariables'; .collectionNoteTakingView-DocumentButtons { - display: flex; + display: none; justify-content: space-between; margin: auto; } @@ -51,6 +51,16 @@ display: flex; } +.collectionNoteTakingViewFieldColumn { + display: flex; + overflow: auto; +} +.collectionNoteTakingViewFieldColumn:hover { + .collectionNoteTakingView-DocumentButtons { + display: flex; + } +} + // TODO:glr Turn this into a seperate class .documentButtonMenu { position: relative; @@ -82,7 +92,6 @@ top: 0; overflow-y: auto; overflow-x: hidden; - flex-wrap: wrap; transition: top 0.5s; > div { @@ -102,6 +111,11 @@ height: auto; } + .collectionNoteTakingView-columnStack { + height: 100%; + width: 100%; + display: inline-block; + } .collectionNoteTakingView-Nodes { width: 100%; height: 100%; @@ -113,6 +127,11 @@ left: 0; width: 100%; position: absolute; + margin: auto; + width: max-content; + height: max-content; + position: relative; + grid-auto-rows: 0px; } .collectionNoteTakingView-description { @@ -133,6 +152,16 @@ margin-left: -5; } + .collectionNoteTakingView-sectionDelete { + display: none; + position: absolute; + right: 0; + width: max-content; + height: max-content; + top: 10; + padding: 2; + } + // Documents in NoteTaking view .collectionNoteTakingView-columnDoc { display: flex; @@ -347,14 +376,6 @@ } } } - - .collectionNoteTakingView-sectionDelete { - position: absolute; - right: 25px; - top: 0; - height: 100%; - display: none; - } } .collectionNoteTakingView-sectionHeader:hover { diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index b359ef420..b0f64ed60 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 { Id } from '../../../fields/FieldSymbols'; +import { Copy, Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; @@ -11,7 +11,7 @@ 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 { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; +import { DocumentType } from '../../documents/DocumentTypes'; import { DragManager, dropActionType } from '../../util/DragManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; @@ -19,7 +19,6 @@ import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { LightboxView } from '../LightboxView'; -import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; @@ -30,13 +29,6 @@ import { CollectionNoteTakingViewDivider } from './CollectionNoteTakingViewDivid import { CollectionSubView } from './CollectionSubView'; const _global = (window /* browser */ || global) /* node */ as any; -export type collectionNoteTakingViewProps = { - chromeHidden?: boolean; - viewType?: CollectionViewType; - NativeWidth?: () => number; - NativeHeight?: () => number; -}; - /** * CollectionNoteTakingView is a column-based view for displaying documents. In this view, the user can (1) * add and remove columns (2) change column sizes and (3) move documents within and between columns. This @@ -45,41 +37,42 @@ export type collectionNoteTakingViewProps = { * the rest of Dash, so it may be worthwhile to transition the headers to simple documents. */ @observer -export class CollectionNoteTakingView extends CollectionSubView<Partial<collectionNoteTakingViewProps>>() { +export class CollectionNoteTakingView extends CollectionSubView() { _disposers: { [key: string]: IReactionDisposer } = {}; _masonryGridRef: HTMLDivElement | null = null; _draggerRef = React.createRef<HTMLDivElement>(); + notetakingCategoryField = 'NotetakingCategory'; + public DividerWidth = 16; @observable docsDraggedRowCol: number[] = []; @observable _cursor: CursorProperty = 'grab'; @observable _scroll = 0; @computed get chromeHidden() { - return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden); + return BoolCast(this.layoutDoc.chromeHidden); } // columnHeaders returns the list of SchemaHeaderFields currently being used by the layout doc to render the columns @computed get columnHeaders() { const columnHeaders = Cast(this.dataDoc.columnHeaders, listSpec(SchemaHeaderField), null); - const needsUnsetCategory = this.childDocs.some(d => !d[this.notetakingCategoryField] && !columnHeaders.find(sh => sh.heading === 'unset')); - if (needsUnsetCategory) { - setTimeout(() => columnHeaders.push(new SchemaHeaderField('unset', undefined, undefined, 1))); + 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 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>(); + } + }); } - return columnHeaders; - } - // notetakingCategoryField returns the key to accessing a document's column value - @computed get notetakingCategoryField() { - return 'NotetakingCategory'; + return columnHeaders ?? ([] as SchemaHeaderField[]); } @computed get headerMargin() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin); } @computed get xMargin() { - return NumCast(this.layoutDoc._xMargin, 2 * Math.min(this.gridGap, 0.05 * this.props.PanelWidth())); - } - // dividerWidth returns the width of a CollectionNoteTakingViewDivider - @computed get dividerWidth() { - return 32; + return NumCast(this.layoutDoc._xMargin, 5); } @computed get yMargin() { - return this.props.yPadding || NumCast(this.layoutDoc._yMargin, 5); + return NumCast(this.layoutDoc._yMargin, 5); } @computed get gridGap() { return NumCast(this.layoutDoc._gridGap, 10); @@ -94,22 +87,13 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti } // maxColWidth returns the maximum column width, which is slightly less than the total available space. @computed get maxColWidth() { - return this.props.PanelWidth() - 2 * this.xMargin; + return this.props.PanelWidth(); } // availableWidth is the total amount of non-divider width. Since widths are stored relatively, // we use availableWidth to convert from a percentage to a pixel count. @computed get availableWidth() { - const numDividers = this.columnHeaders.length - 1; - return this.maxColWidth - numDividers * this.dividerWidth; - } - - // Documents should NOT have column category fields until entering this view, so the contructor creates the 'New Column' - // category for the user to then edit later. - constructor(props: any) { - super(props); - if (this.columnHeaders === undefined) { - this.dataDoc.columnHeaders = new List<SchemaHeaderField>([new SchemaHeaderField('New Column', undefined, undefined, 1)]); - } + const numDividers = this.numGroupColumns - 1; + return this.maxColWidth - numDividers * this.DividerWidth; } // children is passed as a prop to the NoteTakingField, which uses this function @@ -149,7 +133,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti } }); // now we add back in the docs that we're dragging - if (rowCol.length) { + if (rowCol.length && columnHeaders.length > rowCol[1]) { const offset = 0; sections.get(columnHeaders[rowCol[1]])?.splice(rowCol[0] - offset, 0, ...DragManager.docsBeingDragged); } @@ -209,7 +193,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti }; // let's dive in and get the actual document we want to drag/move around - focusDocument = (doc: Doc, options?: DocFocusOptions) => { + 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]); @@ -217,7 +201,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti const top = found.getBoundingClientRect().top; const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top); if (Math.floor(localTop[1]) !== 0) { - smoothScroll((focusSpeed = doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop); + smoothScroll((focusSpeed = NumCast(doc.focusSpeed, 500)), this._mainCont!, localTop[1] + this._mainCont!.scrollTop); } } const endFocus = async (moved: boolean) => (options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing); @@ -236,9 +220,6 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti if (this.props.childOpacity) { return this.props.childOpacity(); } - if (this.Document._currentFrame !== undefined) { - return CollectionFreeFormDocumentView.getValues(doc, NumCast(this.Document._currentFrame))?.opacity; - } } return this.props.styleProvider?.(doc, props, property); }; @@ -315,12 +296,9 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti const heading = !d[this.notetakingCategoryField] ? 'unset' : Field.toString(d[this.notetakingCategoryField] as Field); const existingHeader = this.columnHeaders.find(sh => sh.heading === heading); const existingWidth = existingHeader?.width ? existingHeader.width : 0; - const maxWidth = existingWidth > 0 ? existingWidth * this.availableWidth - 2 * this.xMargin : this.maxColWidth - 2 * this.xMargin; - if (d.type === DocumentType.RTF) { - return maxWidth; - } - const width = d[WidthSym](); - return width < maxWidth ? width : maxWidth; + const maxWidth = existingWidth > 0 ? existingWidth * this.availableWidth : this.maxColWidth; + const width = d.fitWidth ? maxWidth : d[WidthSym](); + return Math.min(maxWidth - CollectionNoteTakingViewColumn.ColumnMargin, width < maxWidth ? width : maxWidth); } // how to get the height of a document. Nothing special here. @@ -332,8 +310,6 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[WidthSym]() : 0); const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[HeightSym]() : 0); if (nw && nh) { - // const colWid = this.columnWidth / this.numGroupColumns; - // const docWid = this.layoutDoc._columnsFill ? colWid : Math.min(this.getDocWidth(d), colWid); const docWid = this.getDocWidth(d); return Math.min(maxHeight, (docWid * nh) / nw); } @@ -392,26 +368,27 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti }); // we alter the pivot fields of the docs in case they are moved to a new column. const colIndex = this.getColumnFromXCoord(xCoord); - const colHeader = StrCast(this.columnHeaders[colIndex].heading); + const colHeader = colIndex === undefined ? 'unset' : StrCast(this.columnHeaders[colIndex].heading); DragManager.docsBeingDragged.forEach(d => (d[this.notetakingCategoryField] = colHeader)); // used to notify sections to re-render this.docsDraggedRowCol.length = 0; - this.docsDraggedRowCol.push(dropInd, this.getColumnFromXCoord(xCoord)); + const columnFromCoord = this.getColumnFromXCoord(xCoord); + columnFromCoord !== undefined && this.docsDraggedRowCol.push(dropInd, columnFromCoord); } }; // getColumnFromXCoord returns the column index for a given x-coordinate (currently always the client's mouse coordinate). // This function is used to know which document a column SHOULD be in while it is being dragged. - getColumnFromXCoord = (xCoord: number): number => { + getColumnFromXCoord = (xCoord: number): number | undefined => { + let colIndex: number | undefined = undefined; const numColumns = this.columnHeaders.length; const coords = []; let colStartXCoord = 0; for (let i = 0; i < numColumns; i++) { coords.push(colStartXCoord); - colStartXCoord += this.columnHeaders[i].width * this.availableWidth + this.dividerWidth; + colStartXCoord += this.columnHeaders[i].width * this.availableWidth + this.DividerWidth; } coords.push(this.PanelWidth); - let colIndex = 0; for (let i = 0; i < numColumns; i++) { if (xCoord > coords[i] && xCoord < coords[i + 1]) { colIndex = i; @@ -423,20 +400,16 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti // getDocsFromXCoord returns the docs of a column based on the x-coordinate provided. getDocsFromXCoord = (xCoord: number): Doc[] => { - const colIndex = this.getColumnFromXCoord(xCoord); - const colHeader = StrCast(this.columnHeaders[colIndex].heading); - // const docs = this.childDocList - const docs = this.childDocs; const docsMatchingHeader: Doc[] = []; - if (docs) { - docs.map(d => { - if (d instanceof Promise) return; - const sectionValue = (d[this.notetakingCategoryField] as object) ?? 'unset'; - if (sectionValue.toString() == colHeader) { - docsMatchingHeader.push(d); - } - }); - } + const colIndex = this.getColumnFromXCoord(xCoord); + const colHeader = colIndex === undefined ? 'unset' : StrCast(this.columnHeaders[colIndex].heading); + this.childDocs?.map(d => { + if (d instanceof Promise) return; + const sectionValue = (d[this.notetakingCategoryField] as object) ?? 'unset'; + if (sectionValue.toString() == colHeader) { + docsMatchingHeader.push(d); + } + }); return docsMatchingHeader; }; @@ -511,7 +484,7 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti this.onPointerMove(true, e.clientX, e.clientY); docus?.map((doc: Doc) => this.addDocument(doc)); const newDoc = this.childDocs.lastElement(); - const colHeader = StrCast(this.columnHeaders[colInd].heading); + const colHeader = colInd === undefined ? 'unset' : StrCast(this.columnHeaders[colInd].heading); newDoc[this.notetakingCategoryField] = colHeader; const docs = this.childDocList; if (docs && targInd !== -1) { @@ -566,13 +539,13 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti numGroupColumns={this.numGroupColumns} gridGap={this.gridGap} pivotField={this.notetakingCategoryField} - dividerWidth={this.dividerWidth} + dividerWidth={this.DividerWidth} maxColWidth={this.maxColWidth} availableWidth={this.availableWidth} PanelWidth={this.PanelWidth} - key={heading?.heading ?? ''} + key={heading?.heading ?? 'unset'} headings={this.headings} - heading={heading?.heading ?? ''} + heading={heading?.heading ?? 'unset'} headingObject={heading} docList={docList} yMargin={this.yMargin} @@ -589,15 +562,19 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti @undoBatch @action addGroup = (value: string) => { - for (const header of this.columnHeaders) { - if (header.heading == value) { - alert('You cannot use an existing column name. Please try a new column name'); - return value; + if (this.columnHeaders) { + for (const header of this.columnHeaders) { + 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 newColWidth = 1 / (this.numGroupColumns + 1); - return value && columnHeaders?.push(new SchemaHeaderField(value, undefined, undefined, newColWidth)) && this.resizeColumns(true, newColWidth, this.columnHeaders.length - 1) ? true : false; + 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]())); + return true; }; onContextMenu = (e: React.MouseEvent): void => { @@ -642,10 +619,10 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti @computed get buttonMenu() { const menuDoc: Doc = Cast(this.rootDoc.buttonMenuDoc, Doc, null); if (menuDoc) { - const width: number = NumCast(menuDoc._width, 30); - const height: number = NumCast(menuDoc._height, 30); + const width = NumCast(menuDoc._width, 30); + const height = NumCast(menuDoc._height, 30); return ( - <div className="buttonMenu-docBtn" style={{ width: width, height: height }}> + <div className="buttonMenu-docBtn" style={{ width, height }}> <DocumentView Document={menuDoc} DataDoc={menuDoc} @@ -678,10 +655,10 @@ export class CollectionNoteTakingView extends CollectionSubView<Partial<collecti } @computed get nativeWidth() { - return this.props.NativeWidth?.() ?? Doc.NativeWidth(this.layoutDoc); + return Doc.NativeWidth(this.layoutDoc); } @computed get nativeHeight() { - return this.props.NativeHeight?.() ?? Doc.NativeHeight(this.layoutDoc); + return Doc.NativeHeight(this.layoutDoc); } @computed get scaling() { diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx index 4610da4e3..84d1c0205 100644 --- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx +++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx @@ -3,7 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; -import { Id } from '../../../fields/FieldSymbols'; +import { Copy, Id } from '../../../fields/FieldSymbols'; import { RichTextField } from '../../../fields/RichTextField'; import { listSpec } from '../../../fields/Schema'; import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; @@ -22,9 +22,6 @@ import { ContextMenuProps } from '../ContextMenuItem'; import { EditableView } from '../EditableView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import './CollectionNoteTakingView.scss'; -const higflyout = require('@hig/flyout'); -export const { anchorPoints } = higflyout; -export const Flyout = higflyout.default; interface CSVFieldColumnProps { Document: Doc; @@ -64,19 +61,15 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu // columnWidth returns the width of a column in absolute pixels @computed get columnWidth() { - if (!this.props.columnHeaders || !this.props.headingObject) { - return this.props.maxColWidth; - } - if (this.props.columnHeaders.length == 1) { - return this.props.maxColWidth; - } + if (!this.props.columnHeaders || !this.props.headingObject || this.props.columnHeaders.length === 1) return '100%'; const i = this.props.columnHeaders.indexOf(this.props.headingObject); - return this.props.columnHeaders[i].width * this.props.availableWidth; + return this.props.columnHeaders[i].width * 100 + '%'; } private dropDisposer?: DragManager.DragDropDisposer; private _headerRef: React.RefObject<HTMLDivElement> = React.createRef(); + public static ColumnMargin = 10; @observable _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading; @observable _color = this.props.headingObject ? this.props.headingObject.color : '#f1efeb'; _ele: HTMLElement | null = null; @@ -148,15 +141,17 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu @undoBatch @action deleteColumn = () => { - const columnHeaders = Cast(this.props.Document.columnHeaders, listSpec(SchemaHeaderField), null); - if (columnHeaders && this.props.headingObject) { - const index = columnHeaders.indexOf(this.props.headingObject); + 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); } }; @@ -247,9 +242,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu ref={this._headerRef} style={{ marginTop: 2 * this.props.yMargin, - // width: (this.props.columnWidth) / - // ((uniqueHeadings.length) || 1) - width: this.columnWidth - 20, + width: 'calc(100% - 5px)', }}> <div className="collectionNoteTakingView-sectionHeader-subCont" @@ -257,48 +250,41 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu style={{ background: evContents !== `No Value` ? this._color : 'inherit' }}> <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine={true} /> </div> + {(this.props.columnHeaders?.length ?? 0) > 1 && ( + <button className="collectionNoteTakingView-sectionDelete" onClick={this.deleteColumn}> + <FontAwesomeIcon icon="trash" size="lg" /> + </button> + )} </div> ) : null; - const templatecols = `${this.columnWidth}px `; + const templatecols = this.columnWidth; const type = this.props.Document.type; return ( <> {headingView} - { - <div style={{ height: '100%' }}> - <div - key={`${heading}-stack`} - className={`collectionNoteTakingView-Nodes`} - style={{ - padding: `${columnYMargin}px ${0}px ${this.props.yMargin}px ${0}px`, - margin: 'auto', - width: 'max-content', //singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`, - height: 'max-content', - position: 'relative', - gridGap: this.props.gridGap, - gridTemplateColumns: templatecols, - gridAutoRows: '0px', - }}> - {this.props.renderChildren(this.props.docList)} - </div> + <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={{ width: this.columnWidth - 20, 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> - {this.props.columnHeaders?.length && this.props.columnHeaders.length > 1 && ( - <button className="collectionNoteTakingView-sectionDelete" onClick={this.deleteColumn}> - <FontAwesomeIcon icon="trash" size="lg" /> - </button> - )} + {!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> - ) : null} - </div> - } + <div key={`${this.props.Document[Id]}-addGroup`} className="collectionNoteTakingView-addDocumentButton"> + <EditableView {...this.props.editableViewProps()} /> + </div> + </div> + ) : null} + </div> </> ); } diff --git a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx index 8d659f790..a1309b11f 100644 --- a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx +++ b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx @@ -57,7 +57,6 @@ export class CollectionNoteTakingViewDivider extends React.Component<DividerProp width: 12, borderRight: '4px solid #282828', borderLeft: '4px solid #282828', - margin: '0px 10px', }} /> </div> diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss index c296e1172..5a107d2ca 100644 --- a/src/client/views/collections/CollectionStackedTimeline.scss +++ b/src/client/views/collections/CollectionStackedTimeline.scss @@ -10,6 +10,7 @@ border-width: 0 2px 0 2px; &:hover { + cursor: default; .collectionStackedTimeline-hover { display: block; } @@ -109,14 +110,15 @@ height: 100%; width: 10px; pointer-events: all; - cursor: ew-resize; z-index: 100; } .collectionStackedTimeline-resizer { right: 0; + cursor: e-resize; } .collectionStackedTimeline-left-resizer { left: 0; + cursor: w-resize; } } @@ -143,5 +145,6 @@ transform: translate(0, -100%); font-weight: bold; + pointer-events: none; } } diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 2543624d3..7bf798656 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -2,7 +2,7 @@ import React = require('react'); import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; -import { Doc, DocListCast, StrListCast } from '../../../fields/Doc'; +import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; @@ -23,11 +23,13 @@ import { AudioWaveform } from '../AudioWaveform'; import { CollectionSubView } from '../collections/CollectionSubView'; import { Colors } from '../global/globalEnums'; import { LightboxView } from '../LightboxView'; -import { DocAfterFocusFunc, DocFocusFunc, DocumentView, DocumentViewProps } from '../nodes/DocumentView'; +import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps, DocumentViewSharedProps } from '../nodes/DocumentView'; import { LabelBox } from '../nodes/LabelBox'; import './CollectionStackedTimeline.scss'; import { VideoBox } from '../nodes/VideoBox'; import { ImageField } from '../../../fields/URLField'; +import { StyleProp } from '../StyleProvider'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; export type CollectionStackedTimelineProps = { Play: () => void; @@ -244,6 +246,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack if (!wasSelecting) { this._markerStart = this._markerEnd = this.toTimeline(clientX - rect.x, rect.width); wasSelecting = true; + this._timelineWrapper && (this._timelineWrapper.style.cursor = 'ew-resize'); } this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width); return false; @@ -260,6 +263,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack setTimeout(() => DocumentManager.Instance.getDocumentView(anchor)?.select(false)); } (!isClick || !wasSelecting) && (this._markerEnd = undefined); + this._timelineWrapper && (this._timelineWrapper.style.cursor = ''); }), (e, doubleTap) => { if (e.button !== 2) { @@ -561,7 +565,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack <div ref={this.createDashEventsTarget} style={{ pointerEvents: SnappingManager.GetIsDragging() ? 'all' : undefined }}> <div className="collectionStackedTimeline-timelineContainer" - style={{ width: this.props.PanelWidth() }} + style={{ width: this.props.PanelWidth(), cursor: SnappingManager.GetIsDragging() ? 'grab' : '' }} onWheel={e => e.stopPropagation()} onScroll={this.setScroll} onMouseMove={e => this.isContentActive() && this.onHover(e)} @@ -691,6 +695,7 @@ interface StackedTimelineAnchorProps { width: number; height: number; toTimeline: (screen_delta: number, width: number) => number; + styleProvider?: (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => any; playLink: (linkDoc: Doc) => void; setTime: (time: number) => void; startTag: string; @@ -801,12 +806,31 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> return [resetTitle]; }; + innerStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => { + if (property === StyleProp.Decorations && doc && NumCast(doc.timecodeToHide) - NumCast(doc.timecodeToShow) < 0.0002) { + return ( + <div className="styleProvider-lock"> + <FontAwesomeIcon + icon={'camera'} + style={{ color: 'red' }} + onClick={e => { + LinkFollower.FollowLink(undefined, doc, props as DocumentViewSharedProps, e.altKey); + e.stopPropagation(); + }} + size="lg" + /> + </div> + ); + } + return this.props.styleProvider?.(doc, props, property); + }; + // renders anchor LabelBox renderInner = computedFn(function (this: StackedTimelineAnchor, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), screenXf: () => Transform, width: () => number, height: () => number) { const anchor = observable({ view: undefined as any }); - const focusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => { + const focusFunc = (doc: Doc, options: DocFocusOptions) => { this.props.playLink(mark); - this.props.focus(doc, { willZoom, scale, afterFocus, docTransform }); + this.props.focus(doc, options); }; return { anchor, @@ -817,6 +841,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> ref={action((r: DocumentView | null) => (anchor.view = r))} Document={mark} DataDoc={undefined} + styleProvider={this.innerStyleProvider} renderDepth={this.props.renderDepth + 1} LayoutTemplate={undefined} LayoutTemplateString={LabelBox.LayoutStringWithTitle('data', this.computeTitle())} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 71834607c..ba29b1d6f 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -80,13 +80,14 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin); } @computed get xMargin() { - return NumCast(this.layoutDoc._xMargin, 2 * Math.min(this.gridGap, 0.05 * this.props.PanelWidth())); + return NumCast(this.layoutDoc._xMargin, Math.min(3, 0.05 * this.props.PanelWidth())); } @computed get yMargin() { - return this.props.yPadding || NumCast(this.layoutDoc._yMargin, 5); - } // 2 * this.gridGap)); } + return this.props.yPadding || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this.props.PanelWidth())); + } + @computed get gridGap() { - return NumCast(this.layoutDoc._gridGap, 10); + return NumCast(this.layoutDoc._gridGap, 5); } // are we stacking or masonry? @computed get isStackingView() { @@ -250,7 +251,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection }; // let's dive in and get the actual document we want to drag/move around - focusDocument = (doc: Doc, options?: DocFocusOptions) => { + focusDocument = (doc: Doc, options: DocFocusOptions) => { Doc.BrushDoc(doc); let focusSpeed = 0; @@ -259,7 +260,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection const top = found.getBoundingClientRect().top; const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top); if (Math.floor(localTop[1]) !== 0) { - smoothScroll((focusSpeed = doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop); + smoothScroll((focusSpeed = NumCast(doc.focusSpeed, 500)), this._mainCont!, localTop[1] + this._mainCont!.scrollTop); } } const endFocus = async (moved: boolean) => options?.afterFocus?.(moved) ?? ViewAdjustment.doNothing; @@ -494,8 +495,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection super.onExternalDrop(e, {}, (docs: Doc[]) => { if (targInd === -1) { this.addDocument(docs); - } - else { + } else { const childDocs = this.childDocList; if (childDocs) { childDocs.splice(targInd, 0, ...docs); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index e33bb77de..30759b766 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -212,7 +212,7 @@ export function CollectionSubView<X>(moreProps?: X) { let added = false; const dropAction = docDragData.dropAction || docDragData.userDropAction; const targetDocments = DocListCast(this.dataDoc[this.props.fieldKey]); - const someMoved = !docDragData.userDropAction && docDragData.draggedDocuments.some(drag => targetDocments.includes(drag)); + const someMoved = !dropAction && docDragData.draggedDocuments.some(drag => targetDocments.includes(drag)); if (someMoved) docDragData.droppedDocuments = docDragData.droppedDocuments.map((drop, i) => (targetDocments.includes(docDragData.draggedDocuments[i]) ? docDragData.draggedDocuments[i] : drop)); if ((!dropAction || dropAction === 'same' || dropAction === 'move' || someMoved) && docDragData.moveDocument) { const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d); @@ -446,6 +446,7 @@ export function CollectionSubView<X>(moreProps?: X) { } this.slowLoadDocuments(files, options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument).then(batch.end); } + slowLoadDocuments = async ( files: File[] | string, options: DocumentOptions, @@ -456,11 +457,24 @@ export function CollectionSubView<X>(moreProps?: X) { clientY: number, addDocument: (doc: Doc | Doc[]) => boolean ) => { - const disposer = OverlayView.Instance.addElement(<ReactLoading type={'spinningBubbles'} color={'green'} height={250} width={250} />, { x: clientX - 125, y: clientY - 125 }); + // create placeholder docs + // inside placeholder docs have some func that + + let pileUpDoc = undefined; if (typeof files === 'string') { - generatedDocuments.push(...(await DocUtils.uploadYoutubeVideo(files, options))); + const loading = Docs.Create.LoadingDocument(files, options); + generatedDocuments.push(loading); + Doc.addCurrentlyLoading(loading); + DocUtils.uploadYoutubeVideoLoading(files, {}, loading); } else { - generatedDocuments.push(...(await DocUtils.uploadFilesToDocs(files, options))); + generatedDocuments.push( + ...files.map(file => { + const loading = Docs.Create.LoadingDocument(file, options); + Doc.addCurrentlyLoading(loading); + DocUtils.uploadFileToDoc(file, {}, loading); + return loading; + }) + ); } if (generatedDocuments.length) { // Creating a dash document @@ -476,7 +490,8 @@ export function CollectionSubView<X>(moreProps?: X) { if (completed) completed(set); else { if (isFreeformView && generatedDocuments.length > 1) { - addDocument(DocUtils.pileup(generatedDocuments, options.x as number, options.y as number)!); + pileUpDoc = DocUtils.pileup(generatedDocuments, options.x as number, options.y as number)!; + addDocument(pileUpDoc); } else { generatedDocuments.forEach(addDocument); } @@ -488,7 +503,6 @@ export function CollectionSubView<X>(moreProps?: X) { alert('Document upload failed - possibly an unsupported file type.'); } } - disposer(); }; } @@ -501,5 +515,4 @@ import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes' import { DragManager, dropActionType } from '../../util/DragManager'; import { SelectionManager } from '../../util/SelectionManager'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; -import { OverlayView } from '../OverlayView'; import { CollectionView, CollectionViewProps } from './CollectionView'; diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index c0561e42c..1aef29b2f 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -2,7 +2,6 @@ .collectionTreeView-container { transform-origin: top left; - height: 100%; } .collectionTreeView-dropTarget { border-width: $COLLECTION_BORDER_WIDTH; @@ -62,7 +61,6 @@ .editableView-container-editing { display: block; text-overflow: ellipsis; - font-size: 1vw; white-space: nowrap; } } @@ -80,9 +78,7 @@ } .collectionTreeView-titleBar { - display: inline-block; width: 100%; - height: max-content; .contentFittingDocumentView { display: block; // makes titleBar take up full width of the treeView (flex doesn't for some reason) } @@ -104,6 +100,7 @@ text-overflow: ellipsis; white-space: pre-wrap; min-width: 10px; + grid-column: 2; } .docContainer-system { font-variant: all-small-caps; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index dce792d19..fe5dc17f5 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -83,7 +83,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree return this.doc === Doc.MyDashboards; } - @observable _explainerHeight = 0; // height of the description of the tree view + @observable _titleHeight = 0; // height of the title bar MainEle = () => this._mainEle; @@ -111,7 +111,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree computeHeight = () => { if (!this._isDisposing) { const titleHeight = !this._titleRef ? this.marginTop() : Number(getComputedStyle(this._titleRef).height.replace('px', '')); - const bodyHeight = Array.from(this.refList).reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), this.marginBot()); + const bodyHeight = Array.from(this.refList).reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), this.marginBot()) + 6; this.layoutDoc._autoHeightMargins = bodyHeight; this.props.setHeight?.(bodyHeight + titleHeight); } @@ -298,7 +298,11 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree } @computed get titleBar() { return this.dataDoc === null ? null : ( - <div className="collectionTreeView-titleBar" key={this.doc[Id]} style={!this.outlineMode ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}} ref={r => (this._titleRef = r)}> + <div + className="collectionTreeView-titleBar" + ref={action((r: any) => (this._titleRef = r) && (this._titleHeight = r.getBoundingClientRect().height * this.props.ScreenToLocalTransform().Scale))} + key={this.doc[Id]} + style={!this.outlineMode ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}}> {this.outlineMode ? this.documentTitle : this.editableTitle} </div> ); @@ -378,40 +382,45 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree const pointerEvents = () => (!this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined); const titleBar = this.props.treeViewHideTitle || this.doc.treeViewHideTitle ? null : this.titleBar; return [ - <div - className="collectionTreeView-contents" - key="tree" - style={{ - ...(!titleBar ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}), - overflow: 'auto', - height: '100%', //this.layoutDoc._autoHeight ? "max-content" : "100%" - }}> - {titleBar} + <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}> + {!this.buttonMenu && !this.noviceExplainer ? null : ( + <div className="documentButtonMenu"> + {this.buttonMenu} + {this.noviceExplainer} + </div> + )} <div - className="collectionTreeView-container" + className="collectionTreeView-contents" + key="tree" style={{ - transform: this.outlineMode ? `scale(${this.nativeDimScaling})` : '', - paddingLeft: `${this.marginX()}px`, - width: this.outlineMode ? `calc(${100 / this.nativeDimScaling}%)` : '', - }} - onContextMenu={this.onContextMenu}> - {!this.buttonMenu && !this.noviceExplainer ? null : ( - <div className="documentButtonMenu" ref={action((r: HTMLDivElement) => r && (this._explainerHeight = r.getBoundingClientRect().height))}> - {this.buttonMenu} - {this.noviceExplainer} - </div> - )} + ...(!titleBar ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}), + overflow: 'auto', + width: '100%', + height: '100%', + }}> + {titleBar} <div - className="collectionTreeView-dropTarget" + className="collectionTreeView-container" style={{ - background: background(), - height: `calc(100% - ${this._explainerHeight}px)`, - pointerEvents: pointerEvents(), + transform: this.outlineMode ? `scale(${this.nativeDimScaling})` : '', + paddingLeft: `${this.marginX()}px`, + width: this.outlineMode ? `calc(${100 / this.nativeDimScaling}%)` : '', + minHeight: `calc(100% - ${this._titleHeight}px)`, }} - onWheel={e => e.stopPropagation()} - onDrop={this.onTreeDrop} - ref={r => !this.doc.treeViewHasOverlay && r && this.createTreeDropTarget(r)}> - <ul className={`no-indent${this.outlineMode ? '-outline' : ''}`}>{this.treeViewElements}</ul> + onContextMenu={this.onContextMenu}> + <div + className="collectionTreeView-dropTarget" + style={{ + background: background(), + pointerEvents: pointerEvents(), + height: `max-content`, + minHeight: '100%', + }} + onWheel={e => e.stopPropagation()} + onDrop={this.onTreeDrop} + ref={r => !this.doc.treeViewHasOverlay && r && this.createTreeDropTarget(r)}> + <ul className={`no-indent${this.outlineMode ? '-outline' : ''}`}>{this.treeViewElements}</ul> + </div> </div> </div> </div>, diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 1ee77d4ce..dcaad5632 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -15,7 +15,6 @@ import { ImageUtils } from '../../util/Import & Export/ImageUtils'; import { InteractionUtils } from '../../util/InteractionUtils'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; -import { DashboardView } from '../DashboardView'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import { CollectionCarousel3DView } from './CollectionCarousel3DView'; @@ -42,6 +41,7 @@ interface CollectionViewProps_ extends FieldViewProps { isAnnotationOverlayScrollable?: boolean; // whether the annotation overlay can be vertically scrolled (just for tree views, currently) layoutEngine?: () => string; setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean) => void) => void; + setBrushViewer?: (func?: (view: { width: number; height: number; panX: number; panY: number }) => void) => void; // property overrides for child documents childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox) @@ -116,6 +116,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab switch (type) { default: case CollectionViewType.Freeform: return <CollectionFreeFormView key="collview" {...props} />; + case CollectionViewType.Docking: return <CollectionDockingView key="collview" {...props} />; case CollectionViewType.Schema: return <CollectionSchemaView key="collview" {...props} />; case CollectionViewType.Docking: return <CollectionDockingView key="collview" {...props} />; case CollectionViewType.Tree: return <CollectionTreeView key="collview" {...props} />; @@ -153,7 +154,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab !Doc.noviceMode && subItems.push({ description: 'Map', event: () => func(CollectionViewType.Map), icon: 'globe-americas' }); subItems.push({ description: 'Grid', event: () => func(CollectionViewType.Grid), icon: 'th-list' }); - if (!Doc.IsSystem(this.rootDoc) && !this.rootDoc.isGroup && !this.rootDoc.annotationOn) { + if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._viewType !== CollectionViewType.Docking && !this.rootDoc.isGroup && !this.rootDoc.annotationOn) { const existingVm = ContextMenu.Instance.findByDescription(category); const catItems = existingVm && 'subitems' in existingVm ? existingVm.subitems : []; catItems.push({ description: 'Add a Perspective...', addDivider: true, noexpand: true, subitems: subItems, icon: 'eye' }); @@ -187,7 +188,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab } !Doc.noviceMode && optionItems.push({ description: `${this.rootDoc.isInPlaceContainer ? 'Unset' : 'Set'} inPlace Container`, event: () => (this.rootDoc.isInPlaceContainer = !this.rootDoc.isInPlaceContainer), icon: 'project-diagram' }); - if (!Doc.noviceMode) { + if (!Doc.noviceMode && false) { optionItems.push({ description: 'Create Branch', event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), 'add:right'), @@ -204,9 +205,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab icon: 'project-diagram', }); } - if (this.Document._viewType === CollectionViewType.Docking) { - optionItems.push({ description: 'Create Dashboard', event: () => DashboardView.createNewDashboard(), icon: 'project-diagram' }); - } !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'hand-point-right' }); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 73574bdb3..9938245ea 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -56,7 +56,9 @@ export class TabDocView extends React.Component<TabDocViewProps> { return this._document && Doc.Layout(this._document); } @computed get tabColor() { - return StrCast(this._document?._backgroundColor, StrCast(this._document?.backgroundColor, DefaultStyleProvider(this._document, undefined, StyleProp.BackgroundColor))); + let tabColor = StrCast(this._document?._backgroundColor, StrCast(this._document?.backgroundColor, DefaultStyleProvider(this._document, undefined, StyleProp.BackgroundColor))); + if (tabColor === 'transparent') return 'black'; + return tabColor; } @computed get tabTextColor() { return this._document?.type === DocumentType.PRES ? 'black' : StrCast(this._document?._color, StrCast(this._document?.color, DefaultStyleProvider(this._document, undefined, StyleProp.Color))); @@ -94,6 +96,18 @@ export class TabDocView extends React.Component<TabDocViewProps> { const iconWrap = document.createElement('div'); const closeWrap = document.createElement('div'); + const getChild = () => { + let child = this.view?.ContentDiv?.children[0]; + while (child?.children.length) { + const next = Array.from(child.children).find(c => c.className?.toString().includes('SVGAnimatedString') || typeof c.className === 'string'); + if (next?.className?.toString().includes(DocumentView.ROOT_DIV)) break; + if (next?.className?.toString().includes(DashFieldView.name)) break; + if (next) child = next; + else break; + } + return child; + }; + titleEle.size = StrCast(doc.title).length + 3; titleEle.value = doc.title; titleEle.onkeydown = (e: KeyboardEvent) => { @@ -117,14 +131,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { action(e => { if (this.view) { SelectionManager.SelectView(this.view, false); - let child = this.view.ContentDiv!.children[0]; - while (child.children.length) { - const next = Array.from(child.children).find(c => c.className?.toString().includes('SVGAnimatedString') || typeof c.className === 'string'); - if (next?.className?.toString().includes(DocumentView.ROOT_DIV)) break; - if (next?.className?.toString().includes(DashFieldView.name)) break; - if (next) child = next; - else break; - } + const child = getChild(); simulateMouseClick(child, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30); } else { this._activated = true; @@ -163,6 +170,15 @@ export class TabDocView extends React.Component<TabDocViewProps> { } }; + tab.element[0].oncontextmenu = (e: MouseEvent) => { + let child = getChild(); + if (child) { + simulateMouseClick(child, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30); + e.stopPropagation(); + e.preventDefault(); + } + }; + // select the tab document when the tab is directly clicked and activate the tab whenver the tab document is selected titleEle.onpointerdown = action((e: any) => { if (e.target.className !== 'lm_iconWrap') { @@ -213,73 +229,75 @@ export class TabDocView extends React.Component<TabDocViewProps> { public static PinDoc(docs: Doc | Doc[], pinProps?: PinProps) { const docList = docs instanceof Doc ? [docs] : docs; - // all docs will be added to the ActivePresentation as stored on CurrentUserUtils - const curPres = Doc.ActivePresentation; - if (curPres) { - const batch = UndoManager.StartBatch('pinning doc'); - docList.forEach(doc => { - // Edge Case 1: Cannot pin document to itself - if (doc === curPres) { - alert('Cannot pin presentation document to itself'); - return; - } - const pinDoc = Doc.MakeAlias(doc); - pinDoc.presentationTargetDoc = 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 = PresMovement.Zoom; - 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 - pinDoc.treeViewRenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area - pinDoc.treeViewHeaderWidth = '100%'; // forces the header to grow to be the same size as its largest sibling. - pinDoc.treeViewChildrenOnRoot = true; // tree view will look for hierarchical children on the root doc, not the data doc. - pinDoc.treeViewFieldKey = 'data'; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field - pinDoc.treeViewExpandedView = 'data'; // in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view - pinDoc.treeViewGrowsHorizontally = true; // the document expands horizontally when displayed as a tree view header - pinDoc.treeViewHideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header - const presArray: Doc[] = PresBox.Instance?.sortArray(); - const size: number = PresBox.Instance?._selectedArray.size; - const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined; - const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], null); + const batch = UndoManager.StartBatch('pinning doc'); + const curPres = Doc.ActivePresentation ?? Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true); - PresBox.pinDocView(pinDoc, pinProps); - pinDoc.onClick = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)'); - Doc.AddDocToList(curPres, 'data', pinDoc, presSelected); - if (!pinProps?.audioRange && duration !== undefined) { - pinDoc.mediaStart = 'manual'; - pinDoc.mediaStop = 'manual'; - pinDoc.presStartTime = NumCast(doc.clipStart); - pinDoc.presEndTime = NumCast(doc.clipEnd, duration); - } - //save position - if (pinProps?.activeFrame !== undefined) { - pinDoc.presActiveFrame = pinProps?.activeFrame; - pinDoc.title = doc.title + ' (move)'; - pinDoc.presMovement = PresMovement.Pan; - } - if (pinDoc.isInkMask) { - pinDoc.presHideAfter = true; - pinDoc.presHideBefore = true; - pinDoc.presMovement = PresMovement.None; - } - if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true; - PresBox.Instance?.clearSelectedArray(); - pinDoc && PresBox.Instance?.addToSelectedArray(pinDoc); //Update selected array - }); - if ( - CollectionDockingView.Instance && - !Array.from(CollectionDockingView.Instance.tabMap) - .map(d => d.DashDoc) - .includes(curPres) - ) { - const docs = Cast(Doc.MyOverlayDocs.data, listSpec(Doc), []); - if (docs.includes(curPres)) docs.splice(docs.indexOf(curPres), 1); - CollectionDockingView.AddSplit(curPres, 'right'); - setTimeout(() => DocumentManager.Instance.jumpToDocument(docList.lastElement(), false, undefined, []), 100); // keeps the pinned doc in view since the sidebar shifts things + if (!Doc.ActivePresentation) { + Doc.AddDocToList(Doc.MyTrails, 'data', curPres); + Doc.ActivePresentation = curPres; + } + + docList.forEach(doc => { + // Edge Case 1: Cannot pin document to itself + if (doc === curPres) { + alert('Cannot pin presentation document to itself'); + return; + } + const pinDoc = Doc.MakeAlias(doc); + pinDoc.presentationTargetDoc = 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 = pinProps?.pinDocView && !pinProps?.pinWithView ? PresMovement.None : PresMovement.Zoom; + 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 + pinDoc.treeViewRenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area + pinDoc.treeViewHeaderWidth = '100%'; // forces the header to grow to be the same size as its largest sibling. + pinDoc.treeViewChildrenOnRoot = true; // tree view will look for hierarchical children on the root doc, not the data doc. + pinDoc.treeViewFieldKey = 'data'; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field + pinDoc.treeViewExpandedView = 'data'; // in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view + pinDoc.treeViewGrowsHorizontally = true; // the document expands horizontally when displayed as a tree view header + pinDoc.treeViewHideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header + const presArray: Doc[] = PresBox.Instance?.sortArray(); + const size: number = PresBox.Instance?.selectedArray.size; + const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined; + const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], null); + + if (!pinProps?.audioRange && duration !== undefined) { + pinDoc.mediaStart = 'manual'; + pinDoc.mediaStop = 'manual'; + pinDoc.presStartTime = NumCast(doc.clipStart); + pinDoc.presEndTime = NumCast(doc.clipEnd, duration); + } + PresBox.pinDocView(pinDoc, pinProps, doc); + pinDoc.onClick = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)'); + Doc.AddDocToList(curPres, 'data', pinDoc, presSelected); + //save position + if (pinProps?.activeFrame !== undefined) { + pinDoc.presActiveFrame = pinProps?.activeFrame; + pinDoc.title = doc.title + ' (move)'; + pinDoc.presMovement = PresMovement.Pan; + } + if (pinDoc.isInkMask) { + pinDoc.presHideAfter = true; + pinDoc.presHideBefore = true; + pinDoc.presMovement = PresMovement.None; } - setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs + if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true; + PresBox.Instance?.clearSelectedArray(); + pinDoc && PresBox.Instance?.addToSelectedArray(pinDoc); //Update selected array + }); + if ( + !Array.from(CollectionDockingView.Instance?.tabMap ?? []) + .map(d => d.DashDoc) + .includes(curPres) + ) { + const docs = Cast(Doc.MyOverlayDocs.data, listSpec(Doc), []); + if (docs.includes(curPres)) docs.splice(docs.indexOf(curPres), 1); + CollectionDockingView.AddSplit(curPres, 'right'); + setTimeout(() => DocumentManager.Instance.jumpToDocument(docList.lastElement(), false, undefined, []), 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 } componentDidMount() { @@ -363,10 +381,10 @@ export class TabDocView extends React.Component<TabDocViewProps> { return NumCast(Cast(PresBox.Instance.childDocs[PresBox.Instance.itemIndex].presentationTargetDoc, Doc, null)._currentFrame); }; @action - focusFunc = (doc: Doc, options?: DocFocusOptions) => { + focusFunc = (doc: Doc, options: DocFocusOptions) => { const shrinkwrap = options?.originalTarget === this._document && this.view?.ComponentView?.shrinkWrap; - if (shrinkwrap && this._document) { - const focusSpeed = 1000; + if (options?.willZoom !== false && shrinkwrap && this._document) { + const focusSpeed = NumCast(this._document.focusSpeed, 500); shrinkwrap(); this._document._viewTransition = `transform ${focusSpeed}ms`; setTimeout( @@ -398,7 +416,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { hideMinimap = () => this.disableMinimap() || BoolCast(this._document?.hideMinimap); @computed get docView() { - return !this._activated || !this._document || this._document._viewType === CollectionViewType.Docking ? null : ( + return !this._activated || !this._document ? null : ( <> <DocumentView key={this._document[Id]} diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss index ce87e6f89..57bb5274d 100644 --- a/src/client/views/collections/TreeView.scss +++ b/src/client/views/collections/TreeView.scss @@ -53,13 +53,16 @@ } .bullet { + grid-column: 1; + display: flex; + justify-content: center; + align-items: center; position: relative; width: $TREE_BULLET_WIDTH; + min-height: 20px; color: $medium-gray; - margin-top: 3px; - // transform: scale(1.3, 1.3); // bcz: why was this here? It makes displaying images in the treeView for documents that have icons harder. border: #80808030 1px solid; - border-radius: 4px; + border-radius: 5px; } } @@ -112,8 +115,15 @@ .treeView-header-editing, .treeView-header { + display: flex; // needed for PresBox's treeView border: transparent 1px solid; - display: flex; + align-items: center; + width: 100%; + border-radius: 5px; + + &:hover { + background-color: #bdddf5; + } //align-items: center; ::-webkit-scrollbar { @@ -140,6 +150,7 @@ .treeView-rightButtons { display: flex; + grid-column: 3; align-items: center; margin-left: 0.25rem; opacity: 0.75; diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index aa1330762..14563f990 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -8,7 +8,7 @@ import { List } from '../../../fields/List'; import { RichTextField } from '../../../fields/RichTextField'; import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; +import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnOne, returnTrue, simulateMouseClick, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; @@ -575,7 +575,7 @@ export class TreeView extends React.Component<TreeViewProps> { <ul key={expandKey + 'more'} title="click to change sort order" - className={this.doc.treeViewHideTitle ? 'no-indent' : ''} + className={''} //this.doc.treeViewHideTitle ? 'no-indent' : ''} onPointerDown={e => { downX = e.clientX; downY = e.clientY; @@ -692,7 +692,7 @@ export class TreeView extends React.Component<TreeViewProps> { <FontAwesomeIcon size="sm" icon={[this.childDocs?.length && !this.treeViewOpen ? 'fas' : 'far', 'circle']} /> ) ) : ( - <div className="treeView-bulletIcons"> + <div className="treeView-bulletIcons" style={{ color: Doc.IsSystem(DocCast(this.doc.proto)) ? 'red' : undefined }}> <div className={`treeView-${this.onCheckedClick ? 'checkIcon' : 'expandIcon'}`}> <FontAwesomeIcon size="sm" icon={checked === 'check' ? 'check' : checked === 'x' ? 'times' : checked === 'unchecked' ? 'square' : !this.treeViewOpen ? 'caret-right' : 'caret-down'} /> </div> @@ -736,7 +736,7 @@ export class TreeView extends React.Component<TreeViewProps> { }} /> )} - {this.doc.treeViewExpandedViewLock || Doc.IsSystem(this.doc) ? null : ( + {Doc.noviceMode ? null : this.doc.treeViewExpandedViewLock || Doc.IsSystem(this.doc) ? null : ( <span className="collectionTreeView-keyHeader" title="type of expanded data" key={this.treeViewExpandedView} onPointerDown={this.expandNextviewType}> {this.treeViewExpandedView} </span> @@ -782,7 +782,7 @@ export class TreeView extends React.Component<TreeViewProps> { onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeViewChildDoubleClick, !this.props.treeView.outlineMode ? this._openScript?.() : null); - refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document); + refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document, {}); ignoreEvent = (e: any) => { if (this.props.isContentActive(true)) { e.stopPropagation(); @@ -953,7 +953,6 @@ export class TreeView extends React.Component<TreeViewProps> { <div className={`treeView-header` + (editing ? '-editing' : '')} key="titleheader" - style={{ width: StrCast(this.doc.treeViewHeaderWidth, 'max-content') }} ref={this._header} onClick={this.ignoreEvent} onPointerDown={this.ignoreEvent} @@ -1038,7 +1037,7 @@ export class TreeView extends React.Component<TreeViewProps> { @computed get renderBorder() { const sorting = StrCast(this.doc.treeViewSortCriterion, TreeSort.None); - const sortings = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; label: string } }; + const sortings = (this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) ?? {}) as { [key: string]: { color: string; label: string } }; return ( <div className={`treeView-border${this.props.treeView.outlineMode ? TreeViewType.outline : ''}`} style={{ borderColor: sortings[sorting]?.color }}> {!this.treeViewOpen ? null : this.renderContent} @@ -1058,7 +1057,7 @@ export class TreeView extends React.Component<TreeViewProps> { render() { TraceMobx(); const hideTitle = this.doc.treeViewHideHeader || (this.doc.treeViewHideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode; - return this.props.renderedIds.indexOf(this.doc[Id]) !== -1 ? ( + return this.props.renderedIds?.indexOf(this.doc[Id]) !== -1 ? ( '<' + this.doc.title + '>' // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles ) : ( <div diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 052cbd3bb..0d061a325 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -44,7 +44,6 @@ import { PresBox } from '../../nodes/trails/PresBox'; import { VideoBox } from '../../nodes/VideoBox'; import { CreateImage } from '../../nodes/WebBoxRenderer'; import { StyleProp } from '../../StyleProvider'; -import { CollectionDockingView } from '../CollectionDockingView'; import { CollectionSubView } from '../CollectionSubView'; import { TreeViewType } from '../CollectionTreeView'; import { TabDocView } from '../TabDocView'; @@ -53,7 +52,6 @@ import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCurso import './CollectionFreeFormView.scss'; import { MarqueeView } from './MarqueeView'; import React = require('react'); -import e = require('connect-flash'); export type collectionFreeformViewProps = { annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox) @@ -90,6 +88,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection private _cachedPool: Map<string, PoolData> = new Map(); private _lastTap = 0; private _batch: UndoManager.Batch | undefined = undefined; + private _brushtimer: any; + private _brushtimer1: any; // private isWritingMode: boolean = true; // private writingModeDocs: Doc[] = []; @@ -116,10 +116,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @observable _marqueeRef = React.createRef<HTMLDivElement>(); @observable _marqueeViewRef = React.createRef<MarqueeView>(); @observable ChildDrag: DocumentView | undefined; // child document view being dragged. needed to update drop areas of groups when a group item is dragged. + @observable _brushedView = { width: 0, height: 0, panX: 0, panY: 0, opacity: 0 }; // highlighted region of freeform canvas used by presentations to indicate a region @computed get views() { - const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask !== -1).map(ele => ele.ele); - const renderableEles = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask === -1).map(ele => ele.ele); + const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask !== -1 && ele.inkMask !== undefined).map(ele => ele.ele); + const renderableEles = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && (ele.inkMask === -1 || ele.inkMask === undefined)).map(ele => ele.ele); if (viewsMask.length) renderableEles.push(<div className={`collectionfreeformview-mask${this._layoutElements.some(ele => (ele.inkMask ?? 0) > 0) ? '' : '-empty'}`}>{viewsMask}</div>); return renderableEles; } @@ -130,7 +131,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }; } @computed get fitContentsToBox() { - return (this.props.fitContentsToBox?.() || this.Document._fitContentsToBox) && !this.isAnnotationOverlay; + return (this.props.fitContentsToBox?.() || this.Document._fitContentsToBox || this.Document.isGroup) && !this.isAnnotationOverlay; } @computed get contentBounds() { const cb = Cast(this.rootDoc.contentBounds, listSpec('number')); @@ -154,8 +155,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections } @computed get cachedCenteringShiftY(): number { + const dv = this.props.DocumentView?.(); const scaling = this.fitContentsToBox || !this.nativeDimScaling ? 1 : this.nativeDimScaling; - return this.props.isAnnotationOverlay ? 0 : this.props.PanelHeight() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections + // if freeform has a native aspect, then the panel height needs to be adjusted to match it + const aspect = dv?.nativeWidth && dv?.nativeHeight && !dv.layoutDoc.fitWidth ? dv.nativeHeight / dv.nativeWidth : this.props.PanelHeight() / this.props.PanelWidth(); + return this.props.isAnnotationOverlay ? 0 : (aspect * this.props.PanelWidth()) / 2 / scaling; // shift so pan position is at center of window for non-overlay collections } @computed get cachedGetLocalTransform(): Transform { return Transform.Identity() @@ -189,6 +193,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); elementFunc = () => this._layoutElements; shrinkWrap = () => { + if (this.props.DocumentView?.().nativeWidth) return; const vals = this.fitToContentVals; this.layoutDoc._panX = vals.bounds.cx; this.layoutDoc._panY = vals.bounds.cy; @@ -233,11 +238,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const newBoxes = newBox instanceof Doc ? [newBox] : newBox; for (const newBox of newBoxes) { if (newBox.activeFrame !== undefined) { - const vals = CollectionFreeFormDocumentView.animFields.map(field => newBox[field]); - CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[`${field}-indexed`]); - CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[field]); + const vals = CollectionFreeFormDocumentView.animFields.map(field => newBox[field.key]); + CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[`${field.key}-indexed`]); + CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[field.key]); delete newBox.activeFrame; - CollectionFreeFormDocumentView.animFields.forEach((field, i) => field !== 'opacity' && (newBox[field] = vals[i])); + CollectionFreeFormDocumentView.animFields.forEach((field, i) => field.key !== 'opacity' && (newBox[field.key] = vals[i])); } } if (this.Document._currentFrame !== undefined && !this.props.isAnnotationOverlay) { @@ -275,10 +280,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const layoutDoc = Doc.Layout(d); if (this.Document._currentFrame !== undefined) { CollectionFreeFormDocumentView.setupKeyframes([d], NumCast(this.Document._currentFrame), false); - const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000)); - vals.x = x + NumCast(vals.x) - dropPos[0]; - vals.y = y + NumCast(vals.y) - dropPos[1]; - vals._scrollTop = this.Document.editScrollProgressivize ? vals._scrollTop : undefined; + const pvals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000)); // get filled in values (uses defaults when not value is specified) for position + const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000), false); // get non-default values for everything else + vals.x = x + NumCast(pvals.x) - dropPos[0]; + vals.y = y + NumCast(pvals.y) - dropPos[1]; CollectionFreeFormDocumentView.setValues(NumCast(this.Document._currentFrame), d, vals); } else { d.x = x + NumCast(d.x) - dropPos[0]; @@ -1100,10 +1105,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action zoomSmoothlyAboutPt(docpt: number[], scale: number, transitionTime = 500) { if (this.Document._isGroup) return; - setTimeout( - action(() => (this._viewTransition = 0)), - (this._viewTransition = transitionTime) - ); // set transition to be smooth, then reset + this._viewTransition = transitionTime; const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]); this.layoutDoc[this.scaleFieldKey] = scale; const newScreenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]); @@ -1111,9 +1113,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const newpan = this.getTransform().transformDirection(scrDelta.x, scrDelta.y); this.layoutDoc._panX = NumCast(this.layoutDoc._panX) - newpan[0]; this.layoutDoc._panY = NumCast(this.layoutDoc._panY) - newpan[1]; + return new Promise<number>(res => setTimeout(() => res(runInAction(() => (this._viewTransition = 0))), this._viewTransition)); // set transition to be smooth, then reset } - focusDocument = (doc: Doc, options?: DocFocusOptions) => { + focusDocument = (doc: Doc, options: DocFocusOptions) => { const state = HistoryUtil.getState(); // TODO This technically isn't correct if type !== "doc", as @@ -1132,22 +1135,22 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection // if (SelectionManager.Views().length !== 1 || SelectionManager.Views()[0].Document !== doc) { // SelectionManager.DeselectAll(); // } - if (this.props.Document.scrollHeight || this.props.Document.scrollTop !== undefined) { + if (this.props.Document.scrollHeight || this.props.Document.scrollTop !== undefined || this.props.Document.currentTimecode !== undefined) { 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?.willZoom ? this.Document[this.scaleFieldKey] : undefined }; const newState = HistoryUtil.getState(); - const cantTransform = /*this.props.isAnnotationOverlay ||*/ (this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc; + const cantTransform = (this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc; const { panX, panY, scale } = cantTransform ? savedState : this.calculatePanIntoView(doc, xfToCollection, options?.willZoom ? options?.scale || 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: panX, panY: panY }; + 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 ? (doc.focusSpeed !== undefined ? Number(doc.focusSpeed) : 500) : 0; + const focusSpeed = options?.instant ? 0 : didMove ? NumCast(doc.focusSpeed, 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) { scale && (this.Document[this.scaleFieldKey] = scale); @@ -1166,8 +1169,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this.Document._panY = restoreState.panY; this.Document[this.scaleFieldKey] = restoreState.scale; } - runInAction(() => (this._viewTransition = 0)); } + runInAction(() => (this._viewTransition = 0)); return resetView; }; const xf = !cantTransform @@ -1176,7 +1179,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection ? 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 ? doc : this.rootDoc, { + 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)))), @@ -1199,10 +1202,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection scale: newScale, }; } - const pw = this.props.PanelWidth() / NumCast(this.layoutDoc._viewScale, 1); - const ph = this.props.PanelHeight() / NumCast(this.layoutDoc._viewScale, 1); - const cx = NumCast(this.layoutDoc._panX); - const cy = NumCast(this.layoutDoc._panY); + + const panelWidth = this.props.isAnnotationOverlay ? this.nativeWidth : this.props.PanelWidth(); + const panelHeight = this.props.isAnnotationOverlay ? this.nativeHeight : this.props.PanelHeight(); + const pw = panelWidth / NumCast(this.layoutDoc._viewScale, 1); + const ph = panelHeight / NumCast(this.layoutDoc._viewScale, 1); + const cx = NumCast(this.layoutDoc._panX) + (this.props.isAnnotationOverlay ? pw / 2 : 0); + const cy = NumCast(this.layoutDoc._panY) + (this.props.isAnnotationOverlay ? ph / 2 : 0); const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 }; if (screen.right - screen.left < bounds.right - bounds.left || screen.bot - screen.top < bounds.bot - bounds.top) { return { @@ -1212,8 +1218,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }; } return { - panX: cx + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right), - panY: cy + Math.min(0, bounds.top - ph / 10 - screen.top) + Math.max(0, bounds.bot + ph / 10 - screen.bot), + panX: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc._panX) : cx) + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right), + panY: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc._panY) : cy) + Math.min(0, bounds.top - ph / 10 - screen.top) + Math.max(0, bounds.bot + ph / 10 - screen.bot), }; }; @@ -1424,15 +1430,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @computed get doInternalLayoutComputation() { TraceMobx(); const newPool = new Map<string, PoolData>(); + // prettier-ignore switch (this.layoutEngine) { - case 'pass': - return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) }; - case 'timeline': - return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) }; - case 'pivot': - return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) }; - case 'starburst': - return { newPool, computedElementData: this.doEngineLayout(newPool, computerStarburstLayout) }; + case 'pass': return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) }; + case 'timeline': return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) }; + case 'pivot': return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) }; + case 'starburst': return { newPool, computedElementData: this.doEngineLayout(newPool, computerStarburstLayout) }; } return { newPool, computedElementData: this.doFreeformLayout(newPool) }; } @@ -1453,7 +1456,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || - newPos.zIndex !== lastPos.zIndex + newPos.zIndex !== lastPos.zIndex || + newPos.transition !== lastPos.transition ) { this._layoutPoolData.set(entry[0], newPos); } @@ -1521,14 +1525,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection componentDidMount() { super.componentDidMount?.(); this.props.setContentView?.(this); + this.props.setBrushViewer?.(this.brushView); setTimeout( action(() => { this._firstRender = false; - this._disposers.layoutComputation = reaction( - () => this.doLayoutComputation, - elements => (this._layoutElements = elements || []), - { fireImmediately: true, name: 'doLayout' } - ); this._marqueeRef.current?.addEventListener('dashDragAutoScroll', this.onDragAutoScroll as any); @@ -1545,10 +1545,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[HeightSym]() / 2]; const p = [NumCast(this.layoutDoc._panX), NumCast(this.layoutDoc._panY)]; const pbounds = { - x: (cbounds.x - p[0]) * this.zoomScaling() + c[0], - y: (cbounds.y - p[1]) * this.zoomScaling() + c[1], - r: (cbounds.r - p[0]) * this.zoomScaling() + c[0], - b: (cbounds.b - p[1]) * this.zoomScaling() + c[1], + x: cbounds.x - p[0] + c[0], + y: cbounds.y - p[1] + c[1], + r: cbounds.r - p[0] + c[0], + b: cbounds.b - p[1] + c[1], }; this.layoutDoc._width = pbounds.r - pbounds.x; this.layoutDoc._height = pbounds.b - pbounds.y; @@ -1560,6 +1560,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }, { fireImmediately: true } ); + + this._disposers.layoutComputation = reaction( + () => this.doLayoutComputation, + elements => (this._layoutElements = elements || []), + { fireImmediately: true, name: 'doLayout' } + ); }) ); } @@ -1841,7 +1847,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @computed get placeholder() { return ( <div className="collectionfreeformview-placeholder" style={{ background: StrCast(this.Document.backgroundColor) }}> - <span className="collectionfreeformview-placeholderSpan">{this.props.Document.title?.toString()}</span> + <span className="collectionfreeformview-placeholderSpan">{this.props.Document.annotationOn ? '' : this.props.Document.title?.toString()}</span> </div> ); } @@ -1855,6 +1861,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection ungroup={this.props.Document._isGroup ? this.promoteCollection : undefined} nudge={this.isAnnotationOverlay || this.props.renderDepth > 0 ? undefined : this.nudge} addDocTab={this.addDocTab} + slowLoadDocuments={this.slowLoadDocuments} trySelectCluster={this.trySelectCluster} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} @@ -1880,6 +1887,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection </div> ) : null} <CollectionFreeFormViewPannableContents + brushView={this._brushedView} isAnnotationOverlay={this.isAnnotationOverlay} isAnnotationOverlayScrollable={this.props.isAnnotationOverlayScrollable} transform={this.contentTransform} @@ -1915,9 +1923,25 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } }; + @action + brushView = (viewport: { width: number; height: number; panX: number; panY: number }) => { + this._brushedView = { ...viewport, panX: viewport.panX - viewport.width / 2, panY: viewport.panY - viewport.height / 2, opacity: 1 }; + this._brushtimer1 && clearTimeout(this._brushtimer1); + this._brushtimer && clearTimeout(this._brushtimer); + this._brushtimer1 = setTimeout( + action(() => { + this._brushedView.opacity = 0; + this._brushtimer = setTimeout( + action(() => (this._brushedView = { width: 0, height: 0, panX: 0, panY: 0, opacity: 0 })), + 500 + ); + }), + 1000 + ); + }; + render() { TraceMobx(); - const clientRect = this._mainCont?.getBoundingClientRect(); return ( <div className={'collectionfreeformview-container'} @@ -2003,6 +2027,7 @@ interface CollectionFreeFormViewPannableContentsProps { presPinView?: boolean; isAnnotationOverlay: boolean | undefined; isAnnotationOverlayScrollable: boolean | undefined; + brushView: { panX: number; panY: number; width: number; height: number; opacity: number }; } @observer @@ -2122,6 +2147,21 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF //willChange: "transform" }}> {this.props.children()} + {!this.props.brushView.width ? null : ( + <div + className="collectionFreeFormView-brushView" + style={{ + zIndex: 1000, + opacity: this.props.brushView.opacity, + border: 'orange solid 2px', + position: 'absolute', + transform: `translate(${this.props.brushView.panX}px, ${this.props.brushView.panY}px)`, + width: this.props.brushView.width, + height: this.props.brushView.height, + transition: 'opacity 2s', + }} + /> + )} {this.presPaths} {this.progressivize} {this.zoomProgressivize} @@ -2199,7 +2239,7 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY 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); + await ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5); } return ViewAdjustment.doNothing; }, diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss index 41e4d6b6a..e0f5cbe5b 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss @@ -1,16 +1,14 @@ - .marqueeView { position: inherit; - top:0; - left:0; - width:100%; - height:100%; + top: 0; + left: 0; + width: 100%; + height: 100%; overflow: hidden; border-radius: inherit; user-select: none; } - .marqueeView:focus-within { overflow: hidden; } @@ -22,13 +20,13 @@ border-color: black; pointer-events: none; .marquee-legend { - bottom:-18px; - left:0; + bottom: -18px; + left: 0; position: absolute; font-size: 9; - white-space:nowrap; + white-space: nowrap; } .marquee-legend::after { - content: "Press <space> for lasso" + content: 'Press <space> for lasso'; } -}
\ No newline at end of file +} diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 65a11cbcb..584c9690f 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -40,6 +40,16 @@ interface MarqueeViewProps { nudge?: (x: number, y: number, nudgeTime?: number) => boolean; ungroup?: () => void; setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean) => void) => void; + slowLoadDocuments: ( + files: File[] | string, + options: DocumentOptions, + generatedDocuments: Doc[], + text: string, + completed: ((doc: Doc[]) => void) | undefined, + clientX: number, + clientY: number, + addDocument: (doc: Doc | Doc[]) => boolean + ) => Promise<void>; } export interface MarqueeViewBounds { @@ -330,7 +340,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque this._downY = y; const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]); if ([AclAdmin, AclEdit, AclAugment].includes(effectiveAcl)) { - PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge); + PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge, this.props.slowLoadDocuments); } this.clearSelection(); } @@ -415,13 +425,11 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque @undoBatch @action pinWithView = async () => { - const scale = Math.min(this.props.PanelWidth() / this.Bounds.width, this.props.PanelHeight() / this.Bounds.height); const doc = this.props.Document; const viewOptions: PinViewProps = { bounds: this.Bounds, - scale: scale, }; - TabDocView.PinDoc(doc, { pinWithView: viewOptions }); + TabDocView.PinDoc(doc, { pinWithView: viewOptions, pinDocView: true }); MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); }; diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index 0d7d67dd8..92f6bbb64 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -198,13 +198,7 @@ export class CollectionLinearView extends CollectionSubView() { <div className={`collectionLinearView-outer ${this.layoutDoc.linearViewSubMenu}`} style={{ backgroundColor: BoolCast(this.layoutDoc.linearViewIsExpanded) ? undefined : 'transparent' }}> <div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu}> {!expandable ? null : ( - <Tooltip - title={ - <> - <div className="dash-tooltip">{BoolCast(this.props.Document.linearViewIsExpanded) ? 'Close' : 'Open'}</div> - </> - } - placement="top"> + <Tooltip title={<div className="dash-tooltip">{BoolCast(this.props.Document.linearViewIsExpanded) ? 'Close' : 'Open'}</div>} placement="top"> {menuOpener} </Tooltip> )} @@ -213,7 +207,17 @@ export class CollectionLinearView extends CollectionSubView() { type="checkbox" checked={BoolCast(this.props.Document.linearViewIsExpanded)} ref={this.addMenuToggle} - onChange={action(() => (this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked))} + onChange={action(e => { + ScriptCast(this.Document.onClick)?.script.run({ + this: this.layoutDoc, + self: this.rootDoc, + _readOnly_: false, + scriptContext: this.props.scriptContext, + thisContainer: this.props.ContainingCollectionDoc, + documentView: this.props.docViewPath().lastElement(), + }); + this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked; + })} /> <div |
