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

mol-plugin: Use React context

parent 9fba2d67
No related branches found
No related tags found
No related merge requests found
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as React from 'react';
import { Observable, Subscription } from 'rxjs';
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> {
static contextType = PluginReactContext;
readonly context: PluginContext;
private subs: Subscription[] | undefined = void 0;
protected subscribe<T>(obs: Observable<T>, action: (v: T) => void) {
if (typeof this.subs === 'undefined') this.subs = []
this.subs.push(obs.subscribe(action));
}
componentWillUnmount() {
if (!this.subs) return;
for (const s of this.subs) s.unsubscribe();
}
protected init?(): void;
constructor(props: P, context?: any) {
super(props, context);
if (this.init) this.init();
}
}
\ No newline at end of file
...@@ -5,29 +5,29 @@ ...@@ -5,29 +5,29 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import { PluginContext } from '../context';
import { Transform, State } from 'mol-state'; import { Transform, State } from 'mol-state';
import { ParametersComponent } from 'mol-app/component/parameters'; import { ParametersComponent } from 'mol-app/component/parameters';
import { StateAction } from 'mol-state/action'; import { StateAction } from 'mol-state/action';
import { PluginCommands } from 'mol-plugin/command'; import { PluginCommands } from 'mol-plugin/command';
import { UpdateTrajectory } from 'mol-plugin/state/actions/basic'; import { UpdateTrajectory } from 'mol-plugin/state/actions/basic';
import { PluginComponent } from './base';
export class Controls extends React.Component<{ plugin: PluginContext }, { id: string }> { export class Controls extends PluginComponent<{ }, { }> {
state = { id: '1grm' }; state = { id: '1grm' };
private _snap: any = void 0; private _snap: any = void 0;
private getSnapshot = () => { private getSnapshot = () => {
this._snap = this.props.plugin.state.getSnapshot(); this._snap = this.context.state.getSnapshot();
console.log(btoa(JSON.stringify(this._snap))); console.log(btoa(JSON.stringify(this._snap)));
} }
private setSnapshot = () => { private setSnapshot = () => {
if (!this._snap) return; if (!this._snap) return;
this.props.plugin.state.setSnapshot(this._snap); this.context.state.setSnapshot(this._snap);
} }
render() { render() {
return <div> return <div>
<button onClick={() => this.props.plugin._test_centerView()}>Center View</button><br /> <button onClick={() => this.context._test_centerView()}>Center View</button><br />
<hr /> <hr />
<button onClick={this.getSnapshot}>Get Snapshot</button> <button onClick={this.getSnapshot}>Get Snapshot</button>
<button onClick={this.setSnapshot}>Set Snapshot</button> <button onClick={this.setSnapshot}>Set Snapshot</button>
...@@ -36,27 +36,27 @@ export class Controls extends React.Component<{ plugin: PluginContext }, { id: s ...@@ -36,27 +36,27 @@ export class Controls extends React.Component<{ plugin: PluginContext }, { id: s
} }
export class _test_TrajectoryControls extends React.Component<{ plugin: PluginContext }> { export class _test_TrajectoryControls extends PluginComponent {
render() { render() {
return <div> return <div>
<b>Trajectory: </b> <b>Trajectory: </b>
<button onClick={() => PluginCommands.State.ApplyAction.dispatch(this.props.plugin, { <button onClick={() => PluginCommands.State.ApplyAction.dispatch(this.context, {
state: this.props.plugin.state.data, state: this.context.state.data,
action: UpdateTrajectory.create({ action: 'advance', by: -1 }) action: UpdateTrajectory.create({ action: 'advance', by: -1 })
})}>&lt;&lt;</button> })}>&lt;&lt;</button>
<button onClick={() => PluginCommands.State.ApplyAction.dispatch(this.props.plugin, { <button onClick={() => PluginCommands.State.ApplyAction.dispatch(this.context, {
state: this.props.plugin.state.data, state: this.context.state.data,
action: UpdateTrajectory.create({ action: 'reset' }) action: UpdateTrajectory.create({ action: 'reset' })
})}>Reset</button> })}>Reset</button>
<button onClick={() => PluginCommands.State.ApplyAction.dispatch(this.props.plugin, { <button onClick={() => PluginCommands.State.ApplyAction.dispatch(this.context, {
state: this.props.plugin.state.data, state: this.context.state.data,
action: UpdateTrajectory.create({ action: 'advance', by: +1 }) action: UpdateTrajectory.create({ action: 'advance', by: +1 })
})}>&gt;&gt;</button><br /> })}>&gt;&gt;</button><br />
</div> </div>
} }
} }
export class _test_ApplyAction extends React.Component<{ plugin: PluginContext, nodeRef: Transform.Ref, state: State, action: StateAction }, { params: any }> { export class _test_ApplyAction extends PluginComponent<{ nodeRef: Transform.Ref, state: State, action: StateAction }, { params: any }> {
private getObj() { private getObj() {
const obj = this.props.state.cells.get(this.props.nodeRef)!; const obj = this.props.state.cells.get(this.props.nodeRef)!;
return obj; return obj;
...@@ -67,7 +67,7 @@ export class _test_ApplyAction extends React.Component<{ plugin: PluginContext, ...@@ -67,7 +67,7 @@ export class _test_ApplyAction extends React.Component<{ plugin: PluginContext,
if (!p || !p.default) return {}; if (!p || !p.default) return {};
const obj = this.getObj(); const obj = this.getObj();
if (!obj.obj) return {}; if (!obj.obj) return {};
return p.default(obj.obj, this.props.plugin); return p.default(obj.obj, this.context);
} }
private getParamDef() { private getParamDef() {
...@@ -75,17 +75,17 @@ export class _test_ApplyAction extends React.Component<{ plugin: PluginContext, ...@@ -75,17 +75,17 @@ export class _test_ApplyAction extends React.Component<{ plugin: PluginContext,
if (!p || !p.controls) return {}; if (!p || !p.controls) return {};
const obj = this.getObj(); const obj = this.getObj();
if (!obj.obj) return {}; if (!obj.obj) return {};
return p.controls(obj.obj, this.props.plugin); return p.controls(obj.obj, this.context);
} }
private create() { private create() {
console.log('Apply Action', this.state.params); console.log('Apply Action', this.state.params);
PluginCommands.State.ApplyAction.dispatch(this.props.plugin, { PluginCommands.State.ApplyAction.dispatch(this.context, {
state: this.props.state, state: this.props.state,
action: this.props.action.create(this.state.params), action: this.props.action.create(this.state.params),
ref: this.props.nodeRef ref: this.props.nodeRef
}); });
// this.props.plugin.applyTransform(this.props.state, this.props.nodeRef, this.props.transformer, this.state.params); // this.context.applyTransform(this.props.state, this.props.nodeRef, this.props.transformer, this.state.params);
} }
state = { params: this.getDefaultParams() } state = { params: this.getDefaultParams() }
...@@ -111,7 +111,7 @@ export class _test_ApplyAction extends React.Component<{ plugin: PluginContext, ...@@ -111,7 +111,7 @@ export class _test_ApplyAction extends React.Component<{ plugin: PluginContext,
} }
} }
export class _test_UpdateTransform extends React.Component<{ plugin: PluginContext, state: State, nodeRef: Transform.Ref }, { params: any }> { export class _test_UpdateTransform extends PluginComponent<{ state: State, nodeRef: Transform.Ref }, { params: any }> {
private getCell(ref?: string) { private getCell(ref?: string) {
return this.props.state.cells.get(ref || this.props.nodeRef)!; return this.props.state.cells.get(ref || this.props.nodeRef)!;
} }
...@@ -130,16 +130,16 @@ export class _test_UpdateTransform extends React.Component<{ plugin: PluginConte ...@@ -130,16 +130,16 @@ export class _test_UpdateTransform extends React.Component<{ plugin: PluginConte
const src = this.getCell(cell.sourceRef); const src = this.getCell(cell.sourceRef);
if (!src || !src.obj) return void 0; if (!src || !src.obj) return void 0;
return def.params.controls(src.obj, this.props.plugin); return def.params.controls(src.obj, this.context);
} }
private update() { private update() {
console.log(this.props.nodeRef, this.state.params); console.log(this.props.nodeRef, this.state.params);
this.props.plugin.updateTransform(this.props.state, this.props.nodeRef, this.state.params); this.context.updateTransform(this.props.state, this.props.nodeRef, this.state.params);
} }
// componentDidMount() { // componentDidMount() {
// const t = this.props.plugin.state.data.tree.nodes.get(this.props.nodeRef)!; // const t = this.context.state.data.tree.nodes.get(this.props.nodeRef)!;
// if (t) this.setState({ params: t.value.params }); // if (t) this.setState({ params: t.value.params });
// } // }
......
...@@ -9,43 +9,44 @@ import { PluginContext } from '../context'; ...@@ -9,43 +9,44 @@ import { PluginContext } from '../context';
import { StateTree } from './state-tree'; import { StateTree } from './state-tree';
import { Viewport } from './viewport'; import { Viewport } from './viewport';
import { Controls, _test_UpdateTransform, _test_ApplyAction, _test_TrajectoryControls } from './controls'; import { Controls, _test_UpdateTransform, _test_ApplyAction, _test_TrajectoryControls } from './controls';
import { PluginComponent, PluginReactContext } from './base';
// TODO: base object with subscribe helpers, separate behavior list etc export class Plugin extends React.Component<{ plugin: PluginContext }, {}> {
export class Plugin extends React.Component<{ plugin: PluginContext }, { }> {
render() { render() {
return <div style={{ position: 'absolute', width: '100%', height: '100%', fontFamily: 'monospace' }}> return <PluginReactContext.Provider value={this.props.plugin}>
<div style={{ position: 'absolute', width: '350px', height: '100%', overflowY: 'scroll', padding: '10px' }}> <div style={{ position: 'absolute', width: '100%', height: '100%', fontFamily: 'monospace' }}>
<StateTree plugin={this.props.plugin} state={this.props.plugin.state.data} /> <div style={{ position: 'absolute', width: '350px', height: '100%', overflowY: 'scroll', padding: '10px' }}>
<h3>Behaviors</h3> <StateTree state={this.props.plugin.state.data} />
<StateTree plugin={this.props.plugin} state={this.props.plugin.state.behavior} /> <h3>Behaviors</h3>
</div> <StateTree state={this.props.plugin.state.behavior} />
<div style={{ position: 'absolute', left: '350px', right: '300px', height: '100%' }}> </div>
<Viewport plugin={this.props.plugin} /> <div style={{ position: 'absolute', left: '350px', right: '300px', height: '100%' }}>
<div style={{ position: 'absolute', left: '10px', top: '10px', height: '100%', color: 'white' }}> <Viewport />
<_test_TrajectoryControls {...this.props} /> <div style={{ position: 'absolute', left: '10px', top: '10px', height: '100%', color: 'white' }}>
<_test_TrajectoryControls />
</div>
</div>
<div style={{ position: 'absolute', width: '300px', right: '0', height: '100%', padding: '10px' }}>
<_test_CurrentObject />
<hr />
<Controls />
</div> </div>
</div> </div>
<div style={{ position: 'absolute', width: '300px', right: '0', height: '100%', padding: '10px' }}> </PluginReactContext.Provider>;
<_test_CurrentObject plugin={this.props.plugin} />
<hr />
<Controls plugin={this.props.plugin} />
</div>
</div>;
} }
} }
export class _test_CurrentObject extends React.Component<{ plugin: PluginContext }, { }> { export class _test_CurrentObject extends PluginComponent {
componentDidMount() { init() {
// TODO: move to constructor? this.subscribe(this.context.behaviors.state.data.currentObject, () => this.forceUpdate());
this.props.plugin.behaviors.state.data.currentObject.subscribe(() => this.forceUpdate());
} }
render() { render() {
const current = this.props.plugin.behaviors.state.data.currentObject.value; const current = this.context.behaviors.state.data.currentObject.value;
const ref = current.ref; const ref = current.ref;
// const n = this.props.plugin.state.data.tree.nodes.get(ref)!; // const n = this.props.plugin.state.data.tree.nodes.get(ref)!;
const obj = this.props.plugin.state.data.cells.get(ref)!; const obj = this.context.state.data.cells.get(ref)!;
const type = obj && obj.obj ? obj.obj.type : void 0; const type = obj && obj.obj ? obj.obj.type : void 0;
...@@ -55,12 +56,12 @@ export class _test_CurrentObject extends React.Component<{ plugin: PluginContext ...@@ -55,12 +56,12 @@ export class _test_CurrentObject extends React.Component<{ plugin: PluginContext
return <div> return <div>
<hr /> <hr />
<h3>Update {ref}</h3> <h3>Update {ref}</h3>
<_test_UpdateTransform key={`${ref} update`} plugin={this.props.plugin} state={current.state} nodeRef={ref} /> <_test_UpdateTransform key={`${ref} update`} state={current.state} nodeRef={ref} />
<hr /> <hr />
<h3>Create</h3> <h3>Create</h3>
{ {
actions.map((act, i) => <_test_ApplyAction key={`${act.id} ${ref} ${i}`} actions.map((act, i) => <_test_ApplyAction key={`${act.id} ${ref} ${i}`}
plugin={this.props.plugin} state={current.state} action={act} nodeRef={ref} />) state={current.state} action={act} nodeRef={ref} />)
} }
</div>; </div>;
} }
......
...@@ -5,34 +5,34 @@ ...@@ -5,34 +5,34 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import { PluginContext } from '../context';
import { PluginStateObject } from 'mol-plugin/state/objects'; import { PluginStateObject } from 'mol-plugin/state/objects';
import { State } from 'mol-state' import { State } from 'mol-state'
import { PluginCommands } from 'mol-plugin/command'; import { PluginCommands } from 'mol-plugin/command';
import { PluginComponent } from './base';
export class StateTree extends React.Component<{ plugin: PluginContext, state: State }, { }> { export class StateTree extends PluginComponent<{ state: State }, { }> {
componentDidMount() { init() {
// TODO: move to constructor? this.subscribe(this.props.state.events.changed, () => this.forceUpdate());
this.props.state.events.changed.subscribe(() => this.forceUpdate());
} }
render() { render() {
// const n = this.props.plugin.state.data.tree.nodes.get(this.props.plugin.state.data.tree.rootRef)!; // const n = this.props.plugin.state.data.tree.nodes.get(this.props.plugin.state.data.tree.rootRef)!;
const n = this.props.state.tree.root.ref; const n = this.props.state.tree.root.ref;
return <div> return <div>
<StateTreeNode plugin={this.props.plugin} state={this.props.state} nodeRef={n} key={n} /> <StateTreeNode state={this.props.state} nodeRef={n} key={n} />
{ /* n.children.map(c => <StateTreeNode plugin={this.props.plugin} nodeRef={c!} key={c} />) */} { /* n.children.map(c => <StateTreeNode plugin={this.props.plugin} nodeRef={c!} key={c} />) */}
</div>; </div>;
} }
} }
export class StateTreeNode extends React.Component<{ plugin: PluginContext, nodeRef: string, state: State }, { }> { export class StateTreeNode extends PluginComponent<{ nodeRef: string, state: State }, { }> {
render() { render() {
const n = this.props.state.tree.nodes.get(this.props.nodeRef)!; const n = this.props.state.tree.nodes.get(this.props.nodeRef)!;
const cell = this.props.state.cells.get(this.props.nodeRef)!; const cell = this.props.state.cells.get(this.props.nodeRef)!;
const remove = <>[<a href='#' onClick={e => { const remove = <>[<a href='#' onClick={e => {
e.preventDefault(); e.preventDefault();
PluginCommands.State.RemoveObject.dispatch(this.props.plugin, { state: this.props.state, ref: this.props.nodeRef }); PluginCommands.State.RemoveObject.dispatch(this.context, { state: this.props.state, ref: this.props.nodeRef });
}}>X</a>]</> }}>X</a>]</>
let label: any; let label: any;
...@@ -40,13 +40,13 @@ export class StateTreeNode extends React.Component<{ plugin: PluginContext, node ...@@ -40,13 +40,13 @@ export class StateTreeNode extends React.Component<{ plugin: PluginContext, node
const name = (n.transformer.definition.display && n.transformer.definition.display.name) || n.transformer.definition.name; const name = (n.transformer.definition.display && n.transformer.definition.display.name) || n.transformer.definition.name;
label = <><b>{cell.status}</b> <a href='#' onClick={e => { label = <><b>{cell.status}</b> <a href='#' onClick={e => {
e.preventDefault(); e.preventDefault();
PluginCommands.State.SetCurrentObject.dispatch(this.props.plugin, { state: this.props.state, ref: this.props.nodeRef }); PluginCommands.State.SetCurrentObject.dispatch(this.context, { state: this.props.state, ref: this.props.nodeRef });
}}>{name}</a>: <i>{cell.errorText}</i></>; }}>{name}</a>: <i>{cell.errorText}</i></>;
} else { } else {
const obj = cell.obj as PluginStateObject.Any; const obj = cell.obj as PluginStateObject.Any;
label = <><a href='#' onClick={e => { label = <><a href='#' onClick={e => {
e.preventDefault(); e.preventDefault();
PluginCommands.State.SetCurrentObject.dispatch(this.props.plugin, { state: this.props.state, ref: this.props.nodeRef }); PluginCommands.State.SetCurrentObject.dispatch(this.context, { state: this.props.state, ref: this.props.nodeRef });
}}>{obj.label}</a> {obj.description ? <small>{obj.description}</small> : void 0}</>; }}>{obj.label}</a> {obj.description ? <small>{obj.description}</small> : void 0}</>;
} }
...@@ -56,7 +56,7 @@ export class StateTreeNode extends React.Component<{ plugin: PluginContext, node ...@@ -56,7 +56,7 @@ export class StateTreeNode extends React.Component<{ plugin: PluginContext, node
{remove} {label} {remove} {label}
{children.size === 0 {children.size === 0
? void 0 ? void 0
: <div style={{ marginLeft: '7px', paddingLeft: '3px', borderLeft: '1px solid #999' }}>{children.map(c => <StateTreeNode plugin={this.props.plugin} state={this.props.state} nodeRef={c!} key={c} />)}</div> : <div style={{ marginLeft: '7px', paddingLeft: '3px', borderLeft: '1px solid #999' }}>{children.map(c => <StateTreeNode state={this.props.state} nodeRef={c!} key={c} />)}</div>
} }
</div>; </div>;
} }
......
...@@ -6,21 +6,15 @@ ...@@ -6,21 +6,15 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import { PluginContext } from '../context';
// import { Loci, EmptyLoci, areLociEqual } from 'mol-model/loci';
// import { MarkerAction } from 'mol-geo/geometry/marker-data';
import { ButtonsType } from 'mol-util/input/input-observer'; import { ButtonsType } from 'mol-util/input/input-observer';
import { Canvas3dIdentifyHelper } from 'mol-plugin/util/canvas3d-identify'; import { Canvas3dIdentifyHelper } from 'mol-plugin/util/canvas3d-identify';
import { PluginComponent } from './base';
interface ViewportProps {
plugin: PluginContext
}
interface ViewportState { interface ViewportState {
noWebGl: boolean noWebGl: boolean
} }
export class Viewport extends React.Component<ViewportProps, ViewportState> { export class Viewport extends PluginComponent<{ }, ViewportState> {
private container: HTMLDivElement | null = null; private container: HTMLDivElement | null = null;
private canvas: HTMLCanvasElement | null = null; private canvas: HTMLCanvasElement | null = null;
...@@ -28,31 +22,31 @@ export class Viewport extends React.Component<ViewportProps, ViewportState> { ...@@ -28,31 +22,31 @@ export class Viewport extends React.Component<ViewportProps, ViewportState> {
noWebGl: false noWebGl: false
}; };
handleResize() { private handleResize = () => {
this.props.plugin.canvas3d.handleResize(); this.context.canvas3d.handleResize();
} }
componentDidMount() { componentDidMount() {
if (!this.canvas || !this.container || !this.props.plugin.initViewer(this.canvas, this.container)) { if (!this.canvas || !this.container || !this.context.initViewer(this.canvas, this.container)) {
this.setState({ noWebGl: true }); this.setState({ noWebGl: true });
} }
this.handleResize(); this.handleResize();
const canvas3d = this.props.plugin.canvas3d; const canvas3d = this.context.canvas3d;
canvas3d.input.resize.subscribe(() => this.handleResize()); this.subscribe(canvas3d.input.resize, this.handleResize);
const idHelper = new Canvas3dIdentifyHelper(this.props.plugin, 15); const idHelper = new Canvas3dIdentifyHelper(this.context, 15);
canvas3d.input.move.subscribe(({x, y, inside, buttons}) => { this.subscribe(canvas3d.input.move, ({x, y, inside, buttons}) => {
if (!inside || buttons) { return; } if (!inside || buttons) { return; }
idHelper.move(x, y); idHelper.move(x, y);
}); });
canvas3d.input.leave.subscribe(() => { this.subscribe(canvas3d.input.leave, () => {
idHelper.leave(); idHelper.leave();
}); });
canvas3d.input.click.subscribe(({x, y, buttons}) => { this.subscribe(canvas3d.input.click, ({x, y, buttons}) => {
if (buttons !== ButtonsType.Flag.Primary) return; if (buttons !== ButtonsType.Flag.Primary) return;
idHelper.select(x, y); idHelper.select(x, y);
}); });
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment