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

mol-plugin: state controls in viewport

parent bbd7f43f
No related branches found
No related tags found
No related merge requests found
......@@ -151,16 +151,32 @@
}
}
.msp-traj-controls {
.msp-viewport-top-left-controls {
position: absolute;
left: $control-spacing;
top: $control-spacing;
line-height: $row-height;
> span {
color: $font-color;
padding-top: 1px;
font-size: 85%;
display: inline-block;
.msp-traj-controls {
line-height: $row-height;
float: left;
margin-right: $control-spacing;
> span {
color: $font-color;
padding-top: 1px;
font-size: 85%;
display: inline-block;
}
}
.msp-state-snapshot-viewport-controls {
line-height: $row-height;
float: left;
margin-right: $control-spacing;
> select {
display: inline-block;
width: 200px;
}
}
}
\ No newline at end of file
......@@ -49,6 +49,23 @@ class PluginStateSnapshotManager extends PluginComponent<{ current?: UUID | unde
return e && e.snapshot;
}
getNextId(id: string | undefined, dir: -1 | 1) {
const xs = this.state.entries;
const keys = xs.keys();
let k = keys.next();
let prev = k.value;
const fst = prev;
while (!k.done) {
k = keys.next();
if (k.value === id && dir === -1) return prev;
if (!k.done && prev === id && dir === 1) return k.value;
if (!k.done) prev = k.value;
else break;
}
if (dir === -1) return prev;
return fst;
}
setRemoteSnapshot(snapshot: PluginStateSnapshotManager.RemoteSnapshot): PluginState.Snapshot | undefined {
this.clear();
const entries = this.state.entries.withMutations(m => {
......
......@@ -25,7 +25,8 @@ export class TrajectoryControls extends PluginUIComponent<{}, { show: boolean, l
.filter(c => c.transform.transformer === StateTransforms.Model.ModelFromTrajectory));
if (models.length === 0) {
this.setState({ show: false })
this.setState({ show: false });
return;
}
let label = '', count = 0, parents = new Set<string>();
......@@ -57,27 +58,85 @@ export class TrajectoryControls extends PluginUIComponent<{}, { show: boolean, l
this.subscribe(this.plugin.state.dataState.events.changed, this.update);
}
reset = () => PluginCommands.State.ApplyAction.dispatch(this.plugin, {
state: this.plugin.state.dataState,
action: UpdateTrajectory.create({ action: 'reset' })
});
prev = () => PluginCommands.State.ApplyAction.dispatch(this.plugin, {
state: this.plugin.state.dataState,
action: UpdateTrajectory.create({ action: 'advance', by: -1 })
});
next = () => PluginCommands.State.ApplyAction.dispatch(this.plugin, {
state: this.plugin.state.dataState,
action: UpdateTrajectory.create({ action: 'advance', by: 1 })
});
render() {
if (!this.state.show) return null;
return <div className='msp-traj-controls'>
<IconButton icon='model-first' title='First Model' onClick={() => PluginCommands.State.ApplyAction.dispatch(this.plugin, {
state: this.plugin.state.dataState,
action: UpdateTrajectory.create({ action: 'reset' })
})} />
<IconButton icon='model-prev' title='Previous Model' onClick={() => PluginCommands.State.ApplyAction.dispatch(this.plugin, {
state: this.plugin.state.dataState,
action: UpdateTrajectory.create({ action: 'advance', by: -1 })
})} />
<IconButton icon='model-next' title='Next Model' onClick={() => PluginCommands.State.ApplyAction.dispatch(this.plugin, {
state: this.plugin.state.dataState,
action: UpdateTrajectory.create({ action: 'advance', by: 1 })
})} />
<IconButton icon='model-first' title='First Model' onClick={this.reset} />
<IconButton icon='model-prev' title='Previous Model' onClick={this.prev} />
<IconButton icon='model-next' title='Next Model' onClick={this.next} />
{ !!this.state.label && <span>{this.state.label}</span> }
</div>;
}
}
export class StateSnapshotViewportControls extends PluginUIComponent<{}, { isBusy: boolean }> {
state = { isBusy: false }
componentDidMount() {
// TODO: this needs to be diabled when the state is updating!
this.subscribe(this.plugin.state.snapshots.events.changed, () => this.forceUpdate());
}
async update(id: string) {
this.setState({ isBusy: true });
await PluginCommands.State.Snapshots.Apply.dispatch(this.plugin, { id });
this.setState({ isBusy: false });
}
change = (e: React.ChangeEvent<HTMLSelectElement>) => {
if (e.target.value === 'none') return;
this.update(e.target.value);
}
prev = () => {
const s = this.plugin.state.snapshots;
const id = s.getNextId(s.state.current, -1);
if (id) this.update(id);
}
next = () => {
const s = this.plugin.state.snapshots;
const id = s.getNextId(s.state.current, 1);
if (id) this.update(id);
}
render() {
const snapshots = this.plugin.state.snapshots;
const count = snapshots.state.entries.size;
if (count < 2) {
return null;
}
const current = snapshots.state.current;
return <div className='msp-state-snapshot-viewport-controls'>
<select className='msp-form-control' value={current || 'none'} onChange={this.change} disabled={this.state.isBusy}>
{!current && <option key='none' value='none'></option>}
{snapshots.state.entries.valueSeq().map((e, i) => <option key={e!.snapshot.id} value={e!.snapshot.id}>{`[${i! + 1}/${count}]`} {e!.name || new Date(e!.timestamp).toLocaleString()}</option>)}
</select>
<IconButton icon='model-prev' title='Previous State' onClick={this.prev} disabled={this.state.isBusy} />
<IconButton icon='model-next' title='Next State' onClick={this.next} disabled={this.state.isBusy} />
</div>;
}
}
export class LociLabelControl extends PluginUIComponent<{}, { entries: ReadonlyArray<LociLabelEntry> }> {
state = { entries: [] }
......
......@@ -81,10 +81,10 @@ export class NumericInput extends React.PureComponent<{
}
}
export function IconButton(props: { icon: string, onClick: (e: React.MouseEvent<HTMLButtonElement>) => void, title?: string, toggleState?: boolean }) {
export function IconButton(props: { icon: string, onClick: (e: React.MouseEvent<HTMLButtonElement>) => void, title?: string, toggleState?: boolean, disabled?: boolean }) {
let className = `msp-btn msp-btn-link msp-btn-icon`;
if (typeof props.toggleState !== 'undefined') className += ` msp-btn-link-toggle-${props.toggleState ? 'on' : 'off'}`
return <button className={className} onClick={props.onClick} title={props.title}>
return <button className={className} onClick={props.onClick} title={props.title} disabled={props.disabled}>
<span className={`msp-icon msp-icon-${props.icon}`}/>
</button>;
}
......
......@@ -11,7 +11,7 @@ import { LogEntry } from 'mol-util/log-entry';
import * as React from 'react';
import { PluginContext } from '../context';
import { PluginReactContext, PluginUIComponent } from './base';
import { LociLabelControl, TrajectoryControls } from './controls';
import { LociLabelControl, TrajectoryControls, StateSnapshotViewportControls } from './controls';
import { StateSnapshots } from './state';
import { StateObjectActions } from './state/actions';
import { AnimationControls } from './state/animation';
......@@ -84,7 +84,10 @@ export class ViewportWrapper extends PluginUIComponent {
render() {
return <>
<Viewport />
<TrajectoryControls />
<div className='msp-viewport-top-left-controls'>
<TrajectoryControls />
<StateSnapshotViewportControls />
</div>
<ViewportControls />
<div style={{ position: 'absolute', left: '10px', bottom: '10px' }}>
<BackgroundTaskProgress />
......@@ -186,7 +189,7 @@ export class CurrentObject extends PluginUIComponent {
if (!showActions) return null;
return <>
{(cell.status === 'ok' || cell.status == 'error') && <UpdateTransformContol state={current.state} transform={transform} /> }
{(cell.status === 'ok' || cell.status === 'error') && <UpdateTransformContol state={current.state} transform={transform} /> }
{cell.status === 'ok' && <StateObjectActions state={current.state} nodeRef={ref} />}
</>;
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment