diff options
| author | Sophie Zhang <sophie_zhang@brown.edu> | 2024-01-25 11:35:26 -0500 |
|---|---|---|
| committer | Sophie Zhang <sophie_zhang@brown.edu> | 2024-01-25 11:35:26 -0500 |
| commit | f3dab2a56db5e4a6a3dca58185d94e1ff7d1dc32 (patch) | |
| tree | a7bc895266b53bb620dbd2dd71bad2e83b555446 /src/client/views/collections/CollectionView.tsx | |
| parent | b5c5410b4af5d2c68d2107d3f064f6e3ec4ac3f2 (diff) | |
| parent | 136f3d9f349d54e8bdd73b6380ea47c19e5edebf (diff) | |
Merge branch 'master' into sophie-ai-images
Diffstat (limited to 'src/client/views/collections/CollectionView.tsx')
| -rw-r--r-- | src/client/views/collections/CollectionView.tsx | 156 |
1 files changed, 83 insertions, 73 deletions
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index ce19b3f9b..18eb4dd1f 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -1,47 +1,43 @@ -import { computed, observable, runInAction } from 'mobx'; +import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { returnEmptyString } from '../../../Utils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { ObjectField } from '../../../fields/ObjectField'; import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { returnEmptyString } from '../../../Utils'; -import { DocUtils } from '../../documents/Documents'; import { CollectionViewType } from '../../documents/DocumentTypes'; +import { DocUtils } from '../../documents/Documents'; import { dropActionType } from '../../util/DragManager'; import { ImageUtils } from '../../util/Import & Export/ImageUtils'; -import { InteractionUtils } from '../../util/InteractionUtils'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; -import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; +import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; import { OpenWhere } from '../nodes/DocumentView'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; +import { CollectionCalendarView } from './CollectionCalendarView'; import { CollectionCarousel3DView } from './CollectionCarousel3DView'; import { CollectionCarouselView } from './CollectionCarouselView'; import { CollectionDockingView } from './CollectionDockingView'; -import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; -import { CollectionGridView } from './collectionGrid/CollectionGridView'; -import { CollectionLinearView } from './collectionLinear'; -import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView'; -import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView'; import { CollectionNoteTakingView } from './CollectionNoteTakingView'; import { CollectionPileView } from './CollectionPileView'; -import { CollectionSchemaView } from './collectionSchema/CollectionSchemaView'; import { CollectionStackingView } from './CollectionStackingView'; import { SubCollectionViewProps } from './CollectionSubView'; import { CollectionTimeView } from './CollectionTimeView'; import { CollectionTreeView } from './CollectionTreeView'; import './CollectionView.scss'; -export const COLLECTION_BORDER_WIDTH = 2; -const path = require('path'); - -interface CollectionViewProps_ extends FieldViewProps { +import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; +import { CollectionGridView } from './collectionGrid/CollectionGridView'; +import { CollectionLinearView } from './collectionLinear'; +import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView'; +import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView'; +import { CollectionSchemaView } from './collectionSchema/CollectionSchemaView'; +export interface CollectionViewProps extends React.PropsWithChildren<FieldViewProps> { isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc) 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, doc: Opt<Doc>) => void) => void; - setBrushViewer?: (func?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number) => void) => void; ignoreUnrendered?: boolean; // property overrides for child documents @@ -52,10 +48,9 @@ interface CollectionViewProps_ extends FieldViewProps { childlayout_showTitle?: () => string; childOpacity?: () => number; childContextMenuItems?: () => { script: ScriptField; label: string }[]; - childHideTitle?: () => boolean; // whether to hide the documentdecorations title for children - childHideDecorationTitle?: () => boolean; - childHideResizeHandles?: () => boolean; childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection + childHideDecorationTitle?: boolean; + childHideResizeHandles?: boolean; childDragAction?: dropActionType; childXPadding?: number; childYPadding?: number; @@ -68,9 +63,8 @@ interface CollectionViewProps_ extends FieldViewProps { RemFromMap?: (treeViewDoc: Doc, index: number[]) => void; hierarchyIndex?: number[]; // hierarchical index of a document up to the rendering root (primarily used for tree views) } -export interface CollectionViewProps extends React.PropsWithChildren<CollectionViewProps_> {} @observer -export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & CollectionViewProps>() { +export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewProps>() implements ViewBoxInterface { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); } @@ -79,12 +73,28 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; } - - protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; + private reactionDisposer: IReactionDisposer | undefined; + @observable _isContentActive: boolean | undefined = undefined; constructor(props: any) { super(props); - runInAction(() => (this._annotationKeySuffix = returnEmptyString)); + makeObservable(this); + this._annotationKeySuffix = returnEmptyString; + } + + componentDidMount() { + // we use a reaction/observable instead of a computed value to reduce invalidations. + // There are many variables that aggregate into this boolean output - a change in any of them + // will cause downstream invalidations even if the computed value doesn't change. By making + // this a reaction, downstream invalidations only occur when the reaction value actually changes. + this.reactionDisposer = reaction( + () => (this.isAnyChildContentActive() ? true : this._props.isContentActive()), + active => (this._isContentActive = active), + { fireImmediately: true } + ); + } + componentWillUnmount() { + this.reactionDisposer?.(); } get collectionViewType(): CollectionViewType | undefined { @@ -101,41 +111,41 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab return viewField as any as CollectionViewType; } - screenToLocalTransform = () => (this.props.renderDepth ? this.props.ScreenToLocalTransform() : this.props.ScreenToLocalTransform().scale(this.props.PanelWidth() / this.bodyPanelWidth())); + screenToLocalTransform = () => (this._props.renderDepth ? this.ScreenToLocalBoxXf() : this.ScreenToLocalBoxXf().scale(this._props.PanelWidth() / this.bodyPanelWidth())); // prettier-ignore private renderSubView = (type: CollectionViewType | undefined, props: SubCollectionViewProps) => { TraceMobx(); if (type === undefined) return null; 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} />; - case CollectionViewType.Multicolumn: return <CollectionMulticolumnView key="collview" {...props} />; - case CollectionViewType.Multirow: return <CollectionMultirowView key="collview" {...props} />; - case CollectionViewType.Linear: return <CollectionLinearView key="collview" {...props} />; - case CollectionViewType.Pile: return <CollectionPileView key="collview" {...props} />; - case CollectionViewType.Carousel: return <CollectionCarouselView key="collview" {...props} />; - case CollectionViewType.Carousel3D: return <CollectionCarousel3DView key="collview" {...props} />; - case CollectionViewType.Stacking: return <CollectionStackingView key="collview" {...props} />; - case CollectionViewType.NoteTaking: return <CollectionNoteTakingView key="collview" {...props} />; - case CollectionViewType.Masonry: return <CollectionStackingView key="collview" {...props} />; - case CollectionViewType.Time: return <CollectionTimeView key="collview" {...props} />; - case CollectionViewType.Grid: return <CollectionGridView key="collview" {...props} />; - //case CollectionViewType.Staff: return <CollectionStaffView key="collview" {...props} />; + case CollectionViewType.Freeform: return <CollectionFreeFormView key="collview" {...props} />; + case CollectionViewType.Schema: return <CollectionSchemaView key="collview" {...props} />; + case CollectionViewType.Calendar: return <CollectionCalendarView key="collview" {...props} />; + case CollectionViewType.Docking: return <CollectionDockingView key="collview" {...props} />; + case CollectionViewType.Tree: return <CollectionTreeView key="collview" {...props} />; + case CollectionViewType.Multicolumn: return <CollectionMulticolumnView key="collview" {...props} />; + case CollectionViewType.Multirow: return <CollectionMultirowView key="collview" {...props} />; + case CollectionViewType.Linear: return <CollectionLinearView key="collview" {...props} />; + case CollectionViewType.Pile: return <CollectionPileView key="collview" {...props} />; + case CollectionViewType.Carousel: return <CollectionCarouselView key="collview" {...props} />; + case CollectionViewType.Carousel3D: return <CollectionCarousel3DView key="collview" {...props} />; + case CollectionViewType.Stacking: return <CollectionStackingView key="collview" {...props} />; + case CollectionViewType.NoteTaking: return <CollectionNoteTakingView key="collview" {...props} />; + case CollectionViewType.Masonry: return <CollectionStackingView key="collview" {...props} />; + case CollectionViewType.Time: return <CollectionTimeView key="collview" {...props} />; + case CollectionViewType.Grid: return <CollectionGridView key="collview" {...props} />; } }; setupViewTypes(category: string, func: (type_collection: CollectionViewType) => Doc) { - if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._type_collection !== CollectionViewType.Docking && !this.rootDoc.isGroup && !this.rootDoc.annotationOn) { + if (!Doc.IsSystem(this.Document) && this.Document._type_collection !== CollectionViewType.Docking && !this.dataDoc.isGroup && !this.Document.annotationOn) { // prettier-ignore const subItems: ContextMenuProps[] = [ { description: 'Freeform', event: () => func(CollectionViewType.Freeform), icon: 'signature' }, { description: 'Schema', event: () => func(CollectionViewType.Schema), icon: 'th-list' }, { description: 'Tree', event: () => func(CollectionViewType.Tree), icon: 'tree' }, { description: 'Stacking', event: () => (func(CollectionViewType.Stacking)._layout_autoHeight = true), icon: 'ellipsis-v' }, + { description: 'Calendar', event: () => func(CollectionViewType.Calendar), icon: 'columns'}, { description: 'Notetaking', event: () => (func(CollectionViewType.NoteTaking)._layout_autoHeight = true), icon: 'ellipsis-v' }, { description: 'Multicolumn', event: () => func(CollectionViewType.Multicolumn), icon: 'columns' }, { description: 'Multirow', event: () => func(CollectionViewType.Multirow), icon: 'columns' }, @@ -159,27 +169,27 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab if (cm && !e.isPropagationStopped()) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 !Doc.noviceMode && - this.setupViewTypes('UI Controls...', vtype => { - const newRendition = Doc.MakeEmbedding(this.rootDoc); + this.setupViewTypes('Appearance...', vtype => { + const newRendition = Doc.MakeEmbedding(this.Document); newRendition._type_collection = vtype; - this.props.addDocTab(newRendition, OpenWhere.addRight); + this._props.addDocTab(newRendition, OpenWhere.addRight); return newRendition; }); const options = cm.findByDescription('Options...'); const optionItems = options && 'subitems' in options ? options.subitems : []; - !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.rootDoc.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => (this.rootDoc.forceActive = !this.rootDoc.forceActive), icon: 'project-diagram' }) : null; - if (this.rootDoc.childLayout instanceof Doc) { - optionItems.push({ description: 'View Child Layout', event: () => this.props.addDocTab(this.rootDoc.childLayout as Doc, OpenWhere.addRight), icon: 'project-diagram' }); + !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.Document.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => (this.Document.forceActive = !this.Document.forceActive), icon: 'project-diagram' }) : null; + if (this.Document.childLayout instanceof Doc) { + optionItems.push({ description: 'View Child Layout', event: () => this._props.addDocTab(this.Document.childLayout as Doc, OpenWhere.addRight), icon: 'project-diagram' }); } - if (this.rootDoc.childClickedOpenTemplateView instanceof Doc) { - optionItems.push({ description: 'View Child Detailed Layout', event: () => this.props.addDocTab(this.rootDoc.childClickedOpenTemplateView as Doc, OpenWhere.addRight), icon: 'project-diagram' }); + if (this.Document.childClickedOpenTemplateView instanceof Doc) { + optionItems.push({ description: 'View Child Detailed Layout', event: () => this._props.addDocTab(this.Document.childClickedOpenTemplateView as Doc, OpenWhere.addRight), icon: 'project-diagram' }); } - !Doc.noviceMode && optionItems.push({ description: `${this.rootDoc._isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => (this.rootDoc._isLightbox = !this.rootDoc._isLightbox), icon: 'project-diagram' }); + !Doc.noviceMode && optionItems.push({ description: `${this.layoutDoc._isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => (this.layoutDoc._isLightbox = !this.layoutDoc._isLightbox), icon: 'project-diagram' }); !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'hand-point-right' }); - if (!Doc.noviceMode && !this.rootDoc.annotationOn) { + if (!Doc.noviceMode && !this.Document.annotationOn && !this._props.hideClickBehaviors) { const existingOnClick = cm.findByDescription('OnClick...'); const onClicks = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : []; const funcs = [ @@ -191,9 +201,9 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab description: `Edit ${func.name} script`, icon: 'edit', event: (obj: any) => { - const embedding = Doc.MakeEmbedding(this.rootDoc); + const embedding = Doc.MakeEmbedding(this.Document); DocUtils.makeCustomViewClicked(embedding, undefined, func.key); - this.props.addDocTab(embedding, OpenWhere.addRight); + this._props.addDocTab(embedding, OpenWhere.addRight); }, }) ); @@ -201,51 +211,51 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab onClicks.push({ description: `Set child ${childClick.title}`, icon: 'edit', - event: () => (Doc.GetProto(this.rootDoc)[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data))), + event: () => (this.dataDoc[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data))), }) ); - !Doc.IsSystem(this.rootDoc) && !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); + !Doc.IsSystem(this.Document) && !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); } if (!Doc.noviceMode) { const more = cm.findByDescription('More...'); const moreItems = more && 'subitems' in more ? more.subitems : []; - moreItems.push({ description: 'Export Image Hierarchy', icon: 'columns', event: () => ImageUtils.ExportHierarchyToFileSystem(this.rootDoc) }); + moreItems.push({ description: 'Export Image Hierarchy', icon: 'columns', event: () => ImageUtils.ExportHierarchyToFileSystem(this.Document) }); !more && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'hand-point-right' }); } } }; - bodyPanelWidth = () => this.props.PanelWidth(); + bodyPanelWidth = () => this._props.PanelWidth(); - childHideResizeHandles = () => this.props.childHideResizeHandles?.() ?? BoolCast(this.Document.childHideResizeHandles); - childHideDecorationTitle = () => this.props.childHideDecorationTitle?.() ?? BoolCast(this.Document.childHideDecorationTitle); - childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.rootDoc.childLayoutTemplate, Doc, null); - isContentActive = (outsideReaction?: boolean) => (this.isAnyChildContentActive() ? true : this.props.isContentActive()); + childLayoutTemplate = () => this._props.childLayoutTemplate?.() || Cast(this.Document.childLayoutTemplate, Doc, null); + isContentActive = (outsideReaction?: boolean) => this._isContentActive; + + pointerEvents = () => + this.layoutDoc._lockedPosition && // + this.Document?._type_collection === CollectionViewType.Freeform; render() { TraceMobx(); + const pointerEvents = this.pointerEvents() ? 'none' : undefined; const props: SubCollectionViewProps = { - ...this.props, + ...this._props, addDocument: this.addDocument, moveDocument: this.moveDocument, removeDocument: this.removeDocument, isContentActive: this.isContentActive, isAnyChildContentActive: this.isAnyChildContentActive, - whenChildContentsActiveChanged: this.whenChildContentsActiveChanged, PanelWidth: this.bodyPanelWidth, - PanelHeight: this.props.PanelHeight, + PanelHeight: this._props.PanelHeight, ScreenToLocalTransform: this.screenToLocalTransform, childLayoutTemplate: this.childLayoutTemplate, - childLayoutString: StrCast(this.rootDoc.childLayoutString, this.props.childLayoutString), - childHideResizeHandles: this.childHideResizeHandles, - childHideDecorationTitle: this.childHideDecorationTitle, + whenChildContentsActiveChanged: this.whenChildContentsActiveChanged, + childLayoutString: StrCast(this.Document.childLayoutString, this._props.childLayoutString), + childHideResizeHandles: this._props.childHideResizeHandles ?? BoolCast(this.Document.childHideResizeHandles), + childHideDecorationTitle: this._props.childHideDecorationTitle ?? BoolCast(this.Document.childHideDecorationTitle), }; return ( - <div - className="collectionView" - onContextMenu={this.onContextMenu} - style={{ pointerEvents: this.props.DocumentView?.()?.props.docViewPath().lastElement()?.rootDoc?._type_collection === CollectionViewType.Freeform && this.rootDoc._lockedPosition ? 'none' : undefined }}> + <div className="collectionView" onContextMenu={this.onContextMenu} style={{ pointerEvents }}> {this.renderSubView(this.collectionViewType, props)} </div> ); |
