From ae484443d8298b57783d4744b6b2e03d3039d554 Mon Sep 17 00:00:00 2001
From: David Sehnal <david.sehnal@gmail.com>
Date: Sun, 8 Mar 2020 14:40:23 +0100
Subject: [PATCH] decorator transforms
---
src/mol-plugin-state/actions/structure.ts | 12 +++---
src/mol-plugin-state/builder/structure.ts | 48 +++++++++++++++--------
src/mol-plugin-state/transforms/model.ts | 6 +--
src/mol-plugin-ui/controls/common.tsx | 23 +++++++++++
src/mol-plugin-ui/controls/parameters.tsx | 25 +-----------
src/mol-plugin-ui/plugin.tsx | 35 +++++++++++++----
src/mol-plugin-ui/state/tree.tsx | 18 ++++++---
src/mol-state/state/builder.ts | 5 +++
src/mol-state/transform.ts | 8 +++-
src/mol-state/tree/spine.ts | 10 +++++
10 files changed, 126 insertions(+), 64 deletions(-)
diff --git a/src/mol-plugin-state/actions/structure.ts b/src/mol-plugin-state/actions/structure.ts
index e46b27280..491bb1d84 100644
--- a/src/mol-plugin-state/actions/structure.ts
+++ b/src/mol-plugin-state/actions/structure.ts
@@ -234,19 +234,19 @@ const DownloadStructure = StateAction.build({
const traj = await plugin.builders.structure.parseTrajectory(data, {
formats: downloadParams.map((_, i) => ({ id: '' + i, format: 'cif' as 'cif' }))
});
- const model = await plugin.builders.structure.createModel(traj, void 0, supportProps);
- const struct = await plugin.builders.structure.createStructure(model, src.params.structure.type);
+ const { model } = await plugin.builders.structure.createModel(traj, { properties: supportProps });
+ const { structure } = await plugin.builders.structure.createStructure(model, { structure: src.params.structure.type, properties: supportProps });
if (createRepr) {
- await plugin.builders.representation.structurePreset(struct.ref, 'auto');
+ await plugin.builders.representation.structurePreset(structure, 'auto');
}
} else {
for (const download of downloadParams) {
const data = await plugin.builders.data.download(download, { state: { isGhost: true } });
const traj = await plugin.builders.structure.parseTrajectory(data, format);
- const model = await plugin.builders.structure.createModel(traj, void 0, supportProps);
- const struct = await plugin.builders.structure.createStructure(model, src.params.structure.type);
+ const { model } = await plugin.builders.structure.createModel(traj, { properties: supportProps });
+ const { structure } = await plugin.builders.structure.createStructure(model, { structure: src.params.structure.type, properties: supportProps });
if (createRepr) {
- await plugin.builders.representation.structurePreset(struct.ref, 'auto');
+ await plugin.builders.representation.structurePreset(structure, 'auto');
}
}
}
diff --git a/src/mol-plugin-state/builder/structure.ts b/src/mol-plugin-state/builder/structure.ts
index 729993089..9eb66bdbb 100644
--- a/src/mol-plugin-state/builder/structure.ts
+++ b/src/mol-plugin-state/builder/structure.ts
@@ -18,6 +18,7 @@ export enum StructureBuilderTags {
Model = 'model',
ModelProperties = 'model-properties',
Structure = 'structure',
+ StructureProperties = 'structure-properties',
Component = 'structure-component'
}
@@ -78,28 +79,43 @@ export class StructureBuilder {
}
}
- async createModel(trajectory: StateObjectRef<SO.Molecule.Trajectory>, params?: StateTransformer.Params<StateTransforms['Model']['ModelFromTrajectory']>, supportProps?: boolean) {
+ async createModel(trajectory: StateObjectRef<SO.Molecule.Trajectory>, params?: {
+ model?: StateTransformer.Params<StateTransforms['Model']['ModelFromTrajectory']>,
+ properties?: boolean | StateTransformer.Params<StateTransforms['Model']['CustomModelProperties']>
+ }) {
const state = this.dataState;
- if (supportProps) {
- const model = state.build().to(trajectory)
- .apply(StateTransforms.Model.ModelFromTrajectory, params || { modelIndex: 0 })
- .apply(StateTransforms.Model.CustomModelProperties, void 0, { tags: [StructureBuilderTags.Model, StructureBuilderTags.ModelProperties] });
- await this.plugin.runTask(this.dataState.updateTree(model, { revertOnError: true }));
- return model.selector;
- } else {
- const model = state.build().to(trajectory)
- .apply(StateTransforms.Model.ModelFromTrajectory, params || { modelIndex: 0 }, { tags: StructureBuilderTags.Model });
- await this.plugin.runTask(this.dataState.updateTree(model, { revertOnError: true }));
- return model.selector;
- }
+
+ const model = state.build().to(trajectory)
+ .apply(StateTransforms.Model.ModelFromTrajectory, params?.model || void 0, { tags: StructureBuilderTags.Model });
+
+ const props = !!params?.properties
+ ? model.apply(StateTransforms.Model.CustomModelProperties, typeof params?.properties !== 'boolean' ? params?.properties : void 0, { tags: StructureBuilderTags.ModelProperties, isDecorator: true })
+ : void 0;
+
+ await this.plugin.runTask(this.dataState.updateTree(model, { revertOnError: true }));
+
+ const modelSelector = model.selector, propertiesSelector = props?.selector;
+
+ return { model: propertiesSelector || modelSelector, index: modelSelector, properties: propertiesSelector };
}
- async createStructure(model: StateObjectRef<SO.Molecule.Model>, params?: RootStructureDefinition.Params) {
+ async createStructure(model: StateObjectRef<SO.Molecule.Model>, params?: {
+ structure?: RootStructureDefinition.Params,
+ properties?: boolean | StateTransformer.Params<StateTransforms['Model']['CustomStructureProperties']>
+ }) {
const state = this.dataState;
const structure = state.build().to(model)
- .apply(StateTransforms.Model.StructureFromModel, { type: params || { name: 'assembly', params: { } } }, { tags: StructureBuilderTags.Structure });
+ .apply(StateTransforms.Model.StructureFromModel, { type: params?.structure || { name: 'assembly', params: { } } }, { tags: StructureBuilderTags.Structure });
+
+ const props = !!params?.properties
+ ? structure.apply(StateTransforms.Model.CustomStructureProperties, typeof params?.properties !== 'boolean' ? params?.properties : void 0, { tags: StructureBuilderTags.StructureProperties, isDecorator: true })
+ : void 0;
+
await this.plugin.runTask(this.dataState.updateTree(structure, { revertOnError: true }));
- return structure.selector;
+
+ const structureSelector = structure.selector, propertiesSelector = props?.selector;
+
+ return { structure: propertiesSelector || structureSelector, definition: structureSelector, properties: propertiesSelector };
}
/** returns undefined if the component is empty/null */
diff --git a/src/mol-plugin-state/transforms/model.ts b/src/mol-plugin-state/transforms/model.ts
index 2b2158fec..5d26e1997 100644
--- a/src/mol-plugin-state/transforms/model.ts
+++ b/src/mol-plugin-state/transforms/model.ts
@@ -689,7 +689,7 @@ const StructureComponent = PluginStateTransform.BuiltIn({
type CustomModelProperties = typeof CustomModelProperties
const CustomModelProperties = PluginStateTransform.BuiltIn({
name: 'custom-model-properties',
- display: { name: 'Custom Properties' },
+ display: { name: 'Custom Model Properties' },
from: SO.Molecule.Model,
to: SO.Molecule.Model,
params: (a, ctx: PluginContext) => {
@@ -699,7 +699,7 @@ const CustomModelProperties = PluginStateTransform.BuiltIn({
apply({ a, params }, ctx: PluginContext) {
return Task.create('Custom Props', async taskCtx => {
await attachModelProps(a.data, ctx, taskCtx, params);
- return new SO.Molecule.Model(a.data, { label: 'Model Props' });
+ return a;
});
},
update({ a, oldParams, newParams }, ctx: PluginContext) {
@@ -745,7 +745,7 @@ const CustomStructureProperties = PluginStateTransform.BuiltIn({
apply({ a, params }, ctx: PluginContext) {
return Task.create('Custom Props', async taskCtx => {
await attachStructureProps(a.data, ctx, taskCtx, params);
- return new SO.Molecule.Structure(a.data, { label: 'Structure Props' });
+ return a;
});
},
update({ a, oldParams, newParams }, ctx: PluginContext) {
diff --git a/src/mol-plugin-ui/controls/common.tsx b/src/mol-plugin-ui/controls/common.tsx
index 575c3e98d..96960b539 100644
--- a/src/mol-plugin-ui/controls/common.tsx
+++ b/src/mol-plugin-ui/controls/common.tsx
@@ -328,4 +328,27 @@ export class ToggleButton extends React.PureComponent<ToggleButtonProps> {
{this.props.isSelected ? <b>{label}</b> : label}
</button>;
}
+}
+
+export class ExpandGroup extends React.PureComponent<{ header: string, initiallyExpanded?: boolean, noOffset?: boolean }, { isExpanded: boolean }> {
+ state = { isExpanded: !!this.props.initiallyExpanded };
+
+ toggleExpanded = () => this.setState({ isExpanded: !this.state.isExpanded });
+
+ render() {
+ return <>
+ <div className='msp-control-group-header' style={{ marginTop: '1px' }}>
+ <button className='msp-btn msp-btn-block' onClick={this.toggleExpanded}>
+ <Icon name={this.state.isExpanded ? 'collapse' : 'expand'} />
+ {this.props.header}
+ </button>
+ </div>
+ {this.state.isExpanded &&
+ (this.props.noOffset
+ ? this.props.children
+ : <div className='msp-control-offset'>
+ {this.props.children}
+ </div>)}
+ </>;
+ }
}
\ No newline at end of file
diff --git a/src/mol-plugin-ui/controls/parameters.tsx b/src/mol-plugin-ui/controls/parameters.tsx
index 66fecd1f3..666838216 100644
--- a/src/mol-plugin-ui/controls/parameters.tsx
+++ b/src/mol-plugin-ui/controls/parameters.tsx
@@ -14,7 +14,7 @@ import { camelCaseToWords } from '../../mol-util/string';
import * as React from 'react';
import LineGraphComponent from './line-graph/line-graph-component';
import { Slider, Slider2 } from './slider';
-import { NumericInput, IconButton, ControlGroup, ToggleButton } from './common';
+import { NumericInput, IconButton, ControlGroup, ToggleButton, ExpandGroup } from './common';
import { _Props, _State, PluginUIComponent } from '../base';
import { legendFor } from './legend';
import { Legend as LegendData } from '../../mol-util/legend';
@@ -114,29 +114,6 @@ export class ParameterMappingControl<S, T> extends PluginUIComponent<{ mapping:
}
}
-class ExpandGroup extends React.PureComponent<{ header: string, initiallyExpanded?: boolean, noOffset?: boolean }, { isExpanded: boolean }> {
- state = { isExpanded: !!this.props.initiallyExpanded };
-
- toggleExpanded = () => this.setState({ isExpanded: !this.state.isExpanded });
-
- render() {
- return <>
- <div className='msp-control-group-header' style={{ marginTop: '1px' }}>
- <button className='msp-btn msp-btn-block' onClick={this.toggleExpanded}>
- <Icon name={this.state.isExpanded ? 'collapse' : 'expand'} />
- {this.props.header}
- </button>
- </div>
- {this.state.isExpanded &&
- (this.props.noOffset
- ? this.props.children
- : <div className='msp-control-offset'>
- {this.props.children}
- </div>)}
- </>;
- }
-}
-
type ParamInfo = [string, PD.Any, ParamControl];
function classifyParams(params: PD.Params) {
function addParam(k: string, p: PD.Any, group: typeof essentials) {
diff --git a/src/mol-plugin-ui/plugin.tsx b/src/mol-plugin-ui/plugin.tsx
index 6458d1cf7..9ce69a933 100644
--- a/src/mol-plugin-ui/plugin.tsx
+++ b/src/mol-plugin-ui/plugin.tsx
@@ -19,8 +19,9 @@ import { StateTransform } from '../mol-state';
import { UpdateTransformControl } from './state/update-transform';
import { SequenceView } from './sequence';
import { Toasts } from './toast';
-import { SectionHeader } from './controls/common';
+import { SectionHeader, ExpandGroup } from './controls/common';
import { LeftPanelControls } from './left-panel';
+import { StateTreeSpine } from '../mol-state/tree/spine';
export class Plugin extends React.Component<{ plugin: PluginContext }, {}> {
region(kind: 'left' | 'right' | 'bottom' | 'main', element: JSX.Element) {
@@ -228,16 +229,34 @@ export class CurrentObject extends PluginUIComponent {
if (!showActions) return null;
- return <>
- {(cell.status === 'ok' || cell.status === 'error') && <>
+ const actions = cell.status === 'ok' && <StateObjectActionSelect state={current.state} nodeRef={ref} plugin={this.plugin} />
+
+ if (cell.status === 'error') {
+ return <>
<SectionHeader icon='flow-cascade' title={`${cell.obj?.label || transform.transformer.definition.display.name}`} desc={transform.transformer.definition.display.name} />
<UpdateTransformControl state={current.state} transform={transform} customHeader='none' />
- </> }
- {cell.status === 'ok' &&
- <StateObjectActionSelect state={current.state} nodeRef={ref} plugin={this.plugin} />
- }
+ {actions}
+ </>;
+ }
+
+ if (cell.status !== 'ok') return null;
+
+ const decoratorChain = StateTreeSpine.getDecoratorChain(this.current.state, this.current.ref);
+ const parent = decoratorChain[decoratorChain.length - 1];
+
+ let decorators: JSX.Element[] | undefined = decoratorChain.length > 1 ? [] : void 0;
+ for (let i = decoratorChain.length - 2; i >= 0; i--) {
+ const d = decoratorChain[i];
+ decorators!.push(<ExpandGroup header={d.transform.transformer.definition.display.name}>
+ <UpdateTransformControl state={current.state} transform={d.transform} customHeader='none' />
+ </ExpandGroup>);
+ }
- {/* <StateObjectActions state={current.state} nodeRef={ref} initiallyCollapsed />} */}
+ return <>
+ <SectionHeader icon='flow-cascade' title={`${parent.obj?.label || parent.transform.transformer.definition.display.name}`} desc={parent.transform.transformer.definition.display.name} />
+ <UpdateTransformControl state={current.state} transform={parent.transform} customHeader='none' />
+ {decorators && <div className='msp-controls-section'>{decorators}</div>}
+ {actions}
</>;
}
}
\ No newline at end of file
diff --git a/src/mol-plugin-ui/state/tree.tsx b/src/mol-plugin-ui/state/tree.tsx
index 9c7a88d25..9ab15d24e 100644
--- a/src/mol-plugin-ui/state/tree.tsx
+++ b/src/mol-plugin-ui/state/tree.tsx
@@ -6,7 +6,7 @@
import * as React from 'react';
import { PluginStateObject } from '../../mol-plugin-state/objects';
-import { State, StateObject, StateTransform, StateObjectCell } from '../../mol-state'
+import { State, StateTree as _StateTree, StateObject, StateTransform, StateObjectCell } from '../../mol-state'
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginUIComponent, _Props, _State } from '../base';
import { Icon } from '../controls/icons';
@@ -85,6 +85,12 @@ class StateTreeNode extends PluginUIComponent<{ cell: StateObjectCell, depth: nu
return { isCollapsed: !!props.cell.state.isCollapsed };
}
+ hasDecorator(children: _StateTree.ChildSet) {
+ if (children.size !== 1) return false;
+ const ref = children.values().next().value;
+ return !!this.props.cell.parent.tree.transforms.get(ref).isDecorator;
+ }
+
render() {
const cell = this.props.cell;
if (!cell || cell.obj === StateObject.Null || !cell.parent.tree.transforms.has(cell.transform.ref)) {
@@ -92,17 +98,17 @@ class StateTreeNode extends PluginUIComponent<{ cell: StateObjectCell, depth: nu
}
const cellState = cell.state;
- const showLabel = (cell.transform.ref !== StateTransform.RootRef) && (cell.status !== 'ok' || !cell.state.isGhost);
const children = cell.parent.tree.children.get(this.ref);
- const newDepth = showLabel ? this.props.depth + 1 : this.props.depth;
-
+ const showLabel = (cell.transform.ref !== StateTransform.RootRef) && (cell.status !== 'ok' || (!cell.state.isGhost && !this.hasDecorator(children)));
+
if (!showLabel) {
if (children.size === 0) return null;
return <div style={{ display: cellState.isCollapsed ? 'none' : 'block' }}>
- {children.map(c => <StateTreeNode cell={cell.parent.cells.get(c!)!} key={c} depth={newDepth} />)}
+ {children.map(c => <StateTreeNode cell={cell.parent.cells.get(c!)!} key={c} depth={this.props.depth} />)}
</div>;
}
-
+
+ const newDepth = this.props.depth + 1;
return <>
<StateTreeNodeLabel cell={cell} depth={this.props.depth} />
{children.size === 0
diff --git a/src/mol-state/state/builder.ts b/src/mol-state/state/builder.ts
index b35f02d89..d00baa243 100644
--- a/src/mol-state/state/builder.ts
+++ b/src/mol-state/state/builder.ts
@@ -115,6 +115,11 @@ namespace StateBuilder {
* If no params are specified (params <- undefined), default params are lazily resolved.
*/
apply<T extends StateTransformer<A, any, any>>(tr: T, params?: StateTransformer.Params<T>, options?: Partial<StateTransform.Options>): To<StateTransformer.To<T>, T> {
+ if (options?.isDecorator) {
+ const children = this.state.tree.children.get(this.ref);
+ if (children.size > 0) throw new Error('Decorators can only be applied to childless nodes.');
+ }
+
const t = tr.apply(this.ref, params, options);
this.state.tree.add(t);
this.editInfo.count++;
diff --git a/src/mol-state/transform.ts b/src/mol-state/transform.ts
index 864147950..bb51b0ed5 100644
--- a/src/mol-state/transform.ts
+++ b/src/mol-state/transform.ts
@@ -14,6 +14,7 @@ interface Transform<T extends StateTransformer = StateTransformer> {
readonly transformer: T,
readonly state: Transform.State,
readonly tags?: string[],
+ readonly isDecorator?: boolean,
readonly ref: Transform.Ref,
/**
* Sibling-like dependency
@@ -90,6 +91,7 @@ namespace Transform {
export interface Options {
ref?: string,
tags?: string | string[],
+ isDecorator?: boolean,
state?: State,
dependsOn?: Ref[]
}
@@ -105,8 +107,9 @@ namespace Transform {
return {
parent,
transformer,
- state: (options && options.state) || { },
+ state: options?.state || { },
tags,
+ isDecorator: options?.isDecorator,
ref,
dependsOn: options && options.dependsOn,
params,
@@ -161,6 +164,7 @@ namespace Transform {
params: any,
state?: State,
tags?: string[],
+ isDecorator?: boolean,
ref: string,
dependsOn?: string[]
version: string
@@ -184,6 +188,7 @@ namespace Transform {
params: t.params ? pToJson(t.params) : void 0,
state,
tags: t.tags,
+ isDecorator: t.isDecorator || void 0,
ref: t.ref,
dependsOn: t.dependsOn,
version: t.version
@@ -201,6 +206,7 @@ namespace Transform {
params: t.params ? pFromJson(t.params) : void 0,
state: t.state || { },
tags: t.tags,
+ isDecorator: t.isDecorator,
ref: t.ref as Ref,
dependsOn: t.dependsOn,
version: t.version
diff --git a/src/mol-state/tree/spine.ts b/src/mol-state/tree/spine.ts
index 3faa3f7b3..c450cba00 100644
--- a/src/mol-state/tree/spine.ts
+++ b/src/mol-state/tree/spine.ts
@@ -49,8 +49,18 @@ namespace StateTreeSpine {
}
constructor(private cells: State.Cells) {
+ }
+ }
+ export function getDecoratorChain(state: State, currentRef: StateTransform.Ref): StateObjectCell[] {
+ const cells = state.cells;
+ let current = cells.get(currentRef)!;
+ const ret: StateObjectCell[] = [current];
+ while (current?.transform.isDecorator) {
+ current = cells.get(current.transform.parent)!;
+ ret.push(current);
}
+ return ret;
}
export function getRootOfType<T extends StateObject.Ctor>(state: State, t: T, ref: string): StateObject.From<T> | undefined {
--
GitLab