Browse Source

Merge branch 'master' into volume

Alexander Rose 6 years ago
parent
commit
467463db64

+ 1 - 1
src/mol-plugin/behavior/behavior.ts

@@ -55,7 +55,7 @@ namespace PluginBehavior {
     export type CreateCategory = typeof CreateCategory
     export const CreateCategory = PluginStateTransform.BuiltIn({
         name: 'create-behavior-category',
-        display: { name: 'Create Cateogry' },
+        display: { name: 'Behavior Category' },
         from: Root,
         to: Category,
         params: {

+ 19 - 1
src/mol-plugin/behavior/static/state.ts

@@ -59,9 +59,27 @@ export function ApplyAction(ctx: PluginContext) {
 }
 
 export function RemoveObject(ctx: PluginContext) {
-    PluginCommands.State.RemoveObject.subscribe(ctx, ({ state, ref }) => {
+    function remove(state: State, ref: string) {
         const tree = state.build().delete(ref).getTree();
         return ctx.runTask(state.updateTree(tree));
+    }
+
+    PluginCommands.State.RemoveObject.subscribe(ctx, ({ state, ref, removeParentGhosts }) => {
+        if (removeParentGhosts) {
+            const tree = state.tree;
+            let curr = tree.transforms.get(ref);
+            if (curr.parent === ref) return remove(state, ref);
+
+            while (true) {
+                const children = tree.children.get(curr.parent);
+                if (curr.parent === curr.ref || children.size > 1) return remove(state, curr.ref);
+                const parent = tree.transforms.get(curr.parent);
+                if (!parent.props || !parent.props.isGhost) return remove(state, curr.ref);
+                curr = parent;
+            }
+        } else {
+            remove(state, ref);
+        }
     });
 }
 

+ 1 - 1
src/mol-plugin/command.ts

@@ -18,7 +18,7 @@ export const PluginCommands = {
         ApplyAction: PluginCommand<{ state: State, action: StateAction.Instance, ref?: StateTransform.Ref }>(),
         Update: PluginCommand<{ state: State, tree: State.Tree | State.Builder, doNotLogTiming?: boolean }>(),
 
-        RemoveObject: PluginCommand<{ state: State, ref: StateTransform.Ref }>(),
+        RemoveObject: PluginCommand<{ state: State, ref: StateTransform.Ref, removeParentGhosts?: boolean }>(),
 
         ToggleExpanded: PluginCommand<{ state: State, ref: StateTransform.Ref }>({ isImmediate: true }),
         ToggleVisibility: PluginCommand<{ state: State, ref: StateTransform.Ref }>({ isImmediate: true }),

+ 39 - 7
src/mol-plugin/skin/base/components/transformer.scss

@@ -42,15 +42,44 @@
     margin-bottom: $control-spacing;
 }
 
-.msp-transform-header {
-    position: relative;
-    border-top: 1px solid $entity-color-Behaviour; // TODO: separate color
+.msp-transform-update-wrapper {
+    border-bottom: $control-spacing solid $control-background;
+}
+
+.msp-transform-update-wrapper-collapsed {
+    margin-bottom: 1px;
+}
 
-    > button {
+.msp-transform-update-wrapper, .msp-transform-update-wrapper-collapsed {
+    > .msp-transform-header > button {
         text-align: left;
-        background: color-lower-contrast($default-background, 4%);
+        padding-left: $row-height;
+        background: $control-background; // color-lower-contrast($default-background, 4%);
         font-weight: bold;
     }
+}
+
+.msp-transform-wrapper > .msp-transform-header > button {
+    text-align: left;
+    background: color-lower-contrast($default-background, 4%);
+    font-weight: bold;
+}
+
+.msp-transform-header {
+    position: relative;
+    // border-top: 1px solid $entity-color-Behaviour; // TODO: separate color
+
+    // > button {
+    //     text-align: left;
+    //     padding-left: $row-height;
+    //     background: $control-background; // color-lower-contrast($default-background, 4%);
+    //     font-weight: bold;
+    // }
+
+    > button > small {
+        font-weight: normal;
+        float: right;
+    }
 
     > button:hover {
         color: color-lower-contrast($font-color, 15%);
@@ -58,9 +87,11 @@
 }
 
 .msp-transform-default-params {
+    background: $default-background;
     position: absolute;
-    right: 0;
+    left: 0;
     top: 0;
+    width: $row-height;
 }
 
 .msp-transform-default-params:hover {
@@ -74,7 +105,8 @@
 }
 
 .msp-transform-refresh {
-    width: $control-label-width + $control-spacing;
+    width: $control-label-width + $control-spacing - $row-height - 1;
+    margin-left: $row-height + 1;
     background: $default-background;
     text-align: right;
 }

+ 2 - 2
src/mol-plugin/state/actions/structure.ts

@@ -71,7 +71,7 @@ const DownloadStructure = StateAction.build({
         default: throw new Error(`${(src as any).name} not supported.`);
     }
 
-    const data = b.toRoot().apply(StateTransforms.Data.Download, downloadParams);
+    const data = b.toRoot().apply(StateTransforms.Data.Download, downloadParams, { props: { isGhost: true }});
     const traj = createModelTree(data, src.name === 'url' ? src.params.format : 'cif');
     return state.updateTree(createStructureTree(ctx, traj, params.source.params.supportProps));
 });
@@ -89,7 +89,7 @@ export const OpenStructure = StateAction.build({
 
 function createModelTree(b: StateBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, format: 'pdb' | 'cif' = 'cif') {
     const parsed = format === 'cif'
-        ? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
+        ? b.apply(StateTransforms.Data.ParseCif, void 0, { props: { isGhost: true }}).apply(StateTransforms.Model.TrajectoryFromMmCif)
         : b.apply(StateTransforms.Model.TrajectoryFromPDB);
 
     return parsed.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 });

+ 1 - 2
src/mol-plugin/state/transforms/model.ts

@@ -64,7 +64,7 @@ export { TrajectoryFromPDB }
 type TrajectoryFromPDB = typeof TrajectoryFromPDB
 const TrajectoryFromPDB = PluginStateTransform.BuiltIn({
     name: 'trajectory-from-pdb',
-    display: { name: 'Parse PDB string and create trajectory' },
+    display: { name: 'Parse PDB', description: 'Parse PDB string and create trajectory.' },
     from: [SO.Data.String],
     to: SO.Molecule.Trajectory
 })({
@@ -168,7 +168,6 @@ const StructureAssemblyFromModel = PluginStateTransform.BuiltIn({
                 return new SO.Molecule.Structure(base, label);
             }
 
-            asm = model.symmetry.assemblies[0];
             id = asm.id;
             const s = await StructureSymmetry.buildAssembly(base, id!).runInContext(ctx);
             const props = { label: `Assembly ${id}`, description: structureDesc(s) };

+ 25 - 26
src/mol-plugin/ui/plugin.tsx

@@ -4,23 +4,22 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
+import { List } from 'immutable';
+import { PluginState } from 'mol-plugin/state';
+import { formatTime } from 'mol-util';
+import { LogEntry } from 'mol-util/log-entry';
 import * as React from 'react';
 import { PluginContext } from '../context';
-import { StateTree } from './state-tree';
-import { Viewport, ViewportControls } from './viewport';
-import { Controls, TrajectoryControls, LociLabelControl } from './controls';
-import { PluginUIComponent, PluginReactContext } from './base';
+import { PluginReactContext, PluginUIComponent } from './base';
 import { CameraSnapshots } from './camera';
+import { Controls, LociLabelControl, TrajectoryControls } from './controls';
 import { StateSnapshots } from './state';
-import { List } from 'immutable';
-import { LogEntry } from 'mol-util/log-entry';
-import { formatTime } from 'mol-util';
-import { BackgroundTaskProgress } from './task';
-import { ApplyActionContol } from './state/apply-action';
-import { PluginState } from 'mol-plugin/state';
-import { UpdateTransformContol } from './state/update-transform';
-import { StateObjectCell } from 'mol-state';
+import { StateObjectActions } from './state/actions';
 import { AnimationControls } from './state/animation';
+import { StateTree } from './state/tree';
+import { BackgroundTaskProgress } from './task';
+import { Viewport, ViewportControls } from './viewport';
+import { StateTransform } from 'mol-state';
 
 export class Plugin extends React.Component<{ plugin: PluginContext }, {}> {
 
@@ -105,8 +104,8 @@ export class State extends PluginUIComponent {
         const kind = this.plugin.state.behavior.kind.value;
         return <div className='msp-scrollable-container'>
             <div className='msp-btn-row-group msp-data-beh'>
-                <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.set('data')} style={{ fontWeight: kind === 'data' ? 'bold' : 'normal'}}>Data</button>
-                <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.set('behavior')} style={{ fontWeight: kind === 'behavior' ? 'bold' : 'normal'}}>Behavior</button>
+                <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.set('data')} style={{ fontWeight: kind === 'data' ? 'bold' : 'normal' }}>Data</button>
+                <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.set('behavior')} style={{ fontWeight: kind === 'behavior' ? 'bold' : 'normal' }}>Behavior</button>
             </div>
             <StateTree state={kind === 'data' ? this.plugin.state.dataState : this.plugin.state.behaviorState} />
         </div>
@@ -172,21 +171,21 @@ export class CurrentObject extends PluginUIComponent {
         const current = this.current;
         const ref = current.ref;
         const cell = current.state.cells.get(ref)!;
-        const parent: StateObjectCell | undefined = (cell.sourceRef && current.state.cells.get(cell.sourceRef)!) || void 0;
-
         const transform = cell.transform;
         const def = transform.transformer.definition;
+        const display = cell.obj ? cell.obj.label : (def.display && def.display.name) || def.name;
 
-        const actions =  current.state.actions.fromCell(cell, this.plugin);
-        return <>
-            <div className='msp-current-header'>
-                {cell.obj ? cell.obj.label : (def.display && def.display.name) || def.name}
-            </div>
-            {(parent && parent.status === 'ok') && <UpdateTransformContol state={current.state} transform={transform} />}
-            {cell.status === 'ok' && <>
-                <div className='msp-section-header'>Actions</div>
-                {actions.map((act, i) => <ApplyActionContol plugin={this.plugin} key={`${act.id}`} state={current.state} action={act} nodeRef={ref} />)}
-                </>}
+        let showActions = true;
+        if (ref === StateTransform.RootRef) {
+            const children = current.state.tree.children.get(ref);
+            showActions = children.size !== 0;
+        }
+
+        if (!showActions) return null;
+
+        return cell.status === 'ok' && <>
+            <div className='msp-section-header'>{`Actions (${display})`}</div>
+            <StateObjectActions state={current.state} nodeRef={ref} />
         </>;
     }
 }

+ 35 - 0
src/mol-plugin/ui/state/actions.tsx

@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2018 - 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import * as React from 'react';
+import { PluginUIComponent } from '../base';
+import { ApplyActionContol } from './apply-action';
+import { State } from 'mol-state';
+
+export class StateObjectActions extends PluginUIComponent<{ state: State, nodeRef: string }> {
+    get current() {
+        return this.plugin.state.behavior.currentObject.value;
+    }
+
+    componentDidMount() {
+        this.subscribe(this.plugin.state.behavior.currentObject, o => {
+            this.forceUpdate();
+        });
+
+        this.subscribe(this.plugin.events.state.object.updated, ({ ref, state }) => {
+            const current = this.current;
+            if (current.ref !== ref || current.state !== state) return;
+            this.forceUpdate();
+        });
+    }
+
+    render() {
+        const { state, nodeRef: ref } = this.props;
+        const cell = state.cells.get(ref)!;
+        const actions = state.actions.fromCell(cell, this.plugin);
+        return actions.map((act, i) => <ApplyActionContol plugin={this.plugin} key={`${act.id}`} state={state} action={act} nodeRef={ref} />);
+    }
+}

+ 17 - 8
src/mol-plugin/ui/state/common.tsx

@@ -87,7 +87,7 @@ namespace StateTransformParameters {
 }
 
 namespace TransformContolBase {
-    export interface ControlState {
+    export interface ComponentState {
         params: any,
         error?: string,
         busy: boolean,
@@ -96,7 +96,7 @@ namespace TransformContolBase {
     }
 }
 
-abstract class TransformContolBase<P, S extends TransformContolBase.ControlState> extends PurePluginUIComponent<P, S> {
+abstract class TransformContolBase<P, S extends TransformContolBase.ComponentState> extends PurePluginUIComponent<P, S> {
     abstract applyAction(): Promise<void>;
     abstract getInfo(): StateTransformParameters.Props['info'];
     abstract getHeader(): StateTransformer.Definition['display'];
@@ -166,7 +166,7 @@ abstract class TransformContolBase<P, S extends TransformContolBase.ControlState
 
     render() {
         const info = this.getInfo();
-        if (info.isEmpty && this.isUpdate()) return null;
+        const isEmpty = info.isEmpty && this.isUpdate();
 
         const display = this.getHeader();
 
@@ -175,17 +175,26 @@ abstract class TransformContolBase<P, S extends TransformContolBase.ControlState
             ? this.plugin.customParamEditors.get(tId)!
             : StateTransformParameters;
 
-        return <div className='msp-transform-wrapper'>
+        const wrapClass = this.isUpdate()
+            ? !isEmpty && !this.state.isCollapsed
+            ? 'msp-transform-update-wrapper'
+            : 'msp-transform-update-wrapper-collapsed'
+            : 'msp-transform-wrapper';
+
+        return <div className={wrapClass}>
             <div className='msp-transform-header'>
-                <button className='msp-btn msp-btn-block' onClick={this.toggleExpanded}>{display.name}</button>
-                {!this.state.isCollapsed && <button className='msp-btn msp-btn-link msp-transform-default-params' onClick={this.setDefault} disabled={this.state.busy} style={{ float: 'right'}} title='Set default params'>↻</button>}
+                <button className='msp-btn msp-btn-block' onClick={this.toggleExpanded} title={display.description}>
+                    {display.name}
+                    {!isEmpty && this.state.isCollapsed && this.isUpdate() && <small>Click to Edit</small>}
+                </button>
             </div>
-            {!this.state.isCollapsed && <>
+            {!isEmpty && !this.state.isCollapsed && <>
                 <ParamEditor info={info} events={this.events} params={this.state.params} isDisabled={this.state.busy} />
 
                 <div className='msp-transform-apply-wrap'>
+                    <button className='msp-btn msp-btn-block msp-transform-default-params' onClick={this.setDefault} disabled={this.state.busy} title='Set default params'>↻</button>
                     <button className='msp-btn msp-btn-block msp-transform-refresh msp-form-control' title='Refresh params' onClick={this.refresh} disabled={this.state.busy || this.state.isInitial}>
-                        ↶ Reset
+                        ↶ Back
                     </button>
                     <div className='msp-transform-apply'>
                         <button className={`msp-btn msp-btn-block msp-btn-commit msp-btn-commit-${this.canApply() ? 'on' : 'off'}`} onClick={this.apply} disabled={!this.canApply()}>

+ 82 - 20
src/mol-plugin/ui/state-tree.tsx → src/mol-plugin/ui/state/tree.tsx

@@ -1,23 +1,48 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018 - 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
 import * as React from 'react';
 import { PluginStateObject } from 'mol-plugin/state/objects';
-import { State, StateObject } from 'mol-state'
+import { State, StateObject, StateObjectCell, StateTransform } from 'mol-state'
 import { PluginCommands } from 'mol-plugin/command';
-import { PluginUIComponent } from './base';
+import { PluginUIComponent } from '../base';
+import { UpdateTransformContol } from './update-transform';
+import { StateObjectActions } from './actions';
+
+export class StateTree extends PluginUIComponent<{ state: State }, { showActions: boolean }> {
+    state = { showActions: true };
+
+    componentDidMount() {
+        this.subscribe(this.plugin.events.state.cell.created, e => {
+            if (e.cell.transform.parent === StateTransform.RootRef) this.forceUpdate();
+        });
+
+        this.subscribe(this.plugin.events.state.cell.removed, e => {
+            if (e.parent === StateTransform.RootRef) this.forceUpdate();
+        });
+    }
+
+    static getDerivedStateFromProps(props: { state: State }, state: { showActions: boolean }) {
+        const n = props.state.tree.root.ref;
+        const children = props.state.tree.children.get(n);
+        const showActions = children.size === 0;
+        if (state.showActions === showActions) return null;
+        return { showActions };
+    }
 
-export class StateTree extends PluginUIComponent<{ state: State }> {
     render() {
-        const n = this.props.state.tree.root.ref;
-        return <StateTreeNode state={this.props.state} nodeRef={n} />;
+        const ref = this.props.state.tree.root.ref;
+        if (this.state.showActions) {
+            return <StateObjectActions state={this.props.state} nodeRef={ref} />
+        }
+        return <StateTreeNode state={this.props.state} nodeRef={ref} depth={0} />;
     }
 }
 
-class StateTreeNode extends PluginUIComponent<{ nodeRef: string, state: State }, { state: State, isCollapsed: boolean }> {
+class StateTreeNode extends PluginUIComponent<{ nodeRef: string, state: State, depth: number }, { state: State, isCollapsed: boolean }> {
     is(e: State.ObjectEvent) {
         return e.ref === this.props.nodeRef && e.state === this.props.state;
     }
@@ -60,24 +85,34 @@ class StateTreeNode extends PluginUIComponent<{ nodeRef: string, state: State },
     }
 
     render() {
-        if (this.props.state.cells.get(this.props.nodeRef)!.obj === StateObject.Null) return null;
+        const cell = this.props.state.cells.get(this.props.nodeRef)!;
+        if (cell.obj === StateObject.Null) return null;
 
         const cellState = this.cellState;
-
+        const showLabel = cell.status !== 'ok' || !cell.transform.props || !cell.transform.props.isGhost;
         const children = this.props.state.tree.children.get(this.props.nodeRef);
-        return <div>
-            <StateTreeNodeLabel nodeRef={this.props.nodeRef} state={this.props.state} />
+        const newDepth = showLabel ? this.props.depth + 1 : this.props.depth;
+
+        if (!showLabel) {
+            if (children.size === 0) return null;
+            return <div style={{ display: cellState.isCollapsed ? 'none' : 'block' }}>
+                {children.map(c => <StateTreeNode state={this.props.state} nodeRef={c!} key={c} depth={newDepth} />)}
+            </div>;
+        }
+
+        return <>
+            <StateTreeNodeLabel nodeRef={this.props.nodeRef} state={this.props.state} depth={this.props.depth} />
             {children.size === 0
                 ? void 0
-                : <div className='msp-tree-children' style={{ display: cellState.isCollapsed ? 'none' : 'block' }}>
-                    {children.map(c => <StateTreeNode state={this.props.state} nodeRef={c!} key={c} />)}
+                : <div style={{ display: cellState.isCollapsed ? 'none' : 'block' }}>
+                    {children.map(c => <StateTreeNode state={this.props.state} nodeRef={c!} key={c} depth={newDepth} />)}
                 </div>
             }
-        </div>;
+        </>;
     }
 }
 
-class StateTreeNodeLabel extends PluginUIComponent<{ nodeRef: string, state: State }, { state: State, isCurrent: boolean, isCollapsed: boolean }> {
+class StateTreeNodeLabel extends PluginUIComponent<{ nodeRef: string, state: State, depth: number }, { state: State, isCurrent: boolean, isCollapsed: boolean }> {
     is(e: State.ObjectEvent) {
         return e.ref === this.props.nodeRef && e.state === this.props.state;
     }
@@ -126,7 +161,7 @@ class StateTreeNodeLabel extends PluginUIComponent<{ nodeRef: string, state: Sta
 
     remove = (e: React.MouseEvent<HTMLElement>) => {
         e.preventDefault();
-        PluginCommands.State.RemoveObject.dispatch(this.plugin, { state: this.props.state, ref: this.props.nodeRef });
+        PluginCommands.State.RemoveObject.dispatch(this.plugin, { state: this.props.state, ref: this.props.nodeRef, removeParentGhosts: true });
     }
 
     toggleVisible = (e: React.MouseEvent<HTMLElement>) => {
@@ -171,7 +206,11 @@ class StateTreeNodeLabel extends PluginUIComponent<{ nodeRef: string, state: Sta
         } else {
             const obj = cell.obj as PluginStateObject.Any;
             const title = `${obj.label} ${obj.description ? obj.description : ''}`
-            label = <><a title={title} href='#' onClick={this.setCurrent}>{obj.label}</a> {obj.description ? <small>{obj.description}</small> : void 0}</>;
+            if (this.state.isCurrent) {
+                label = <><b>{obj.label}</b> {obj.description ? <small>{obj.description}</small> : void 0}</>;
+            } else {
+                label = <><a title={title} href='#' onClick={this.setCurrent}>{obj.label}</a> {obj.description ? <small>{obj.description}</small> : void 0}</>;
+            }
         }
 
         const children = this.props.state.tree.children.get(this.props.nodeRef);
@@ -181,14 +220,37 @@ class StateTreeNodeLabel extends PluginUIComponent<{ nodeRef: string, state: Sta
             <span className='msp-icon msp-icon-visual-visibility' />
         </button>;
 
-        return <div className={`msp-tree-row${isCurrent ? ' msp-tree-row-current' : ''}`} onMouseEnter={this.highlight} onMouseLeave={this.clearHighlight}>
-            {isCurrent ? <b>{label}</b> : label}
+        const row = <div className={`msp-tree-row${isCurrent ? ' msp-tree-row-current' : ''}`} onMouseEnter={this.highlight} onMouseLeave={this.clearHighlight}
+            style={{ marginLeft: this.state.isCurrent ? '0px' : `${this.props.depth * 10}px`, borderRadius: this.state.isCurrent ? '0' : void 0 }}>
+            {label}
             {children.size > 0 &&  <button onClick={this.toggleExpanded} className='msp-btn msp-btn-link msp-tree-toggle-exp-button'>
                 <span className={`msp-icon msp-icon-${cellState.isCollapsed ? 'expand' : 'collapse'}`} />
             </button>}
             {!cell.transform.props.isLocked && <button onClick={this.remove} className='msp-btn msp-btn-link msp-tree-remove-button'>
                 <span className='msp-icon msp-icon-remove' />
             </button>}{visibility}
-        </div>
+        </div>;
+
+        if (this.state.isCurrent) {
+            return <>
+                {row}
+                <StateTreeNodeTransform {...this.props}/>
+            </>
+        }
+
+        return row;
+    }
+}
+
+class StateTreeNodeTransform extends PluginUIComponent<{ nodeRef: string, state: State, depth: number }, { isExpanded: boolean }> {
+    render() {
+        const ref = this.props.nodeRef;
+        const cell = this.props.state.cells.get(ref)!;
+        const parent: StateObjectCell | undefined = (cell.sourceRef && this.props.state.cells.get(cell.sourceRef)!) || void 0;
+
+        if (!parent || parent.status !== 'ok') return null;
+
+        const transform = cell.transform;
+        return <UpdateTransformContol state={this.props.state} transform={transform} initiallyCollapsed={true} />;
     }
 }

+ 6 - 9
src/mol-plugin/ui/state/update-transform.tsx

@@ -13,15 +13,12 @@ export { UpdateTransformContol };
 namespace UpdateTransformContol {
     export interface Props {
         transform: StateTransform,
-        state: State
+        state: State,
+        initiallyCollapsed?: boolean
     }
 
-    export interface ComponentState {
-        transform: StateTransform,
-        params: any,
-        error?: string,
-        busy: boolean,
-        isInitial: boolean
+    export interface ComponentState extends TransformContolBase.ComponentState {
+        transform: StateTransform
     }
 }
 
@@ -48,10 +45,10 @@ class UpdateTransformContol extends TransformContolBase<UpdateTransformContol.Pr
 
     private _getInfo = memoizeLatest((t: StateTransform) => StateTransformParameters.infoFromTransform(this.plugin, this.props.state, this.props.transform));
 
-    state: UpdateTransformContol.ComponentState = { transform: this.props.transform, error: void 0, isInitial: true, params: this.getInfo().initialValues, busy: false };
+    state: UpdateTransformContol.ComponentState = { transform: this.props.transform, error: void 0, isInitial: true, params: this.getInfo().initialValues, busy: false, isCollapsed: this.props.initiallyCollapsed };
 
     static getDerivedStateFromProps(props: UpdateTransformContol.Props, state: UpdateTransformContol.ComponentState) {
-        if (props.transform === state.transform) return null;
+        if (props.transform === state.transform && props.initiallyCollapsed) return null;
         const cell = props.state.cells.get(props.transform.ref)!;
         const newState: Partial<UpdateTransformContol.ComponentState> = {
             transform: props.transform,