diff --git a/src/mol-plugin-state/helpers/root-structure.ts b/src/mol-plugin-state/helpers/root-structure.ts
index 73e8573f6c4dfe21481556c68244dc6158d59d40..a95104bce9bc8a3e651e5c6a89b3aa0f2676f0b3 100644
--- a/src/mol-plugin-state/helpers/root-structure.ts
+++ b/src/mol-plugin-state/helpers/root-structure.ts
@@ -47,7 +47,7 @@ export namespace RootStructureDefinition {
: PD.Text('', { label: 'Asm Id', description: 'Assembly Id (use empty for the 1st assembly)' }))
}, { isFlat: true }),
'symmetry-mates': PD.Group({
- radius: PD.Numeric(5)
+ radius: PD.Numeric(5, { min: 0, max: 50, step: 1 })
}, { isFlat: true }),
'symmetry': PD.Group({
ijkMin: PD.Vec3(Vec3.create(-1, -1, -1), { step: 1 }, { label: 'Min IJK', fieldLabels: { x: 'I', y: 'J', z: 'K' } }),
diff --git a/src/mol-plugin-state/manager/structure/hierarchy-state.ts b/src/mol-plugin-state/manager/structure/hierarchy-state.ts
index b543b03718631788e13f0de80bee70b0f5fb4626..3ab715cbfa1e29c29a7fe62ba06d01ebb75a7dc0 100644
--- a/src/mol-plugin-state/manager/structure/hierarchy-state.ts
+++ b/src/mol-plugin-state/manager/structure/hierarchy-state.ts
@@ -10,6 +10,8 @@ import { StructureBuilderTags } from '../../builder/structure';
import { RepresentationProviderTags } from '../../builder/structure/provider';
import { StructureRepresentationInteractionTags } from '../../../mol-plugin/behavior/dynamic/selection/structure-representation-interaction';
import { StateTransforms } from '../../transforms';
+import { VolumeStreaming } from '../../../mol-plugin/behavior/dynamic/volume-streaming/behavior';
+import { CreateVolumeStreamingBehavior } from '../../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
export function buildStructureHierarchy(state: State, previous?: StructureHierarchy) {
const build = BuildState(state, previous || StructureHierarchy());
@@ -39,7 +41,8 @@ interface RefBase<K extends string = string, O extends StateObject = StateObject
export type HierarchyRef =
| TrajectoryRef
| ModelRef | ModelPropertiesRef
- | StructureRef | StructurePropertiesRef | StructureComponentRef | StructureRepresentationRef
+ | StructureRef | StructurePropertiesRef | StructureVolumeStreamingRef | StructureComponentRef | StructureRepresentationRef
+ | GenericRepresentationRef
export interface TrajectoryRef extends RefBase<'trajectory', SO.Molecule.Trajectory> {
models: ModelRef[]
@@ -77,7 +80,7 @@ export interface StructureRef extends RefBase<'structure', SO.Molecule.Structure
surroundings?: StructureComponentRef,
},
genericRepresentations?: GenericRepresentationRef[],
- // volumeStreaming?: ....
+ volumeStreaming?: StructureVolumeStreamingRef
}
function StructureRef(cell: StateObjectCell<SO.Molecule.Structure>, model?: ModelRef): StructureRef {
@@ -92,6 +95,14 @@ function StructurePropertiesRef(cell: StateObjectCell<SO.Molecule.Structure>, st
return { kind: 'structure-properties', cell, version: cell.transform.version, structure };
}
+export interface StructureVolumeStreamingRef extends RefBase<'structure-volume-streaming', VolumeStreaming, CreateVolumeStreamingBehavior> {
+ structure: StructureRef
+}
+
+function StructureVolumeStreamingRef(cell: StateObjectCell<VolumeStreaming>, structure: StructureRef): StructureVolumeStreamingRef {
+ return { kind: 'structure-volume-streaming', cell, version: cell.transform.version, structure };
+}
+
export interface StructureComponentRef extends RefBase<'structure-component', SO.Molecule.Structure, StateTransforms['Model']['StructureComponent']> {
structure: StructureRef,
key?: string,
@@ -157,9 +168,10 @@ function createOrUpdateRefList<R extends HierarchyRef, C extends any[]>(state: B
return ref;
}
-function createOrUpdateRef<R extends HierarchyRef, C extends any[]>(state: BuildState, cell: StateObjectCell, old: R | undefined, ctor: (...args: C) => R, ...args: C) {
+function createOrUpdateRef<R extends HierarchyRef, C extends any[]>(state: BuildState, cell: StateObjectCell, ctor: (...args: C) => R, ...args: C) {
const ref: R = ctor(...args);
state.hierarchy.refs.set(cell.transform.ref, ref);
+ const old = state.oldHierarchy.refs.get(cell.transform.ref);
if (old) {
if (old.version !== cell.transform.version) state.updated.push(ref);
} else {
@@ -176,25 +188,25 @@ const tagMap: [string, (state: BuildState, cell: StateObjectCell) => boolean | v
if (state.currentTrajectory) {
state.currentModel = createOrUpdateRefList(state, cell, state.currentTrajectory.models, ModelRef, cell, state.currentTrajectory);
} else {
- state.currentModel = ModelRef(cell)
+ state.currentModel = createOrUpdateRef(state, cell, ModelRef, cell);
}
state.hierarchy.models.push(state.currentModel);
}, state => state.currentModel = void 0],
[StructureBuilderTags.ModelProperties, (state, cell) => {
if (!state.currentModel) return false;
- state.currentModel.properties = createOrUpdateRef(state, cell, state.currentModel.properties, ModelPropertiesRef, cell, state.currentModel);
+ state.currentModel.properties = createOrUpdateRef(state, cell, ModelPropertiesRef, cell, state.currentModel);
}, state => { }],
[StructureBuilderTags.Structure, (state, cell) => {
if (state.currentModel) {
state.currentStructure = createOrUpdateRefList(state, cell, state.currentModel.structures, StructureRef, cell, state.currentModel);
} else {
- state.currentStructure = StructureRef(cell);
+ state.currentStructure = createOrUpdateRef(state, cell, StructureRef, cell);
}
state.hierarchy.structures.push(state.currentStructure);
}, state => state.currentStructure = void 0],
[StructureBuilderTags.StructureProperties, (state, cell) => {
if (!state.currentStructure) return false;
- state.currentStructure.properties = createOrUpdateRef(state, cell, state.currentStructure.properties, StructurePropertiesRef, cell, state.currentStructure);
+ state.currentStructure.properties = createOrUpdateRef(state, cell, StructurePropertiesRef, cell, state.currentStructure);
}, state => { }],
[StructureBuilderTags.Component, (state, cell) => {
if (!state.currentStructure) return false;
@@ -207,13 +219,13 @@ const tagMap: [string, (state: BuildState, cell: StateObjectCell) => boolean | v
[StructureRepresentationInteractionTags.ResidueSel, (state, cell) => {
if (!state.currentStructure) return false;
if (!state.currentStructure.currentFocus) state.currentStructure.currentFocus = { };
- state.currentStructure.currentFocus.focus = StructureComponentRef(cell, state.currentStructure);
+ state.currentStructure.currentFocus.focus = createOrUpdateRef(state, cell, StructureComponentRef, cell, state.currentStructure);
state.currentComponent = state.currentStructure.currentFocus.focus;
}, state => state.currentComponent = void 0],
[StructureRepresentationInteractionTags.SurrSel, (state, cell) => {
if (!state.currentStructure) return false;
if (!state.currentStructure.currentFocus) state.currentStructure.currentFocus = { };
- state.currentStructure.currentFocus.surroundings = StructureComponentRef(cell, state.currentStructure);
+ state.currentStructure.currentFocus.surroundings = createOrUpdateRef(state, cell, StructureComponentRef, cell, state.currentStructure);
state.currentComponent = state.currentStructure.currentFocus.surroundings;
}, state => state.currentComponent = void 0]
]
@@ -257,8 +269,11 @@ function _doPreOrder(ctx: VisitorCtx, root: StateTransform) {
const genericTarget = state.currentComponent || state.currentModel || state.currentStructure;
if (genericTarget) {
if (!genericTarget.genericRepresentations) genericTarget.genericRepresentations = [];
- genericTarget.genericRepresentations.push(GenericRepresentationRef(cell, genericTarget));
+ genericTarget.genericRepresentations.push(createOrUpdateRef(state, cell, GenericRepresentationRef, cell, genericTarget));
}
+ } else if (state.currentStructure && VolumeStreaming.is(cell.obj)) {
+ state.currentStructure.volumeStreaming = createOrUpdateRef(state, cell, StructureVolumeStreamingRef, cell, state.currentStructure);
+ return;
}
const children = ctx.tree.children.get(root.ref);
diff --git a/src/mol-plugin-state/manager/structure/hierarchy.ts b/src/mol-plugin-state/manager/structure/hierarchy.ts
index 424dcaf04c65f89ab547ddd14db7fc4e1ccc0979..1eaf661d7af3af7e8ef3506e7e15bed8a462e024 100644
--- a/src/mol-plugin-state/manager/structure/hierarchy.ts
+++ b/src/mol-plugin-state/manager/structure/hierarchy.ts
@@ -21,7 +21,7 @@ interface StructureHierarchyManagerState {
export class StructureHierarchyManager extends PluginComponent<StructureHierarchyManagerState> {
readonly behaviors = {
- changed: this.ev.behavior({
+ selection: this.ev.behavior({
hierarchy: this.state.hierarchy,
trajectories: this.state.selection.trajectories,
models: this.state.selection.models,
@@ -101,7 +101,7 @@ export class StructureHierarchyManager extends PluginComponent<StructureHierarch
this.nextSelection.clear();
this.updateState({ hierarchy, selection: { trajectories, models, structures } });
- this.behaviors.changed.next({ hierarchy, trajectories, models, structures });
+ this.behaviors.selection.next({ hierarchy, trajectories, models, structures });
}
updateCurrent(refs: HierarchyRef[], action: 'add' | 'remove') {
@@ -132,7 +132,7 @@ export class StructureHierarchyManager extends PluginComponent<StructureHierarch
// if (structures.length === 0 && hierarchy.structures.length > 0) structures.push(hierarchy.structures[0]);
this.updateState({ selection: { trajectories, models, structures } });
- this.behaviors.changed.next({ hierarchy, trajectories, models, structures });
+ this.behaviors.selection.next({ hierarchy, trajectories, models, structures });
}
remove(refs: HierarchyRef[], canUndo?: boolean) {
diff --git a/src/mol-plugin-ui/base.tsx b/src/mol-plugin-ui/base.tsx
index 8fc03d1ea70047b0b1237792f1dbdd423922e7b4..200231b0f6ecc4b2f495cee9a6fb5893fa6375ec 100644
--- a/src/mol-plugin-ui/base.tsx
+++ b/src/mol-plugin-ui/base.tsx
@@ -70,7 +70,7 @@ export type _State<C extends React.Component> = C extends React.Component<any, i
//
export type CollapsableProps = { initiallyCollapsed?: boolean, header?: string }
-export type CollapsableState = { isCollapsed: boolean, header: string }
+export type CollapsableState = { isCollapsed: boolean, header: string, isHidden?: boolean }
export abstract class CollapsableControls<P = {}, S = {}, SS = {}> extends PluginUIComponent<P & CollapsableProps, S & CollapsableState, SS> {
toggleCollapsed = () => {
@@ -81,6 +81,8 @@ export abstract class CollapsableControls<P = {}, S = {}, SS = {}> extends Plugi
protected abstract renderControls(): JSX.Element | null
render() {
+ if (this.state.isHidden) return null;
+
const wrapClass = this.state.isCollapsed
? 'msp-transform-wrapper msp-transform-wrapper-collapsed'
: 'msp-transform-wrapper';
diff --git a/src/mol-plugin-ui/controls.tsx b/src/mol-plugin-ui/controls.tsx
index 9c1f88fe40d9090f8591d8a37f12ac87a16deff6..71fb91e96116eaa70bc8e3bf0162ead105f023a0 100644
--- a/src/mol-plugin-ui/controls.tsx
+++ b/src/mol-plugin-ui/controls.tsx
@@ -21,6 +21,7 @@ import { StructureMeasurementsControls } from './structure/measurements';
import { Icon } from './controls/icons';
import { StructureComponentControls } from './structure/components';
import { StructureSourceControls } from './structure/source';
+import { VolumeStreamingControls } from './structure/volume';
export class TrajectoryViewportControls extends PluginUIComponent<{}, { show: boolean, label: string }> {
state = { show: false, label: '' }
@@ -272,6 +273,7 @@ export class DefaultStructureTools extends PluginUIComponent {
<StructureSelectionControls />
<StructureComponentControls />
<StructureMeasurementsControls />
+ <VolumeStreamingControls />
</>;
}
}
diff --git a/src/mol-plugin-ui/controls/parameters.tsx b/src/mol-plugin-ui/controls/parameters.tsx
index dad863d979764c35da0302461e5416ace10cdb15..7b8d954b7a90968be73a3d09bb6dd3587a9b1a6c 100644
--- a/src/mol-plugin-ui/controls/parameters.tsx
+++ b/src/mol-plugin-ui/controls/parameters.tsx
@@ -857,6 +857,13 @@ export class MappedControl extends React.PureComponent<ParamProps<PD.Mapped<any>
toggleExpanded = () => this.setState({ isExpanded: !this.state.isExpanded });
+ areParamsEmpty(params: PD.Params) {
+ for (const k of Object.keys(params)) {
+ if (!params[k].isHidden) return false;
+ }
+ return true;
+ }
+
render() {
const value: PD.Mapped<any>['defaultValue'] = this.props.value;
const param = this.props.param.map(value.name);
@@ -880,7 +887,7 @@ export class MappedControl extends React.PureComponent<ParamProps<PD.Mapped<any>
}
if (param.type === 'group' && !param.isFlat) {
- if (Object.keys(param.params).length > 0) {
+ if (!this.areParamsEmpty(param.params)) {
return <div className='msp-mapped-parameter-group'>
{Select}
<IconButton icon='dot-3' onClick={this.toggleExpanded} toggleState={this.state.isExpanded} title={`${label} Properties`} />
@@ -1036,12 +1043,12 @@ export class ObjectListControl extends React.PureComponent<ParamProps<PD.ObjectL
<div className='msp-control-row'>
<span>{label}</span>
<div>
- <button onClick={this.toggleExpanded}>{value}</button>
+ <button onClick={this.toggleExpanded} disabled={this.props.isDisabled}>{value}</button>
</div>
</div>
{this.state.isExpanded && <div className='msp-control-offset'>
- {this.props.value.map((v, i) => <ObjectListItem key={i} param={this.props.param} value={v} index={i} actions={this.actions} />)}
+ {this.props.value.map((v, i) => <ObjectListItem key={i} param={this.props.param} value={v} index={i} actions={this.actions} isDisabled={this.props.isDisabled} />)}
<ControlGroup header='New Item'>
<ObjectListEditor params={this.props.param.element} apply={this.add} value={this.props.param.ctor()} isDisabled={this.props.isDisabled} />
</ControlGroup>
diff --git a/src/mol-plugin-ui/custom/volume.tsx b/src/mol-plugin-ui/custom/volume.tsx
index 95fd9cec46b34556ea605d1d545a68686c8e8324..939fb0fd7cce89c6fdaf2f7f2878aee8c55d87b1 100644
--- a/src/mol-plugin-ui/custom/volume.tsx
+++ b/src/mol-plugin-ui/custom/volume.tsx
@@ -38,7 +38,7 @@ function Channel(props: {
const channel = props.channels[props.name]!;
const { min, max, mean, sigma } = stats;
- const value = channel.isoValue.kind === 'relative' ? channel.isoValue.relativeValue : channel.isoValue.absoluteValue;
+ const value = Math.round(100 * (channel.isoValue.kind === 'relative' ? channel.isoValue.relativeValue : channel.isoValue.absoluteValue)) / 100;
const relMin = (min - mean) / sigma;
const relMax = (max - mean) / sigma;
const step = toPrecision(isRelative ? Math.round(((max - min) / sigma)) / 100 : sigma / 100, 2)
@@ -180,25 +180,30 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
const sampling = b.info.header.sampling[0];
- // TODO: factor common things out
+ const isRelativeParam = PD.Boolean(isRelative, { description: 'Use normalized or absolute isocontour scale.', label: 'Normalized' });
+
+ const isOff = params.entry.params.view.name === 'off';
+ // TODO: factor common things out, cache
const OptionsParams = {
- entry: PD.Select(params.entry.name, b.data.entries.map(info => [info.dataId, info.dataId] as [string, string]), { description: 'Which entry with volume data to display.' }),
+ entry: PD.Select(params.entry.name, b.data.entries.map(info => [info.dataId, info.dataId] as [string, string]), { isHidden: isOff, description: 'Which entry with volume data to display.' }),
view: PD.MappedStatic(params.entry.params.view.name, {
- 'off': PD.Group({}, { description: 'Display off.' }),
+ 'off': PD.Group({
+ isRelative: PD.Boolean(isRelative, { isHidden: true })
+ }, { description: 'Display off.' }),
'box': PD.Group({
bottomLeft: PD.Vec3(Vec3.zero()),
topRight: PD.Vec3(Vec3.zero()),
detailLevel,
- isRelative: PD.Boolean(isRelative, { description: 'Use normalized or absolute isocontour scale.', label: 'Normalized' })
+ isRelative: isRelativeParam
}, { description: 'Static box defined by cartesian coords.' }),
'selection-box': PD.Group({
radius: PD.Numeric(5, { min: 0, max: 50, step: 0.5 }, { description: 'Radius in \u212B within which the volume is shown.' }),
detailLevel,
- isRelative: PD.Boolean(isRelative, { description: 'Use normalized or absolute isocontour scale.', label: 'Normalized' })
+ isRelative: isRelativeParam
}, { description: 'Box around last-interacted element.' }),
'cell': PD.Group({
detailLevel,
- isRelative: PD.Boolean(isRelative, { description: 'Use normalized or absolute isocontour scale.', label: 'Normalized' })
+ isRelative: isRelativeParam
}, { description: 'Box around the structure\'s bounding box.' }),
// 'auto': PD.Group({ }), // TODO based on camera distance/active selection/whatever, show whole structure or slice.
}, { options: VolumeStreaming.ViewTypeOptions, description: 'Controls what of the volume is displayed. "Off" hides the volume alltogether. "Bounded box" shows the volume inside the given box. "Around Interaction" shows the volume around the element/atom last interacted with. "Whole Structure" shows the volume for the whole structure.' })
@@ -217,6 +222,10 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
}
};
+ if (isOff) {
+ return <ParameterControls onChange={this.changeOption} params={OptionsParams} values={options} onEnter={this.props.events.onEnter} />;
+ }
+
return <>
{!isEM && <Channel label='2Fo-Fc' name='2fo-fc' channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[0]} />}
{!isEM && <Channel label='Fo-Fc(+ve)' name='fo-fc(+ve)' channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[1]} />}
diff --git a/src/mol-plugin-ui/state/actions.tsx b/src/mol-plugin-ui/state/actions.tsx
index b0d554766bd931172a4e91100dfc57f260ebd852..246c0d7fbd79664b3744759a6e94455c01ac0944 100644
--- a/src/mol-plugin-ui/state/actions.tsx
+++ b/src/mol-plugin-ui/state/actions.tsx
@@ -5,11 +5,10 @@
*/
import * as React from 'react';
+import { State } from '../../mol-state';
import { PluginUIComponent } from '../base';
-import { ApplyActionControl } from './apply-action';
-import { State, StateAction } from '../../mol-state';
import { Icon } from '../controls/icons';
-import { PluginContext } from '../../mol-plugin/context';
+import { ApplyActionControl } from './apply-action';
export class StateObjectActions extends PluginUIComponent<{ state: State, nodeRef: string, hideHeader?: boolean, initiallyCollapsed?: boolean, alwaysExpandFirst?: boolean }> {
get current() {
@@ -41,86 +40,86 @@ export class StateObjectActions extends PluginUIComponent<{ state: State, nodeRe
return <div className='msp-state-actions'>
{!this.props.hideHeader && <div className='msp-section-header'><Icon name='code' /> {`Actions (${display})`}</div> }
{actions.map((act, i) => <ApplyActionControl
- plugin={this.plugin} key={`${act.id}`} state={state} action={act} nodeRef={ref}
+ key={`${act.id}`} state={state} action={act} nodeRef={ref}
initiallyCollapsed={i === 0 ? !this.props.alwaysExpandFirst && this.props.initiallyCollapsed : this.props.initiallyCollapsed} />)}
</div>;
}
}
-interface StateObjectActionSelectProps {
- plugin: PluginContext,
- state: State,
- nodeRef: string
-}
-
-interface StateObjectActionSelectState {
- state: State,
- nodeRef: string,
- version: string,
- actions: readonly StateAction[],
- currentActionIndex: number
-}
-
-function createStateObjectActionSelectState(props: StateObjectActionSelectProps): StateObjectActionSelectState {
- const cell = props.state.cells.get(props.nodeRef)!;
- const actions = [...props.state.actions.fromCell(cell, props.plugin)];
- actions.sort((a, b) => a.definition.display.name < b.definition.display.name ? -1 : a.definition.display.name === b.definition.display.name ? 0 : 1);
- return {
- state: props.state,
- nodeRef: props.nodeRef,
- version: cell.transform.version,
- actions,
- currentActionIndex: -1
- }
-}
-
-export class StateObjectActionSelect extends PluginUIComponent<StateObjectActionSelectProps, StateObjectActionSelectState> {
- state = createStateObjectActionSelectState(this.props);
-
- get current() {
- return this.plugin.state.behavior.currentObject.value;
- }
-
- static getDerivedStateFromProps(props: StateObjectActionSelectProps, state: StateObjectActionSelectState) {
- if (state.state !== props.state || state.nodeRef !== props.nodeRef) return createStateObjectActionSelectState(props);
- const cell = props.state.cells.get(props.nodeRef)!;
- if (cell.transform.version !== state.version) return createStateObjectActionSelectState(props);
- return null;
- }
-
- componentDidMount() {
- // TODO: handle tree change: some state actions might become invalid
- // this.subscribe(this.props.state.events.changed, o => {
- // this.setState(createStateObjectActionSelectState(this.props));
- // });
-
- this.subscribe(this.plugin.events.state.object.updated, ({ ref, state }) => {
- const current = this.current;
- if (current.ref !== ref || current.state !== state) return;
- this.setState(createStateObjectActionSelectState(this.props));
- });
- }
-
- onChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
- this.setState({ currentActionIndex: parseInt(e.target.value, 10) });
- }
-
- render() {
- const actions = this.state.actions;
- if (actions.length === 0) return null;
-
- const current = this.state.currentActionIndex >= 0 && actions[this.state.currentActionIndex];
- const title = current ? current.definition.display.description : 'Select Action';
-
- return <>
- <div className='msp-contol-row msp-action-select'>
- <select className='msp-form-control' title={title} value={this.state.currentActionIndex} onChange={this.onChange} style={{ fontWeight: 'bold' }}>
- <option key={-1} value={-1} style={{ color: '#999' }}>[ Select Action ]</option>
- {actions.map((a, i) => <option key={i} value={i}>{a.definition.display.name}</option>)}
- </select>
- <Icon name='flow-tree' />
- </div>
- {current && <ApplyActionControl key={current.id} plugin={this.plugin} state={this.props.state} action={current} nodeRef={this.props.nodeRef} hideHeader />}
- </>;
- }
-}
\ No newline at end of file
+// interface StateObjectActionSelectProps {
+// plugin: PluginContext,
+// state: State,
+// nodeRef: string
+// }
+
+// interface StateObjectActionSelectState {
+// state: State,
+// nodeRef: string,
+// version: string,
+// actions: readonly StateAction[],
+// currentActionIndex: number
+// }
+
+// function createStateObjectActionSelectState(props: StateObjectActionSelectProps): StateObjectActionSelectState {
+// const cell = props.state.cells.get(props.nodeRef)!;
+// const actions = [...props.state.actions.fromCell(cell, props.plugin)];
+// actions.sort((a, b) => a.definition.display.name < b.definition.display.name ? -1 : a.definition.display.name === b.definition.display.name ? 0 : 1);
+// return {
+// state: props.state,
+// nodeRef: props.nodeRef,
+// version: cell.transform.version,
+// actions,
+// currentActionIndex: -1
+// }
+// }
+
+// export class StateObjectActionSelect extends PluginUIComponent<StateObjectActionSelectProps, StateObjectActionSelectState> {
+// state = createStateObjectActionSelectState(this.props);
+
+// get current() {
+// return this.plugin.state.behavior.currentObject.value;
+// }
+
+// static getDerivedStateFromProps(props: StateObjectActionSelectProps, state: StateObjectActionSelectState) {
+// if (state.state !== props.state || state.nodeRef !== props.nodeRef) return createStateObjectActionSelectState(props);
+// const cell = props.state.cells.get(props.nodeRef)!;
+// if (cell.transform.version !== state.version) return createStateObjectActionSelectState(props);
+// return null;
+// }
+
+// componentDidMount() {
+// // TODO: handle tree change: some state actions might become invalid
+// // this.subscribe(this.props.state.events.changed, o => {
+// // this.setState(createStateObjectActionSelectState(this.props));
+// // });
+
+// this.subscribe(this.plugin.events.state.object.updated, ({ ref, state }) => {
+// const current = this.current;
+// if (current.ref !== ref || current.state !== state) return;
+// this.setState(createStateObjectActionSelectState(this.props));
+// });
+// }
+
+// onChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
+// this.setState({ currentActionIndex: parseInt(e.target.value, 10) });
+// }
+
+// render() {
+// const actions = this.state.actions;
+// if (actions.length === 0) return null;
+
+// const current = this.state.currentActionIndex >= 0 && actions[this.state.currentActionIndex];
+// const title = current ? current.definition.display.description : 'Select Action';
+
+// return <>
+// <div className='msp-contol-row msp-action-select'>
+// <select className='msp-form-control' title={title} value={this.state.currentActionIndex} onChange={this.onChange} style={{ fontWeight: 'bold' }}>
+// <option key={-1} value={-1} style={{ color: '#999' }}>[ Select Action ]</option>
+// {actions.map((a, i) => <option key={i} value={i}>{a.definition.display.name}</option>)}
+// </select>
+// <Icon name='flow-tree' />
+// </div>
+// {current && <ApplyActionControl key={current.id} plugin={this.plugin} state={this.props.state} action={current} nodeRef={this.props.nodeRef} hideHeader />}
+// </>;
+// }
+// }
\ No newline at end of file
diff --git a/src/mol-plugin-ui/state/apply-action.tsx b/src/mol-plugin-ui/state/apply-action.tsx
index f1149642587ed58e346e133197f1edab3c0a2eae..a1140e10872f1f91bb295bb0fb9b4d16362f8567 100644
--- a/src/mol-plugin-ui/state/apply-action.tsx
+++ b/src/mol-plugin-ui/state/apply-action.tsx
@@ -10,20 +10,19 @@ import { State, StateTransform, StateAction } from '../../mol-state';
import { memoizeLatest } from '../../mol-util/memoize';
import { StateTransformParameters, TransformControlBase } from './common';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
-
export { ApplyActionControl };
namespace ApplyActionControl {
export interface Props {
- plugin: PluginContext,
nodeRef: StateTransform.Ref,
state: State,
action: StateAction,
hideHeader?: boolean,
initiallyCollapsed?: boolean
}
-
+
export interface ComponentState {
+ plugin: PluginContext,
ref: StateTransform.Ref,
version: string,
params: any,
@@ -52,7 +51,7 @@ class ApplyActionControl extends TransformControlBase<ApplyActionControl.Props,
private _getInfo = memoizeLatest((t: StateTransform.Ref, v: string) => StateTransformParameters.infoFromAction(this.plugin, this.props.state, this.props.action, this.props.nodeRef));
- state = { ref: this.props.nodeRef, version: this.props.state.transforms.get(this.props.nodeRef).version, error: void 0, isInitial: true, params: this.getInfo().initialValues, busy: false, isCollapsed: this.props.initiallyCollapsed };
+ state = { plugin: this.plugin, ref: this.props.nodeRef, version: this.props.state.transforms.get(this.props.nodeRef).version, error: void 0, isInitial: true, params: this.getInfo().initialValues, busy: false, isCollapsed: this.props.initiallyCollapsed };
static getDerivedStateFromProps(props: ApplyActionControl.Props, state: ApplyActionControl.ComponentState) {
if (props.nodeRef === state.ref) return null;
@@ -61,10 +60,11 @@ class ApplyActionControl extends TransformControlBase<ApplyActionControl.Props,
const source = props.state.cells.get(props.nodeRef)!.obj!;
const params = props.action.definition.params
- ? PD.getDefaultValues(props.action.definition.params(source, props.plugin))
+ ? PD.getDefaultValues(props.action.definition.params(source, state.plugin))
: { };
const newState: Partial<ApplyActionControl.ComponentState> = {
+ plugin: state.plugin,
ref: props.nodeRef,
version,
params,
diff --git a/src/mol-plugin-ui/state/common.tsx b/src/mol-plugin-ui/state/common.tsx
index b799ce30ff0c145ed05ca98ac1407584d827ce4c..b97943573203cf26fe717ce180028eb11abafc6f 100644
--- a/src/mol-plugin-ui/state/common.tsx
+++ b/src/mol-plugin-ui/state/common.tsx
@@ -11,8 +11,8 @@ import { ParameterControls, ParamOnChange } from '../controls/parameters';
import { PluginContext } from '../../mol-plugin/context';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Subject } from 'rxjs';
-import { Icon } from '../controls/icons';
-import { ExpandGroup } from '../controls/common';
+import { Icon, IconName } from '../controls/icons';
+import { ExpandGroup, ToggleButton } from '../controls/common';
export { StateTransformParameters, TransformControlBase };
@@ -99,9 +99,18 @@ namespace TransformControlBase {
simpleOnly?: boolean,
isCollapsed?: boolean
}
+
+ export interface CommonProps {
+ simpleApply?: { header: string, icon: IconName },
+ noMargin?: boolean,
+ applyLabel?: string,
+ onApply?: () => void,
+ autoHideApply?: boolean,
+ wrapInExpander?: boolean
+ }
}
-abstract class TransformControlBase<P, S extends TransformControlBase.ComponentState> extends PurePluginUIComponent<P & { noMargin?: boolean, applyLabel?: string, onApply?: () => void, wrapInExpander?: boolean }, S> {
+abstract class TransformControlBase<P, S extends TransformControlBase.ComponentState> extends PurePluginUIComponent<P & TransformControlBase.CommonProps, S> {
abstract applyAction(): Promise<void>;
abstract getInfo(): StateTransformParameters.Props['info'];
abstract getHeader(): StateTransformer.Definition['display'] | 'none';
@@ -154,6 +163,10 @@ abstract class TransformControlBase<P, S extends TransformControlBase.ComponentS
}
}
+ componentDidMount() {
+ this.subscribe(this.plugin.behaviors.state.isBusy, b => this.busy.next(b));
+ }
+
init() {
this.busy = new Subject();
this.subscribe(this.busy, busy => this.setState({ busy }));
@@ -173,7 +186,27 @@ abstract class TransformControlBase<P, S extends TransformControlBase.ComponentS
this.setState({ isCollapsed: !this.state.isCollapsed });
}
- render() {
+ renderApply() {
+ const showBack = this.isUpdate() && !(this.state.busy || this.state.isInitial);
+ const canApply = this.canApply();
+
+ return this.props.autoHideApply && !canApply
+ ? null
+ : <div className='msp-transform-apply-wrap'>
+ <button className='msp-btn msp-btn-block msp-transform-default-params' onClick={this.setDefault} disabled={this.state.busy} title='Set default params'><Icon name='cw' /></button>
+ {showBack && <button className='msp-btn msp-btn-block msp-transform-refresh msp-form-control' title='Refresh params' onClick={this.refresh} disabled={this.state.busy || this.state.isInitial}>
+ <Icon name='back' /> Back
+ </button>}
+ <div className={`msp-transform-apply${!showBack ? ' msp-transform-apply-wider' : ''}`}>
+ <button className={`msp-btn msp-btn-block msp-btn-commit msp-btn-commit-${canApply ? 'on' : 'off'}`} onClick={this.apply} disabled={!canApply}>
+ {canApply && <Icon name='ok' />}
+ {this.props.applyLabel || this.applyText()}
+ </button>
+ </div>
+ </div>;
+ }
+
+ renderDefault() {
const info = this.getInfo();
const isEmpty = info.isEmpty && this.isUpdate();
@@ -189,8 +222,7 @@ abstract class TransformControlBase<P, S extends TransformControlBase.ComponentS
: 'msp-transform-wrapper';
const { a, b } = this.getSourceAndTarget();
-
- const showBack = this.isUpdate() && !(this.state.busy || this.state.isInitial);
+ const applyControl = this.renderApply();
const ctrl = <div className={wrapClass} style={{ marginBottom: this.props.noMargin ? 0 : void 0 }}>
{display !== 'none' && !this.props.wrapInExpander && <div className='msp-transform-header'>
@@ -201,19 +233,7 @@ abstract class TransformControlBase<P, S extends TransformControlBase.ComponentS
</div>}
{!isEmpty && !this.state.isCollapsed && <>
<ParamEditor info={info} a={a} b={b} events={this.events} params={this.state.params} isDisabled={this.state.busy} />
-
- <div className='msp-transform-apply-wrap'>
- <button className='msp-btn msp-btn-block msp-transform-default-params' onClick={this.setDefault} disabled={this.state.busy} title='Set default params'><Icon name='cw' /></button>
- {showBack && <button className='msp-btn msp-btn-block msp-transform-refresh msp-form-control' title='Refresh params' onClick={this.refresh} disabled={this.state.busy || this.state.isInitial}>
- <Icon name='back' /> Back
- </button>}
- <div className={`msp-transform-apply${!showBack ? ' msp-transform-apply-wider' : ''}`}>
- <button className={`msp-btn msp-btn-block msp-btn-commit msp-btn-commit-${this.canApply() ? 'on' : 'off'}`} onClick={this.apply} disabled={!this.canApply()}>
- {this.canApply() && <Icon name='ok' />}
- {this.props.applyLabel || this.applyText()}
- </button>
- </div>
- </div>
+ {applyControl}
</>}
</div>;
@@ -223,4 +243,33 @@ abstract class TransformControlBase<P, S extends TransformControlBase.ComponentS
{ctrl}
</ExpandGroup>;
}
+
+ renderSimple() {
+ const info = this.getInfo();
+ const canApply = this.canApply();
+ const apply = <div className='msp-control-row msp-select-row'>
+ <button disabled={this.state.busy || !canApply} onClick={this.apply}>
+ <Icon name={this.props.simpleApply?.icon} />
+ {this.props.simpleApply?.header}
+ </button>
+ {!info.isEmpty && <ToggleButton icon='cog' label='' title='Options' toggle={this.toggleExpanded} isSelected={!this.state.isCollapsed} disabled={this.state.busy} style={{ flex: '0 0 40px' }} />}
+ </div>
+
+ if (this.state.isCollapsed) return apply;
+
+ const tId = this.getTransformerId();
+ const ParamEditor: StateTransformParameters.Class = this.plugin.customParamEditors.has(tId)
+ ? this.plugin.customParamEditors.get(tId)!
+ : StateTransformParameters;
+ const { a, b } = this.getSourceAndTarget();
+
+ return <>
+ {apply}
+ <ParamEditor info={info} a={a} b={b} events={this.events} params={this.state.params} isDisabled={this.state.busy} />
+ </>
+ }
+
+ render() {
+ return this.props.simpleApply ? this.renderSimple() : this.renderDefault();
+ }
}
\ 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 b80f33007735e114627fef83688019a8897ec5b9..e4dda875a6fabd5802057e50a76ccee1a263b9b9 100644
--- a/src/mol-plugin-ui/state/tree.tsx
+++ b/src/mol-plugin-ui/state/tree.tsx
@@ -306,7 +306,7 @@ class StateTreeNodeLabel extends PluginUIComponent<{ cell: StateObjectCell, dept
return <div style={{ marginBottom: '1px' }}>
{row}
<ControlGroup header={`Apply ${this.state.currentAction.definition.display.name}`} initialExpanded={true} hideExpander={true} hideOffset={false} onHeaderClick={this.hideAction} topRightIcon='off'>
- <ApplyActionControl onApply={this.hideAction} plugin={this.plugin} state={this.props.cell.parent} action={this.state.currentAction} nodeRef={this.props.cell.transform.ref} hideHeader noMargin />
+ <ApplyActionControl onApply={this.hideAction} state={this.props.cell.parent} action={this.state.currentAction} nodeRef={this.props.cell.transform.ref} hideHeader noMargin />
</ControlGroup>
</div>
}
diff --git a/src/mol-plugin-ui/structure/components.tsx b/src/mol-plugin-ui/structure/components.tsx
index 1cb52fec1d7815e34207e5276c18e7979c137099..ff6ccc6a76180c180451bec6d26ea364051234ef 100644
--- a/src/mol-plugin-ui/structure/components.tsx
+++ b/src/mol-plugin-ui/structure/components.tsx
@@ -55,7 +55,7 @@ class ComponentEditorControls extends PurePluginUIComponent<{}, ComponentEditorC
}
componentDidMount() {
- this.subscribe(this.plugin.managers.structure.hierarchy.behaviors.changed, c => this.setState({
+ this.subscribe(this.plugin.managers.structure.hierarchy.behaviors.selection, c => this.setState({
action: this.state.action !== 'options' || c.structures.length === 0 ? void 0 : 'options',
isEmpty: c.structures.length === 0
}));
@@ -184,7 +184,7 @@ class ComponentOptionsControls extends PurePluginUIComponent<{ isDisabled: boole
class ComponentListControls extends PurePluginUIComponent {
get current() {
- return this.plugin.managers.structure.hierarchy.behaviors.changed;
+ return this.plugin.managers.structure.hierarchy.behaviors.selection;
}
componentDidMount() {
diff --git a/src/mol-plugin-ui/structure/selection.tsx b/src/mol-plugin-ui/structure/selection.tsx
index e8e6edd7b71be9db982ec5db741c7633375dbd8a..c08494fcd1e5412858bd6103be040b28d71889b7 100644
--- a/src/mol-plugin-ui/structure/selection.tsx
+++ b/src/mol-plugin-ui/structure/selection.tsx
@@ -54,7 +54,7 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
this.forceUpdate()
});
- this.subscribe(this.plugin.managers.structure.hierarchy.behaviors.changed, c => {
+ this.subscribe(this.plugin.managers.structure.hierarchy.behaviors.selection, c => {
const isEmpty = c.structures.length === 0;
if (this.state.isEmpty !== isEmpty) {
this.setState({ isEmpty });
diff --git a/src/mol-plugin-ui/structure/source.tsx b/src/mol-plugin-ui/structure/source.tsx
index c07b439b615d422cb9050f2f561ab2cdac47c544..c36bcb64ea69dc478e46e3318a9bba52f4973ba4 100644
--- a/src/mol-plugin-ui/structure/source.tsx
+++ b/src/mol-plugin-ui/structure/source.tsx
@@ -30,7 +30,7 @@ export class StructureSourceControls extends CollapsableControls<{}, StructureSo
}
componentDidMount() {
- this.subscribe(this.plugin.managers.structure.hierarchy.behaviors.changed, () => this.forceUpdate());
+ this.subscribe(this.plugin.managers.structure.hierarchy.behaviors.selection, () => this.forceUpdate());
this.subscribe(this.plugin.behaviors.state.isBusy, v => {
this.setState({ isBusy: v })
});
diff --git a/src/mol-plugin-ui/structure/volume.tsx b/src/mol-plugin-ui/structure/volume.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a46d6243efd7fa67ee7c1dfd345981f154d0c8d2
--- /dev/null
+++ b/src/mol-plugin-ui/structure/volume.tsx
@@ -0,0 +1,61 @@
+/**
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import * as React from 'react';
+import { InitVolumeStreaming } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
+import { CollapsableControls, CollapsableState } from '../base';
+import { ApplyActionControl } from '../state/apply-action';
+import { UpdateTransformControl } from '../state/update-transform';
+
+interface VolumeStreamingControlState extends CollapsableState {
+ isBusy: boolean
+}
+
+export class VolumeStreamingControls extends CollapsableControls<{}, VolumeStreamingControlState> {
+ protected defaultState(): VolumeStreamingControlState {
+ return {
+ header: 'Volume Streaming',
+ isCollapsed: false,
+ isBusy: false,
+ isHidden: true
+ };
+ }
+
+ componentDidMount() {
+ // TODO: do not hide this but instead show some help text??
+ this.subscribe(this.plugin.managers.structure.hierarchy.behaviors.selection, () => this.setState({ isHidden: !this.canEnable() }));
+ this.subscribe(this.plugin.behaviors.state.isBusy, v => this.setState({ isBusy: v }));
+ }
+
+ get pivot() {
+ return this.plugin.managers.structure.hierarchy.selection.structures[0];
+ }
+
+ canEnable() {
+ const { selection } = this.plugin.managers.structure.hierarchy;
+ if (selection.structures.length !== 1) return false;
+ const pivot = this.pivot.cell;
+ if (!pivot.obj) return false;
+ return !!InitVolumeStreaming.definition.isApplicable?.(pivot.obj, pivot.transform, this.plugin);
+ }
+
+ renderEnable() {
+ const pivot = this.pivot.cell;
+ return <ApplyActionControl state={pivot.parent} action={InitVolumeStreaming} initiallyCollapsed={true} nodeRef={pivot.transform.ref} simpleApply={{ header: 'Enable', icon: 'check' }} />;
+ }
+
+ renderParams() {
+ const pivot = this.pivot;
+ return <UpdateTransformControl state={pivot.cell.parent} transform={pivot.volumeStreaming!.cell.transform} customHeader='none' noMargin autoHideApply />;
+ }
+
+ renderControls() {
+ const pivot = this.pivot;
+ if (!pivot) return null;
+ if (!pivot.volumeStreaming) return this.renderEnable();
+ return this.renderParams();
+ }
+}
\ No newline at end of file
diff --git a/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts b/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts
index 051df9fc70f974317dbbc9a49df43453a8a65cdd..a981754dc8cc7320e361d04aae9c0dd661dd4c5f 100644
--- a/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts
+++ b/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts
@@ -21,6 +21,7 @@ import { VolumeRepresentationRegistry } from '../../../../mol-repr/volume/regist
import { Theme } from '../../../../mol-theme/theme';
import { Box3D } from '../../../../mol-math/geometry';
import { Vec3 } from '../../../../mol-math/linear-algebra';
+import { PluginConfig } from '../../../config';
function addEntry(entries: InfoEntryProps[], method: VolumeServerInfo.Kind, dataId: string, emDefaultContourLevel: number) {
entries.push({
@@ -34,7 +35,7 @@ function addEntry(entries: InfoEntryProps[], method: VolumeServerInfo.Kind, data
export const InitVolumeStreaming = StateAction.build({
display: { name: 'Volume Streaming' },
from: SO.Molecule.Structure,
- params(a) {
+ params(a, plugin: PluginContext) {
const method = getStreamingMethod(a && a.data);
const ids = getIds(method, a && a.data);
return {
@@ -42,7 +43,7 @@ export const InitVolumeStreaming = StateAction.build({
entries: PD.ObjectList({ id: PD.Text(ids[0] || '') }, ({ id }) => id, { defaultValue: ids.map(id => ({ id })) }),
defaultView: PD.Select<VolumeStreaming.ViewTypes>(method === 'em' ? 'cell' : 'selection-box', VolumeStreaming.ViewTypeOptions as any),
options: PD.Group({
- serverUrl: PD.Text('https://ds.litemol.org'),
+ serverUrl: PD.Text(plugin.config.get(PluginConfig.VolumeStreaming.DefaultServer) || 'https://ds.litemol.org'),
behaviorRef: PD.Text('', { isHidden: true }),
emContourProvider: PD.Select<'wwpdb' | 'pdbe'>('wwpdb', [['wwpdb', 'wwPDB'], ['pdbe', 'PDBe']], { isHidden: true }),
bindings: PD.Value(VolumeStreaming.DefaultBindings, { isHidden: true }),
@@ -50,7 +51,11 @@ export const InitVolumeStreaming = StateAction.build({
})
};
},
- isApplicable: (a) => a.data.models.length === 1
+ isApplicable: (a, _, plugin: PluginContext) => {
+ const canStreamTest = plugin.config.get(PluginConfig.VolumeStreaming.CanStream);
+ if (canStreamTest) return canStreamTest(a.data, plugin);
+ return a.data.models.length === 1;
+ }
})(({ ref, state, params }, plugin: PluginContext) => Task.create('Volume Streaming', async taskCtx => {
const entries: InfoEntryProps[] = []
diff --git a/src/mol-plugin/config.ts b/src/mol-plugin/config.ts
index 7f386b79b502ea0221e93d76a5e1a5c160e1e245..68c4cd82f9f45f3d39f121bd5f08c5fe82a92fd6 100644
--- a/src/mol-plugin/config.ts
+++ b/src/mol-plugin/config.ts
@@ -4,6 +4,9 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
+import { Structure } from '../mol-model/structure';
+import { PluginContext } from './context';
+
export class PluginConfigItem<T = any> {
toString() { return this.key; }
valueOf() { return this.key; }
@@ -17,6 +20,10 @@ export const PluginConfig = {
State: {
DefaultServer: item('plugin-state.server', 'https://webchem.ncbr.muni.cz/molstar-state'),
CurrentServer: item('plugin-state.server', 'https://webchem.ncbr.muni.cz/molstar-state')
+ },
+ VolumeStreaming: {
+ DefaultServer: item('volume-streaming.server', 'https://ds.litemol.org'),
+ CanStream: item('volume-streaming.can-stream', (s: Structure, plugin: PluginContext) => s.models.length === 1)
}
}