Ver código fonte

Merge branch 'master' of https://github.com/molstar/molstar

Alexander Rose 5 anos atrás
pai
commit
8e20e163f9

+ 44 - 46
src/mol-plugin-state/actions/structure.ts

@@ -17,7 +17,7 @@ import { FileInfo } from '../../mol-util/file-info';
 import { Task } from '../../mol-task';
 import { StructureElement } from '../../mol-model/structure';
 import { createDefaultStructureComplex } from '../../mol-plugin/util/structure-complex-helper';
-import { ModelStructureRepresentation } from '../representation/model';
+import { RootStructureDefinition } from '../helpers/root-structure';
 
 export const MmcifProvider: DataFormatProvider<any> = {
     label: 'mmCIF',
@@ -121,7 +121,7 @@ type StructureFormat = 'pdb' | 'cif' | 'gro' | '3dg'
 //
 
 const DownloadModelRepresentationOptions = PD.Group({
-    type: ModelStructureRepresentation.getParams(void 0, 'assembly').type,
+    type: RootStructureDefinition.getParams(void 0, 'assembly').type,
     noRepresentation: PD.Optional(PD.Boolean(false, { description: 'Omit creating default representation.' }))
 }, { isExpanded: false });
 
@@ -141,27 +141,27 @@ const DownloadStructure = StateAction.build({
                 id: PD.Text('1cbs', { label: 'PDB Id(s)', description: 'One or more comma separated PDB ids.' }),
                 structure: DownloadModelRepresentationOptions,
                 options: DownloadStructurePdbIdSourceOptions
-            }, { isFlat: true }),
+            }, { isFlat: true, label: 'PDBe Updated' }),
             'rcsb': PD.Group({
                 id: PD.Text('1tqn', { label: 'PDB Id(s)', description: 'One or more comma separated PDB ids.' }),
                 structure: DownloadModelRepresentationOptions,
                 options: DownloadStructurePdbIdSourceOptions
-            }, { isFlat: true }),
+            }, { isFlat: true, label: 'RCSB' }),
             'pdb-dev': PD.Group({
                 id: PD.Text('PDBDEV_00000001', { label: 'PDBDev Id(s)', description: 'One or more comma separated ids.' }),
                 structure: DownloadModelRepresentationOptions,
                 options: DownloadStructurePdbIdSourceOptions
-            }, { isFlat: true }),
+            }, { isFlat: true, label: 'PDBDEV' }),
             'bcif-static': PD.Group({
                 id: PD.Text('1tqn', { label: 'PDB Id(s)', description: 'One or more comma separated PDB ids.' }),
                 structure: DownloadModelRepresentationOptions,
                 options: DownloadStructurePdbIdSourceOptions
-            }, { isFlat: true }),
+            }, { isFlat: true, label: 'BinaryCIF (static PDBe Updated)' }),
             'swissmodel': PD.Group({
                 id: PD.Text('Q9Y2I8', { label: 'UniProtKB AC(s)', description: 'One or more comma separated ACs.' }),
                 structure: DownloadModelRepresentationOptions,
                 options: DownloadStructurePdbIdSourceOptions
-            }, { isFlat: true, description: 'Loads the best homology model or experimental structure' }),
+            }, { isFlat: true, label: 'SWISS-MODEL', description: 'Loads the best homology model or experimental structure' }),
             'url': PD.Group({
                 url: PD.Text(''),
                 format: PD.Select('cif', [['cif', 'CIF'], ['pdb', 'PDB']] as ['cif' | 'pdb', string][]),
@@ -171,16 +171,7 @@ const DownloadStructure = StateAction.build({
                     supportProps: PD.Optional(PD.Boolean(false)),
                     createRepresentation: PD.Optional(PD.Boolean(true))
                 })
-            }, { isFlat: true })
-        }, {
-            options: [
-                ['pdbe-updated', 'PDBe Updated'],
-                ['rcsb', 'RCSB'],
-                ['pdb-dev', 'PDBDEV'],
-                ['bcif-static', 'BinaryCIF (static PDBe Updated)'],
-                ['swissmodel', 'SWISS-MODEL'],
-                ['url', 'URL']
-            ]
+            }, { isFlat: true, label: 'URL' })
         })
     }
 })(({ params, state }, plugin: PluginContext) => Task.create('Download Structure', async ctx => {
@@ -234,25 +225,27 @@ const DownloadStructure = StateAction.build({
 
     const createRepr = !params.source.params.structure.noRepresentation;
 
-    if (downloadParams.length > 0 && asTrajectory) {
-        const traj = createSingleTrajectoryModel(downloadParams, state.build());
-        const struct = createStructure(traj, supportProps, src.params.structure.type);
-        await state.updateTree(struct, { revertIfAborted: true }).runInContext(ctx);
-        if (createRepr) {
-            await plugin.structureRepresentation.manager.apply(struct.ref, plugin.structureRepresentation.manager.defaultProvider);
-        }
-    } else {
-        for (const download of downloadParams) {
-            const data = state.build().toRoot().apply(StateTransforms.Data.Download, download, { state: { isGhost: true } });
-            const traj = createModelTree(data, format);
-
-            const struct = createStructure(traj, supportProps, src.params.structure.type);
+    await state.transaction(async () => {
+        if (downloadParams.length > 0 && asTrajectory) {
+            const traj = await createSingleTrajectoryModel(plugin, state, downloadParams);
+            const struct = createStructure(state.build().to(traj), supportProps, src.params.structure.type);
             await state.updateTree(struct, { revertIfAborted: true }).runInContext(ctx);
             if (createRepr) {
-                await plugin.structureRepresentation.manager.apply(struct.ref, plugin.structureRepresentation.manager.defaultProvider);
+                await plugin.builders.structureRepresentation.apply(struct.ref, 'auto');
+            }
+        } else {
+            for (const download of downloadParams) {
+                const data = await plugin.builders.data.download(download, { state: { isGhost: true } });
+                const traj = createModelTree(state.build().to(data), format);
+
+                const struct = createStructure(traj, supportProps, src.params.structure.type);
+                await state.updateTree(struct, { revertIfAborted: true }).runInContext(ctx);
+                if (createRepr) {
+                    await plugin.builders.structureRepresentation.apply(struct.ref, 'auto');
+                }
             }
         }
-    }
+    }).runInContext(ctx);
 }));
 
 function getDownloadParams(src: string, url: (id: string) => string, label: (id: string) => string, isBinary: boolean): StateTransformer.Params<Download>[] {
@@ -264,16 +257,21 @@ function getDownloadParams(src: string, url: (id: string) => string, label: (id:
     return ret;
 }
 
-function createSingleTrajectoryModel(sources: StateTransformer.Params<Download>[], b: StateBuilder.Root) {
-    return b.toRoot()
-        .apply(StateTransforms.Data.DownloadBlob, {
-            sources: sources.map((src, i) => ({ id: '' + i, url: src.url, isBinary: src.isBinary })),
-            maxConcurrency: 6
-        }, { state: { isGhost: true } }).apply(StateTransforms.Data.ParseBlob, {
+async function createSingleTrajectoryModel(plugin: PluginContext, state: State, sources: StateTransformer.Params<Download>[]) {
+    const data = await plugin.builders.data.downloadBlob({
+        sources: sources.map((src, i) => ({ id: '' + i, url: src.url, isBinary: src.isBinary })),
+        maxConcurrency: 6
+    }, { state: { isGhost: true } });
+
+    const trajectory = state.build().to(data)
+        .apply(StateTransforms.Data.ParseBlob, {
             formats: sources.map((_, i) => ({ id: '' + i, format: 'cif' as 'cif' }))
         }, { state: { isGhost: true } })
         .apply(StateTransforms.Model.TrajectoryFromBlob)
         .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 });
+
+    await plugin.runTask(state.updateTree(trajectory, { revertIfAborted: true }));
+    return trajectory.selector;
 }
 
 export function createModelTree(b: StateBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, format: StructureFormat = 'cif') {
@@ -299,7 +297,7 @@ export function createModelTree(b: StateBuilder.To<PluginStateObject.Data.Binary
     return parsed.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 });
 }
 
-function createStructure(b: StateBuilder.To<PluginStateObject.Molecule.Model>, supportProps: boolean, params?: ModelStructureRepresentation.Params) {
+function createStructure(b: StateBuilder.To<PluginStateObject.Molecule.Model>, supportProps: boolean, params?: RootStructureDefinition.Params) {
     let root = b;
     if (supportProps) {
         root = root.apply(StateTransforms.Model.CustomModelProperties);
@@ -307,7 +305,7 @@ function createStructure(b: StateBuilder.To<PluginStateObject.Molecule.Model>, s
     return root.apply(StateTransforms.Model.StructureFromModel, { type: params || { name: 'assembly', params: { } } });
 }
 
-function createStructureAndVisuals(ctx: PluginContext, b: StateBuilder.To<PluginStateObject.Molecule.Model>, supportProps: boolean, params?: ModelStructureRepresentation.Params) {
+function createStructureAndVisuals(ctx: PluginContext, b: StateBuilder.To<PluginStateObject.Molecule.Model>, supportProps: boolean, params?: RootStructureDefinition.Params) {
     const structure = createStructure(b, supportProps, params);
     createDefaultStructureComplex(ctx, structure);
     return b;
@@ -316,28 +314,28 @@ function createStructureAndVisuals(ctx: PluginContext, b: StateBuilder.To<Plugin
 export const Create3DRepresentationPreset = StateAction.build({
     display: { name: '3D Representation Preset', description: 'Create one of preset 3D representations.' },
     from: PluginStateObject.Molecule.Structure,
-    isApplicable(a, _, plugin: PluginContext) { return plugin.structureRepresentation.manager.hasProvider(a.data); },
+    isApplicable(a, _, plugin: PluginContext) { return plugin.builders.structureRepresentation.hasProvider(a.data); },
     params(a, plugin: PluginContext) {
         return {
-            type: plugin.structureRepresentation.manager.getOptions(a.data)
+            type: plugin.builders.structureRepresentation.getOptions(a.data)
         };
     }
 })(({ ref, params }, plugin: PluginContext) => {
-    plugin.structureRepresentation.manager.apply(ref, params.type.name, params.type.params);
+    plugin.builders.structureRepresentation.apply(ref, params.type.name, params.type.params);
 });
 
 export const Remove3DRepresentationPreset = StateAction.build({
     display: { name: 'Remove 3D Representation Preset', description: 'Remove 3D representations.' },
     from: PluginStateObject.Molecule.Structure,
-    isApplicable(_, t, plugin: PluginContext) { return plugin.structureRepresentation.manager.hasManagedRepresentation(t.ref); },
+    isApplicable(_, t, plugin: PluginContext) { return plugin.builders.structureRepresentation.hasManagedRepresentation(t.ref); },
     params(a, plugin: PluginContext) {
         return {
-            type: plugin.structureRepresentation.manager.getOptions(a.data).select
+            type: plugin.builders.structureRepresentation.getOptions(a.data).select
         };
     }
 })(({ ref, params }, plugin: PluginContext) => {
     // TODO: this will be completely handled by the managed and is just for testing purposes
-    plugin.structureRepresentation.manager.remove(params.type, ref);
+    plugin.builders.structureRepresentation.remove(params.type, ref);
 });
 
 export const UpdateTrajectory = StateAction.build({

+ 44 - 0
src/mol-plugin-state/builder/data.ts

@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { StateTransformer, StateTransform } from '../../mol-state';
+import { PluginContext } from '../../mol-plugin/context';
+import { Download, ReadFile, DownloadBlob, RawData } from '../transforms/data';
+import { getFileInfo } from '../../mol-util/file-info';
+
+export class DataBuilder {
+    private get dataState() {
+        return this.plugin.state.dataState;
+    }
+
+    async rawData(params: StateTransformer.Params<RawData>, options?: Partial<StateTransform.Options>) {
+        const data = this.dataState.build().toRoot().apply(RawData, params, options);
+        await this.plugin.runTask(this.dataState.updateTree(data, { revertOnError: true }));
+        return data.selector;
+    }
+
+    async download(params: StateTransformer.Params<Download>, options?: Partial<StateTransform.Options>) {
+        const data = this.dataState.build().toRoot().apply(Download, params, options);
+        await this.plugin.runTask(this.dataState.updateTree(data, { revertOnError: true }));
+        return data.selector;
+    }
+
+    async downloadBlob(params: StateTransformer.Params<DownloadBlob>, options?: Partial<StateTransform.Options>) {        
+        const data = this.dataState.build().toRoot().apply(DownloadBlob, params, options);
+        await this.plugin.runTask(this.dataState.updateTree(data, { revertOnError: true }));
+        return data.selector;
+    }
+
+    async readFile(params: StateTransformer.Params<ReadFile>, options?: Partial<StateTransform.Options>) {
+        const data = this.dataState.build().toRoot().apply(ReadFile, params, options);
+        const fileInfo = getFileInfo(params.file);
+        await this.plugin.runTask(this.dataState.updateTree(data, { revertOnError: true }));
+        return { data: data.selector, fileInfo };
+    }
+
+    constructor(public plugin: PluginContext) {
+    }
+}

+ 1 - 0
src/mol-plugin-state/builder/parser.ts

@@ -0,0 +1 @@
+// TODO: parsing

+ 1 - 0
src/mol-plugin-state/builder/structure-model.ts

@@ -0,0 +1 @@
+// TODO: create managed trajectory / model / structure

+ 35 - 26
src/mol-plugin-state/representation/structure.ts → src/mol-plugin-state/builder/structure-representation.ts

@@ -1,12 +1,12 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
 import { arrayFind } from '../../mol-data/util';
 import { Structure } from '../../mol-model/structure';
-import { StateTransform, StateTree, StateSelection } from '../../mol-state';
+import { StateTransform, StateTree, StateSelection, StateObjectRef } from '../../mol-state';
 import { Task } from '../../mol-task';
 import { isProductionMode } from '../../mol-util/debug';
 import { objectForEach } from '../../mol-util/object';
@@ -19,12 +19,20 @@ import { UniqueArray } from '../../mol-data/generic';
 // TODO: support quality
 // TODO: support ignore hydrogens
 
-export class StructureRepresentationManager {
+export type StructureRepresentationProviderRef = keyof PresetStructureReprentations | StructureRepresentationProvider | string
+
+export class StructureRepresentationBuilder {
     private providers: StructureRepresentationProvider[] = [];
     private providerMap: Map<string, StructureRepresentationProvider> = new Map();
 
     readonly defaultProvider = PresetStructureReprentations.auto;
 
+    private resolveProvider(ref: StructureRepresentationProviderRef) {
+        return typeof ref === 'string'
+            ? PresetStructureReprentations[ref as keyof PresetStructureReprentations] ?? arrayFind(this.providers, p => p.id === ref)
+            : ref;
+    }
+
     hasProvider(s: Structure) {
         for (const p of this.providers) {
             if (!p.isApplicable || p.isApplicable(s, this.plugin)) return true;
@@ -45,10 +53,12 @@ export class StructureRepresentationManager {
         return PD.MappedStatic(options[0][0], map, { options });
     }
 
-    hasManagedRepresentation(ref: StateTransform.Ref) {
-        const tree = this.plugin.state.dataState.tree;
+    hasManagedRepresentation(ref: StateObjectRef) {
         // TODO: make this state selection function?
-        return StateTree.doPreOrder(tree, tree.transforms.get(ref), { found: false, map: this.providerMap }, (n, _, s) => {
+        const tree = this.plugin.state.dataState.tree;
+        const root = StateObjectRef.resolve(this.plugin.state.dataState, ref);
+        if (!root) return false;
+        return StateTree.doPreOrder(tree, root.transform,  { found: false, map: this.providerMap }, (n, _, s) => {
             if (!n.tags) return;
             for (const t of n.tags) {
                 if (s.map.has(t)) {
@@ -59,10 +69,11 @@ export class StructureRepresentationManager {
         }).found;
     }
 
-    getManagedRepresentations(ref: StateTransform.Ref) {
-        // TODO: check if Structure etc.
+    getManagedRepresentations(ref: StateObjectRef) {
         const tree = this.plugin.state.dataState.tree;
-        return StateTree.doPreOrder(tree, tree.transforms.get(ref), { found: UniqueArray.create<string, StructureRepresentationProvider>(), map: this.providerMap }, (n, _, s) => {
+        const root = StateObjectRef.resolve(this.plugin.state.dataState, ref);
+        if (!root) return [];
+        return StateTree.doPreOrder(tree, root.transform, { found: UniqueArray.create<string, StructureRepresentationProvider>(), map: this.providerMap }, (n, _, s) => {
             if (!n.tags) return;
             for (const t of n.tags) {
                 if (s.map.has(t)) UniqueArray.add(s.found, t, s.map.get(t)!);
@@ -79,11 +90,12 @@ export class StructureRepresentationManager {
         this.providerMap.set(provider.id, provider);
     }
 
-    remove(providerOrId: StructureRepresentationProvider | string, structureRoot?: StateTransform.Ref) {
-        const root = structureRoot || StateTransform.RootRef;
-        const id = typeof providerOrId === 'string' ? providerOrId : providerOrId.id;
-
+    remove(providerRef: StructureRepresentationProviderRef, structureRoot?: StateObjectRef) {
+        const id = this.resolveProvider(providerRef)?.id;
+        if (!id) return;
+        
         const state = this.plugin.state.dataState;
+        const root = StateObjectRef.resolveRef(state, structureRoot) || StateTransform.RootRef;
         const reprs = StateSelection.findWithAllTags(state.tree, root, new Set([id, RepresentationProviderTags.Representation]));
 
         const builder = state.build();
@@ -101,33 +113,30 @@ export class StructureRepresentationManager {
         if (builder.editInfo.count === 0) return;
         return this.plugin.runTask(state.updateTree(builder));
     }
-
-    apply<P = any, S = {}>(ref: StateTransform.Ref, providerOrId: StructureRepresentationProvider<P, S> | string, params?: P) {
-        const provider = typeof providerOrId === 'string'
-            ? arrayFind(this.providers, p => p.id === providerOrId)
-            : providerOrId;
+    
+    apply<K extends keyof PresetStructureReprentations>(parent: StateObjectRef, preset: K, params?: StructureRepresentationProvider.Params<PresetStructureReprentations[K]>): Promise<StructureRepresentationProvider.State<PresetStructureReprentations[K]>> | undefined
+    apply<P = any, S = {}>(parent: StateObjectRef, providers: StructureRepresentationProvider<P, S>, params?: P): Promise<S> | undefined
+    apply(parent: StateObjectRef, providerId: string, params?: any): Promise<any> | undefined
+    apply(parent: StateObjectRef, providerRef: string | StructureRepresentationProvider, params?: any): Promise<any> | undefined {
+        const provider = this.resolveProvider(providerRef);
         if (!provider) return;
 
         const state = this.plugin.state.dataState;
-        const cell = state.cells.get(ref);
-        if (!cell || !cell.obj || cell.status !== 'ok') {
+        const cell = StateObjectRef.resolveAndCheck(state, parent);
+        if (!cell) {
             if (!isProductionMode) console.warn(`Applying structure repr. provider to bad cell.`);
             return;
         }
 
         const prms = params || (provider.params
-            ? PD.getDefaultValues(provider.params(cell.obj.data, this.plugin))
+            ? PD.getDefaultValues(provider.params(cell.obj!.data, this.plugin))
             : {})
 
 
-        const task = Task.create<S>(`${provider.display.name}`, ctx => provider.apply(ctx, state, cell, prms, this.plugin) as Promise<S>);
+        const task = Task.create(`${provider.display.name}`, ctx => provider.apply(ctx, state, cell, prms, this.plugin) as Promise<any>);
         return this.plugin.runTask(task);
     }
 
-    // init() {
-    //     objectForEach(PresetStructureReprentations, r => this.register(r));
-    // }
-
     constructor(public plugin: PluginContext) {
         objectForEach(PresetStructureReprentations, r => this.register(r));
     }

+ 1 - 0
src/mol-plugin-state/builder/structure/component.ts

@@ -0,0 +1 @@
+// TODO: built in structure components

+ 20 - 12
src/mol-plugin-state/representation/structure/preset.ts → src/mol-plugin-state/builder/structure/preset.ts

@@ -5,13 +5,13 @@
  */
 
 import { StateTransforms } from '../../transforms';
-import { StructureComplexElementTypes } from '../../transforms/model';
 import { StructureRepresentation3DHelpers } from '../../transforms/representation';
 import { StructureSelectionQueries as Q } from '../../../mol-plugin/util/structure-selection-helper';
 import { BuiltInStructureRepresentations } from '../../../mol-repr/structure/registry';
 import { StructureRepresentationProvider, RepresentationProviderTags } from './provider';
 import { StateBuilder } from '../../../mol-state';
 import { PluginStateObject } from '../../objects';
+import { StaticStructureComponentType } from '../../helpers/structure-component';
 
 const auto = StructureRepresentationProvider({
     id: 'preset-structure-representation-auto',
@@ -71,7 +71,7 @@ const defaultPreset = StructureRepresentationProvider({
             .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D,
                 StructureRepresentation3DHelpers.getDefaultParamsWithTheme(plugin, 'spacefill', 'polymer-id', structure, {}));
 
-        await state.updateTree(root, { revertIfAborted: true }).runInContext(ctx);
+        await state.updateTree(root, { revertOnError: true }).runInContext(ctx);
 
         return {
             ligand: {
@@ -98,7 +98,7 @@ const proteinAndNucleic = StructureRepresentationProvider({
             .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D,
                 StructureRepresentation3DHelpers.getDefaultParams(plugin, 'gaussian-surface', structure));
 
-        await state.updateTree(root, { revertIfAborted: true }).runInContext(ctx);
+        await state.updateTree(root, { revertOnError: true }).runInContext(ctx);
         return {};
     }
 });
@@ -119,7 +119,7 @@ const capsid = StructureRepresentationProvider({
         applySelection(root, 'polymer')
             .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D, params);
 
-        await state.updateTree(root, { revertIfAborted: true }).runInContext(ctx);
+        await state.updateTree(root, { revertOnError: true }).runInContext(ctx);
         return {};
     }
 });
@@ -143,7 +143,7 @@ const coarseCapsid = StructureRepresentationProvider({
         applySelection(root, 'trace')
             .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D, params);
 
-        await state.updateTree(root, { revertIfAborted: true }).runInContext(ctx);
+        await state.updateTree(root, { revertOnError: true }).runInContext(ctx);
         return {};
     }
 });
@@ -164,19 +164,26 @@ const cartoon = StructureRepresentationProvider({
         applySelection(root, 'polymer')
             .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D, params);
 
-        await state.updateTree(root, { revertIfAborted: true }).runInContext(ctx);
+        await state.updateTree(root, { revertOnError: true }).runInContext(ctx);
         return {};
     }
 });
 
-function applyComplex(to: StateBuilder.To<PluginStateObject.Molecule.Structure>, type: keyof typeof StructureComplexElementTypes) {
-    return to.applyOrUpdateTagged(type, StateTransforms.Model.StructureComplexElement, { type }, { tags: RepresentationProviderTags.Selection });
+function applyComplex(to: StateBuilder.To<PluginStateObject.Molecule.Structure>, type: StaticStructureComponentType) {
+    return to.applyOrUpdateTagged(type, StateTransforms.Model.StructureComponent, { 
+        type: { name: 'static', params: type },
+        nullIfEmpty: true,
+        label: ''
+    }, { tags: RepresentationProviderTags.Selection });
 }
 
 function applySelection(to: StateBuilder.To<PluginStateObject.Molecule.Structure>, query: keyof typeof Q) {
-    return to.applyOrUpdateTagged(query, StateTransforms.Model.StructureSelectionFromExpression,
-        { expression: Q[query].expression, label: Q[query].label },
-        { tags: RepresentationProviderTags.Selection });
+    return to.applyOrUpdateTagged(query, StateTransforms.Model.StructureComponent, { 
+        type: { name: 'expression', params: Q[query].expression },
+        nullIfEmpty: true,
+        label: Q[query].label
+    },
+    { tags: RepresentationProviderTags.Selection });
 }
 
 export const PresetStructureReprentations = {
@@ -186,4 +193,5 @@ export const PresetStructureReprentations = {
     capsid,
     coarseCapsid,
     cartoon
-};
+};
+export type PresetStructureReprentations = typeof PresetStructureReprentations;

+ 5 - 0
src/mol-plugin-state/representation/structure/provider.ts → src/mol-plugin-state/builder/structure/provider.ts

@@ -21,6 +21,11 @@ export interface StructureRepresentationProvider<P = any, S = {}> {
     // remove?(state: State, ref: string, plugin: PluginContext): void
 }
 
+export namespace StructureRepresentationProvider {
+    export type Params<P extends StructureRepresentationProvider> = P extends StructureRepresentationProvider<infer T> ? T : never;
+    export type State<P extends StructureRepresentationProvider> = P extends StructureRepresentationProvider<infer _, infer S> ? S : never;
+}
+
 export const enum RepresentationProviderTags {
     Representation = 'preset-structure-representation',
     Selection = 'preset-structure-selection'

+ 0 - 0
src/mol-plugin-state/builder/structure/representation.ts


+ 1 - 1
src/mol-plugin-state/representation/model.ts → src/mol-plugin-state/helpers/root-structure.ts

@@ -16,7 +16,7 @@ import { Assembly, Symmetry } from '../../mol-model/structure/model/properties/s
 import { PluginStateObject as SO } from '../objects';
 import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
 
-export namespace ModelStructureRepresentation {
+export namespace RootStructureDefinition {
     export function getParams(model?: Model, defaultValue?: 'deposited' | 'assembly' | 'symmetry' | 'symmetry-mates' | 'symmetry-assembly') {
         const symmetry = model && ModelSymmetry.Provider.get(model)
 

+ 158 - 0
src/mol-plugin-state/helpers/structure-component.ts

@@ -0,0 +1,158 @@
+/**
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { ParamDefinition as PD } from '../../mol-util/param-definition';
+import Expression from '../../mol-script/language/expression';
+import { MolScriptBuilder } from '../../mol-script/language/builder';
+import { StructureElement, Structure, StructureSelection as Sel, StructureQuery, Queries, QueryContext } from '../../mol-model/structure';
+import { StructureQueryHelper } from '../../mol-plugin/util/structure-query';
+import { PluginStateObject as SO } from '../objects';
+import { StructureSelectionQueries } from '../../mol-plugin/util/structure-selection-helper';
+import { StateTransformer, StateObject } from '../../mol-state';
+import { Script } from '../../mol-script/script';
+
+export const StaticStructureComponentTypes = [
+    'all',
+
+    'protein-or-nucleic',
+
+    'protein',
+    'nucleic',
+    'water',
+
+    'branched',
+    'ligand',
+    'non-standard',
+
+    'coarse'
+] as const;
+
+export type StaticStructureComponentType = (typeof StaticStructureComponentTypes)[number]
+
+export const StructureComponentParams = {
+    type: PD.MappedStatic('static', {
+        static: PD.Text<StaticStructureComponentType>('protein-or-nucleic'),
+        expression: PD.Value<Expression>(MolScriptBuilder.struct.generator.all),
+        bundle: PD.Value<StructureElement.Bundle>(StructureElement.Bundle.Empty),
+        script: PD.Script({ language: 'mol-script', expression: '(sel.atom.all)' }),
+    }, { isHidden: true }),
+    nullIfEmpty: PD.Boolean(true, { isHidden: true }),
+    label: PD.Text('', { isHidden: true })
+};
+export type StructureComponentParams = PD.Values<typeof StructureComponentParams>
+
+export function createStructureComponent(a: Structure, params: StructureComponentParams, cache: { source: Structure, entry?: StructureQueryHelper.CacheEntry }) {
+    cache.source = a;
+
+    let component: Structure = Structure.Empty;
+    let label: string | undefined = void 0;
+    switch (params.type.name) {
+        case 'static': {
+            let query: StructureQuery;
+            switch (params.type.params) {
+                case 'all': query = StructureSelectionQueries.all.query; label = 'Sequence'; break;
+
+                case 'protein-or-nucleic': query = StructureSelectionQueries.proteinOrNucleic.query; label = 'Sequence'; break;
+    
+                case 'protein': query = StructureSelectionQueries.protein.query; label = 'Protein'; break;
+                case 'nucleic': query = StructureSelectionQueries.nucleic.query; label = 'Nucleic'; break;
+                case 'water': query = Queries.internal.water(); label = 'Water'; break;
+    
+                case 'branched': query = StructureSelectionQueries.branchedPlusConnected.query; label = 'Branched'; break;
+                case 'ligand': query = StructureSelectionQueries.ligandPlusConnected.query; label = 'Ligand'; break;
+    
+                case 'non-standard': query = StructureSelectionQueries.nonStandardPolymer.query; label = 'Non-standard'; break;
+    
+                case 'coarse': query = StructureSelectionQueries.coarse.query; label = 'Coarse'; break;
+        
+                default: throw new Error(`${params.type} is a not valid complex element.`);
+            }
+            const result = query(new QueryContext(a));
+            component = Sel.unionStructure(result);
+            break;
+        }
+        case 'script':
+        case 'expression': {
+            const { selection, entry } = StructureQueryHelper.createAndRun(a, params.type.params);
+            cache.entry = entry;
+            component = Sel.unionStructure(selection);
+            break;
+        }
+        case 'bundle': {
+            if (params.type.params.hash !== a.hashCode) break;
+            component = StructureElement.Bundle.toStructure(params.type.params, a);
+            break;
+        }
+    }
+
+    if (params.nullIfEmpty && component.elementCount === 0) return StateObject.Null;
+
+    const props = { label: `${params.label || label || 'Component'}`, description: Structure.elementDescription(component) };
+    return new SO.Molecule.Structure(component, props);
+}
+
+export function updateStructureComponent(a: Structure, b: SO.Molecule.Structure, oldParams: StructureComponentParams, newParams: StructureComponentParams, cache: { source: Structure, entry?: StructureQueryHelper.CacheEntry }) {
+    if (oldParams.type.name !== newParams.type.name) return StateTransformer.UpdateResult.Recreate;
+    
+    let updated = false;
+
+    switch (newParams.type.name) {
+        case 'static': {
+            if (a !== cache.source || oldParams.type.params !== newParams.type.params) {
+                return StateTransformer.UpdateResult.Recreate;
+            }
+            break;
+        }
+        case 'script':
+            if (!Script.areEqual(oldParams.type.params as Script, newParams.type.params)) {
+                return StateTransformer.UpdateResult.Recreate;
+            }
+        case 'expression': {
+            if ((oldParams.type.params as Expression) !== newParams.type.params) {
+                return StateTransformer.UpdateResult.Recreate;
+            }
+
+            if (a !== cache.source) return StateTransformer.UpdateResult.Unchanged;
+
+            const entry = (cache as { entry: StructureQueryHelper.CacheEntry }).entry;
+
+            const selection = StructureQueryHelper.updateStructure(entry, a);
+            cache.source = a;
+            b.data = Sel.unionStructure(selection);
+            StructureQueryHelper.updateStructureObject(b, selection, newParams.label);
+            updated = true;
+            break;
+        }
+        case 'bundle': {
+            if (a === cache.source && StructureElement.Bundle.areEqual(oldParams.type.params as StructureElement.Bundle, newParams.type.params)) {
+                break;
+            }
+
+            cache.source = a;
+            if (newParams.type.params.hash !== a.hashCode) {
+                updated = b.data.elementCount !== 0;
+                b.data = b.data.elementCount === 0 ? b.data : Structure.Empty;
+            } else {
+                updated = true;
+                b.data = StructureElement.Bundle.toStructure(newParams.type.params, a);
+            }
+            break;
+        }
+    }
+
+    if (updated) {
+        if (newParams.nullIfEmpty && b.data.elementCount === 0) return StateTransformer.UpdateResult.Null;
+
+        b.description = Structure.elementDescription(b.data);
+    }
+
+    if (oldParams.label !== newParams.label) {
+        updated = true;
+        b.label = `${newParams.label || b.label}`;
+    }
+
+    return updated ? StateTransformer.UpdateResult.Updated : StateTransformer.UpdateResult.Unchanged;
+}

+ 33 - 0
src/mol-plugin-state/transforms/data.ts

@@ -17,6 +17,7 @@ import * as CCP4 from '../../mol-io/reader/ccp4/parser'
 import * as DSN6 from '../../mol-io/reader/dsn6/parser'
 import * as PLY from '../../mol-io/reader/ply/parser'
 import { parsePsf } from '../../mol-io/reader/psf/parser';
+import { isTypedArray } from '../../mol-data/db/column-helpers';
 
 export { Download }
 type Download = typeof Download
@@ -95,6 +96,38 @@ const DownloadBlob = PluginStateTransform.BuiltIn({
     // }
 });
 
+export { RawData }
+type RawData = typeof RawData
+const RawData = PluginStateTransform.BuiltIn({
+    name: 'raw-data',
+    display: { name: 'Raw Data', description: 'Raw data supplied by value.' },
+    from: [SO.Root],
+    to: [SO.Data.String, SO.Data.Binary],
+    params: {
+        data: PD.Value<string | number[]>('', { isHidden: true }),
+        label: PD.Optional(PD.Text(''))
+    }
+})({
+    apply({ params: p }) {
+        return Task.create('Raw Data', async () => {
+            if (typeof p.data !== 'string' && isTypedArray(p.data)) {
+                throw new Error('Supplied binary data must be a plain array.');
+            }
+            return typeof p.data === 'string'
+                ? new SO.Data.String(p.data as string, { label: p.label ? p.label : 'String' })
+                : new SO.Data.Binary(new Uint8Array(p.data), { label: p.label ? p.label : 'Binary' });
+        });
+    },
+    update({ oldParams, newParams, b }) {
+        if (oldParams.data !== newParams.data) return StateTransformer.UpdateResult.Recreate;
+        if (oldParams.label !== newParams.label) {
+            b.label = newParams.label || b.label;
+            return StateTransformer.UpdateResult.Updated;
+        }
+        return StateTransformer.UpdateResult.Unchanged;
+    }
+});
+
 export { ReadFile }
 type ReadFile = typeof ReadFile
 const ReadFile = PluginStateTransform.BuiltIn({

+ 21 - 3
src/mol-plugin-state/transforms/model.ts

@@ -26,11 +26,12 @@ import { parse3DG } from '../../mol-io/reader/3dg/parser';
 import { trajectoryFrom3DG } from '../../mol-model-formats/structure/3dg';
 import { StructureSelectionQueries } from '../../mol-plugin/util/structure-selection-helper';
 import { StructureQueryHelper } from '../../mol-plugin/util/structure-query';
-import { ModelStructureRepresentation } from '../representation/model';
+import { RootStructureDefinition } from '../helpers/root-structure';
 import { parseDcd } from '../../mol-io/reader/dcd/parser';
 import { coordinatesFromDcd } from '../../mol-model-formats/structure/dcd';
 import { topologyFromPsf } from '../../mol-model-formats/structure/psf';
 import { deepEqual } from '../../mol-util';
+import { StructureComponentParams, createStructureComponent, updateStructureComponent } from '../helpers/structure-component';
 
 export { CoordinatesFromDcd };
 export { TopologyFromPsf };
@@ -50,6 +51,7 @@ export { MultiStructureSelectionFromExpression }
 export { StructureSelectionFromScript };
 export { StructureSelectionFromBundle };
 export { StructureComplexElement };
+export { StructureComponent }
 export { CustomModelProperties };
 export { CustomStructureProperties };
 
@@ -273,11 +275,11 @@ const StructureFromModel = PluginStateTransform.BuiltIn({
     display: { name: 'Structure', description: 'Create a molecular structure (deposited, assembly, or symmetry) from the specified model.' },
     from: SO.Molecule.Model,
     to: SO.Molecule.Structure,
-    params(a) { return ModelStructureRepresentation.getParams(a && a.data); }
+    params(a) { return RootStructureDefinition.getParams(a && a.data); }
 })({
     apply({ a, params }, plugin: PluginContext) {
         return Task.create('Build Structure', async ctx => {
-            return ModelStructureRepresentation.create(plugin, ctx, a.data, params && params.type);
+            return RootStructureDefinition.create(plugin, ctx, a.data, params && params.type);
         })
     },
     update: ({ a, b, oldParams, newParams }) => {
@@ -668,6 +670,22 @@ const StructureComplexElement = PluginStateTransform.BuiltIn({
     }
 });
 
+type StructureComponent = typeof StructureComponent
+const StructureComponent = PluginStateTransform.BuiltIn({
+    name: 'structure-component',
+    display: { name: 'Component', description: 'A molecular structure component.' },
+    from: SO.Molecule.Structure,
+    to: SO.Molecule.Structure,
+    params: StructureComponentParams
+})({
+    apply({ a, params, cache }) {
+        return createStructureComponent(a.data, params, cache as any);
+    },
+    update: ({ a, b, oldParams, newParams, cache }) => {
+        return updateStructureComponent(a.data, b, oldParams, newParams, cache as any);
+    }
+});
+
 type CustomModelProperties = typeof CustomModelProperties
 const CustomModelProperties = PluginStateTransform.BuiltIn({
     name: 'custom-model-properties',

+ 8 - 3
src/mol-plugin/context.ts

@@ -43,9 +43,10 @@ import { StructureOverpaintHelper } from './util/structure-overpaint-helper';
 import { PluginToastManager } from './util/toast';
 import { StructureMeasurementManager } from './util/structure-measurement';
 import { ViewportScreenshotHelper } from './util/viewport-screenshot';
-import { StructureRepresentationManager } from '../mol-plugin-state/representation/structure';
+import { StructureRepresentationBuilder } from '../mol-plugin-state/builder/structure-representation';
 import { CustomProperty } from '../mol-model-props/common/custom-property';
 import { PluginConfigManager } from './config';
+import { DataBuilder } from '../mol-plugin-state/builder/data';
 
 export class PluginContext {
     private disposed = false;
@@ -112,7 +113,6 @@ export class PluginContext {
     readonly structureRepresentation = {
         registry: new StructureRepresentationRegistry(),
         themeCtx: { colorThemeRegistry: ColorTheme.createRegistry(), sizeThemeRegistry: SizeTheme.createRegistry() } as ThemeRegistryContext,
-        manager: void 0 as any as StructureRepresentationManager
     } as const
 
     readonly volumeRepresentation = {
@@ -124,6 +124,11 @@ export class PluginContext {
         registry: new DataFormatRegistry()
     } as const
 
+    readonly builders = {
+        data: new DataBuilder(this),
+        structureRepresentation: void 0 as any as StructureRepresentationBuilder
+    };
+
     readonly customModelProperties = new CustomProperty.Registry<Model>();
     readonly customStructureProperties = new CustomProperty.Registry<Structure>();
     readonly customParamEditors = new Map<string, StateTransformParameters.Class>();
@@ -277,7 +282,7 @@ export class PluginContext {
         this.interactivity = new Interactivity(this);
         this.lociLabels = new LociLabelManager(this);
 
-        (this.structureRepresentation.manager as StructureRepresentationManager)= new StructureRepresentationManager(this);
+        (this.builders.structureRepresentation as StructureRepresentationBuilder)= new StructureRepresentationBuilder(this);
 
         this.log.message(`Mol* Plugin ${PLUGIN_VERSION} [${PLUGIN_VERSION_DATE.toLocaleString()}]`);
         if (!isProductionMode) this.log.message(`Development mode enabled`);

+ 24 - 0
src/mol-state/object.ts

@@ -169,4 +169,28 @@ export class StateObjectSelector<S extends StateObject = StateObject, T extends
 export namespace StateObjectSelector {
     export type Obj<S extends StateObjectSelector> = S extends StateObjectSelector<infer A> ? A : never
     export type Transformer<S extends StateObjectSelector> = S extends StateObjectSelector<any, infer T> ? T : never
+}
+
+export type StateObjectRef<S extends StateObject = StateObject> = StateObjectSelector<S> | StateObjectCell<S> | StateTransform.Ref
+
+export namespace StateObjectRef {
+    export function resolveRef<S extends StateObject>(state: State, ref?: StateObjectRef<S>): StateTransform.Ref | undefined {
+        if (!ref) return;
+        if (typeof ref === 'string') return ref;
+        if (StateObjectCell.is(ref)) return ref.transform.ref;
+        return ref.cell?.transform.ref;
+    }
+
+    export function resolve<S extends StateObject>(state: State, ref?: StateObjectRef<S>): StateObjectCell<S> | undefined {
+        if (!ref) return;
+        if (StateObjectCell.is(ref)) return ref;
+        if (typeof ref === 'string') return state.cells.get(ref) as StateObjectCell<S> | undefined;
+        return ref.cell;
+    }
+
+    export function resolveAndCheck<S extends StateObject>(state: State, ref?: StateObjectRef<S>): StateObjectCell<S> | undefined {
+        const cell = resolve(state, ref);
+        if (!cell || !cell.obj || cell.status !== 'ok') return;
+        return cell;
+    }
 }

+ 29 - 6
src/mol-state/state.ts

@@ -126,6 +126,26 @@ class State {
         });
     }
 
+    /** Apply series of updates to the state. If any of them fail, revert to the original state. */
+    transaction(edits: () => Promise<void> | void) {
+        return Task.create('State Transaction', async ctx => {
+            const snapshot = this._tree.asImmutable();
+            let restored = false;
+            try {
+                await edits();
+
+                let hasError = false;
+                this.cells.forEach(c => hasError = hasError || c.state === 'error');
+                if (hasError) {
+                    restored = true;
+                    this.updateTree(snapshot).runInContext(ctx);
+                }
+            } catch (e) {
+                if (!restored) this.updateTree(snapshot).runInContext(ctx);
+            }
+        });
+    }
+
     /**
      * Queues up a reconciliation of the existing state tree.
      *
@@ -142,8 +162,8 @@ class State {
             if (!removed) return;
 
             try {
-                const ret = options && options.revertIfAborted
-                    ? await this._revertibleTreeUpdate(taskCtx, params)
+                const ret = options && (options.revertIfAborted || options.revertOnError)
+                    ? await this._revertibleTreeUpdate(taskCtx, params, options)
                     : await this._updateTree(taskCtx, params);
                 return ret.cell;
             } finally {
@@ -156,10 +176,11 @@ class State {
 
     private updateQueue = new AsyncQueue<UpdateParams>();
 
-    private async _revertibleTreeUpdate(taskCtx: RuntimeContext, params: UpdateParams) {
+    private async _revertibleTreeUpdate(taskCtx: RuntimeContext, params: UpdateParams, options: Partial<State.UpdateOptions>) {
         const old = this.tree;
         const ret = await this._updateTree(taskCtx, params);
-        if (ret.ctx.wasAborted) return await this._updateTree(taskCtx, { tree: old, options: params.options });
+        let revert = ((ret.ctx.hadError || ret.ctx.wasAborted) && options.revertOnError) || (ret.ctx.wasAborted && options.revertIfAborted);
+        if (revert) return await this._updateTree(taskCtx, { tree: old, options: params.options });
         return ret;
     }
 
@@ -256,7 +277,8 @@ namespace State {
     export interface UpdateOptions {
         doNotLogTiming: boolean,
         doNotUpdateCurrent: boolean,
-        revertIfAborted: boolean
+        revertIfAborted: boolean,
+        revertOnError: boolean
     }
 
     export function create(rootObject: StateObject, params?: { globalContext?: unknown, rootState?: StateTransform.State }) {
@@ -267,7 +289,8 @@ namespace State {
 const StateUpdateDefaultOptions: State.UpdateOptions = {
     doNotLogTiming: false,
     doNotUpdateCurrent: false,
-    revertIfAborted: false
+    revertIfAborted: false,
+    revertOnError: false
 };
 
 type Ref = StateTransform.Ref

+ 12 - 4
src/mol-util/data-source.ts

@@ -73,25 +73,33 @@ function isDone(data: XMLHttpRequest | FileReader) {
     throw new Error('unknown data type')
 }
 
+function genericError(isDownload: boolean) {
+    if (isDownload) return 'Failed to download data. Possible reasons: Resource is not available, or CORS is not allowed on the server.';
+    return 'Failed to open file.';
+}
+
 function readData<T extends XMLHttpRequest | FileReader>(ctx: RuntimeContext, action: string, data: T): Promise<T> {
-    return new Promise<T>((resolve, reject) => {
+    return new Promise<T>((resolve, reject) => {        
         // first check if data reading is already done
         if (isDone(data)) {
             const { error } = data as FileReader;
             if (error !== null && error !== undefined) {
-                reject(error ?? 'Failed.');
+                reject(error ?? genericError(data instanceof XMLHttpRequest));
             } else {
                 resolve(data);
             }
             return
         }
 
+        let hasError = false;
+
         data.onerror = (e: ProgressEvent) => {
+            if (hasError) return;
+
             const { error } = e.target as FileReader;
-            reject(error ?? 'Failed.');
+            reject(error ?? genericError(data instanceof XMLHttpRequest));
         };
 
-        let hasError = false;
         data.onprogress = (e: ProgressEvent) => {
             if (!ctx.shouldUpdate || hasError) return;
 

+ 1 - 1
src/mol-util/param-definition.ts

@@ -221,7 +221,7 @@ export namespace ParamDefinition {
     export function MappedStatic<C extends Params>(defaultKey: keyof C, map: C, info?: Info & { options?: [keyof C, string][] }): Mapped<NamedParamUnion<C>> {
         const options: [string, string][] = info && info.options
             ? info.options as [string, string][]
-            : Object.keys(map).map(k => [k, stringToWords(k)]) as [string, string][];
+            : Object.keys(map).map(k => [k, map[k].label || stringToWords(k)]) as [string, string][];
         const name = checkDefaultKey(defaultKey, options);
         return setInfo<Mapped<NamedParamUnion<C>>>({
             type: 'mapped',