diff --git a/src/mol-plugin/behavior/behavior.ts b/src/mol-plugin/behavior/behavior.ts index f687f69d5626ffedd65aa097db2451974ec8e644..ed03b478adbda9fe13de86988d76d453dc741c57 100644 --- a/src/mol-plugin/behavior/behavior.ts +++ b/src/mol-plugin/behavior/behavior.ts @@ -25,12 +25,22 @@ interface PluginBehavior<P = unknown> { namespace PluginBehavior { export class Root extends PluginStateObject.Create({ name: 'Root', typeClass: 'Root' }) { } + export class Category extends PluginStateObject.Create({ name: 'Category', typeClass: 'Object' }) { } export class Behavior extends PluginStateObject.CreateBehavior<PluginBehavior>({ name: 'Behavior' }) { } export interface Ctor<P = undefined> { new(ctx: PluginContext, params: P): PluginBehavior<P> } + export const Categories = { + 'common': 'Common', + 'representation': 'Representation', + 'interaction': 'Interaction', + 'custom-props': 'Custom Properties', + 'misc': 'Miscellaneous' + }; + export interface CreateParams<P> { name: string, + category: keyof typeof Categories, ctor: Ctor<P>, canAutoUpdate?: StateTransformer.Definition<Root, Behavior, P>['canAutoUpdate'], label?: (params: P) => { label: string, description?: string }, @@ -42,9 +52,28 @@ namespace PluginBehavior { params?(a: Root, globalCtx: PluginContext): { [K in keyof P]: ParamDefinition.Any } } + export type CreateCategory = typeof CreateCategory + export const CreateCategory = PluginStateTransform.BuiltIn({ + name: 'create-behavior-category', + display: { name: 'Create Cateogry' }, + from: Root, + to: Category, + params: { + label: ParamDefinition.Text('', { isHidden: true }), + } + })({ + apply({ params }) { + return new Category({}, { label: params.label }); + } + }); + + const categoryMap = new Map<string, string>(); + export function getCategoryId(t: StateTransformer) { + return categoryMap.get(t.id)!; + } + export function create<P>(params: CreateParams<P>) { - // TODO: cache groups etc - return PluginStateTransform.CreateBuiltIn<Root, Behavior, P>({ + const t = PluginStateTransform.CreateBuiltIn<Category, Behavior, P>({ name: params.name, display: params.display, from: [Root], @@ -63,6 +92,8 @@ namespace PluginBehavior { }, canAutoUpdate: params.canAutoUpdate }); + categoryMap.set(t.id, params.category); + return t; } export function simpleCommandHandler<T>(cmd: PluginCommand<T>, action: (data: T, ctx: PluginContext) => void | Promise<void>) { diff --git a/src/mol-plugin/behavior/dynamic/animation.ts b/src/mol-plugin/behavior/dynamic/animation.ts index cdff329ad41bace7b6f717c0b5b34129846a4ef9..a2c863ed388f5a86bd2bf3be4d56a4243435fb97 100644 --- a/src/mol-plugin/behavior/dynamic/animation.ts +++ b/src/mol-plugin/behavior/dynamic/animation.ts @@ -29,6 +29,7 @@ type StructureAnimationProps = PD.Values<typeof StructureAnimationParams> */ export const StructureAnimation = PluginBehavior.create<StructureAnimationProps>({ name: 'structure-animation', + category: 'representation', display: { name: 'Structure Animation', group: 'Animation' }, canAutoUpdate: () => true, ctor: class extends PluginBehavior.Handler<StructureAnimationProps> { diff --git a/src/mol-plugin/behavior/dynamic/camera.ts b/src/mol-plugin/behavior/dynamic/camera.ts index 4431eec954a3fcc90d73705940c338339fbe2e23..b2caee3fefa673d61b9bcf5eb6cacc1c485bd4d5 100644 --- a/src/mol-plugin/behavior/dynamic/camera.ts +++ b/src/mol-plugin/behavior/dynamic/camera.ts @@ -10,6 +10,7 @@ import { PluginBehavior } from '../behavior'; export const FocusLociOnSelect = PluginBehavior.create<{ minRadius: number, extraRadius: number }>({ name: 'focus-loci-on-select', + category: 'interaction', ctor: class extends PluginBehavior.Handler<{ minRadius: number, extraRadius: number }> { register(): void { this.subscribeObservable(this.ctx.behaviors.canvas.selectLoci, current => { diff --git a/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts b/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts index 6c09982015da5a9d71bba13f2351d6dd8d40df83..dc882b0efb0991435ff6f0ff618ecdbdf17ca957 100644 --- a/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts +++ b/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts @@ -16,6 +16,7 @@ import { ThemeDataContext } from 'mol-theme/theme'; export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: boolean }>({ name: 'pdbe-structure-quality-report-prop', + category: 'custom-props', display: { name: 'PDBe Structure Quality Report', group: 'Custom Props' }, ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean }> { private attach = StructureQualityReport.createAttachTask( diff --git a/src/mol-plugin/behavior/dynamic/custom-props/rcsb/assembly-symmetry.ts b/src/mol-plugin/behavior/dynamic/custom-props/rcsb/assembly-symmetry.ts index 3079bbcab9e21bcf2cb79c2b3f9cc4ef11bd5627..fc131da26a344fac0b72dc545072c2b8263f50f1 100644 --- a/src/mol-plugin/behavior/dynamic/custom-props/rcsb/assembly-symmetry.ts +++ b/src/mol-plugin/behavior/dynamic/custom-props/rcsb/assembly-symmetry.ts @@ -16,6 +16,7 @@ import { Table } from 'mol-data/db'; export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean }>({ name: 'rcsb-assembly-symmetry-prop', + category: 'custom-props', display: { name: 'RCSB Assembly Symmetry', group: 'Custom Props' }, ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean }> { private attach = AssemblySymmetry.createAttachTask(this.ctx.fetch); diff --git a/src/mol-plugin/behavior/dynamic/labels.ts b/src/mol-plugin/behavior/dynamic/labels.ts index 42966e07ced097c087db3b72e62f1aebda93018e..f3f17ea828a20e69695883d059d8597bca38a0bf 100644 --- a/src/mol-plugin/behavior/dynamic/labels.ts +++ b/src/mol-plugin/behavior/dynamic/labels.ts @@ -73,6 +73,7 @@ function getLabelsText(data: LabelsData, props: PD.Values<Text.Params>, text?: T export const SceneLabels = PluginBehavior.create<SceneLabelsProps>({ name: 'scene-labels', + category: 'representation', display: { name: 'Scene Labels', group: 'Labels' }, canAutoUpdate: () => true, ctor: class extends PluginBehavior.Handler<SceneLabelsProps> { diff --git a/src/mol-plugin/behavior/dynamic/representation.ts b/src/mol-plugin/behavior/dynamic/representation.ts index b5bcf3251c9d25b0e3836755aa70f95fe3077787..e5566019b12186bbe2de08e2ca3f34dd49b28a93 100644 --- a/src/mol-plugin/behavior/dynamic/representation.ts +++ b/src/mol-plugin/behavior/dynamic/representation.ts @@ -17,6 +17,7 @@ import { PluginBehavior } from '../behavior'; export const HighlightLoci = PluginBehavior.create({ name: 'representation-highlight-loci', + category: 'interaction', ctor: class extends PluginBehavior.Handler { register(): void { let prevLoci: Loci = EmptyLoci, prevRepr: any = void 0; @@ -37,6 +38,7 @@ export const HighlightLoci = PluginBehavior.create({ export const SelectLoci = PluginBehavior.create({ name: 'representation-select-loci', + category: 'interaction', ctor: class extends PluginBehavior.Handler { register(): void { let prevLoci: Loci = EmptyLoci, prevRepr: any = void 0; @@ -59,6 +61,7 @@ export const SelectLoci = PluginBehavior.create({ export const DefaultLociLabelProvider = PluginBehavior.create({ name: 'default-loci-label-provider', + category: 'interaction', ctor: class implements PluginBehavior<undefined> { private f = labelFirst; register(): void { this.ctx.lociLabels.addProvider(this.f); } diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index e7ebde9fab4834fcd79f1a0bbee3f404cbaf7924..9be0646bf8e61bb9525715eeb5d098d39f94a0ba 100644 --- a/src/mol-plugin/context.ts +++ b/src/mol-plugin/context.ts @@ -31,6 +31,7 @@ import { PluginLayout } from './layout'; import { List } from 'immutable'; import { StateTransformParameters } from './ui/state/common'; import { DataFormatRegistry } from './state/actions/basic'; +import { PluginBehavior } from './behavior/behavior'; export class PluginContext { private disposed = false; @@ -166,8 +167,12 @@ export class PluginContext { private async initBehaviors() { const tree = this.state.behaviorState.build(); + for (const cat of Object.keys(PluginBehavior.Categories)) { + tree.toRoot().apply(PluginBehavior.CreateCategory, { label: (PluginBehavior.Categories as any)[cat] }, { ref: cat, props: { isLocked: true } }); + } + for (const b of this.spec.behaviors) { - tree.toRoot().apply(b.transformer, b.defaultParams, { ref: b.transformer.id }); + tree.to(PluginBehavior.getCategoryId(b.transformer)).apply(b.transformer, b.defaultParams, { ref: b.transformer.id }); } await this.runTask(this.state.behaviorState.updateTree(tree, true)); diff --git a/src/mol-plugin/state.ts b/src/mol-plugin/state.ts index 86d08ec73f24dcea3fbc75af8dffdfbd1a01dfcc..9c5e4c13bb433290fbd7b215c558612eda44224e 100644 --- a/src/mol-plugin/state.ts +++ b/src/mol-plugin/state.ts @@ -77,7 +77,7 @@ class PluginState { constructor(private plugin: import('./context').PluginContext) { this.dataState = State.create(new SO.Root({ }), { globalContext: plugin }); - this.behaviorState = State.create(new PluginBehavior.Root({ }), { globalContext: plugin }); + this.behaviorState = State.create(new PluginBehavior.Root({ }), { globalContext: plugin, rootProps: { isLocked: true } }); this.dataState.behaviors.currentObject.subscribe(o => { if (this.behavior.kind.value === 'data') this.behavior.currentObject.next(o); diff --git a/src/mol-state/state.ts b/src/mol-state/state.ts index 9ac0289e5eea865653b981efc34e5e8de4fe91d8..81b56a729fc1dac6954a907dbe5e90ee4617f040 100644 --- a/src/mol-state/state.ts +++ b/src/mol-state/state.ts @@ -22,7 +22,7 @@ import { ParamDefinition } from 'mol-util/param-definition'; export { State } class State { - private _tree: TransientTree = StateTree.createEmpty().asTransient(); + private _tree: TransientTree; protected errorFree = true; private transformCache = new Map<StateTransform.Ref, unknown>(); @@ -174,7 +174,8 @@ class State { return ctx; } - constructor(rootObject: StateObject, params?: { globalContext?: unknown }) { + constructor(rootObject: StateObject, params?: { globalContext?: unknown, rootProps?: StateTransform.Props }) { + this._tree = StateTree.createEmpty(StateTransform.createRoot(params && params.rootProps)).asTransient(); const tree = this._tree; const root = tree.root; @@ -209,7 +210,7 @@ namespace State { readonly tree: StateTree.Serialized } - export function create(rootObject: StateObject, params?: { globalContext?: unknown, defaultObjectProps?: unknown }) { + export function create(rootObject: StateObject, params?: { globalContext?: unknown, rootProps?: StateTransform.Props }) { return new State(rootObject, params); } } diff --git a/src/mol-state/transform.ts b/src/mol-state/transform.ts index 22794b34c2138aa0f97ef31f795c397e1ea5802f..d4acdb81a2a1a8bec45b3d4e44cccd5024518a18 100644 --- a/src/mol-state/transform.ts +++ b/src/mol-state/transform.ts @@ -52,8 +52,8 @@ namespace Transform { return { ...t, params, version: UUID.create22() }; } - export function createRoot(): Transform { - return create(RootRef, StateTransformer.ROOT, {}, { ref: RootRef }); + export function createRoot(props?: Props): Transform { + return create(RootRef, StateTransformer.ROOT, {}, { ref: RootRef, props }); } export interface Serialized { diff --git a/src/mol-state/tree/immutable.ts b/src/mol-state/tree/immutable.ts index 8cb9ed73a89771e48c6dc88d90d4410e1cd18cfa..ab3d8f60301414dc4deb67d41348a393eea6e2f5 100644 --- a/src/mol-state/tree/immutable.ts +++ b/src/mol-state/tree/immutable.ts @@ -59,8 +59,8 @@ namespace StateTree { /** * Create an instance of an immutable tree. */ - export function createEmpty(): StateTree { - const root = StateTransform.createRoot(); + export function createEmpty(customRoot?: StateTransform): StateTree { + const root = customRoot || StateTransform.createRoot(); return create(ImmutableMap([[root.ref, root]]), ImmutableMap([[root.ref, OrderedSet()]]), ImmutableMap([[root.ref, StateObjectCell.DefaultState]])); }