diff --git a/src/mol-plugin/behavior/static/state.ts b/src/mol-plugin/behavior/static/state.ts index b934f76d66426dd1c1a43eff1e0e31ce12bb7834..83c2d1072be246594149f3a75abf7d9c41b8f23d 100644 --- a/src/mol-plugin/behavior/static/state.ts +++ b/src/mol-plugin/behavior/static/state.ts @@ -49,7 +49,7 @@ export function SetCurrentObject(ctx: PluginContext) { } export function Update(ctx: PluginContext) { - PluginCommands.State.Update.subscribe(ctx, ({ state, tree, doNotLogTiming }) => ctx.runTask(state.updateTree(tree, doNotLogTiming))); + PluginCommands.State.Update.subscribe(ctx, ({ state, tree, options }) => ctx.runTask(state.updateTree(tree, options))); } export function ApplyAction(ctx: PluginContext) { diff --git a/src/mol-plugin/command.ts b/src/mol-plugin/command.ts index f2867ffb04fda5a53f09d40e55ae894dff28b3f3..530e721e2f381fd56cff42ef51dd83a57dbd27a2 100644 --- a/src/mol-plugin/command.ts +++ b/src/mol-plugin/command.ts @@ -17,7 +17,7 @@ export const PluginCommands = { State: { SetCurrentObject: PluginCommand<{ state: State, ref: StateTransform.Ref }>(), ApplyAction: PluginCommand<{ state: State, action: StateAction.Instance, ref?: StateTransform.Ref }>(), - Update: PluginCommand<{ state: State, tree: State.Tree | State.Builder, doNotLogTiming?: boolean }>(), + Update: PluginCommand<{ state: State, tree: State.Tree | State.Builder, options?: Partial<State.UpdateOptions> }>(), RemoveObject: PluginCommand<{ state: State, ref: StateTransform.Ref, removeParentGhosts?: boolean }>(), diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index 5f953100ae706045ce1a3f694c7f3a3736a02a27..459b569e7cd26e03d52a6c29f5a1690e3dfbaa0b 100644 --- a/src/mol-plugin/context.ts +++ b/src/mol-plugin/context.ts @@ -180,7 +180,7 @@ export class PluginContext { tree.to(PluginBehavior.getCategoryId(b.transformer)).apply(b.transformer, b.defaultParams, { ref: b.transformer.id }); } - await this.runTask(this.state.behaviorState.updateTree(tree, true)); + await this.runTask(this.state.behaviorState.updateTree(tree, { doNotUpdateCurrent: true, doNotLogTiming: true })); } private initDataActions() { diff --git a/src/mol-plugin/state/animation/built-in.ts b/src/mol-plugin/state/animation/built-in.ts index 9c85d8df57e786b42370d64c6b24d3b2a934d462..91f581b1ad65433989e16a3906994cc85b8a93ec 100644 --- a/src/mol-plugin/state/animation/built-in.ts +++ b/src/mol-plugin/state/animation/built-in.ts @@ -33,6 +33,11 @@ export const AnimateModelIndex = PluginStateAnimation.create({ const models = state.selectQ(q => q.rootsOfType(PluginStateObject.Molecule.Model) .filter(c => c.transform.transformer === StateTransforms.Model.ModelFromTrajectory)); + if (models.length === 0) { + // nothing more to do here + return { kind: 'finished' }; + } + const update = state.build(); const params = ctx.params; @@ -70,7 +75,7 @@ export const AnimateModelIndex = PluginStateAnimation.create({ }); } - await PluginCommands.State.Update.dispatch(ctx.plugin, { state, tree: update, doNotLogTiming: true }); + await PluginCommands.State.Update.dispatch(ctx.plugin, { state, tree: update, options: { doNotLogTiming: true } }); if (params.mode.name === 'once' && isEnd) return { kind: 'finished' }; if (params.mode.name === 'palindrome') return { kind: 'next', state: { palindromeDirections } }; diff --git a/src/mol-plugin/ui/state/tree.tsx b/src/mol-plugin/ui/state/tree.tsx index cb4dd6cc261d4f8cb97a4e4ea1424a03e39475a8..c48ee107237ad18799a598a89a732fefbfdebb46 100644 --- a/src/mol-plugin/ui/state/tree.tsx +++ b/src/mol-plugin/ui/state/tree.tsx @@ -84,8 +84,8 @@ class StateTreeNode extends PluginUIComponent<{ nodeRef: string, state: State, d } render() { - const cell = this.props.state.cells.get(this.props.nodeRef)!; - if (cell.obj === StateObject.Null) return null; + const cell = this.props.state.cells.get(this.props.nodeRef); + if (!cell || cell.obj === StateObject.Null) return null; const cellState = this.cellState; const showLabel = cell.status !== 'ok' || !cell.transform.props || !cell.transform.props.isGhost; @@ -202,7 +202,8 @@ class StateTreeNodeLabel extends PluginUIComponent< render() { const n = this.props.state.transforms.get(this.props.nodeRef)!; - const cell = this.props.state.cells.get(this.props.nodeRef)!; + const cell = this.props.state.cells.get(this.props.nodeRef); + if (!cell) return null; const isCurrent = this.is(this.props.state.behaviors.currentObject.value); diff --git a/src/mol-state/state.ts b/src/mol-state/state.ts index 81b56a729fc1dac6954a907dbe5e90ee4617f040..61f3473ce3efaa56e2aab914803d083273d67069 100644 --- a/src/mol-state/state.ts +++ b/src/mol-state/state.ts @@ -126,13 +126,13 @@ class State { * @param tree Tree instance or a tree builder instance * @param doNotReportTiming Indicates whether to log timing of the individual transforms */ - updateTree<T extends StateObject>(tree: StateTree | StateBuilder | StateBuilder.To<T>, doNotLogTiming?: boolean): Task<T> - updateTree(tree: StateTree | StateBuilder, doNotLogTiming?: boolean): Task<void> - updateTree(tree: StateTree | StateBuilder, doNotLogTiming: boolean = false): Task<any> { + updateTree<T extends StateObject>(tree: StateBuilder.To<T>, options?: Partial<State.UpdateOptions>): Task<T> + updateTree(tree: StateTree | StateBuilder, options?: Partial<State.UpdateOptions>): Task<void> + updateTree(tree: StateTree | StateBuilder, options?: Partial<State.UpdateOptions>): Task<any> { return Task.create('Update Tree', async taskCtx => { let updated = false; try { - const ctx = this.updateTreeAndCreateCtx(tree, taskCtx, doNotLogTiming); + const ctx = this.updateTreeAndCreateCtx(tree, taskCtx, options); updated = await update(ctx); if (StateBuilder.isTo(tree)) { const cell = this.select(tree.ref)[0]; @@ -144,7 +144,7 @@ class State { }); } - private updateTreeAndCreateCtx(tree: StateTree | StateBuilder, taskCtx: RuntimeContext, doNotLogTiming: boolean) { + private updateTreeAndCreateCtx(tree: StateTree | StateBuilder, taskCtx: RuntimeContext, options: Partial<State.UpdateOptions> | undefined) { const _tree = (StateBuilder.is(tree) ? tree.getTree() : tree).asTransient(); const oldTree = this._tree; this._tree = _tree; @@ -162,7 +162,7 @@ class State { results: [], - silent: doNotLogTiming, + options: { ...StateUpdateDefaultOptions, ...options }, changed: false, hadError: false, @@ -210,11 +210,21 @@ namespace State { readonly tree: StateTree.Serialized } + export interface UpdateOptions { + doNotLogTiming: boolean, + doNotUpdateCurrent: boolean + } + export function create(rootObject: StateObject, params?: { globalContext?: unknown, rootProps?: StateTransform.Props }) { return new State(rootObject, params); } } +const StateUpdateDefaultOptions: State.UpdateOptions = { + doNotLogTiming: false, + doNotUpdateCurrent: false +}; + type Ref = StateTransform.Ref interface UpdateContext { @@ -231,7 +241,7 @@ interface UpdateContext { results: UpdateNodeResult[], // suppress timing messages - silent: boolean, + options: State.UpdateOptions, changed: boolean, hadError: boolean, @@ -324,13 +334,13 @@ async function update(ctx: UpdateContext) { } } - if (newCurrent) ctx.parent.setCurrent(newCurrent); - else { + if (newCurrent) { + if (!ctx.options.doNotUpdateCurrent) ctx.parent.setCurrent(newCurrent); + } else { // check if old current or its parent hasn't become null const current = ctx.parent.current; const currentCell = ctx.cells.get(current); - if (currentCell && ( - currentCell.obj === StateObject.Null + if (currentCell && (currentCell.obj === StateObject.Null || (currentCell.status === 'error' && currentCell.errorText === ParentNullErrorText))) { newCurrent = findNewCurrent(ctx.oldTree, current, [], ctx.cells); ctx.parent.setCurrent(newCurrent); @@ -515,13 +525,13 @@ async function updateSubtree(ctx: UpdateContext, root: Ref) { ctx.results.push(update); if (update.action === 'created') { isNull = update.obj === StateObject.Null; - if (!isNull && !ctx.silent) ctx.parent.events.log.next(LogEntry.info(`Created ${update.obj.label} in ${formatTimespan(time)}.`)); + if (!isNull && !ctx.options.doNotLogTiming) ctx.parent.events.log.next(LogEntry.info(`Created ${update.obj.label} in ${formatTimespan(time)}.`)); } else if (update.action === 'updated') { isNull = update.obj === StateObject.Null; - if (!isNull && !ctx.silent) ctx.parent.events.log.next(LogEntry.info(`Updated ${update.obj.label} in ${formatTimespan(time)}.`)); + if (!isNull && !ctx.options.doNotLogTiming) ctx.parent.events.log.next(LogEntry.info(`Updated ${update.obj.label} in ${formatTimespan(time)}.`)); } else if (update.action === 'replaced') { isNull = update.obj === StateObject.Null; - if (!isNull && !ctx.silent) ctx.parent.events.log.next(LogEntry.info(`Updated ${update.obj.label} in ${formatTimespan(time)}.`)); + if (!isNull && !ctx.options.doNotLogTiming) ctx.parent.events.log.next(LogEntry.info(`Updated ${update.obj.label} in ${formatTimespan(time)}.`)); } } catch (e) { ctx.changed = true;