import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { returnEmptyString } from '../../../ClientUtils'; import { Doc, DocListCast } from '../../../fields/Doc'; import { ObjectField } from '../../../fields/ObjectField'; import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { DocUtils } from '../../documents/DocUtils'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { ImageUtils } from '../../util/Import & Export/ImageUtils'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { FieldView } from '../nodes/FieldView'; import { OpenWhere } from '../nodes/OpenWhere'; import { CalendarBox } from '../nodes/calendarBox/CalendarBox'; import { CollectionCardView } from './CollectionCardDeckView'; import { CollectionCarousel3DView } from './CollectionCarousel3DView'; import { CollectionCarouselView } from './CollectionCarouselView'; import { CollectionDockingView } from './CollectionDockingView'; import { CollectionNoteTakingView } from './CollectionNoteTakingView'; import { CollectionPileView } from './CollectionPileView'; import { CollectionPivotView } from './CollectionPivotView'; import { CollectionStackingView } from './CollectionStackingView'; import { CollectionViewProps, SubCollectionViewProps } from './CollectionSubView'; import { CollectionTimeView } from './CollectionTimeView'; import { CollectionTreeView } from './CollectionTreeView'; import './CollectionView.scss'; 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'; @observer export class CollectionView extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); } @observable private static _safeMode = false; public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; } private reactionDisposer: IReactionDisposer | undefined; @observable _isContentActive: boolean | undefined = undefined; constructor(props: CollectionViewProps) { super(props); 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 { const viewField = StrCast(this.layoutDoc._type_collection) as CollectionViewType; if (CollectionView._safeMode) { switch (viewField) { case CollectionViewType.Freeform: case CollectionViewType.Schema: return CollectionViewType.Tree; case CollectionViewType.Invalid: return CollectionViewType.Freeform; default: } // prettier-ignore } return viewField; } screenToLocalTransform = this.ScreenToLocalBoxXf; // prettier-ignore private renderSubView = (type: CollectionViewType | undefined, props: SubCollectionViewProps) => { TraceMobx(); if (type === undefined) return null; switch (type) { default: case CollectionViewType.Freeform: return ; case CollectionViewType.Schema: return ; case CollectionViewType.Calendar: return ; case CollectionViewType.Docking: return ; case CollectionViewType.Tree: return ; case CollectionViewType.Multicolumn: return ; case CollectionViewType.Multirow: return ; case CollectionViewType.Linear: return ; case CollectionViewType.Pile: return ; case CollectionViewType.Carousel: return ; case CollectionViewType.Carousel3D: return ; case CollectionViewType.Stacking: return ; case CollectionViewType.NoteTaking: return ; case CollectionViewType.Masonry: return ; case CollectionViewType.Time: return ; case CollectionViewType.Pivot: return ; case CollectionViewType.Grid: return ; case CollectionViewType.Card: return ; } }; setupViewTypes(category: string, func: (type_collection: CollectionViewType) => Doc) { if (!Doc.IsSystem(this.Document) && this.Document._type_collection !== CollectionViewType.Docking && !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' }, { description: 'Masonry', event: () => func(CollectionViewType.Masonry), icon: 'columns' }, { description: 'Carousel', event: () => func(CollectionViewType.Carousel), icon: 'columns' }, { description: '3D Carousel', event: () => func(CollectionViewType.Carousel3D), icon: 'columns' }, { description: 'Calendar', event: () => func(CollectionViewType.Calendar), icon: 'columns' }, { description: 'Pivot', event: () => func(CollectionViewType.Pivot), icon: 'columns' }, { description: 'Time', event: () => func(CollectionViewType.Time), icon: 'columns' }, { description: 'Grid', event: () => func(CollectionViewType.Grid), icon: 'th-list' }, ]; const existingVm = ContextMenu.Instance.findByDescription(category); const catItems = existingVm?.subitems ?? []; catItems.push({ description: 'Add a Perspective...', addDivider: true, noexpand: true, subitems: subItems, icon: 'eye' }); !existingVm && ContextMenu.Instance.addItem({ description: category, subitems: catItems, icon: 'eye' }); } } onContextMenu = (e: React.MouseEvent): void => { const cm = ContextMenu.Instance; 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('Appearance...', vtype => { const newRendition = Doc.MakeEmbedding(this.Document); newRendition._type_collection = vtype; this._props.addDocTab(newRendition, OpenWhere.addRight); return newRendition; }); const options = cm.findByDescription('Options...'); const optionItems = options?.subitems ?? []; !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; // prettier-ignore 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.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.dataDoc.$isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => { this.dataDoc.$isLightbox = !this.dataDoc.$isLightbox; }, icon: 'project-diagram' }); // prettier-ignore !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'hand-point-right' }); if (!Doc.noviceMode && !this.Document.annotationOn && !this._props.hideClickBehaviors) { const existingOnClick = cm.findByDescription('OnClick...'); const onClicks = existingOnClick?.subitems ?? []; const funcs = [ { key: 'onChildClick', name: 'On Child Clicked' }, { key: 'onChildDoubleClick', name: 'On Child Double Clicked' }, ]; funcs.map(func => onClicks.push({ description: `Edit ${func.name} script`, icon: 'edit', event: () => { const embedding = Doc.MakeEmbedding(this.Document); DocUtils.makeCustomViewClicked(embedding, undefined, func.key); this._props.addDocTab(embedding, OpenWhere.addRight); }, }) ); DocListCast(Cast(Doc.UserDoc()['clickFuncs-child'], Doc, null)?.data) .filter(childClick => ScriptCast(childClick.data)) .forEach(childClick => onClicks.push({ description: `Set child ${childClick.title}`, icon: 'edit', event: () => { this.dataDoc[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)!); }, }) ); !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 ?? []; moreItems.push({ description: 'Export Image Hierarchy', icon: 'columns', event: () => ImageUtils.ExportHierarchyToFileSystem(this.Document) }); !more && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'hand-point-right' }); } } }; childLayoutTemplate = () => this._props.childLayoutTemplate?.() || Cast(this.Document.childLayoutTemplate, Doc, null); isContentActive = () => 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, addDocument: this.addDocument, moveDocument: this.moveDocument, removeDocument: this.removeDocument, isContentActive: this.isContentActive, isAnyChildContentActive: this.isAnyChildContentActive, PanelWidth: this._props.PanelWidth, PanelHeight: this._props.PanelHeight, ScreenToLocalTransform: this.screenToLocalTransform, childLayoutTemplate: this.childLayoutTemplate, 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 (
{this.renderSubView(this.collectionViewType, props)}
); } } Docs.Prototypes.TemplateMap.set(DocumentType.COL, { layout: { view: CollectionView, dataField: 'data' }, options: { acl: '', _layout_fitWidth: true, freeform: '', _freeform_panX: 0, _freeform_panY: 0, _freeform_scale: 1, _layout_nativeDimEditable: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true, systemIcon: 'BsFillCollectionFill', }, });