diff --git a/src/mol-plugin-ui/controls.tsx b/src/mol-plugin-ui/controls.tsx index 151f5fd8647731a70ada498dda0b347b07b204d2..3a4d088f61cb5290489312439b196dab45b9c39f 100644 --- a/src/mol-plugin-ui/controls.tsx +++ b/src/mol-plugin-ui/controls.tsx @@ -116,7 +116,7 @@ export class StateSnapshotViewportControls extends PluginUIComponent<{}, { isBus componentDidMount() { // TODO: this needs to be diabled when the state is updating! this.subscribe(this.plugin.state.snapshots.events.changed, () => this.forceUpdate()); - this.subscribe(this.plugin.behaviors.state.isUpdating, isBusy => this.setState({ isBusy })); + this.subscribe(this.plugin.behaviors.state.isBusy, isBusy => this.setState({ isBusy })); this.subscribe(this.plugin.behaviors.state.isAnimating, isBusy => this.setState({ isBusy })) window.addEventListener('keyup', this.keyUp, false); @@ -202,17 +202,17 @@ export class StateSnapshotViewportControls extends PluginUIComponent<{}, { isBus } } -export class AnimationViewportControls extends PluginUIComponent<{}, { isEmpty: boolean, isExpanded: boolean, isUpdating: boolean, isAnimating: boolean, isPlaying: boolean }> { - state = { isEmpty: true, isExpanded: false, isUpdating: false, isAnimating: false, isPlaying: false }; +export class AnimationViewportControls extends PluginUIComponent<{}, { isEmpty: boolean, isExpanded: boolean, isBusy: boolean, isAnimating: boolean, isPlaying: boolean }> { + state = { isEmpty: true, isExpanded: false, isBusy: false, isAnimating: false, isPlaying: false }; componentDidMount() { this.subscribe(this.plugin.state.snapshots.events.changed, () => { if (this.plugin.state.snapshots.state.isPlaying) this.setState({ isPlaying: true, isExpanded: false }); else this.setState({ isPlaying: false }); }); - this.subscribe(this.plugin.behaviors.state.isUpdating, isUpdating => { - if (isUpdating) this.setState({ isUpdating: true, isExpanded: false, isEmpty: this.plugin.state.data.tree.transforms.size < 2 }); - else this.setState({ isUpdating: false, isEmpty: this.plugin.state.data.tree.transforms.size < 2 }); + this.subscribe(this.plugin.behaviors.state.isBusy, isBusy => { + if (isBusy) this.setState({ isBusy: true, isExpanded: false, isEmpty: this.plugin.state.data.tree.transforms.size < 2 }); + else this.setState({ isBusy: false, isEmpty: this.plugin.state.data.tree.transforms.size < 2 }); }); this.subscribe(this.plugin.behaviors.state.isAnimating, isAnimating => { if (isAnimating) this.setState({ isAnimating: true, isExpanded: false }); @@ -237,9 +237,9 @@ export class AnimationViewportControls extends PluginUIComponent<{}, { isEmpty: <div className='msp-semi-transparent-background' /> <IconButton icon={isAnimating || isPlaying ? 'stop' : 'tape'} title={isAnimating ? 'Stop' : 'Select Animation'} onClick={isAnimating || isPlaying ? this.stop : this.toggleExpanded} - disabled={isAnimating|| isPlaying ? false : this.state.isUpdating || this.state.isPlaying || this.state.isEmpty} /> + disabled={isAnimating|| isPlaying ? false : this.state.isBusy || this.state.isPlaying || this.state.isEmpty} /> </div> - {(this.state.isExpanded && !this.state.isUpdating) && <div className='msp-animation-viewport-controls-select'> + {(this.state.isExpanded && !this.state.isBusy) && <div className='msp-animation-viewport-controls-select'> <AnimationControls onStart={this.toggleExpanded} /> </div>} </div>; diff --git a/src/mol-plugin-ui/controls/parameters.tsx b/src/mol-plugin-ui/controls/parameters.tsx index 46c024b740a2481638bd1585ada92c2cefa980be..955b640b14dcdc884ed307266b9ef2edb0ac323e 100644 --- a/src/mol-plugin-ui/controls/parameters.tsx +++ b/src/mol-plugin-ui/controls/parameters.tsx @@ -884,6 +884,8 @@ export class GroupControl extends React.PureComponent<ParamProps<PD.Group<any>> export class MappedControl extends React.PureComponent<ParamProps<PD.Mapped<any>>, { isExpanded: boolean }> { state = { isExpanded: false } + // TODO: this could lead to a rare bug where the component is reused with different mapped control. + // I think there are currently no cases where this could happen in the UI, but still need to watch out.. private valuesCache: { [name: string]: PD.Values<any> } = {} private setValues(name: string, values: PD.Values<any>) { this.valuesCache[name] = values diff --git a/src/mol-plugin/config.ts b/src/mol-plugin/config.ts index 9fb8adb7892f3047428db3efc48bf59cc1163602..3b5dd48893eb313d3e1271f59534477ca89587cf 100644 --- a/src/mol-plugin/config.ts +++ b/src/mol-plugin/config.ts @@ -20,7 +20,8 @@ export const PluginConfig = { item, 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') + CurrentServer: item('plugin-state.server', 'https://webchem.ncbr.muni.cz/molstar-state'), + IsBusyTimeoutMs: item('plugin-state.is-busy-timeout', 750) }, VolumeStreaming: { DefaultServer: item('volume-streaming.server', 'https://ds.litemol.org'), diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index bf2d1d535cf021c402f328bcafc43b7a216c0b1f..7143b2417deb051e26d88e79e1322078fef61ee6 100644 --- a/src/mol-plugin/context.ts +++ b/src/mol-plugin/context.ts @@ -8,6 +8,7 @@ import { setAutoFreeze } from 'immer'; import { List } from 'immutable'; import { merge } from 'rxjs'; +import { debounceTime } from 'rxjs/operators'; import { Canvas3D } from '../mol-canvas3d/canvas3d'; import { CustomProperty } from '../mol-model-props/common/custom-property'; import { Model, Structure } from '../mol-model/structure'; @@ -15,14 +16,19 @@ import { DataFormatRegistry } from '../mol-plugin-state/actions/data-format'; import { DataBuilder } from '../mol-plugin-state/builder/data'; import { StructureBuilder } from '../mol-plugin-state/builder/structure'; import { TrajectoryFormatRegistry } from '../mol-plugin-state/formats/trajectory'; +import { StructureSelectionQueryRegistry } from '../mol-plugin-state/helpers/structure-selection-query'; import { CameraManager } from '../mol-plugin-state/manager/camera'; import { InteractivityManager } from '../mol-plugin-state/manager/interactivity'; import { LociLabelEntry, LociLabelManager } from '../mol-plugin-state/manager/loci-label'; import { StructureComponentManager } from '../mol-plugin-state/manager/structure/component'; +import { StructureFocusManager } from '../mol-plugin-state/manager/structure/focus'; import { StructureHierarchyManager } from '../mol-plugin-state/manager/structure/hierarchy'; +import { HierarchyRef } from '../mol-plugin-state/manager/structure/hierarchy-state'; import { StructureMeasurementManager } from '../mol-plugin-state/manager/structure/measurement'; import { StructureSelectionManager } from '../mol-plugin-state/manager/structure/selection'; +import { PluginUIComponent } from '../mol-plugin-ui/base'; import { StateTransformParameters } from '../mol-plugin-ui/state/common'; +import { Representation } from '../mol-repr/representation'; import { StructureRepresentationRegistry } from '../mol-repr/structure/registry'; import { VolumeRepresentationRegistry } from '../mol-repr/volume/registry'; import { State, StateBuilder, StateTree } from '../mol-state'; @@ -40,7 +46,7 @@ import { BuiltInPluginBehaviors } from './behavior'; import { PluginBehavior } from './behavior/behavior'; import { PluginCommandManager } from './command'; import { PluginCommands } from './commands'; -import { PluginConfigManager } from './config'; +import { PluginConfigManager, PluginConfig } from './config'; import { LeftPanelTabName, PluginLayout } from './layout'; import { PluginSpec } from './spec'; import { PluginState } from './state'; @@ -49,11 +55,6 @@ import { TaskManager } from './util/task-manager'; import { PluginToastManager } from './util/toast'; import { ViewportScreenshotHelper } from './util/viewport-screenshot'; import { PLUGIN_VERSION, PLUGIN_VERSION_DATE } from './version'; -import { Representation } from '../mol-repr/representation'; -import { HierarchyRef } from '../mol-plugin-state/manager/structure/hierarchy-state'; -import { PluginUIComponent } from '../mol-plugin-ui/base'; -import { StructureFocusManager } from '../mol-plugin-state/manager/structure/focus'; -import { StructureSelectionQueryRegistry } from '../mol-plugin-state/helpers/structure-selection-query'; export class PluginContext { private disposed = false; @@ -241,7 +242,8 @@ export class PluginContext { this.behaviors.state.isUpdating.next(u); }); - merge(this.behaviors.state.isUpdating, this.behaviors.state.isAnimating).subscribe(() => { + const timeout = this.config.get(PluginConfig.State.IsBusyTimeoutMs) || 500; + merge(debounceTime(timeout)(this.behaviors.state.isUpdating), debounceTime(timeout)(this.behaviors.state.isAnimating)).subscribe(() => { const isUpdating = this.behaviors.state.isUpdating.value; const isAnimating = this.behaviors.state.isAnimating.value; const isBusy = this.behaviors.state.isBusy;