Skip to content
Snippets Groups Projects
Commit 72d69e91 authored by David Sehnal's avatar David Sehnal
Browse files

mol-plugin: add custom props on demand, UI tweaks

parent 45822b2f
No related branches found
No related tags found
No related merge requests found
...@@ -30,7 +30,7 @@ import { PLUGIN_VERSION, PLUGIN_VERSION_DATE } from './version'; ...@@ -30,7 +30,7 @@ import { PLUGIN_VERSION, PLUGIN_VERSION_DATE } from './version';
import { PluginLayout } from './layout'; import { PluginLayout } from './layout';
import { List } from 'immutable'; import { List } from 'immutable';
import { StateTransformParameters } from './ui/state/common'; import { StateTransformParameters } from './ui/state/common';
import { DataFormatRegistry } from './state/actions/basic'; import { DataFormatRegistry } from './state/actions/volume';
import { PluginBehavior } from './behavior/behavior'; import { PluginBehavior } from './behavior/behavior';
export class PluginContext { export class PluginContext {
......
...@@ -11,10 +11,10 @@ import * as React from 'react'; ...@@ -11,10 +11,10 @@ import * as React from 'react';
import * as ReactDOM from 'react-dom'; import * as ReactDOM from 'react-dom';
import { PluginCommands } from './command'; import { PluginCommands } from './command';
import { PluginSpec } from './spec'; import { PluginSpec } from './spec';
import { DownloadStructure, CreateComplexRepresentation, OpenStructure, OpenVolume, DownloadDensity } from './state/actions/basic';
import { StateTransforms } from './state/transforms'; import { StateTransforms } from './state/transforms';
import { PluginBehaviors } from './behavior'; import { PluginBehaviors } from './behavior';
import { AnimateModelIndex } from './state/animation/built-in'; import { AnimateModelIndex } from './state/animation/built-in';
import { StateActions } from './state/actions';
function getParam(name: string, regex: string): string { function getParam(name: string, regex: string): string {
let r = new RegExp(`${name}=(${regex})[&]?`, 'i'); let r = new RegExp(`${name}=(${regex})[&]?`, 'i');
...@@ -23,11 +23,12 @@ function getParam(name: string, regex: string): string { ...@@ -23,11 +23,12 @@ function getParam(name: string, regex: string): string {
export const DefaultPluginSpec: PluginSpec = { export const DefaultPluginSpec: PluginSpec = {
actions: [ actions: [
PluginSpec.Action(DownloadStructure), PluginSpec.Action(StateActions.Structure.DownloadStructure),
PluginSpec.Action(DownloadDensity), PluginSpec.Action(StateActions.Volume.DownloadDensity),
PluginSpec.Action(OpenStructure), PluginSpec.Action(StateActions.Structure.OpenStructure),
PluginSpec.Action(OpenVolume), PluginSpec.Action(StateActions.Volume.OpenVolume),
PluginSpec.Action(CreateComplexRepresentation), PluginSpec.Action(StateActions.Structure.CreateComplexRepresentation),
PluginSpec.Action(StateActions.Structure.EnableModelCustomProps),
PluginSpec.Action(StateTransforms.Data.Download), PluginSpec.Action(StateTransforms.Data.Download),
PluginSpec.Action(StateTransforms.Data.ParseCif), PluginSpec.Action(StateTransforms.Data.ParseCif),
PluginSpec.Action(StateTransforms.Data.ParseCcp4), PluginSpec.Action(StateTransforms.Data.ParseCcp4),
......
// TODO
\ No newline at end of file
// TODO
\ No newline at end of file
...@@ -14,6 +14,15 @@ ...@@ -14,6 +14,15 @@
// border-bottom: 1px solid $entity-color-Group; // TODO separate color // border-bottom: 1px solid $entity-color-Group; // TODO separate color
} }
.msp-current-header {
height: $row-height;
line-height: $row-height;
margin-bottom: $control-spacing;
text-align: center;
font-weight: bold;
background: $default-background;
}
.msp-btn-row-group { .msp-btn-row-group {
display:flex; display:flex;
flex-direction:row; flex-direction:row;
......
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as Structure from './actions/structure'
import * as Volume from './actions/volume'
export const StateActions = {
Structure,
Volume
}
\ No newline at end of file
/**
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { PluginContext } from 'mol-plugin/context';
import { StateAction, StateBuilder, StateSelection, StateTransformer } from 'mol-state';
import { ParamDefinition as PD } from 'mol-util/param-definition';
import { PluginStateObject } from '../objects';
import { StateTransforms } from '../transforms';
import { Download } from '../transforms/data';
import { StructureRepresentation3DHelpers } from '../transforms/representation';
import { CustomModelProperties } from '../transforms/model';
// TODO: "structure/volume parser provider"
export { DownloadStructure };
type DownloadStructure = typeof DownloadStructure
const DownloadStructure = StateAction.build({
from: PluginStateObject.Root,
display: { name: 'Download Structure', description: 'Load a structure from the provided source and create its default Assembly and visual.' },
params: {
source: PD.MappedStatic('bcif-static', {
'pdbe-updated': PD.Group({
id: PD.Text('1cbs', { label: 'Id' }),
supportProps: PD.Boolean(false)
}, { isFlat: true }),
'rcsb': PD.Group({
id: PD.Text('1tqn', { label: 'Id' }),
supportProps: PD.Boolean(false)
}, { isFlat: true }),
'bcif-static': PD.Group({
id: PD.Text('1tqn', { label: 'Id' }),
supportProps: PD.Boolean(false)
}, { isFlat: true }),
'url': PD.Group({
url: PD.Text(''),
format: PD.Select('cif', [['cif', 'CIF'], ['pdb', 'PDB']]),
isBinary: PD.Boolean(false),
supportProps: PD.Boolean(false)
}, { isFlat: true })
}, {
options: [
['pdbe-updated', 'PDBe Updated'],
['rcsb', 'RCSB'],
['bcif-static', 'BinaryCIF (static PDBe Updated)'],
['url', 'URL']
]
})
}
})(({ params, state }, ctx: PluginContext) => {
const b = state.build();
const src = params.source;
let downloadParams: StateTransformer.Params<Download>;
switch (src.name) {
case 'url':
downloadParams = { url: src.params.url, isBinary: src.params.isBinary };
break;
case 'pdbe-updated':
downloadParams = { url: `https://www.ebi.ac.uk/pdbe/static/entry/${src.params.id.toLowerCase()}_updated.cif`, isBinary: false, label: `PDBe: ${src.params.id}` };
break;
case 'rcsb':
downloadParams = { url: `https://files.rcsb.org/download/${src.params.id.toUpperCase()}.cif`, isBinary: false, label: `RCSB: ${src.params.id}` };
break;
case 'bcif-static':
downloadParams = { url: `https://webchem.ncbr.muni.cz/ModelServer/static/bcif/${src.params.id.toLowerCase()}`, isBinary: true, label: `BinaryCIF: ${src.params.id}` };
break;
default: throw new Error(`${(src as any).name} not supported.`);
}
const data = b.toRoot().apply(StateTransforms.Data.Download, downloadParams);
const traj = createModelTree(data, src.name === 'url' ? src.params.format : 'cif');
return state.updateTree(createStructureTree(ctx, traj, params.source.params.supportProps));
});
export const OpenStructure = StateAction.build({
display: { name: 'Open Structure', description: 'Load a structure from file and create its default Assembly and visual' },
from: PluginStateObject.Root,
params: { file: PD.File({ accept: '.cif,.bcif' }) }
})(({ params, state }, ctx: PluginContext) => {
const b = state.build();
const data = b.toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: /\.bcif$/i.test(params.file.name) });
const traj = createModelTree(data, 'cif');
return state.updateTree(createStructureTree(ctx, traj, false));
});
function createModelTree(b: StateBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, format: 'pdb' | 'cif' = 'cif') {
const parsed = format === 'cif'
? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
: b.apply(StateTransforms.Model.TrajectoryFromPDB);
return parsed.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 });
}
function createStructureTree(ctx: PluginContext, b: StateBuilder.To<PluginStateObject.Molecule.Model>, supportProps: boolean) {
let root = b;
if (supportProps) {
root = root.apply(StateTransforms.Model.CustomModelProperties);
}
const structure = root.apply(StateTransforms.Model.StructureAssemblyFromModel);
complexRepresentation(ctx, structure);
return root;
}
function complexRepresentation(ctx: PluginContext, root: StateBuilder.To<PluginStateObject.Molecule.Structure>) {
root.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' })
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'cartoon'));
root.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' })
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'ball-and-stick'));
root.apply(StateTransforms.Model.StructureComplexElement, { type: 'water' })
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'ball-and-stick', { alpha: 0.51 }));
root.apply(StateTransforms.Model.StructureComplexElement, { type: 'spheres' })
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'spacefill'));
}
export const CreateComplexRepresentation = StateAction.build({
display: { name: 'Create Complex', description: 'Split the structure into Sequence/Water/Ligands/... ' },
from: PluginStateObject.Molecule.Structure
})(({ ref, state }, ctx: PluginContext) => {
const root = state.build().to(ref);
complexRepresentation(ctx, root);
return state.updateTree(root);
});
export const UpdateTrajectory = StateAction.build({
display: { name: 'Update Trajectory' },
params: {
action: PD.Select<'advance' | 'reset'>('advance', [['advance', 'Advance'], ['reset', 'Reset']]),
by: PD.makeOptional(PD.Numeric(1, { min: -1, max: 1, step: 1 }))
}
})(({ params, state }) => {
const models = state.selectQ(q => q.rootsOfType(PluginStateObject.Molecule.Model)
.filter(c => c.transform.transformer === StateTransforms.Model.ModelFromTrajectory));
const update = state.build();
if (params.action === 'reset') {
for (const m of models) {
update.to(m.transform.ref).update(StateTransforms.Model.ModelFromTrajectory,
() => ({ modelIndex: 0 }));
}
} else {
for (const m of models) {
const parent = StateSelection.findAncestorOfType(state.tree, state.cells, m.transform.ref, [PluginStateObject.Molecule.Trajectory]);
if (!parent || !parent.obj) continue;
const traj = parent.obj as PluginStateObject.Molecule.Trajectory;
update.to(m.transform.ref).update(StateTransforms.Model.ModelFromTrajectory,
old => {
let modelIndex = (old.modelIndex + params.by!) % traj.data.length;
if (modelIndex < 0) modelIndex += traj.data.length;
return { modelIndex };
});
}
}
return state.updateTree(update);
});
export const EnableModelCustomProps = StateAction.build({
display: { name: 'Custom Properties', description: 'Enable the addition of custom properties to the model.' },
from: PluginStateObject.Molecule.Model,
params(a, ctx: PluginContext) {
if (!a) return { properties: PD.MultiSelect([], [], { description: 'A list of property descriptor ids.' }) };
return { properties: ctx.customModelProperties.getSelect(a.data) };
},
isApplicable(a, t, ctx: PluginContext) {
return t.transformer !== CustomModelProperties;
}
})(({ ref, params, state }, ctx: PluginContext) => {
const root = state.build().to(ref).insert(CustomModelProperties, params);
return state.updateTree(root);
});
...@@ -5,169 +5,17 @@ ...@@ -5,169 +5,17 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de> * @author Alexander Rose <alexander.rose@weirdbyte.de>
*/ */
import { VolumeIsoValue } from 'mol-model/volume';
import { PluginContext } from 'mol-plugin/context'; import { PluginContext } from 'mol-plugin/context';
import { StateTree, StateTransformer, StateObject, State, StateBuilder, StateSelection, StateAction } from 'mol-state'; import { State, StateAction, StateBuilder, StateObject, StateTransformer } from 'mol-state';
import { Task } from 'mol-task';
import { ColorNames } from 'mol-util/color/tables';
import { FileInfo, getFileInfo } from 'mol-util/file-info';
import { ParamDefinition as PD } from 'mol-util/param-definition'; import { ParamDefinition as PD } from 'mol-util/param-definition';
import { PluginStateObject } from '../objects'; import { PluginStateObject } from '../objects';
import { StateTransforms } from '../transforms'; import { StateTransforms } from '../transforms';
import { Download } from '../transforms/data'; import { Download } from '../transforms/data';
import { StructureRepresentation3DHelpers, VolumeRepresentation3DHelpers } from '../transforms/representation'; import { VolumeRepresentation3DHelpers } from '../transforms/representation';
import { getFileInfo, FileInfo } from 'mol-util/file-info';
import { Task } from 'mol-task';
import { ColorNames } from 'mol-util/color/tables';
import { VolumeIsoValue } from 'mol-model/volume';
// TODO: "structure/volume parser provider"
export { DownloadStructure };
type DownloadStructure = typeof DownloadStructure
const DownloadStructure = StateAction.build({
from: PluginStateObject.Root,
display: { name: 'Download Structure', description: 'Load a structure from the provided source and create its default Assembly and visual.' },
params: {
source: PD.MappedStatic('bcif-static', {
'pdbe-updated': PD.Group({
id: PD.Text('1cbs', { label: 'Id' }),
supportProps: PD.Boolean(false)
}, { isFlat: true }),
'rcsb': PD.Group({
id: PD.Text('1tqn', { label: 'Id' }),
supportProps: PD.Boolean(false)
}, { isFlat: true }),
'bcif-static': PD.Group({
id: PD.Text('1tqn', { label: 'Id' }),
supportProps: PD.Boolean(false)
}, { isFlat: true }),
'url': PD.Group({
url: PD.Text(''),
format: PD.Select('cif', [['cif', 'CIF'], ['pdb', 'PDB']]),
isBinary: PD.Boolean(false),
supportProps: PD.Boolean(false)
}, { isFlat: true })
}, {
options: [
['pdbe-updated', 'PDBe Updated'],
['rcsb', 'RCSB'],
['bcif-static', 'BinaryCIF (static PDBe Updated)'],
['url', 'URL']
]
})
}
})(({ params, state }, ctx: PluginContext) => {
const b = state.build();
const src = params.source;
let downloadParams: StateTransformer.Params<Download>;
switch (src.name) {
case 'url':
downloadParams = { url: src.params.url, isBinary: src.params.isBinary };
break;
case 'pdbe-updated':
downloadParams = { url: `https://www.ebi.ac.uk/pdbe/static/entry/${src.params.id.toLowerCase()}_updated.cif`, isBinary: false, label: `PDBe: ${src.params.id}` };
break;
case 'rcsb':
downloadParams = { url: `https://files.rcsb.org/download/${src.params.id.toUpperCase()}.cif`, isBinary: false, label: `RCSB: ${src.params.id}` };
break;
case 'bcif-static':
downloadParams = { url: `https://webchem.ncbr.muni.cz/ModelServer/static/bcif/${src.params.id.toLowerCase()}`, isBinary: true, label: `BinaryCIF: ${src.params.id}` };
break;
default: throw new Error(`${(src as any).name} not supported.`);
}
const data = b.toRoot().apply(StateTransforms.Data.Download, downloadParams);
const traj = createModelTree(data, src.name === 'url' ? src.params.format : 'cif');
return state.updateTree(createStructureTree(ctx, traj, params.source.params.supportProps));
});
export const OpenStructure = StateAction.build({
display: { name: 'Open Structure', description: 'Load a structure from file and create its default Assembly and visual' },
from: PluginStateObject.Root,
params: { file: PD.File({ accept: '.cif,.bcif' }) }
})(({ params, state }, ctx: PluginContext) => {
const b = state.build();
const data = b.toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: /\.bcif$/i.test(params.file.name) });
const traj = createModelTree(data, 'cif');
return state.updateTree(createStructureTree(ctx, traj, false));
});
function createModelTree(b: StateBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, format: 'pdb' | 'cif' = 'cif') {
const parsed = format === 'cif'
? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
: b.apply(StateTransforms.Model.TrajectoryFromPDB);
return parsed.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 });
}
function createStructureTree(ctx: PluginContext, b: StateBuilder.To<PluginStateObject.Molecule.Model>, supportProps: boolean): StateTree {
let root = b;
if (supportProps) {
root = root.apply(StateTransforms.Model.CustomModelProperties);
}
const structure = root.apply(StateTransforms.Model.StructureAssemblyFromModel);
complexRepresentation(ctx, structure);
return root.getTree();
}
function complexRepresentation(ctx: PluginContext, root: StateBuilder.To<PluginStateObject.Molecule.Structure>) {
root.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' })
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'cartoon'));
root.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' })
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'ball-and-stick'));
root.apply(StateTransforms.Model.StructureComplexElement, { type: 'water' })
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'ball-and-stick', { alpha: 0.51 }));
root.apply(StateTransforms.Model.StructureComplexElement, { type: 'spheres' })
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'spacefill'));
}
export const CreateComplexRepresentation = StateAction.build({
display: { name: 'Create Complex', description: 'Split the structure into Sequence/Water/Ligands/... ' },
from: PluginStateObject.Molecule.Structure
})(({ ref, state }, ctx: PluginContext) => {
const root = state.build().to(ref);
complexRepresentation(ctx, root);
return state.updateTree(root.getTree());
});
export const UpdateTrajectory = StateAction.build({
display: { name: 'Update Trajectory' },
params: {
action: PD.Select<'advance' | 'reset'>('advance', [['advance', 'Advance'], ['reset', 'Reset']]),
by: PD.makeOptional(PD.Numeric(1, { min: -1, max: 1, step: 1 }))
}
})(({ params, state }) => {
const models = state.selectQ(q => q.rootsOfType(PluginStateObject.Molecule.Model)
.filter(c => c.transform.transformer === StateTransforms.Model.ModelFromTrajectory));
const update = state.build();
if (params.action === 'reset') {
for (const m of models) {
update.to(m.transform.ref).update(StateTransforms.Model.ModelFromTrajectory,
() => ({ modelIndex: 0 }));
}
} else {
for (const m of models) {
const parent = StateSelection.findAncestorOfType(state.tree, state.cells, m.transform.ref, [PluginStateObject.Molecule.Trajectory]);
if (!parent || !parent.obj) continue;
const traj = parent.obj as PluginStateObject.Molecule.Trajectory;
update.to(m.transform.ref).update(StateTransforms.Model.ModelFromTrajectory,
old => {
let modelIndex = (old.modelIndex + params.by!) % traj.data.length;
if (modelIndex < 0) modelIndex += traj.data.length;
return { modelIndex };
});
}
}
return state.updateTree(update);
});
//
export class DataFormatRegistry<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String, M extends StateObject> { export class DataFormatRegistry<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String, M extends StateObject> {
private _list: { name: string, provider: DataFormatProvider<D> }[] = [] private _list: { name: string, provider: DataFormatProvider<D> }[] = []
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
import * as React from 'react'; import * as React from 'react';
import { PluginCommands } from 'mol-plugin/command'; import { PluginCommands } from 'mol-plugin/command';
import { UpdateTrajectory } from 'mol-plugin/state/actions/basic'; import { UpdateTrajectory } from 'mol-plugin/state/actions/structure';
import { PluginUIComponent } from './base'; import { PluginUIComponent } from './base';
import { LociLabelEntry } from 'mol-plugin/util/loci-label-manager'; import { LociLabelEntry } from 'mol-plugin/util/loci-label-manager';
......
...@@ -174,19 +174,19 @@ export class CurrentObject extends PluginUIComponent { ...@@ -174,19 +174,19 @@ export class CurrentObject extends PluginUIComponent {
const cell = current.state.cells.get(ref)!; const cell = current.state.cells.get(ref)!;
const parent: StateObjectCell | undefined = (cell.sourceRef && current.state.cells.get(cell.sourceRef)!) || void 0; const parent: StateObjectCell | undefined = (cell.sourceRef && current.state.cells.get(cell.sourceRef)!) || void 0;
const type = cell && cell.obj ? cell.obj.type : void 0;
const transform = cell.transform; const transform = cell.transform;
const def = transform.transformer.definition; const def = transform.transformer.definition;
const actions = type ? current.state.actions.fromType(type) : []; const actions = current.state.actions.fromCell(cell, this.plugin);
return <> return <>
<div className='msp-section-header'> <div className='msp-current-header'>
{cell.obj ? cell.obj.label : (def.display && def.display.name) || def.name} {cell.obj ? cell.obj.label : (def.display && def.display.name) || def.name}
</div> </div>
{ (parent && parent.status === 'ok') && <UpdateTransformContol state={current.state} transform={transform} /> } {(parent && parent.status === 'ok') && <UpdateTransformContol state={current.state} transform={transform} />}
{cell.status === 'ok' && {cell.status === 'ok' && <>
actions.map((act, i) => <ApplyActionContol plugin={this.plugin} key={`${act.id}`} state={current.state} action={act} nodeRef={ref} />) <div className='msp-section-header'>Actions</div>
} {actions.map((act, i) => <ApplyActionContol plugin={this.plugin} key={`${act.id}`} state={current.state} action={act} nodeRef={ref} />)}
</>}
</>; </>;
} }
} }
\ No newline at end of file
...@@ -10,6 +10,7 @@ import { ParamDefinition as PD } from 'mol-util/param-definition'; ...@@ -10,6 +10,7 @@ import { ParamDefinition as PD } from 'mol-util/param-definition';
import { StateObject, StateObjectCell } from './object'; import { StateObject, StateObjectCell } from './object';
import { State } from './state'; import { State } from './state';
import { StateTransformer } from './transformer'; import { StateTransformer } from './transformer';
import { StateTransform } from './transform';
export { StateAction }; export { StateAction };
...@@ -45,7 +46,7 @@ namespace StateAction { ...@@ -45,7 +46,7 @@ namespace StateAction {
run(params: ApplyParams<A, P>, globalCtx: unknown): T | Task<T>, run(params: ApplyParams<A, P>, globalCtx: unknown): T | Task<T>,
/** Test if the transform can be applied to a given node */ /** Test if the transform can be applied to a given node */
isApplicable?(a: A, globalCtx: unknown): boolean isApplicable?(a: A, aTransform: StateTransform<any, A, any>, globalCtx: unknown): boolean
} }
export interface Definition<A extends StateObject = StateObject, T = any, P extends {} = {}> extends DefinitionBase<A, T, P> { export interface Definition<A extends StateObject = StateObject, T = any, P extends {} = {}> extends DefinitionBase<A, T, P> {
...@@ -69,6 +70,9 @@ namespace StateAction { ...@@ -69,6 +70,9 @@ namespace StateAction {
from: def.from, from: def.from,
display: def.display, display: def.display,
params: def.params as StateTransformer.Definition<StateTransformer.From<T>, any, StateTransformer.Params<T>>['params'], params: def.params as StateTransformer.Definition<StateTransformer.From<T>, any, StateTransformer.Params<T>>['params'],
isApplicable: transformer.definition.isApplicable
? (a, t, ctx) => transformer.definition.isApplicable!(a, ctx)
: void 0,
run({ cell, state, params }) { run({ cell, state, params }) {
const tree = state.build().to(cell.transform.ref).apply(transformer, params); const tree = state.build().to(cell.transform.ref).apply(transformer, params);
return state.updateTree(tree) as Task<void>; return state.updateTree(tree) as Task<void>;
...@@ -80,7 +84,8 @@ namespace StateAction { ...@@ -80,7 +84,8 @@ namespace StateAction {
export interface Type<A extends StateObject.Ctor, P extends { }> { export interface Type<A extends StateObject.Ctor, P extends { }> {
from?: A | A[], from?: A | A[],
params?: PD.For<P> | ((a: StateObject.From<A>, globalCtx: any) => PD.For<P>), params?: PD.For<P> | ((a: StateObject.From<A>, globalCtx: any) => PD.For<P>),
display?: string | { name: string, description?: string } display?: string | { name: string, description?: string },
isApplicable?: DefinitionBase<StateObject.From<A>, any, P>['isApplicable']
} }
export interface Root { export interface Root {
...@@ -106,6 +111,7 @@ namespace StateAction { ...@@ -106,6 +111,7 @@ namespace StateAction {
: !!info.params : !!info.params
? info.params as any ? info.params as any
: void 0, : void 0,
isApplicable: info.isApplicable,
...(typeof def === 'function' ...(typeof def === 'function'
? { run: def } ? { run: def }
: def) : def)
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
*/ */
import { StateAction } from '../action'; import { StateAction } from '../action';
import { StateObject } from '../object'; import { StateObject, StateObjectCell } from '../object';
import { StateTransformer } from '../transformer'; import { StateTransformer } from '../transformer';
export { StateActionManager } export { StateActionManager }
...@@ -32,7 +32,31 @@ class StateActionManager { ...@@ -32,7 +32,31 @@ class StateActionManager {
return this; return this;
} }
fromType(type: StateObject.Type): ReadonlyArray<StateAction> { fromCell(cell: StateObjectCell, ctx: unknown): ReadonlyArray<StateAction> {
return this.fromTypeIndex.get(type) || []; const obj = cell.obj;
if (!obj) return [];
const actions = this.fromTypeIndex.get(obj.type);
if (!actions) return [];
let hasTest = false;
for (const a of actions) {
if (a.definition.isApplicable) {
hasTest = true;
break;
}
}
if (!hasTest) return actions;
const ret: StateAction[] = [];
for (const a of actions) {
if (a.definition.isApplicable) {
if (a.definition.isApplicable(obj, cell.transform, ctx)) {
ret.push(a);
}
} else {
ret.push(a);
}
}
return ret;
} }
} }
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment