import { action, observable, ObservableMap } from 'mobx'; import { computedFn } from 'mobx-utils'; import { Doc, Opt } from '../../fields/Doc'; import { DocCast } from '../../fields/Types'; import { CollectionViewType } from '../documents/DocumentTypes'; import { DocumentView } from '../views/nodes/DocumentView'; import { LinkManager } from './LinkManager'; import { ScriptingGlobals } from './ScriptingGlobals'; export namespace SelectionManager { class Manager { @observable IsDragging: boolean = false; SelectedViewsMap: ObservableMap = new ObservableMap(); @observable SelectedViews: DocumentView[] = []; @observable SelectedSchemaDocument: Doc | undefined; @action SelectSchemaViewDoc(doc: Opt) { manager.SelectedSchemaDocument = doc; } @action SelectView(docView: DocumentView, ctrlPressed: boolean): void { // if doc is not in SelectedDocuments, add it if (!manager.SelectedViewsMap.get(docView)) { if (!ctrlPressed) { if (LinkManager.currentLink && !LinkManager.Links(docView.rootDoc).includes(LinkManager.currentLink) && docView.rootDoc !== LinkManager.currentLink) { LinkManager.currentLink = undefined; } this.DeselectAll(); } manager.SelectedViews.push(docView); manager.SelectedViewsMap.set(docView, docView.rootDoc); docView.props.whenChildContentsActiveChanged(true); } else if (!ctrlPressed && (Array.from(manager.SelectedViewsMap.entries()).length > 1 || manager.SelectedSchemaDocument)) { Array.from(manager.SelectedViewsMap.keys()).map(dv => dv !== docView && dv.props.whenChildContentsActiveChanged(false)); manager.SelectedSchemaDocument = undefined; manager.SelectedViews.length = 0; manager.SelectedViewsMap.clear(); manager.SelectedViews.push(docView); manager.SelectedViewsMap.set(docView, docView.rootDoc); } } @action DeselectView(docView?: DocumentView): void { if (docView && manager.SelectedViewsMap.get(docView)) { manager.SelectedViewsMap.delete(docView); manager.SelectedViews.splice(manager.SelectedViews.indexOf(docView), 1); docView.props.whenChildContentsActiveChanged(false); } } @action DeselectAll(): void { manager.SelectedSchemaDocument = undefined; Array.from(manager.SelectedViewsMap.keys()).forEach(dv => dv.props.whenChildContentsActiveChanged(false)); manager.SelectedViewsMap.clear(); manager.SelectedViews.length = 0; } } const manager = new Manager(); export function DeselectView(docView?: DocumentView): void { manager.DeselectView(docView); } export function SelectView(docView: DocumentView | undefined, ctrlPressed: boolean): void { if (!docView) DeselectAll(); else manager.SelectView(docView, ctrlPressed); } export function SelectSchemaViewDoc(document: Opt, deselectAllFirst?: boolean): void { if (deselectAllFirst) manager.DeselectAll(); manager.SelectSchemaViewDoc(document); } const IsSelectedCache = computedFn(function isSelected(doc: DocumentView) { // wrapping get() in a computedFn only generates mobx() invalidations when the return value of the function for the specific get parameters has changed return manager.SelectedViewsMap.get(doc) ? true : false; }); // computed functions, such as used in IsSelected generate errors if they're called outside of a // reaction context. Specifying the context with 'outsideReaction' allows an efficiency feature // to avoid unnecessary mobx invalidations when running inside a reaction. export function IsSelected(doc: DocumentView | undefined, outsideReaction?: boolean): boolean { return !doc ? false : outsideReaction ? manager.SelectedViewsMap.get(doc) ? true : false // get() accesses a hashtable -- setting anything in the hashtable generates a mobx invalidation for every get() : IsSelectedCache(doc); } export function DeselectAll(except?: Doc): void { let found: DocumentView | undefined = undefined; if (except) { for (const view of Array.from(manager.SelectedViewsMap.keys())) { if (view.props.Document === except) found = view; } } manager.DeselectAll(); if (found) manager.SelectView(found, false); } export function Views(): Array { return manager.SelectedViews; // Array.from(manager.SelectedViewsMap.keys()); //.filter(dv => manager.SelectedViews.get(dv)?._type_collection !== CollectionViewType.Docking); } export function SelectedSchemaDoc(): Doc | undefined { return manager.SelectedSchemaDocument; } export function Docs(): Doc[] { return manager.SelectedViews.map(dv => dv.rootDoc).filter(doc => doc?._type_collection !== CollectionViewType.Docking); // Array.from(manager.SelectedViewsMap.values()).filter(doc => doc?._type_collection !== CollectionViewType.Docking); } } ScriptingGlobals.add(function SelectionManager_selectedDocType(type: string, expertMode: boolean, checkContext?: boolean) { if (Doc.noviceMode && expertMode) return false; if (type === 'tab') { return SelectionManager.Views().lastElement()?.props.renderDepth === 0; } let selected = (sel => (checkContext ? DocCast(sel?.context) : sel))(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement()); return selected?.type === type || selected?.type_collection === type || !type; });