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

mol-state & mol-plugin: updating

parent a74c8f1d
No related branches found
No related tags found
No related merge requests found
......@@ -8,6 +8,7 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
width: 100%;
......
......@@ -48,7 +48,7 @@ export class ParametersComponent<P extends PD.Params> extends React.Component<Pa
}
render() {
return <div>
return <div style={{ width: '100%' }}>
{ Object.keys(this.props.params).map(k => {
const param = this.props.params[k]
const value = this.props.values[k]
......
......@@ -100,6 +100,11 @@ export class PluginContext {
PluginCommands.Data.Update.dispatch(this, { tree });
}
_test_updateTransform(a: Transform.Ref, params: any) {
const tree = StateTree.updateParams(this.state.data.tree, a, params);
PluginCommands.Data.Update.dispatch(this, { tree });
}
_test_createState(url: string) {
const b = StateTree.build(this.state.data.tree);
......
......@@ -6,7 +6,7 @@
import * as React from 'react';
import { PluginContext } from '../context';
import { Transform, Transformer } from 'mol-state';
import { Transform, Transformer, StateSelection } from 'mol-state';
import { ParametersComponent } from 'mol-app/component/parameters';
export class Controls extends React.Component<{ plugin: PluginContext }, { id: string }> {
......@@ -88,4 +88,55 @@ export class _test_CreateTransform extends React.Component<{ plugin: PluginConte
<button onClick={() => this.create()} style={{ width: '100%' }}>Create</button>
</div>
}
}
export class _test_UpdateTransform extends React.Component<{ plugin: PluginContext, nodeRef: Transform.Ref }, { params: any }> {
private getTransform() {
const t = this.props.plugin.state.data.tree.nodes.get(this.props.nodeRef)!;
return t ? t.value : t;
}
private getParamDef() {
const def = this.getTransform().transformer.definition;
if (!def.params || !def.params.controls) return void 0;
const src = StateSelection.ancestorOfType(this.props.nodeRef, def.from).select(this.props.plugin.state.data)[0];
console.log(src, def.from);
if (!src || !src.obj) return void 0;
return def.params.controls(src.obj, this.props.plugin);
}
private update() {
console.log(this.props.nodeRef, this.state.params);
this.props.plugin._test_updateTransform(this.props.nodeRef, this.state.params);
}
// componentDidMount() {
// const t = this.props.plugin.state.data.tree.nodes.get(this.props.nodeRef)!;
// if (t) this.setState({ params: t.value.params });
// }
state = { params: this.getTransform() ? this.getTransform().params : { } };
render() {
const transform = this.getTransform();
if (!transform || transform.ref === this.props.plugin.state.data.tree.rootRef) {
return <div />;
}
const params = this.getParamDef();
if (!params) return <div />;
const tr = transform.transformer;
return <div key={`${this.props.nodeRef} ${tr.id}`}>
<div style={{ borderBottom: '1px solid #999'}}>{(tr.definition.display && tr.definition.display.name) || tr.definition.name}</div>
<ParametersComponent params={params} values={this.state.params as any} onChange={(k, v) => {
this.setState({ params: { ...this.state.params, [k]: v } });
}} />
<button onClick={() => this.update()} style={{ width: '100%' }}>Update</button>
</div>
}
}
\ No newline at end of file
......@@ -8,7 +8,7 @@ import * as React from 'react';
import { PluginContext } from '../context';
import { StateTree } from './state-tree';
import { Viewport } from './viewport';
import { Controls, _test_CreateTransform } from './controls';
import { Controls, _test_CreateTransform, _test_UpdateTransform } from './controls';
import { Transformer } from 'mol-state';
// TODO: base object with subscribe helpers, separate behavior list etc
......@@ -52,6 +52,10 @@ export class _test_CurrentObject extends React.Component<{ plugin: PluginContext
return <div>
Current Ref: {this.props.plugin.behaviors.state.data.currentObject.value.ref}
<hr />
<h3>Update</h3>
<_test_UpdateTransform key={`${ref} update`} plugin={this.props.plugin} nodeRef={ref} />
<hr />
<h3>Create</h3>
{
transforms.map((t, i) => <_test_CreateTransform key={`${t.id} ${ref} ${i}`} plugin={this.props.plugin} transformer={t} nodeRef={ref} />)
}
......
......@@ -35,19 +35,25 @@ export class StateTreeNode extends React.Component<{ plugin: PluginContext, node
PluginCommands.Data.RemoveObject.dispatch(this.props.plugin, { ref: this.props.nodeRef });
}}>X</a>]</>
let label: any;
if (cell.status !== 'ok' || !cell.obj) {
return <div style={{ borderLeft: '1px solid black', paddingLeft: '7px' }}>
{remove} {cell.status} {cell.errorText}
</div>;
}
const obj = cell.obj as PluginStateObject.Any;
const props = obj.props;
const type = obj.type;
return <div style={{ borderLeft: '0px solid #999', paddingLeft: '0px' }}>
{remove}[<span title={type.description}>{ type.shortName }</span>] <a href='#' onClick={e => {
const name = (n.value.transformer.definition.display && n.value.transformer.definition.display.name) || n.value.transformer.definition.name;
label = <><b>{cell.status}</b> <a href='#' onClick={e => {
e.preventDefault();
PluginCommands.Data.SetCurrentObject.dispatch(this.props.plugin, { ref: this.props.nodeRef });
}}>{name}</a>: <i>{cell.errorText}</i></>;
} else {
const obj = cell.obj as PluginStateObject.Any;
const props = obj.props;
const type = obj.type;
label = <>[<span title={type.description}>{ type.shortName }</span>] <a href='#' onClick={e => {
e.preventDefault();
PluginCommands.Data.SetCurrentObject.dispatch(this.props.plugin, { ref: this.props.nodeRef });
}}>{props.label}</a> {props.description ? <small>{props.description}</small> : void 0}
}}>{props.label}</a> {props.description ? <small>{props.description}</small> : void 0}</>;
}
return <div>
{remove}{label}
{n.children.size === 0
? void 0
: <div style={{ marginLeft: '7px', paddingLeft: '3px', borderLeft: '1px solid #999' }}>{n.children.map(c => <StateTreeNode plugin={this.props.plugin} state={this.props.state} nodeRef={c!} key={c} />)}</div>
......
......@@ -72,7 +72,7 @@ export namespace ImmutableTree {
/**
* Visit all nodes in a subtree in "post order", meaning leafs get visited first.
*/
export function doPostOrder<T, S>(tree: ImmutableTree<T>, root: Node<T>, state: S, f: (node: Node<T>, nodes: Nodes<T>, state: S) => boolean | undefined | void) {
export function doPostOrder<T, S>(tree: ImmutableTree<T>, root: Node<T>, state: S, f: (node: Node<T>, nodes: Nodes<T>, state: S) => boolean | undefined | void): S {
const ctx: VisitorCtx = { nodes: tree.nodes, state, f };
_doPostOrder(ctx, root);
return ctx.state;
......@@ -91,7 +91,7 @@ export namespace ImmutableTree {
* Visit all nodes in a subtree in "pre order", meaning leafs get visited last.
* If the visitor function returns false, the visiting for that branch is interrupted.
*/
export function doPreOrder<T, S>(tree: ImmutableTree<T>, root: Node<T>, state: S, f: (node: Node<T>, nodes: Nodes<T>, state: S) => boolean | undefined | void) {
export function doPreOrder<T, S>(tree: ImmutableTree<T>, root: Node<T>, state: S, f: (node: Node<T>, nodes: Nodes<T>, state: S) => boolean | undefined | void): S {
const ctx: VisitorCtx = { nodes: tree.nodes, state, f };
_doPreOrder(ctx, root);
return ctx.state;
......@@ -218,18 +218,23 @@ export namespace ImmutableTree {
return node;
}
remove<T>(ref: ImmutableTree.Ref): Node<T>[] {
remove(ref: ImmutableTree.Ref): Node<T>[] {
if (ref === this.rootRef) {
return this.removeChildren(ref);
}
const { nodes, mutations } = this;
const node = nodes.get(ref);
if (!node) return [];
const parent = nodes.get(node.parent)!;
const children = this.mutate(parent.ref).children;
this.mutate(parent.ref).children.delete(ref);
const st = subtreePostOrder(this, node);
if (ref !== this.rootRef) children.delete(ref);
for (const n of st) {
nodes.delete(n.value.ref);
mutations.delete(n.value.ref);
nodes.delete(n.ref);
mutations.delete(n.ref);
}
return st;
}
......@@ -239,11 +244,12 @@ export namespace ImmutableTree {
if (!node || !node.children.size) return [];
node = this.mutate(ref);
const st = subtreePostOrder(this, node);
// remove the last node which is the parent
st.pop();
node.children.clear();
for (const n of st) {
if (n === node) continue;
nodes.delete(n.value.ref);
mutations.delete(n.value.ref);
nodes.delete(n.ref);
mutations.delete(n.ref);
}
return st;
}
......
......@@ -47,13 +47,20 @@ namespace StateSelection {
parent(): Builder;
first(): Builder;
filter(p: (n: StateObjectCell) => boolean): Builder;
withStatus(s: StateObjectCell.Status): Builder;
subtree(): Builder;
children(): Builder;
ofType(t: StateObject.Type): Builder;
ancestorOfType(t: StateObject.Type): Builder;
select(state: State): CellSeq
}
const BuilderPrototype: any = {};
const BuilderPrototype: any = {
select(state: State) {
return select(this, state);
}
};
function registerModifier(name: string, f: Function) {
BuilderPrototype[name] = function (this: any, ...args: any[]) { return f.call(void 0, this, ...args) };
......@@ -135,6 +142,9 @@ namespace StateSelection {
registerModifier('filter', filter);
export function filter(b: Selector, p: (n: StateObjectCell) => boolean) { return flatMap(b, n => p(n) ? [n] : []); }
registerModifier('withStatus', withStatus);
export function withStatus(b: Selector, s: StateObjectCell.Status) { return filter(b, n => n.status === s); }
registerModifier('subtree', subtree);
export function subtree(b: Selector) {
return flatMap(b, (n, s) => {
......@@ -157,20 +167,22 @@ namespace StateSelection {
export function ofType(b: Selector, t: StateObject.Type) { return filter(b, n => n.obj ? n.obj.type === t : false); }
registerModifier('ancestorOfType', ancestorOfType);
export function ancestorOfType(b: Selector, t: StateObject.Type) { return unique(mapEntity(b, (n, s) => findAncestorOfType(s, n.ref, t))); }
export function ancestorOfType(b: Selector, types: StateObject.Ctor[]) { return unique(mapEntity(b, (n, s) => findAncestorOfType(s, n.ref, types))); }
registerModifier('parent', parent);
export function parent(b: Selector) { return unique(mapEntity(b, (n, s) => s.cells.get(s.tree.nodes.get(n.ref)!.parent))); }
function findAncestorOfType({ tree, cells }: State, root: string, type: StateObject.Type): StateObjectCell | undefined {
let current = tree.nodes.get(root)!;
function findAncestorOfType({ tree, cells }: State, root: string, types: StateObject.Ctor[]): StateObjectCell | undefined {
let current = tree.nodes.get(root)!, len = types.length;
while (true) {
current = tree.nodes.get(current.parent)!;
if (current.ref === tree.rootRef) {
return cells.get(tree.rootRef);
}
const obj = cells.get(current.ref)!.obj!;
if (obj.type === type) return cells.get(current.ref);
for (let i = 0; i < len; i++) {
if (obj.type === types[i].type) return cells.get(current.ref);
}
}
}
}
......
......@@ -68,7 +68,7 @@ class State {
taskCtx,
oldTree,
tree: tree,
objects: this.cells,
cells: this.cells,
transformCache: this.transformCache
};
// TODO: have "cancelled" error? Or would this be handled automatically?
......@@ -120,16 +120,16 @@ namespace State {
taskCtx: RuntimeContext,
oldTree: StateTree,
tree: StateTree,
objects: State.Cells,
cells: State.Cells,
transformCache: Map<Ref, unknown>
}
async function update(ctx: UpdateContext) {
const roots = findUpdateRoots(ctx.objects, ctx.tree);
const roots = findUpdateRoots(ctx.cells, ctx.tree);
const deletes = findDeletes(ctx);
for (const d of deletes) {
const obj = ctx.objects.has(d) ? ctx.objects.get(d)!.obj : void 0;
ctx.objects.delete(d);
const obj = ctx.cells.has(d) ? ctx.cells.get(d)!.obj : void 0;
ctx.cells.delete(d);
ctx.transformCache.delete(d);
ctx.stateCtx.events.object.removed.next({ ref: d, obj });
// TODO: handle current object change
......@@ -168,7 +168,7 @@ namespace State {
function findDeletes(ctx: UpdateContext): Ref[] {
// TODO: do this in some sort of "tree order"?
const deletes: Ref[] = [];
const keys = ctx.objects.keys();
const keys = ctx.cells.keys();
while (true) {
const key = keys.next();
if (key.done) break;
......@@ -179,14 +179,14 @@ namespace State {
function setObjectState(ctx: UpdateContext, ref: Ref, status: StateObjectCell.Status, errorText?: string) {
let changed = false;
if (ctx.objects.has(ref)) {
const obj = ctx.objects.get(ref)!;
if (ctx.cells.has(ref)) {
const obj = ctx.cells.get(ref)!;
changed = obj.status !== status;
obj.status = status;
obj.errorText = errorText;
} else {
const obj: StateObjectCell = { ref, status, version: UUID.create(), errorText, props: { ...ctx.stateCtx.defaultObjectProps } };
ctx.objects.set(ref, obj);
ctx.cells.set(ref, obj);
changed = true;
}
if (changed) ctx.stateCtx.events.object.stateChanged.next({ ref });
......@@ -204,7 +204,7 @@ namespace State {
function doError(ctx: UpdateContext, ref: Ref, errorText: string) {
setObjectState(ctx, ref, 'error', errorText);
const wrap = ctx.objects.get(ref)!;
const wrap = ctx.cells.get(ref)!;
if (wrap.obj) {
ctx.stateCtx.events.object.removed.next({ ref });
ctx.transformCache.delete(ref);
......@@ -258,14 +258,14 @@ namespace State {
}
async function updateNode(ctx: UpdateContext, currentRef: Ref) {
const { oldTree, tree, objects } = ctx;
const { oldTree, tree, cells } = ctx;
const transform = tree.getValue(currentRef)!;
const parent = findAncestor(tree, objects, currentRef, transform.transformer.definition.from);
const parent = findAncestor(tree, cells, currentRef, transform.transformer.definition.from);
// console.log('parent', transform.transformer.id, transform.transformer.definition.from[0].type, parent ? parent.ref : 'undefined')
if (!oldTree.nodes.has(currentRef) || !objects.has(currentRef)) {
if (!oldTree.nodes.has(currentRef) || !cells.has(currentRef)) {
// console.log('creating...', transform.transformer.id, oldTree.nodes.has(currentRef), objects.has(currentRef));
const obj = await createObject(ctx, currentRef, transform.transformer, parent, transform.params);
objects.set(currentRef, {
cells.set(currentRef, {
ref: currentRef,
obj,
status: 'ok',
......@@ -275,12 +275,17 @@ namespace State {
return { action: 'created', obj };
} else {
// console.log('updating...', transform.transformer.id);
const current = objects.get(currentRef)!;
const current = cells.get(currentRef)!;
const oldParams = oldTree.getValue(currentRef)!.params;
switch (await updateObject(ctx, currentRef, transform.transformer, parent, current.obj!, oldParams, transform.params)) {
const updateKind = current.status === 'ok'
? await updateObject(ctx, currentRef, transform.transformer, parent, current.obj!, oldParams, transform.params)
: Transformer.UpdateResult.Recreate;
switch (updateKind) {
case Transformer.UpdateResult.Recreate: {
const obj = await createObject(ctx, currentRef, transform.transformer, parent, transform.params);
objects.set(currentRef, {
cells.set(currentRef, {
ref: currentRef,
obj,
status: 'ok',
......
......@@ -42,8 +42,8 @@ export namespace Transformer {
export interface Definition<A extends StateObject = StateObject, B extends StateObject = StateObject, P = unknown> {
readonly name: string,
readonly from: { type: StateObject.Type }[],
readonly to: { type: StateObject.Type }[],
readonly from: StateObject.Ctor[],
readonly to: StateObject.Ctor[],
readonly display?: { readonly name: string, readonly description?: string },
/**
......
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