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

mol-plugin: refactor PluginComponent

parent 209e35af
No related branches found
No related tags found
No related merge requests found
......@@ -4,41 +4,38 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { PluginContext } from './context';
import { shallowMergeArray } from 'mol-util/object';
import { RxEventHelper } from 'mol-util/rx-event-helper';
export class PluginComponent<State> {
private _state: BehaviorSubject<State>;
private _updated = new Subject();
private _ev: RxEventHelper;
updateState(...states: Partial<State>[]): boolean {
const latest = this.latestState;
protected get ev() {
return this._ev || (this._ev = RxEventHelper.create());
}
private _state: State;
protected updateState(...states: Partial<State>[]): boolean {
const latest = this.state;
const s = shallowMergeArray(latest, states);
if (s !== latest) {
this._state.next(s);
this._state = s;
return true;
}
return false;
}
get states() {
return <Observable<State>>this._state;
}
get latestState() {
return this._state.value;
}
get updated() {
return <Observable<{}>>this._updated;
get state() {
return this._state;
}
triggerUpdate() {
this._updated.next({});
dispose() {
if (this._ev) this._ev.dispose();
}
constructor(public context: PluginContext, initialState: State) {
this._state = new BehaviorSubject<State>(initialState);
this._state = initialState;
}
}
\ No newline at end of file
......@@ -93,7 +93,7 @@ export class PluginContext {
initViewer(canvas: HTMLCanvasElement, container: HTMLDivElement) {
try {
this.layout.setRoot(container);
if (this.spec.initialLayout) this.layout.updateState(this.spec.initialLayout);
if (this.spec.initialLayout) this.layout.setProps(this.spec.initialLayout);
(this.canvas3d as Canvas3D) = Canvas3D.create(canvas, container);
PluginCommands.Canvas3D.SetSettings.dispatch(this, { settings: { backgroundColor: Color(0xFCFBF9) } });
this.canvas3d.animate();
......@@ -135,6 +135,7 @@ export class PluginContext {
this.ev.dispose();
this.state.dispose();
this.tasks.dispose();
this.layout.dispose();
this.disposed = true;
}
......
......@@ -42,21 +42,30 @@ interface RootState {
}
export class PluginLayout extends PluginComponent<PluginLayoutStateProps> {
readonly events = {
updated: this.ev()
}
private updateProps(state: Partial<PluginLayoutStateProps>) {
let prevExpanded = !!this.latestState.isExpanded;
let prevExpanded = !!this.state.isExpanded;
this.updateState(state);
if (this.root && typeof state.isExpanded === 'boolean' && state.isExpanded !== prevExpanded) this.handleExpand();
this.triggerUpdate();
this.events.updated.next();
}
private root: HTMLElement;
private rootState: RootState | undefined = void 0;
private expandedViewport: HTMLMetaElement;
setProps(props: PluginLayoutStateProps) {
this.updateState(props);
}
setRoot(root: HTMLElement) {
this.root = root;
if (this.latestState.isExpanded) this.handleExpand();
if (this.state.isExpanded) this.handleExpand();
}
private getScrollElement() {
......@@ -72,7 +81,7 @@ export class PluginLayout extends PluginComponent<PluginLayoutStateProps> {
if (!body || !head) return;
if (this.latestState.isExpanded) {
if (this.state.isExpanded) {
let children = head.children;
let hasExp = false;
let viewports: HTMLElement[] = [];
......
......@@ -73,6 +73,7 @@ class PluginState {
this.dataState.dispose();
this.behaviorState.dispose();
this.cameraSnapshots.dispose();
this.animation.dispose();
}
constructor(private plugin: import('./context').PluginContext) {
......
......@@ -21,9 +21,17 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat
private _current: PluginAnimationManager.Current;
private _params?: PD.For<PluginAnimationManager.State['params']> = void 0;
readonly events = {
updated: this.ev()
};
get isEmpty() { return this.animations.length === 0; }
get current() { return this._current!; }
private triggerUpdate() {
this.events.updated.next();
}
getParams(): PD.Params {
if (!this._params) {
this._params = {
......@@ -36,8 +44,8 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat
}
updateParams(newParams: Partial<PluginAnimationManager.State['params']>) {
this.updateState({ params: { ...this.latestState.params, ...newParams } });
const anim = this.map.get(this.latestState.params.current)!;
this.updateState({ params: { ...this.state.params, ...newParams } });
const anim = this.map.get(this.state.params.current)!;
const params = anim.params(this.context) as PD.Params;
this._current = {
anim,
......@@ -98,7 +106,7 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat
}
get isAnimating() {
return this.latestState.animationState === 'playing';
return this.state.animationState === 'playing';
}
private _frame: number | undefined = void 0;
......@@ -116,17 +124,17 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat
} else if (newState.kind === 'next') {
this._current.state = newState.state;
this._current.lastTime = t - this._current.startedTime;
if (this.latestState.animationState === 'playing') this._frame = requestAnimationFrame(this.animate);
if (this.state.animationState === 'playing') this._frame = requestAnimationFrame(this.animate);
} else if (newState.kind === 'skip') {
if (this.latestState.animationState === 'playing') this._frame = requestAnimationFrame(this.animate);
if (this.state.animationState === 'playing') this._frame = requestAnimationFrame(this.animate);
}
}
getSnapshot(): PluginAnimationManager.Snapshot {
if (!this.current) return { state: this.latestState };
if (!this.current) return { state: this.state };
return {
state: this.latestState,
state: this.state,
current: {
paramValues: this._current.paramValues,
state: this._current.anim.stateSerialization ? this._current.anim.stateSerialization.toJSON(this._current.state) : this._current.state
......@@ -144,7 +152,7 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat
? this._current.anim.stateSerialization.fromJSON(snapshot.current.state)
: snapshot.current.state;
this.triggerUpdate();
if (this.latestState.animationState === 'playing') this.resume();
if (this.state.animationState === 'playing') this.resume();
}
}
......
......@@ -10,7 +10,7 @@ import { PluginContext } from '../context';
export const PluginReactContext = React.createContext(void 0 as any as PluginContext);
export abstract class PluginComponent<P = {}, S = {}, SS = {}> extends React.Component<P, S, SS> {
export abstract class PluginUIComponent<P = {}, S = {}, SS = {}> extends React.Component<P, S, SS> {
static contextType = PluginReactContext;
readonly plugin: PluginContext;
......@@ -35,7 +35,7 @@ export abstract class PluginComponent<P = {}, S = {}, SS = {}> extends React.Com
}
}
export abstract class PurePluginComponent<P = {}, S = {}, SS = {}> extends React.PureComponent<P, S, SS> {
export abstract class PurePluginUIComponent<P = {}, S = {}, SS = {}> extends React.PureComponent<P, S, SS> {
static contextType = PluginReactContext;
readonly plugin: PluginContext;
......
......@@ -6,11 +6,11 @@
import { PluginCommands } from 'mol-plugin/command';
import * as React from 'react';
import { PluginComponent } from './base';
import { PluginUIComponent } from './base';
import { ParamDefinition as PD } from 'mol-util/param-definition';
import { ParameterControls } from './controls/parameters';
export class CameraSnapshots extends PluginComponent<{ }, { }> {
export class CameraSnapshots extends PluginUIComponent<{ }, { }> {
render() {
return <div>
<div className='msp-section-header'>Camera Snapshots</div>
......@@ -20,7 +20,7 @@ export class CameraSnapshots extends PluginComponent<{ }, { }> {
}
}
class CameraSnapshotControls extends PluginComponent<{ }, { name: string, description: string }> {
class CameraSnapshotControls extends PluginUIComponent<{ }, { name: string, description: string }> {
static Params = {
name: PD.Text(),
description: PD.Text()
......@@ -48,7 +48,7 @@ class CameraSnapshotControls extends PluginComponent<{ }, { name: string, descri
}
}
class CameraSnapshotList extends PluginComponent<{ }, { }> {
class CameraSnapshotList extends PluginUIComponent<{ }, { }> {
componentDidMount() {
this.subscribe(this.plugin.events.state.cameraSnapshots.changed, () => this.forceUpdate());
}
......
......@@ -7,10 +7,10 @@
import * as React from 'react';
import { PluginCommands } from 'mol-plugin/command';
import { UpdateTrajectory } from 'mol-plugin/state/actions/basic';
import { PluginComponent } from './base';
import { PluginUIComponent } from './base';
import { LociLabelEntry } from 'mol-plugin/util/loci-label-manager';
export class Controls extends PluginComponent<{ }, { }> {
export class Controls extends PluginUIComponent<{ }, { }> {
render() {
return <>
......@@ -18,7 +18,7 @@ export class Controls extends PluginComponent<{ }, { }> {
}
}
export class TrajectoryControls extends PluginComponent {
export class TrajectoryControls extends PluginUIComponent {
render() {
return <div>
<button className='msp-btn msp-btn-link' onClick={() => PluginCommands.State.ApplyAction.dispatch(this.plugin, {
......@@ -37,7 +37,7 @@ export class TrajectoryControls extends PluginComponent {
}
}
export class LociLabelControl extends PluginComponent<{}, { entries: ReadonlyArray<LociLabelEntry> }> {
export class LociLabelControl extends PluginUIComponent<{}, { entries: ReadonlyArray<LociLabelEntry> }> {
state = { entries: [] }
componentDidMount() {
......
......@@ -9,7 +9,7 @@ import { PluginContext } from '../context';
import { StateTree } from './state-tree';
import { Viewport, ViewportControls } from './viewport';
import { Controls, TrajectoryControls, LociLabelControl } from './controls';
import { PluginComponent, PluginReactContext } from './base';
import { PluginUIComponent, PluginReactContext } from './base';
import { CameraSnapshots } from './camera';
import { StateSnapshots } from './state';
import { List } from 'immutable';
......@@ -39,9 +39,9 @@ export class Plugin extends React.Component<{ plugin: PluginContext }, {}> {
}
}
class Layout extends PluginComponent {
class Layout extends PluginUIComponent {
componentDidMount() {
this.subscribe(this.plugin.layout.updated, () => this.forceUpdate());
this.subscribe(this.plugin.layout.events.updated, () => this.forceUpdate());
}
region(kind: 'left' | 'right' | 'bottom' | 'main', element: JSX.Element) {
......@@ -53,7 +53,7 @@ class Layout extends PluginComponent {
}
render() {
const layout = this.plugin.layout.latestState;
const layout = this.plugin.layout.state;
return <div className='msp-plugin'>
<div className={`msp-plugin-content ${layout.isExpanded ? 'msp-layout-expanded' : 'msp-layout-standard msp-layout-standard-outside'}`}>
<div className={layout.showControls ? 'msp-layout-hide-top' : 'msp-layout-hide-top msp-layout-hide-right msp-layout-hide-bottom msp-layout-hide-left'}>
......@@ -73,7 +73,7 @@ class Layout extends PluginComponent {
}
}
export class ViewportWrapper extends PluginComponent {
export class ViewportWrapper extends PluginUIComponent {
render() {
return <>
<Viewport />
......@@ -91,7 +91,7 @@ export class ViewportWrapper extends PluginComponent {
}
}
export class State extends PluginComponent {
export class State extends PluginUIComponent {
componentDidMount() {
this.subscribe(this.plugin.state.behavior.kind, () => this.forceUpdate());
}
......@@ -113,7 +113,7 @@ export class State extends PluginComponent {
}
}
export class Log extends PluginComponent<{}, { entries: List<LogEntry> }> {
export class Log extends PluginUIComponent<{}, { entries: List<LogEntry> }> {
private wrapper = React.createRef<HTMLDivElement>();
componentDidMount() {
......@@ -151,7 +151,7 @@ export class Log extends PluginComponent<{}, { entries: List<LogEntry> }> {
}
}
export class CurrentObject extends PluginComponent {
export class CurrentObject extends PluginUIComponent {
get current() {
return this.plugin.state.behavior.currentObject.value;
}
......
......@@ -8,16 +8,16 @@ import * as React from 'react';
import { PluginStateObject } from 'mol-plugin/state/objects';
import { State, StateObject } from 'mol-state'
import { PluginCommands } from 'mol-plugin/command';
import { PluginComponent } from './base';
import { PluginUIComponent } from './base';
export class StateTree extends PluginComponent<{ state: State }> {
export class StateTree extends PluginUIComponent<{ state: State }> {
render() {
const n = this.props.state.tree.root.ref;
return <StateTreeNode state={this.props.state} nodeRef={n} />;
}
}
class StateTreeNode extends PluginComponent<{ nodeRef: string, state: State }, { state: State, isCollapsed: boolean }> {
class StateTreeNode extends PluginUIComponent<{ nodeRef: string, state: State }, { state: State, isCollapsed: boolean }> {
is(e: State.ObjectEvent) {
return e.ref === this.props.nodeRef && e.state === this.props.state;
}
......@@ -77,7 +77,7 @@ class StateTreeNode extends PluginComponent<{ nodeRef: string, state: State }, {
}
}
class StateTreeNodeLabel extends PluginComponent<{ nodeRef: string, state: State }, { state: State, isCurrent: boolean, isCollapsed: boolean }> {
class StateTreeNodeLabel extends PluginUIComponent<{ nodeRef: string, state: State }, { state: State, isCurrent: boolean, isCollapsed: boolean }> {
is(e: State.ObjectEvent) {
return e.ref === this.props.nodeRef && e.state === this.props.state;
}
......
......@@ -6,14 +6,14 @@
import { PluginCommands } from 'mol-plugin/command';
import * as React from 'react';
import { PluginComponent } from './base';
import { PluginUIComponent } from './base';
import { shallowEqual } from 'mol-util';
import { List } from 'immutable';
import { ParameterControls } from './controls/parameters';
import { ParamDefinition as PD} from 'mol-util/param-definition';
import { Subject } from 'rxjs';
export class StateSnapshots extends PluginComponent<{ }, { serverUrl: string }> {
export class StateSnapshots extends PluginUIComponent<{ }, { serverUrl: string }> {
state = { serverUrl: 'https://webchem.ncbr.muni.cz/molstar-state' }
updateServerUrl = (serverUrl: string) => { this.setState({ serverUrl }) };
......@@ -31,7 +31,7 @@ export class StateSnapshots extends PluginComponent<{ }, { serverUrl: string }>
// TODO: this is not nice: device some custom event system.
const UploadedEvent = new Subject();
class StateSnapshotControls extends PluginComponent<{ serverUrl: string, serverChanged: (url: string) => void }, { name: string, description: string, serverUrl: string, isUploading: boolean }> {
class StateSnapshotControls extends PluginUIComponent<{ serverUrl: string, serverChanged: (url: string) => void }, { name: string, description: string, serverUrl: string, isUploading: boolean }> {
state = { name: '', description: '', serverUrl: this.props.serverUrl, isUploading: false };
static Params = {
......@@ -93,7 +93,7 @@ class StateSnapshotControls extends PluginComponent<{ serverUrl: string, serverC
}
}
class LocalStateSnapshotList extends PluginComponent<{ }, { }> {
class LocalStateSnapshotList extends PluginUIComponent<{ }, { }> {
componentDidMount() {
this.subscribe(this.plugin.events.state.snapshots.changed, () => this.forceUpdate());
}
......@@ -121,7 +121,7 @@ class LocalStateSnapshotList extends PluginComponent<{ }, { }> {
}
type RemoteEntry = { url: string, removeUrl: string, timestamp: number, id: string, name: string, description: string }
class RemoteStateSnapshotList extends PluginComponent<{ serverUrl: string }, { entries: List<RemoteEntry>, isFetching: boolean }> {
class RemoteStateSnapshotList extends PluginUIComponent<{ serverUrl: string }, { entries: List<RemoteEntry>, isFetching: boolean }> {
state = { entries: List<RemoteEntry>(), isFetching: false };
componentDidMount() {
......
......@@ -5,12 +5,12 @@
*/
import * as React from 'react';
import { PluginComponent } from '../base';
import { PluginUIComponent } from '../base';
import { ParameterControls, ParamOnChange } from '../controls/parameters';
export class AnimationControls extends PluginComponent<{ }> {
export class AnimationControls extends PluginUIComponent<{ }> {
componentDidMount() {
this.subscribe(this.plugin.state.animation.updated, () => this.forceUpdate());
this.subscribe(this.plugin.state.animation.events.updated, () => this.forceUpdate());
}
updateParams: ParamOnChange = p => {
......@@ -23,7 +23,7 @@ export class AnimationControls extends PluginComponent<{ }> {
startOrStop = () => {
const anim = this.plugin.state.animation;
if (anim.latestState.animationState === 'playing') anim.stop();
if (anim.state.animationState === 'playing') anim.stop();
else anim.start();
}
......@@ -31,17 +31,17 @@ export class AnimationControls extends PluginComponent<{ }> {
const anim = this.plugin.state.animation;
if (anim.isEmpty) return null;
const isDisabled = anim.latestState.animationState === 'playing';
const isDisabled = anim.state.animationState === 'playing';
return <div className='msp-animation-section'>
<div className='msp-section-header'>Animations</div>
<ParameterControls params={anim.getParams()} values={anim.latestState.params} onChange={this.updateParams} isDisabled={isDisabled} />
<ParameterControls params={anim.getParams()} values={anim.state.params} onChange={this.updateParams} isDisabled={isDisabled} />
<ParameterControls params={anim.current.params} values={anim.current.paramValues} onChange={this.updateCurrentParams} isDisabled={isDisabled} />
<div className='msp-btn-row-group'>
<button className='msp-btn msp-btn-block msp-form-control' onClick={this.startOrStop}>
{anim.latestState.animationState === 'playing' ? 'Stop' : 'Start'}
{anim.state.animationState === 'playing' ? 'Stop' : 'Start'}
</button>
</div>
</div>
......
......@@ -6,7 +6,7 @@
import { State, Transform, Transformer } from 'mol-state';
import * as React from 'react';
import { PurePluginComponent } from '../base';
import { PurePluginUIComponent } from '../base';
import { ParameterControls, ParamOnChange } from '../controls/parameters';
import { StateAction } from 'mol-state/action';
import { PluginContext } from 'mol-plugin/context';
......@@ -15,7 +15,7 @@ import { Subject } from 'rxjs';
export { StateTransformParameters, TransformContolBase };
class StateTransformParameters extends PurePluginComponent<StateTransformParameters.Props> {
class StateTransformParameters extends PurePluginUIComponent<StateTransformParameters.Props> {
validate(params: any) {
// TODO
return void 0;
......@@ -97,7 +97,7 @@ namespace TransformContolBase {
}
}
abstract class TransformContolBase<P, S extends TransformContolBase.ControlState> extends PurePluginComponent<P, S> {
abstract class TransformContolBase<P, S extends TransformContolBase.ControlState> extends PurePluginUIComponent<P, S> {
abstract applyAction(): Promise<void>;
abstract getInfo(): StateTransformParameters.Props['info'];
abstract getHeader(): Transformer.Definition['display'];
......
......@@ -5,13 +5,13 @@
*/
import * as React from 'react';
import { PluginComponent } from './base';
import { PluginUIComponent } from './base';
import { OrderedMap } from 'immutable';
import { TaskManager } from 'mol-plugin/util/task-manager';
import { filter } from 'rxjs/operators';
import { Progress } from 'mol-task';
export class BackgroundTaskProgress extends PluginComponent<{ }, { tracked: OrderedMap<number, TaskManager.ProgressEvent> }> {
export class BackgroundTaskProgress extends PluginUIComponent<{ }, { tracked: OrderedMap<number, TaskManager.ProgressEvent> }> {
componentDidMount() {
this.subscribe(this.plugin.events.task.progress.pipe(filter(e => e.level !== 'none')), e => {
this.setState({ tracked: this.state.tracked.set(e.id, e) })
......@@ -30,7 +30,7 @@ export class BackgroundTaskProgress extends PluginComponent<{ }, { tracked: Orde
}
}
class ProgressEntry extends PluginComponent<{ event: TaskManager.ProgressEvent }> {
class ProgressEntry extends PluginUIComponent<{ event: TaskManager.ProgressEvent }> {
render() {
const root = this.props.event.progress.root;
const subtaskCount = countSubtasks(this.props.event.progress.root) - 1;
......
......@@ -8,7 +8,7 @@
import * as React from 'react';
import { ButtonsType } from 'mol-util/input/input-observer';
import { Canvas3dIdentifyHelper } from 'mol-plugin/util/canvas3d-identify';
import { PluginComponent } from './base';
import { PluginUIComponent } from './base';
import { PluginCommands } from 'mol-plugin/command';
import { ParamDefinition as PD } from 'mol-util/param-definition';
import { ParameterControls } from './controls/parameters';
......@@ -20,7 +20,7 @@ interface ViewportState {
noWebGl: boolean
}
export class ViewportControls extends PluginComponent {
export class ViewportControls extends PluginUIComponent {
state = {
isSettingsExpanded: false
}
......@@ -35,11 +35,11 @@ export class ViewportControls extends PluginComponent {
}
toggleControls = () => {
PluginCommands.Layout.Update.dispatch(this.plugin, { state: { showControls: !this.plugin.layout.latestState.showControls } });
PluginCommands.Layout.Update.dispatch(this.plugin, { state: { showControls: !this.plugin.layout.state.showControls } });
}
toggleExpanded = () => {
PluginCommands.Layout.Update.dispatch(this.plugin, { state: { isExpanded: !this.plugin.layout.latestState.isExpanded } });
PluginCommands.Layout.Update.dispatch(this.plugin, { state: { isExpanded: !this.plugin.layout.state.isExpanded } });
}
setSettings = (p: { param: PD.Base<any>, name: string, value: any }) => {
......@@ -55,7 +55,7 @@ export class ViewportControls extends PluginComponent {
this.forceUpdate();
});
this.subscribe(this.plugin.layout.updated, () => {
this.subscribe(this.plugin.layout.events.updated, () => {
this.forceUpdate();
});
}
......@@ -73,15 +73,15 @@ export class ViewportControls extends PluginComponent {
// TODO: show some icons dimmed etc..
return <div className={'msp-viewport-controls'}>
<div className='msp-viewport-controls-buttons'>
{this.icon('tools', this.toggleControls, 'Toggle Controls', this.plugin.layout.latestState.showControls)}
{this.icon('expand-layout', this.toggleExpanded, 'Toggle Expanded', this.plugin.layout.latestState.isExpanded)}
{this.icon('tools', this.toggleControls, 'Toggle Controls', this.plugin.layout.state.showControls)}
{this.icon('expand-layout', this.toggleExpanded, 'Toggle Expanded', this.plugin.layout.state.isExpanded)}
{this.icon('settings', this.toggleSettingsExpanded, 'Settings', this.state.isSettingsExpanded)}
{this.icon('reset-scene', this.resetCamera, 'Reset Camera')}
</div>
{this.state.isSettingsExpanded &&
<div className='msp-viewport-controls-scene-options'>
<ControlGroup header='Layout' initialExpanded={true}>
<ParameterControls params={PluginLayoutStateParams} values={this.plugin.layout.latestState} onChange={this.setLayout} />
<ParameterControls params={PluginLayoutStateParams} values={this.plugin.layout.state} onChange={this.setLayout} />
</ControlGroup>
<ControlGroup header='Viewport' initialExpanded={true}>
<ParameterControls params={Canvas3DParams} values={this.plugin.canvas3d.props} onChange={this.setSettings} />
......@@ -91,7 +91,7 @@ export class ViewportControls extends PluginComponent {
}
}
export class Viewport extends PluginComponent<{ }, ViewportState> {
export class Viewport extends PluginUIComponent<{ }, ViewportState> {
private container = React.createRef<HTMLDivElement>();
private canvas = React.createRef<HTMLCanvasElement>();
......@@ -128,7 +128,7 @@ export class Viewport extends PluginComponent<{ }, ViewportState> {
idHelper.select(x, y);
});
this.subscribe(this.plugin.layout.updated, () => {
this.subscribe(this.plugin.layout.events.updated, () => {
setTimeout(this.handleResize, 50);
});
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment