diff options
Diffstat (limited to 'src/client/views/collections')
16 files changed, 343 insertions, 341 deletions
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index ac3541f2c..78e44dfa2 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -89,6 +89,10 @@ position: relative; } +.lm_stack { + position: relative; +} + .lm_drag_tab { padding: 0; width: 15px !important; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index e9b41de25..92319d080 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -1,6 +1,6 @@ import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOM from 'react-dom/client'; import * as GoldenLayout from '../../../client/goldenLayout'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; @@ -318,7 +318,7 @@ export class CollectionDockingView extends CollectionSubView() { } }; - componentDidMount: () => void = () => { + componentDidMount: () => void = async () => { if (this._containerRef.current) { this._lightboxReactionDisposer = reaction( () => LightboxView.LightboxDoc, @@ -335,8 +335,11 @@ export class CollectionDockingView extends CollectionSubView() { this._ignoreStateChange = ''; } ); - setTimeout(this.setupGoldenLayout); - //window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window + reaction( + () => this.props.PanelWidth(), + width => !this._goldenLayout && width > 20 && setTimeout(() => this.setupGoldenLayout()), // need to wait for the collectiondockingview-container to have it's width/height since golden layout reads that to configure its windows + { fireImmediately: true } + ); } }; @@ -460,7 +463,7 @@ export class CollectionDockingView extends CollectionSubView() { }; tabDestroyed = (tab: any) => { - if (![DocumentType.KVP, DocumentType.PRES].includes(tab.DashDoc?.type)) { + if (tab.DashDoc && ![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); } @@ -470,8 +473,8 @@ export class CollectionDockingView extends CollectionSubView() { Doc.RemoveDocFromList(dview, fieldKey, tab.DashDoc); this.tabMap.delete(tab); tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.()); - tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele)); - setTimeout(this.stateChanged); + //tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele)); + this.stateChanged(); } }; tabCreated = (tab: any) => { diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 46e8494ab..db81f28f6 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -40,6 +40,7 @@ import { CollectionLinearView } from './collectionLinear'; import './CollectionMenu.scss'; import { COLLECTION_BORDER_WIDTH } from './CollectionView'; import { TabDocView } from './TabDocView'; +import { GestureUtils } from '../../../pen-gestures/GestureUtils'; interface CollectionMenuProps { panelHeight: () => number; @@ -767,7 +768,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView private _draw = ['∿', '=', '⎯', '→', '↔︎', 'ロ', 'O']; private _head = ['', '', '', '', 'arrow', '', '']; private _end = ['', '', '', 'arrow', 'arrow', '', '']; - private _shapePrims = ['', '', 'line', 'line', 'line', 'rectangle', 'circle']; + private _shapePrims = ['', '', 'line', 'line', 'line', 'rectangle', 'circle'] as GestureUtils.Gestures[]; private _title = ['pen', 'highlighter', 'line', 'line with arrow', 'line with double arrows', 'square', 'circle']; private _faName = ['pen-fancy', 'highlighter', 'minus', 'long-arrow-alt-right', 'arrows-alt-h', 'square', 'circle']; @observable _selectedPrimitive = this._shapePrims.length; @@ -848,13 +849,13 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView SetActiveArrowEnd(this._end[i]); SetActiveBezierApprox('300'); - GestureOverlay.Instance.InkShape = this._shapePrims[i]; + if (GestureOverlay.Instance) GestureOverlay.Instance.InkShape = this._shapePrims[i]; } else { this._selectedPrimitive = this._shapePrims.length; Doc.ActiveTool = InkTool.None; SetActiveArrowStart(''); SetActiveArrowEnd(''); - GestureOverlay.Instance.InkShape = ''; + if (GestureOverlay.Instance) GestureOverlay.Instance.InkShape = undefined; SetActiveBezierApprox('0'); } e.stopPropagation(); diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index ba29b1d6f..175051d5c 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -3,12 +3,12 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { CursorProperty } from 'csstype'; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import { DataSym, Doc, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; +import { DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; -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, returnFalse, returnTrue, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; @@ -33,6 +33,7 @@ import { CollectionSubView } from './CollectionSubView'; const _global = (window /* browser */ || global) /* node */ as any; export type collectionStackingViewProps = { + sortFunc?: (a: Doc, b: Doc) => number; chromeHidden?: boolean; // view type is stacking viewType?: CollectionViewType; @@ -73,7 +74,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection } // filteredChildren is what you want to work with. It's the list of things that you're currently displaying @computed get filteredChildren() { - return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.hidden).map(pair => pair.layout); + const children = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.hidden).map(pair => pair.layout); + if (this.props.sortFunc) children.sort(this.props.sortFunc); + return children; } // how much margin we give the header @computed get headerMargin() { @@ -305,6 +308,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection isChildContentActive = () => this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined; + isChildButtonContentActive = () => (this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined); // this is what renders the document that you see on the screen // called in Children: this actually adds a document to our children list getDisplayDoc(doc: Doc, width: () => number) { @@ -326,7 +330,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection styleProvider={this.styleProvider} docViewPath={this.props.docViewPath} fitWidth={this.props.childFitWidth} - isContentActive={this.isChildContentActive} + isContentActive={doc.isLinkButton ? this.isChildButtonContentActive : this.isChildContentActive} onKey={this.onKeyDown} isDocumentActive={this.isContentActive} LayoutTemplate={this.props.childLayoutTemplate} @@ -350,6 +354,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection searchFilterDocs={this.searchFilterDocs} ContainingCollectionDoc={this.props.CollectionView?.props.Document} ContainingCollectionView={this.props.CollectionView} + xPadding={NumCast(this.layoutDoc._childXPadding, this.props.childXPadding)} + yPadding={NumCast(this.layoutDoc._childYPadding, this.props.childYPadding)} addDocument={this.props.addDocument} moveDocument={this.props.moveDocument} removeDocument={this.props.removeDocument} diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 30759b766..7bc273d7d 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -218,12 +218,7 @@ export function CollectionSubView<X>(moreProps?: X) { const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d); const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d); if (movedDocs.length) { - const canAdd = - this.props.Document._viewType === CollectionViewType.Pile || - de.embedKey || - !this.props.isAnnotationOverlay || - this.props.Document.allowOverlayDrop || - Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.props.Document); + const canAdd = this.props.Document._viewType === CollectionViewType.Pile || de.embedKey || this.props.Document.allowOverlayDrop || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.props.Document); added = docDragData.moveDocument(movedDocs, this.props.Document, canAdd ? this.addDocument : returnFalse); } else { ScriptCast(this.props.Document.dropConverter)?.script.run({ dragData: docDragData }); diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index 1aef29b2f..3785b7d61 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -13,8 +13,6 @@ width: 100%; position: relative; top: 0; - padding-left: 10px; - padding-right: 10px; background: $light-gray; font-size: 13px; overflow: auto; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index fe5dc17f5..0ff89c5a7 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -58,7 +58,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree private _isDisposing = false; // notes that instance is in process of being disposed private refList: Set<any> = new Set(); // list of tree view items to monitor for height changes private observer: any; // observer for monitoring tree view items. - private static expandViewLabelSize = 20; @computed get doc() { return this.props.Document; @@ -100,7 +99,10 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree Object.values(this._disposers).forEach(disposer => disposer?.()); } + shrinkWrap = () => {}; // placeholder to allow setContentView to work + componentDidMount() { + //this.props.setContentView?.(this); this._disposers.autoheight = reaction( () => this.rootDoc.autoHeight, auto => auto && this.computeHeight(), @@ -147,6 +149,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree } }; + screenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this._headerHeight); + @action remove = (doc: Doc | Doc[]): boolean => { const docs = doc instanceof Doc ? [doc] : doc; @@ -195,9 +199,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree onTreeDrop = (e: React.DragEvent, addDocs?: (docs: Doc[]) => void) => this.onExternalDrop(e, {}, addDocs); @undoBatch - makeTextCollection = (childDocs: Doc[]) => { - this.addDoc(TreeView.makeTextBullet(), childDocs.length ? childDocs[0] : undefined, true); - }; + makeTextCollection = (childDocs: Doc[]) => this.addDoc(TreeView.makeTextBullet(), childDocs.length ? childDocs[0] : undefined, true); get editableTitle() { return ( @@ -256,6 +258,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree const icons = StrListCast(this.doc.childContextMenuIcons); return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label })); }; + headerFields = () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields); @computed get treeViewElements() { TraceMobx(); const dropAction = StrCast(this.doc.childDropAction) as dropActionType; @@ -275,11 +278,11 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree dropAction, this.props.addDocTab, this.props.styleProvider, - this.props.ScreenToLocalTransform, + this.screenToLocalTransform, this.isContentActive, this.panelWidth, this.props.renderDepth, - () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields), + this.headerFields, [], this.props.onCheckedClick, this.onChildClick, @@ -302,7 +305,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree 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() } : {}}> + style={!this.outlineMode ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}}> {this.outlineMode ? this.documentTitle : this.editableTitle} </div> ); @@ -370,13 +373,14 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree documentTitleHeight = () => (this.layoutDoc?.[HeightSym]() || 0) - NumCast(this.layoutDoc.autoHeightMargins); truncateTitleWidth = () => this.treeViewtruncateTitleWidth; onChildClick = () => this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick); - panelWidth = () => Math.max(0, this.props.PanelWidth() - this.marginX() - CollectionTreeView.expandViewLabelSize) * (this.props.NativeDimScaling?.() || 1); + panelWidth = () => Math.max(0, this.props.PanelWidth() - 2 * this.marginX() * (this.props.NativeDimScaling?.() || 1)); addAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.addDocument(doc, `${this.props.fieldKey}-annotations`) || false; remAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.removeDocument(doc, `${this.props.fieldKey}-annotations`) || false; moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => this.props.CollectionView?.moveDocument(doc, targetCollection, addDocument, `${this.props.fieldKey}-annotations`) || false; + @observable _headerHeight = 0; contentFunc = () => { const background = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor); const pointerEvents = () => (!this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined); @@ -384,7 +388,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree return [ <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}> {!this.buttonMenu && !this.noviceExplainer ? null : ( - <div className="documentButtonMenu"> + <div className="documentButtonMenu" ref={action((r: HTMLDivElement | null) => r && (this._headerHeight = Number(getComputedStyle(r).height.replace(/px/, ''))))}> {this.buttonMenu} {this.noviceExplainer} </div> @@ -393,7 +397,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree className="collectionTreeView-contents" key="tree" style={{ - ...(!titleBar ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}), + ...(!titleBar ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}), overflow: 'auto', width: '100%', height: '100%', @@ -402,9 +406,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree <div className="collectionTreeView-container" style={{ - transform: this.outlineMode ? `scale(${this.nativeDimScaling})` : '', - paddingLeft: `${this.marginX()}px`, - width: this.outlineMode ? `calc(${100 / this.nativeDimScaling}%)` : '', + marginLeft: `${this.marginX()}px`, minHeight: `calc(100% - ${this._titleHeight}px)`, }} onContextMenu={this.onContextMenu}> @@ -429,24 +431,29 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree render() { TraceMobx(); - return !(this.doc instanceof Doc) || !this.treeChildren ? null : this.doc.treeViewHasOverlay ? ( - <CollectionFreeFormView - {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit} - isAnnotationOverlay={true} - isAnnotationOverlayScrollable={true} - childDocumentsActive={this.props.isDocumentActive} - fieldKey={this.props.fieldKey + '-annotations'} - dropAction={'move'} - select={emptyFunction} - addDocument={this.addAnnotationDocument} - removeDocument={this.remAnnotationDocument} - moveDocument={this.moveAnnotationDocument} - bringToFront={emptyFunction} - renderDepth={this.props.renderDepth + 1}> - {this.contentFunc} - </CollectionFreeFormView> - ) : ( - this.contentFunc() + const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1) || 1; + return ( + <div style={{ transform: `scale(${scale})`, transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%` }}> + {!(this.doc instanceof Doc) || !this.treeChildren ? null : this.doc.treeViewHasOverlay ? ( + <CollectionFreeFormView + {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit} + isAnnotationOverlay={true} + isAnnotationOverlayScrollable={true} + childDocumentsActive={this.props.isDocumentActive} + fieldKey={this.props.fieldKey + '-annotations'} + dropAction={'move'} + select={emptyFunction} + addDocument={this.addAnnotationDocument} + removeDocument={this.remAnnotationDocument} + moveDocument={this.moveAnnotationDocument} + bringToFront={emptyFunction} + renderDepth={this.props.renderDepth + 1}> + {this.contentFunc} + </CollectionFreeFormView> + ) : ( + this.contentFunc() + )} + </div> ); } } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index dcaad5632..9f63a11aa 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -54,6 +54,8 @@ interface CollectionViewProps_ extends FieldViewProps { childHideDecorationTitle?: () => boolean; childHideResizeHandles?: () => boolean; childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection + childXPadding?: number; + childYPadding?: number; childLayoutString?: string; childIgnoreNativeSize?: boolean; childClickScript?: ScriptField; diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 9938245ea..31ed5a83b 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -4,7 +4,7 @@ import { Tooltip } from '@material-ui/core'; import { clamp } from 'lodash'; import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOM from 'react-dom/client'; import { DataSym, Doc, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; @@ -143,8 +143,8 @@ export class TabDocView extends React.Component<TabDocViewProps> { const docIcon = <FontAwesomeIcon onPointerDown={dragBtnDown} icon={iconType} />; const closeIcon = <FontAwesomeIcon icon={'eye'} />; - ReactDOM.render(docIcon, iconWrap); - ReactDOM.render(closeIcon, closeWrap); + ReactDOM.createRoot(iconWrap).render(docIcon); + ReactDOM.createRoot(closeWrap).render(closeIcon); tab.reactComponents = [iconWrap, closeWrap]; tab.element[0].prepend(iconWrap); tab._disposers.layerDisposer = reaction( @@ -247,7 +247,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { 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.presMovement = doc.type === DocumentType.SCRIPTING || pinProps?.pinDocLayout ? 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 @@ -256,7 +256,6 @@ export class TabDocView extends React.Component<TabDocViewProps> { 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; diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss index 57bb5274d..83fee013a 100644 --- a/src/client/views/collections/TreeView.scss +++ b/src/client/views/collections/TreeView.scss @@ -24,6 +24,7 @@ // width: $TREE_BULLET_WIDTH; width: 100%; height: 100%; + position: absolute; .treeView-expandIcon { display: none; @@ -118,7 +119,7 @@ display: flex; // needed for PresBox's treeView border: transparent 1px solid; align-items: center; - width: 100%; + width: max-content; border-radius: 5px; &:hover { diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 14563f990..ac8562d5a 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -10,11 +10,12 @@ import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; 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 { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager, dropActionType } from '../../util/DragManager'; +import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; @@ -31,7 +32,6 @@ import { CollectionTreeView, TreeViewType } from './CollectionTreeView'; import { CollectionView } from './CollectionView'; import './TreeView.scss'; import React = require('react'); -import { ScriptingGlobals } from '../../util/ScriptingGlobals'; export interface TreeViewProps { treeView: CollectionTreeView; @@ -55,7 +55,7 @@ export interface TreeViewProps { indentDocument?: (editTitle: boolean) => void; outdentDocument?: (editTitle: boolean) => void; ScreenToLocalTransform: () => Transform; - contextMenuItems: { script: ScriptField; filter: ScriptField; icon: string; label: string }[]; + contextMenuItems?: { script: ScriptField; filter: ScriptField; icon: string; label: string }[]; dontRegisterView?: boolean; styleProvider?: StyleProviderFunc | undefined; treeViewHideHeaderFields: () => boolean; @@ -302,7 +302,7 @@ export class TreeView extends React.Component<TreeViewProps> { const pt = [e.clientX, e.clientY]; const rect = this._header.current!.getBoundingClientRect(); const before = pt[1] < rect.top + rect.height / 2; - const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocList.length); + const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocs?.length); this._header.current!.className = 'treeView-header'; if (!this.props.treeView.outlineMode || DragManager.DocDragData?.treeViewDoc === this.props.treeView.rootDoc) { if (inside) this._header.current!.className += ' treeView-header-inside'; @@ -321,6 +321,7 @@ export class TreeView extends React.Component<TreeViewProps> { _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, + _fitWidth: true, treeViewType: TreeViewType.outline, x: 0, y: 0, @@ -361,7 +362,7 @@ export class TreeView extends React.Component<TreeViewProps> { if (!this._header.current) return; const rect = this._header.current.getBoundingClientRect(); const before = pt[1] < rect.top + rect.height / 2; - const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocList.length); + const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocs?.length ? true : false); if (de.complete.linkDragData) { const sourceDoc = de.complete.linkDragData.linkSourceGetAnchor(); const destDoc = this.doc; @@ -398,29 +399,21 @@ export class TreeView extends React.Component<TreeViewProps> { }; docTransform = () => this.refTransform(this._dref?.ContentRef?.current); getTransform = () => this.refTransform(this._tref.current); - docWidth = () => { - const layoutDoc = this.layoutDoc; - const aspect = Doc.NativeAspect(layoutDoc); - if (layoutDoc._fitWidth) return Math.min(this.props.panelWidth() - treeBulletWidth(), layoutDoc[WidthSym]()); - if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT * aspect, this.props.panelWidth() - treeBulletWidth())); - return Math.min((this.props.panelWidth() - treeBulletWidth()) / (this.props.treeView.props.NativeDimScaling?.() || 1), Doc.NativeWidth(layoutDoc) ? layoutDoc[WidthSym]() : this.layoutDoc[WidthSym]()); - }; - docHeight = () => { - const layoutDoc = this.layoutDoc; - return Math.max( - 70, - Math.min( - this.MAX_EMBED_HEIGHT, - (() => { - const aspect = Doc.NativeAspect(layoutDoc); - if (aspect) return this.docWidth() / (aspect || 1); - return layoutDoc._fitWidth - ? !Doc.NativeHeight(this.doc) - ? NumCast(this.props.containerCollection._height) - : Math.min((this.docWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containerCollection._height))) - : layoutDoc[HeightSym]() || 50; - })() - ) + embeddedPanelWidth = () => this.props.panelWidth() / (this.props.treeView.props.NativeDimScaling?.() || 1); + embeddedPanelHeight = () => { + const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc; + return Math.min( + layoutDoc[HeightSym](), + this.MAX_EMBED_HEIGHT, + (() => { + const aspect = Doc.NativeAspect(layoutDoc); + if (aspect) return this.embeddedPanelWidth() / (aspect || 1); + return layoutDoc._fitWidth + ? !Doc.NativeHeight(layoutDoc) + ? NumCast(layoutDoc._height) //this.props.containerCollection._height) + : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containerCollection._height))) + : (this.embeddedPanelWidth() * layoutDoc[HeightSym]()) / layoutDoc[WidthSym](); + })() ); }; @@ -512,32 +505,10 @@ export class TreeView extends React.Component<TreeViewProps> { return rows; } - rtfWidth = () => { - const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc; - return Math.min(layout[WidthSym](), this.props.panelWidth() - treeBulletWidth()) / (this.props.treeView.props.NativeDimScaling?.() || 1); - }; - rtfHeight = () => { - const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc; - return this.rtfWidth() <= layout[WidthSym]() ? Math.min(layout[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT; - }; - rtfOutlineHeight = () => Math.max(this.layoutDoc?.[HeightSym](), treeBulletWidth()); - expandPanelHeight = () => { - if (this.layoutDoc._fitWidth) return this.docHeight(); - const aspect = this.layoutDoc[WidthSym]() / this.layoutDoc[HeightSym](); - const docAspect = this.docWidth() / this.docHeight(); - return docAspect < aspect ? this.docWidth() / aspect : this.docHeight(); - }; - expandPanelWidth = () => { - if (this.layoutDoc._fitWidth) return this.docWidth(); - const aspect = this.layoutDoc[WidthSym]() / this.layoutDoc[HeightSym](); - const docAspect = this.docWidth() / this.docHeight(); - return docAspect > aspect ? this.docHeight() * aspect : this.docWidth(); - }; - @computed get renderContent() { TraceMobx(); const expandKey = this.treeViewExpandedView; - 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 } }) ?? {}; if (['links', 'annotations', 'aliases', this.fieldKey].includes(expandKey)) { const sorting = StrCast(this.doc.treeViewSortCriterion, TreeSort.None); const sortKeys = Object.keys(sortings); @@ -629,7 +600,7 @@ export class TreeView extends React.Component<TreeViewProps> { } else if (this.treeViewExpandedView === 'fields') { return ( <ul key={this.doc[Id] + this.doc.title}> - <div style={{ display: 'inline-block' }}>{this.expandedField}</div> + <div>{this.expandedField}</div> </ul> ); } @@ -757,7 +728,7 @@ export class TreeView extends React.Component<TreeViewProps> { const openAlias = { script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, icon: 'copy', label: 'Open Alias' }; const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: 'eye', label: 'Focus or Open' }; return [ - ...this.props.contextMenuItems.filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result)), + ...(this.props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result)), ...(this.doc.isFolder ? folderOp : Doc.IsSystem(this.doc) @@ -792,29 +763,39 @@ export class TreeView extends React.Component<TreeViewProps> { titleStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => { if (!doc || doc !== this.doc) return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView + const treeView = this.props.treeView; + // prettier-ignore switch (property.split(':')[0]) { - case StyleProp.Opacity: - return this.props.treeView.outlineMode ? undefined : 1; - case StyleProp.BackgroundColor: - return this.selected ? '#7089bb' : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor)); + case StyleProp.Opacity: return this.props.treeView.outlineMode ? undefined : 1; + case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor)); + case StyleProp.Highlighting: if (this.props.treeView.outlineMode) return undefined; + case StyleProp.Hidden: return false; + case StyleProp.BoxShadow: return undefined; case StyleProp.DocContents: - return this.props.treeView.outlineMode ? null : ( + const highlightIndex = this.props.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.isBrushedHighlightedDegree(doc); + const highlightColor = ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex]; + return treeView.outlineMode ? null : ( <div className="treeView-label" style={{ // just render a title for a tree view label (identified by treeViewDoc being set in 'props') maxWidth: props?.PanelWidth() || undefined, background: props?.styleProvider?.(doc, props, StyleProp.BackgroundColor), + outline: `solid ${highlightColor} ${highlightIndex}px`, + paddingLeft: NumCast(treeView.rootDoc.childXPadding, NumCast(treeView.props.childXPadding, Doc.IsComicStyle(doc)?20:0)), + paddingRight: NumCast(treeView.rootDoc.childXPadding, NumCast(treeView.props.childXPadding, Doc.IsComicStyle(doc)?20:0)), + paddingTop: treeView.props.childYPadding, + paddingBottom: treeView.props.childYPadding, }}> {StrCast(doc?.title)} </div> ); - default: - return this.props?.treeView?.props.styleProvider?.(doc, props, property); } + return treeView.props.styleProvider?.(doc, props, property); }; embeddedStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => { if (property.startsWith(StyleProp.Decorations)) return null; + if (property.startsWith(StyleProp.Hidden)) return false; return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView }; onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { @@ -841,7 +822,7 @@ export class TreeView extends React.Component<TreeViewProps> { } return false; }; - titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth() - 2 * treeBulletWidth())); + titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth())) / (this.props.treeView.props.NativeDimScaling?.() || 1) - 3 * treeBulletWidth(); return18 = () => 18; /** @@ -885,6 +866,7 @@ export class TreeView extends React.Component<TreeViewProps> { } })} Document={this.doc} + fitWidth={returnTrue} DataDoc={undefined} scriptContext={this} hideDecorationTitle={this.props.treeView.outlineMode} @@ -903,7 +885,7 @@ export class TreeView extends React.Component<TreeViewProps> { removeDocument={this.props.removeDoc} ScreenToLocalTransform={this.getTransform} NativeHeight={this.return18} - NativeWidth={this.titleWidth} + NativeWidth={returnZero} PanelWidth={this.titleWidth} PanelHeight={this.return18} contextMenuItems={this.contextMenuItems} @@ -916,10 +898,12 @@ export class TreeView extends React.Component<TreeViewProps> { disableDocBrushing={this.props.treeView.props.disableDocBrushing} hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)} dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)} + xPadding={NumCast(this.props.treeView.props.Document.childXPadding, this.props.treeView.props.childXPadding)} + yPadding={NumCast(this.props.treeView.props.Document.childYPadding, this.props.treeView.props.childYPadding)} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} + ContainingCollectionView={this.props.treeView.props.CollectionView} ContainingCollectionDoc={this.props.treeView.props.Document} /> ); @@ -966,52 +950,52 @@ export class TreeView extends React.Component<TreeViewProps> { }; renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => { - const isExpandable = this.doc._treeViewGrowsHorizontally; - const panelWidth = asText || isExpandable ? this.rtfWidth : this.expandPanelWidth; - const panelHeight = asText ? this.rtfOutlineHeight : isExpandable ? this.rtfHeight : this.expandPanelHeight; return ( - <DocumentView - key={this.doc[Id]} - ref={action((r: DocumentView | null) => (this._dref = r))} - Document={this.doc} - DataDoc={undefined} - PanelWidth={panelWidth} - PanelHeight={panelHeight} - NativeWidth={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfWidth : undefined} - NativeHeight={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfHeight : undefined} - LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined} - LayoutTemplate={this.props.treeView.props.childLayoutTemplate} - isContentActive={isActive} - isDocumentActive={isActive} - styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider} - hideTitle={asText} - fitContentsToBox={returnTrue} - hideDecorationTitle={this.props.treeView.outlineMode} - hideResizeHandles={this.props.treeView.outlineMode} - onClick={this.onChildClick} - focus={this.refocus} - onKey={this.onKeyDown} - hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)} - dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)} - ScreenToLocalTransform={this.docTransform} - renderDepth={this.props.renderDepth + 1} - treeViewDoc={this.props.treeView?.props.Document} - rootSelected={returnTrue} - docViewPath={this.props.treeView.props.docViewPath} - docFilters={returnEmptyFilter} - docRangeFilters={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - ContainingCollectionDoc={this.props.containerCollection} - ContainingCollectionView={undefined} - addDocument={this.props.addDocument} - moveDocument={this.move} - removeDocument={this.props.removeDoc} - whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} - addDocTab={this.props.addDocTab} - pinToPres={this.props.treeView.props.pinToPres} - disableDocBrushing={this.props.treeView.props.disableDocBrushing} - bringToFront={returnFalse} - /> + <div style={{ height: this.embeddedPanelHeight(), width: this.embeddedPanelWidth() }}> + <DocumentView + key={this.doc[Id]} + ref={action((r: DocumentView | null) => (this._dref = r))} + Document={this.doc} + DataDoc={undefined} + PanelWidth={this.embeddedPanelWidth} + PanelHeight={this.embeddedPanelHeight} + LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined} + LayoutTemplate={this.props.treeView.props.childLayoutTemplate} + isContentActive={isActive} + isDocumentActive={isActive} + styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider} + hideTitle={asText} + //fitContentsToBox={returnTrue} + hideDecorationTitle={this.props.treeView.outlineMode} + hideResizeHandles={this.props.treeView.outlineMode} + onClick={this.onChildClick} + focus={this.refocus} + onKey={this.onKeyDown} + hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)} + dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)} + ScreenToLocalTransform={this.docTransform} + renderDepth={this.props.renderDepth + 1} + treeViewDoc={this.props.treeView?.props.Document} + rootSelected={returnTrue} + docViewPath={this.props.treeView.props.docViewPath} + docFilters={returnEmptyFilter} + docRangeFilters={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + ContainingCollectionDoc={this.props.containerCollection} + ContainingCollectionView={undefined} + addDocument={this.props.addDocument} + moveDocument={this.move} + removeDocument={this.props.removeDoc} + whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} + xPadding={NumCast(this.props.treeView.props.Document.childXPadding, this.props.treeView.props.childXPadding)} + yPadding={NumCast(this.props.treeView.props.Document.childYPadding, this.props.treeView.props.childYPadding)} + addDocTab={this.props.addDocTab} + pinToPres={this.props.treeView.props.pinToPres} + disableDocBrushing={this.props.treeView.props.disableDocBrushing} + bringToFront={returnFalse} + scriptContext={this} + /> + </div> ); }; @@ -1049,7 +1033,7 @@ export class TreeView extends React.Component<TreeViewProps> { const pt = [de.clientX, de.clientY]; const rect = this._header.current!.getBoundingClientRect(); const before = pt[1] < rect.top + rect.height / 2; - const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocList.length); + const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocs?.length ? true : false); const docs = this.props.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, false)); }; @@ -1145,7 +1129,7 @@ export class TreeView extends React.Component<TreeViewProps> { } const docs = TreeView.sortDocs(childDocs, StrCast(containerCollection.treeViewSortCriterion, TreeSort.None)); - const rowWidth = () => panelWidth() - treeBulletWidth(); + const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView.props.NativeDimScaling?.() || 1); const treeViewRefs = new Map<Doc, TreeView | undefined>(); return docs .filter(child => child instanceof Doc) diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 0d061a325..ac3777aa6 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -26,7 +26,7 @@ import { InteractionUtils } from '../../../util/InteractionUtils'; import { ReplayMovements } from '../../../util/ReplayMovements'; import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; import { SelectionManager } from '../../../util/SelectionManager'; -import { ColorScheme } from '../../../util/SettingsManager'; +import { ColorScheme, freeformScrollMode } from '../../../util/SettingsManager'; import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; @@ -113,7 +113,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @observable _clusterSets: Doc[][] = []; @observable _deleteList: DocumentView[] = []; @observable _timelineRef = React.createRef<Timeline>(); - @observable _marqueeRef = React.createRef<HTMLDivElement>(); + @observable _marqueeRef: HTMLDivElement | null = null; @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 @@ -568,6 +568,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @undoBatch onGesture = (e: Event, ge: GestureUtils.GestureEvent) => { switch (ge.gesture) { + default: + case GestureUtils.Gestures.Line: + case GestureUtils.Gestures.Circle: + case GestureUtils.Gestures.Rectangle: + case GestureUtils.Gestures.Triangle: case GestureUtils.Gestures.Stroke: const points = ge.points; const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height); @@ -597,34 +602,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this.addDocument(inkDoc); e.stopPropagation(); break; - case GestureUtils.Gestures.Box: - const lt = this.getTransform().transformPoint(Math.min(...ge.points.map(p => p.X)), Math.min(...ge.points.map(p => p.Y))); - const rb = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y))); - const bounds = { x: lt[0], r: rb[0], y: lt[1], b: rb[1] }; - const bWidth = bounds.r - bounds.x; - const bHeight = bounds.b - bounds.y; - const sel = this.getActiveDocuments().filter(doc => { - const l = NumCast(doc.x); - const r = l + doc[WidthSym](); - const t = NumCast(doc.y); - const b = t + doc[HeightSym](); - const pass = !(bounds.x > r || bounds.r < l || bounds.y > b || bounds.b < t); - if (pass) { - doc.x = l - bounds.x - bWidth / 2; - doc.y = t - bounds.y - bHeight / 2; - } - return pass; - }); - this.addDocument(Docs.Create.FreeformDocument(sel, { title: 'nested collection', x: bounds.x, y: bounds.y, _width: bWidth, _height: bHeight, _panX: 0, _panY: 0 })); - sel.forEach(d => this.props.removeDocument?.(d)); - e.stopPropagation(); - break; - case GestureUtils.Gestures.StartBracket: - const start = this.getTransform().transformPoint(Math.min(...ge.points.map(p => p.X)), Math.min(...ge.points.map(p => p.Y))); - this._inkToTextStartX = start[0]; - this._inkToTextStartY = start[1]; - break; - case GestureUtils.Gestures.EndBracket: + case GestureUtils.Gestures.Rectangle: if (this._inkToTextStartX && this._inkToTextStartY) { const end = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y))); const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === 'rtf' && s.color); @@ -723,6 +701,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }; @action + scrollPan = (e: WheelEvent | { deltaX: number; deltaY: number }): void => { + const dx = e.deltaX; + const dy = e.deltaY; + this.setPan(NumCast(this.Document._panX) - dx, NumCast(this.Document._panY) - dy, 0, true); + }; + + @action pan = (e: PointerEvent | React.Touch | { clientX: number; clientY: number }): void => { const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); this.setPan(NumCast(this.Document._panX) - dx, NumCast(this.Document._panY) - dy, 0, true); @@ -801,15 +786,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const prevPointInkSpace = inkStroke.ptFromScreen(lastPoint); const currPointInkSpace = inkStroke.ptFromScreen(currPoint); for (var i = 0; i < inkData.length - 3; i += 4) { - const intersects = Array.from( - new Set( - InkField.Segment(inkData, i).intersects({ - // compute all unique intersections - p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y }, - p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y }, - }) as (number | string)[] - ) - ); // convert to more manageable union array type + const rawIntersects = InkField.Segment(inkData, i).intersects({ + // compute all unique intersections + p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y }, + p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y }, + }); + const intersects = Array.from(new Set(rawIntersects as (number | string)[])); // convert to more manageable union array type + if (intersects.length) { + console.log(); + } // return tuples of the inkingStroke intersected, and the t value of the intersection intersections.push(...intersects.map(t => ({ inkView, t: +t + Math.floor(i / 4) }))); // convert string t's to numbers and add start of curve segment to convert from local t value to t value along complete curve } @@ -858,6 +843,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return segments; }; + // for some reason bezier.js doesn't handle the case of intersecting a linear curve, so we wrap the intersection + // call in a test for linearity + bintersects = (curve: Bezier, otherCurve: Bezier) => { + if ((otherCurve as any)._linear) { + return curve.lineIntersects({ p1: otherCurve.points[0], p2: otherCurve.points[3] }); + } + return curve.intersects(otherCurve); + }; + /** * Determines all possible intersections of the current curve of the intersected ink stroke with all other curves of all * ink strokes in the current collection. @@ -882,7 +876,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection if (ink?.Document === otherInk.props.Document && neighboringSegment) continue; const otherCurve = new Bezier(otherCtrlPts.slice(j, j + 4).map(p => ({ x: p.X, y: p.Y }))); - curve.intersects(otherCurve).forEach((val: string | number, i: number) => { + this.bintersects(curve, otherCurve).forEach((val: string | number, i: number) => { // Converting the Bezier.js Split type to a t-value number. const t = +val.toString().split('/')[0]; if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical). @@ -1012,13 +1006,38 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action onPointerWheel = (e: React.WheelEvent): void => { if (this.layoutDoc._Transform || DocListCast(Doc.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return; - if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { - // things that can scroll vertically should do that instead of zooming - e.stopPropagation(); - } else if (this.props.isContentActive(true) && !this.Document._isGroup) { - e.stopPropagation(); - e.preventDefault(); - !this.props.isAnnotationOverlayScrollable && this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc? + e.stopPropagation(); + e.preventDefault(); + switch (Doc.UserDoc().freeformScrollMode) { + case freeformScrollMode.Pan: + // if shift is selected then zoom + if (e.ctrlKey) { + if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { + // things that can scroll vertically should do that instead of zooming + } else if (this.props.isContentActive(true) && !this.Document._isGroup) { + !this.props.isAnnotationOverlayScrollable && this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc? + } + // otherwise pan + } else if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { + // things that can scroll vertically should do that instead of zooming + } else if (this.props.isContentActive(true) && !this.Document._isGroup) { + const dx = e.deltaX; + const dy = e.deltaY; + if (e.shiftKey) { + !this.props.isAnnotationOverlayScrollable && this.scrollPan({ deltaX: dy, deltaY: 0 }); + } else { + !this.props.isAnnotationOverlayScrollable && this.scrollPan({ deltaX: dx, deltaY: dy }); + } + } + break; + default: + case freeformScrollMode.Zoom: + if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { + // things that can scroll vertically should do that instead of zooming + } else if (this.props.isContentActive(true) && !this.Document._isGroup) { + !this.props.isAnnotationOverlayScrollable && this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc? + } + break; } }; @@ -1210,6 +1229,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection 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 }; + const maxYShift = Math.max(0, screen.bot - screen.top - (bounds.bot - bounds.top)); + const phborder = bounds.top < screen.top || bounds.bot > screen.bot ? Math.min(ph / 10, maxYShift / 2) : 0; if (screen.right - screen.left < bounds.right - bounds.left || screen.bot - screen.top < bounds.bot - bounds.top) { return { panX: (bounds.left + bounds.right) / 2, @@ -1219,7 +1240,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } return { 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), + panY: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc._panY) : cy) + Math.min(0, bounds.top - phborder - screen.top) + Math.max(0, bounds.bot + phborder - screen.bot), }; }; @@ -1298,7 +1319,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection dontScaleFilter={this.props.dontScaleFilter} dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView} pointerEvents={this.pointerEvents} - jitterRotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0} + rotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0} //fitContentsToBox={this.props.fitContentsToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this /> ); @@ -1327,8 +1348,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const { backgroundColor, color } = curFrame === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, curFrame); const { x, y, opacity } = curFrame === undefined ? { x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() } : CollectionFreeFormDocumentView.getValues(childDoc, curFrame); return { - x: NumCast(x), - y: NumCast(y), + x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x), + y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y), z: Cast(z, 'number'), color: Cast(color, 'string') ? StrCast(color) : this.props.styleProvider?.(childDoc, this.props, StyleProp.Color), backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.props.styleProvider?.(childDoc, this.props, StyleProp.BackgroundColor), @@ -1529,9 +1550,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection setTimeout( action(() => { this._firstRender = false; - - this._marqueeRef.current?.addEventListener('dashDragAutoScroll', this.onDragAutoScroll as any); - this._disposers.groupBounds = reaction( () => { if (this.props.Document._isGroup && this.childDocs.length === this.childDocList?.length) { @@ -1649,7 +1667,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection componentWillUnmount() { Object.values(this._disposers).forEach(disposer => disposer?.()); - this._marqueeRef.current?.removeEventListener('dashDragAutoScroll', this.onDragAutoScroll as any); + this._marqueeRef?.removeEventListener('dashDragAutoScroll', this.onDragAutoScroll as any); } @action @@ -1662,10 +1680,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection if ((e as any).handlePan || this.props.isAnnotationOverlay) return; (e as any).handlePan = true; - if (!this.layoutDoc._noAutoscroll && !this.props.renderDepth && this._marqueeRef?.current) { + if (!this.layoutDoc._noAutoscroll && !this.props.renderDepth && this._marqueeRef) { const dragX = e.detail.clientX; const dragY = e.detail.clientY; - const bounds = this._marqueeRef.current?.getBoundingClientRect(); + const bounds = this._marqueeRef?.getBoundingClientRect(); const deltaX = dragX - bounds.left < 25 ? -(25 + (bounds.left - dragX)) : bounds.right - dragX < 25 ? 25 - (bounds.right - dragX) : 0; const deltaY = dragY - bounds.top < 25 ? -(25 + (bounds.top - dragY)) : bounds.bottom - dragY < 25 ? 25 - (bounds.bottom - dragY) : 0; @@ -1723,7 +1741,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection event: () => (this.Document._fitContentsToBox = !this.fitContentsToBox), icon: !this.fitContentsToBox ? 'expand-arrows-alt' : 'compress-arrows-alt', }); - appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, { pinDocView: true, panelWidth: this.props.PanelWidth(), panelHeight: this.props.PanelHeight() }), icon: 'map-pin' }); + appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, { pinViewport: MarqueeView.CurViewBounds(this.rootDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' }); //appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" }); this.props.ContainingCollectionView && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' }); @@ -1823,10 +1841,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection DragManager.SetSnapLines(horizLines, vertLines); }; - onPointerOver = (e: React.PointerEvent) => { - e.stopPropagation(); - }; - incrementalRender = action(() => { if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) { const unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])); @@ -1870,7 +1884,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}> - <div className="marqueeView-div" ref={this._marqueeRef} style={{ opacity: this.props.dontRenderDocuments ? 0.7 : undefined }}> + <div + className="marqueeView-div" + ref={r => { + this._marqueeRef = r; + r?.addEventListener('dashDragAutoScroll', this.onDragAutoScroll as any); + }} + style={{ opacity: this.props.dontRenderDocuments ? 0.7 : undefined }}> {this.layoutDoc._backgroundGridShow ? ( <div> <CollectionFreeFormBackgroundGrid // bcz : UGHH don't know why, but if we don't wrap in a div, then PDF's don't render whenn taking snapshot of a dashboard and the background grid is on!!? @@ -1946,7 +1966,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection <div className={'collectionfreeformview-container'} ref={this.createDashEventsTarget} - onPointerOver={this.onPointerOver} onWheel={this.onPointerWheel} onClick={this.onClick} onPointerDown={this.onPointerDown} @@ -2253,6 +2272,6 @@ ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) { !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true); }); -ScriptingGlobals.add(function pinWithView(readOnly: boolean) { - !readOnly && SelectionManager.Views().forEach(view => TabDocView.PinDoc(view.rootDoc, { pinDocView: true, panelWidth: view.props.PanelWidth(), panelHeight: view.props.PanelHeight() })); +ScriptingGlobals.add(function pinWithView(readOnly: boolean, pinDocContent: boolean) { + !readOnly && SelectionManager.Views().forEach(view => TabDocView.PinDoc(view.rootDoc, { pinDocContent, pinViewport: MarqueeView.CurViewBounds(view.rootDoc, view.props.PanelWidth(), view.props.PanelHeight()) })); }); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx index 8a8b528f6..488f51d77 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx @@ -1,11 +1,11 @@ -import React = require("react"); -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Tooltip } from "@material-ui/core"; -import { observer } from "mobx-react"; -import { unimplementedFunction } from "../../../../Utils"; -import { DocumentType } from "../../../documents/DocumentTypes"; -import { SelectionManager } from "../../../util/SelectionManager"; -import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu"; +import React = require('react'); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Tooltip } from '@material-ui/core'; +import { observer } from 'mobx-react'; +import { unimplementedFunction } from '../../../../Utils'; +import { DocumentType } from '../../../documents/DocumentTypes'; +import { SelectionManager } from '../../../util/SelectionManager'; +import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; @observer export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> { @@ -25,44 +25,34 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> { } render() { - const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ margin: "auto", width: 19, transform: 'translate(-2px, -2px)' }} />; + const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ margin: 'auto', width: 19, transform: 'translate(-2px, -2px)' }} />; const buttons = [ <Tooltip key="collect" title={<div className="dash-tooltip">Create a Collection</div>} placement="bottom"> - <button - className="antimodeMenu-button" - onPointerDown={this.createCollection}> + <button className="antimodeMenu-button" onPointerDown={this.createCollection}> <FontAwesomeIcon icon="object-group" size="lg" /> </button> </Tooltip>, <Tooltip key="group" title={<div className="dash-tooltip">Create a Grouping</div>} placement="bottom"> - <button - className="antimodeMenu-button" - onPointerDown={e => this.createCollection(e, true)}> + <button className="antimodeMenu-button" onPointerDown={e => this.createCollection(e, true)}> <FontAwesomeIcon icon="layer-group" size="lg" /> </button> </Tooltip>, <Tooltip key="summarize" title={<div className="dash-tooltip">Summarize Documents</div>} placement="bottom"> - <button - className="antimodeMenu-button" - onPointerDown={this.summarize}> + <button className="antimodeMenu-button" onPointerDown={this.summarize}> <FontAwesomeIcon icon="compress-arrows-alt" size="lg" /> </button> </Tooltip>, <Tooltip key="delete" title={<div className="dash-tooltip">Delete Documents</div>} placement="bottom"> - <button - className="antimodeMenu-button" - onPointerDown={this.delete}> + <button className="antimodeMenu-button" onPointerDown={this.delete}> <FontAwesomeIcon icon="trash-alt" size="lg" /> </button> </Tooltip>, - <Tooltip key="pinWithView" title={<div className="dash-tooltip">Pin with selected region</div>} placement="bottom"> - <button - className="antimodeMenu-button" - onPointerDown={this.pinWithView}> + <Tooltip key="pinWithView" title={<div className="dash-tooltip">Pin selected region to trail</div>} placement="bottom"> + <button className="antimodeMenu-button" onPointerDown={this.pinWithView}> {presPinWithViewIcon} </button> </Tooltip>, ]; return this.getElement(buttons); } -}
\ 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 584c9690f..a020b67cd 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -18,7 +18,7 @@ import { Transform } from '../../../util/Transform'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; -import { PinViewProps, PresBox } from '../../nodes/trails/PresBox'; +import { PresBox } from '../../nodes/trails/PresBox'; import { VideoBox } from '../../nodes/VideoBox'; import { pasteImageBitmap } from '../../nodes/WebBoxRenderer'; import { PreviewCursor } from '../../PreviewCursor'; @@ -61,6 +61,11 @@ export interface MarqueeViewBounds { @observer export class MarqueeView extends React.Component<SubCollectionViewProps & MarqueeViewProps> { + public static CurViewBounds(pinDoc: Doc, panelWidth: number, panelHeight: number) { + const ps = NumCast(pinDoc._viewScale, 1); + return { left: NumCast(pinDoc._panX) - panelWidth / 2 / ps, top: NumCast(pinDoc._panY) - panelHeight / 2 / ps, width: panelWidth / ps, height: panelHeight / ps }; + } + private _commandExecuted = false; @observable _lastX: number = 0; @observable _lastY: number = 0; @@ -426,10 +431,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque @action pinWithView = async () => { const doc = this.props.Document; - const viewOptions: PinViewProps = { - bounds: this.Bounds, - }; - TabDocView.PinDoc(doc, { pinWithView: viewOptions, pinDocView: true }); + TabDocView.PinDoc(doc, { pinViewport: this.Bounds }); MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); }; diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.scss b/src/client/views/collections/collectionLinear/CollectionLinearView.scss index e8df192cf..521bcda1e 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.scss +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.scss @@ -1,6 +1,10 @@ -@import "../../global/globalCssVariables"; -@import "../../_nodeModuleOverrides"; +@import '../../global/globalCssVariables'; +@import '../../_nodeModuleOverrides'; +.collectionLinearView-label { + color: black; + background-color: $light-gray; +} .collectionLinearView-outer { overflow: visible; height: 100%; diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index 92f6bbb64..9778fc4fe 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -1,12 +1,12 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; +import { StylesProvider, Tooltip } from '@material-ui/core'; import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, HeightSym, Opt, WidthSym } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; -import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, returnEmptyDoclist, returnTrue, Utils } from '../../../../Utils'; +import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; +import { emptyFunction, returnEmptyDoclist, returnTrue, StopEvent, Utils } from '../../../../Utils'; import { CollectionViewType } from '../../../documents/DocumentTypes'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; @@ -119,7 +119,49 @@ export class CollectionLinearView extends CollectionSubView() { e.preventDefault(); }; + getLinkUI = () => { + return !DocumentLinksButton.StartLink ? null : ( + <span className="bottomPopup-background" style={{ pointerEvents: 'all' }} onPointerDown={e => e.stopPropagation()}> + <span className="bottomPopup-text"> + Creating link from:{' '} + <b> + {(DocumentLinksButton.AnnotationId ? 'Annotation in ' : ' ') + + (StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...')} + </b> + </span> + + <Tooltip title={<div className="dash-tooltip">{'Toggle description pop-up'} </div>} placement="top"> + <span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}> + Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : 'ON'} + </span> + </Tooltip> + + <Tooltip title={<div className="dash-tooltip">Exit linking mode</div>} placement="top"> + <span className="bottomPopup-exit" onClick={this.exitLongLinks}> + Stop + </span> + </Tooltip> + </span> + ); + }; + getCurrentlyPlayingUI = () => { + return !CollectionStackedTimeline.CurrentlyPlaying?.length ? null : ( + <span className="bottomPopup-background"> + <span className="bottomPopup-text"> + Currently playing: + {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => ( + <span className="audio-title" onPointerDown={() => DocumentManager.Instance.jumpToDocument(clip, true, undefined, [])}> + {clip.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? '' : ',')} + </span> + ))} + </span> + </span> + ); + }; getDisplayDoc = (doc: Doc, preview: boolean = false) => { + if (doc.icon === 'linkui') return this.getLinkUI(); + if (doc.icon === 'currentlyplayingui') return this.getCurrentlyPlayingUI(); + const nested = doc._viewType === CollectionViewType.Linear; const hidden = doc.hidden === true; @@ -172,40 +214,32 @@ export class CollectionLinearView extends CollectionSubView() { }; render() { - const guid = Utils.GenerateGuid(); // Generate a unique ID to use as the label - const flexDir: any = StrCast(this.Document.flexDirection); // Specify direction of linear view content - const flexGap: number = NumCast(this.Document.flexGap); // Specify the gap between linear view content - const expandable: boolean = BoolCast(this.props.Document.linearViewExpandable); // Specify whether it is expandable or not - const floating: boolean = BoolCast(this.props.Document.linearViewFloating); // Specify whether it is expandable or not + const flexDir = StrCast(this.Document.flexDirection); // Specify direction of linear view content + const flexGap = NumCast(this.Document.flexGap); // Specify the gap between linear view content + const isExpanded = BoolCast(this.layoutDoc.linearViewIsExpanded); - const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); - const icon: string = StrCast(this.props.Document.icon); // Menu opener toggle const menuOpener = ( <label - htmlFor={`${guid}`} - style={{ - boxShadow: floating ? Shadows.STANDARD_SHADOW : undefined, - color: BoolCast(this.layoutDoc.linearViewIsExpanded) ? undefined : Colors.BLACK, - backgroundColor: backgroundColor === color ? 'black' : BoolCast(this.layoutDoc.linearViewIsExpanded) ? undefined : Colors.LIGHT_GRAY, - }} - onPointerDown={e => e.stopPropagation()}> - <div className="collectionLinearView-menuOpener">{BoolCast(this.layoutDoc.linearViewIsExpanded) ? icon ? icon : <FontAwesomeIcon icon={'minus'} /> : icon ? icon : <FontAwesomeIcon icon={'plus'} />}</div> + className={`collectionlinearView-label${isExpanded ? '-expanded' : ''}`} + htmlFor={this.Document[Id] + '-input'} + style={{ boxShadow: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BoxShadow) }} + onPointerDown={StopEvent}> + <div className="collectionLinearView-menuOpener">{Cast(this.props.Document.icon, 'string', null) ?? <FontAwesomeIcon icon={isExpanded ? 'minus' : 'plus'} />}</div> </label> ); return ( - <div className={`collectionLinearView-outer ${this.layoutDoc.linearViewSubMenu}`} style={{ backgroundColor: BoolCast(this.layoutDoc.linearViewIsExpanded) ? undefined : 'transparent' }}> + <div className={`collectionLinearView-outer ${this.layoutDoc.linearViewSubMenu}`} style={{ backgroundColor: 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"> + {!this.props.Document.linearViewExpandable ? null : ( + <Tooltip title={<div className="dash-tooltip">{isExpanded ? 'Close' : 'Open'}</div>} placement="top"> {menuOpener} </Tooltip> )} <input - id={`${guid}`} + id={this.Document[Id] + '-input'} type="checkbox" - checked={BoolCast(this.props.Document.linearViewIsExpanded)} + checked={isExpanded} ref={this.addMenuToggle} onChange={action(e => { ScriptCast(this.Document.onClick)?.script.run({ @@ -216,7 +250,7 @@ export class CollectionLinearView extends CollectionSubView() { thisContainer: this.props.ContainingCollectionDoc, documentView: this.props.docViewPath().lastElement(), }); - this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked; + this.layoutDoc.linearViewIsExpanded = this.addMenuToggle.current!.checked; })} /> @@ -224,58 +258,11 @@ export class CollectionLinearView extends CollectionSubView() { className="collectionLinearView-content" style={{ height: this.dimension(), - flexDirection: flexDir, + flexDirection: flexDir as any, gap: flexGap, }}> {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))} </div> - {!DocumentLinksButton.StartLink || this.layoutDoc !== Doc.MyDockedBtns ? null : ( - <span className="bottomPopup-background" style={{ pointerEvents: 'all' }} onPointerDown={e => e.stopPropagation()}> - <span className="bottomPopup-text"> - Creating link from:{' '} - <b> - {(DocumentLinksButton.AnnotationId ? 'Annotation in ' : ' ') + - (StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...')} - </b> - </span> - - <Tooltip - title={ - <> - <div className="dash-tooltip">{'Toggle description pop-up'} </div> - </> - } - placement="top"> - <span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}> - Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : 'ON'} - </span> - </Tooltip> - - <Tooltip - title={ - <> - <div className="dash-tooltip">Exit linking mode</div> - </> - } - placement="top"> - <span className="bottomPopup-exit" onClick={this.exitLongLinks}> - Stop - </span> - </Tooltip> - </span> - )} - {!CollectionStackedTimeline.CurrentlyPlaying || !CollectionStackedTimeline.CurrentlyPlaying.length || this.layoutDoc !== Doc.MyDockedBtns ? null : ( - <span className="bottomPopup-background"> - <span className="bottomPopup-text"> - Currently playing: - {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => ( - <span className="audio-title" onPointerDown={() => DocumentManager.Instance.jumpToDocument(clip, true, undefined, [])}> - {clip.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? '' : ',')} - </span> - ))} - </span> - </span> - )} </div> </div> ); |
