Browse Source

plugin: wip styling

David Sehnal 6 years ago
parent
commit
a00e766e75

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

@@ -46,7 +46,7 @@ export function Snapshots(ctx: PluginContext) {
     });
 
     PluginCommands.Camera.Snapshots.Add.subscribe(ctx, ({ name, description }) => {
-        const entry = CameraSnapshotManager.Entry(name || new Date().toLocaleTimeString(), ctx.canvas3d.camera.getSnapshot(), description);
+        const entry = CameraSnapshotManager.Entry(ctx.canvas3d.camera.getSnapshot(), name, description);
         ctx.state.cameraSnapshots.add(entry);
     });
 

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

@@ -85,7 +85,7 @@ export function Snapshots(ctx: PluginContext) {
     });
 
     PluginCommands.State.Snapshots.Add.subscribe(ctx, ({ name, description }) => {
-        const entry = PluginStateSnapshotManager.Entry(name || new Date().toLocaleTimeString(), ctx.state.getSnapshot(), description);
+        const entry = PluginStateSnapshotManager.Entry(ctx.state.getSnapshot(), name, description);
         ctx.state.snapshots.add(entry);
     });
 

+ 3 - 3
src/mol-plugin/skin/base/bootstrap/bootstrap/_buttons.scss

@@ -154,9 +154,9 @@ a.msp-btn {
 }
 
 // Vertically space out multiple block buttons
-.msp-btn-block + .msp-btn-block {
-  margin-top: 5px;
-}
+// .msp-btn-block + .msp-btn-block {
+//   margin-top: 5px;
+// }
 
 // Specificity overrides
 input[type="submit"],

+ 1 - 0
src/mol-plugin/skin/base/components/controls-base.scss

@@ -118,6 +118,7 @@
     line-height: $row-height;
     border: none;
     overflow: hidden;
+    font-weight: bold;
     
     .msp-icon {
         display: block-inline;

+ 56 - 0
src/mol-plugin/skin/base/components/temp.scss

@@ -0,0 +1,56 @@
+.msp-right-controls {
+    padding-top: $control-spacing;
+}
+
+.msp-section-header {
+    height: $row-height;
+    line-height: $row-height;
+    margin-bottom: $control-spacing;
+    text-align: center;
+    font-weight: bold;
+    background: $default-background;
+}
+
+.msp-btn-row-group {
+    display:flex;
+    flex-direction:row;
+    height: $row-height;
+    margin-top: 1px;
+    width: 100%;
+
+    > button {
+        margin: 0;
+        flex: 1 1 auto;
+        margin-right: 1px;
+        height: $row-height;
+    }
+
+    > button:last-child {
+        margin-right: 0;
+    }
+}
+
+.msp-state-list {
+    list-style: none;
+    margin-top: $control-spacing;
+
+    > li {
+        position: relative;
+
+        > button:first-child {
+            border-left: $control-spacing solid color-increase-contrast($default-background, 12%) !important;
+        }
+    }
+
+    button {
+        margin-bottom: 1px;
+        text-align: left;
+    }
+}
+
+.msp-state-list-remove-button {
+    position: absolute;
+    right: 0;
+    top: 0;
+    width: $row-height;
+}

+ 48 - 0
src/mol-plugin/skin/base/components/transformer.scss

@@ -36,4 +36,52 @@
             bottom: $row-height + 1;
         }
     }
+}
+
+.msp-transform-wrapper {
+    margin-bottom: $control-spacing;
+}
+
+.msp-transform-header {
+    position: relative;
+
+    > button {
+        text-align: left;
+        background: color-lower-contrast($default-background, 4%);
+        font-weight: bold;
+    }
+
+    > button:hover {
+        color: color-lower-contrast($font-color, 15%);
+    }
+}
+
+.msp-transform-default-params {
+    position: absolute;
+    right: 0;
+    top: 0;
+}
+
+.msp-transform-default-params:hover {
+    background: color-lower-contrast($default-background, 20%);
+}
+
+.msp-transform-apply-wrap {
+    position: relative;
+    margin-top: 1px;
+    width: 100%;
+}
+
+.msp-transform-refresh {
+    width: $control-label-width + $control-spacing;
+    background: $default-background;
+    text-align: right;
+}
+
+.msp-transform-apply {
+    display: block;
+    position: absolute;
+    left: $control-label-width + $control-spacing;
+    right: 0;
+    top: 0;
 }

+ 2 - 1
src/mol-plugin/skin/base/ui.scss

@@ -39,4 +39,5 @@
 @import 'components/log';
 @import 'components/transformer';
 @import 'components/toast';
-@import 'components/help';
+@import 'components/help';
+@import 'components/temp';

+ 4 - 3
src/mol-plugin/state/camera.ts

@@ -64,13 +64,14 @@ class CameraSnapshotManager {
 namespace CameraSnapshotManager {
     export interface Entry {
         id: UUID,
-        name: string,
+        timestamp: string,
+        name?: string,
         description?: string,
         snapshot: Camera.Snapshot
     }
 
-    export function Entry(name: string, snapshot: Camera.Snapshot, description?: string): Entry {
-        return { id: UUID.create22(), name, snapshot, description };
+    export function Entry(snapshot: Camera.Snapshot, name?: string, description?: string): Entry {
+        return { id: UUID.create22(), timestamp: new Date().toLocaleString(), name, snapshot, description };
     }
 
     export interface StateSnapshot {

+ 4 - 3
src/mol-plugin/state/snapshots.ts

@@ -50,13 +50,14 @@ class PluginStateSnapshotManager {
 namespace PluginStateSnapshotManager {
     export interface Entry {
         id: UUID,
-        name: string,
+        timestamp: string,
+        name?: string,
         description?: string,
         snapshot: PluginState.Snapshot
     }
 
-    export function Entry(name: string, snapshot: PluginState.Snapshot, description?: string): Entry {
-        return { id: UUID.create22(), name, snapshot, description };
+    export function Entry(snapshot: PluginState.Snapshot, name?: string, description?: string): Entry {
+        return { id: UUID.create22(), timestamp: new Date().toLocaleString(), name, snapshot, description };
     }
 
     export interface StateSnapshot {

+ 19 - 10
src/mol-plugin/ui/camera.tsx

@@ -7,11 +7,13 @@
 import { PluginCommands } from 'mol-plugin/command';
 import * as React from 'react';
 import { PluginComponent } from './base';
+import { ParamDefinition as PD } from 'mol-util/param-definition';
+import { ParameterControls } from './controls/parameters';
 
 export class CameraSnapshots extends PluginComponent<{ }, { }> {
     render() {
         return <div>
-            <h3>Camera Snapshots</h3>
+            <div className='msp-section-header'>Camera Snapshots</div>
             <CameraSnapshotControls />
             <CameraSnapshotList />
         </div>;
@@ -19,7 +21,11 @@ export class CameraSnapshots extends PluginComponent<{ }, { }> {
 }
 
 class CameraSnapshotControls extends PluginComponent<{ }, { name: string, description: string }> {
-    state = { name: '', description: '' };
+    static Params = {
+        name: PD.Text(),
+        description: PD.Text()
+    }
+    state = PD.getDefaultValues(CameraSnapshotControls.Params);
 
     add = () => {
         PluginCommands.Camera.Snapshots.Add.dispatch(this.plugin, this.state);
@@ -32,10 +38,12 @@ class CameraSnapshotControls extends PluginComponent<{ }, { name: string, descri
 
     render() {
         return <div>
-            <input type='text' value={this.state.name} placeholder='Name...' style={{ width: '33%', display: 'block', float: 'left' }} onChange={e => this.setState({ name: e.target.value })} />
-            <input type='text' value={this.state.description} placeholder='Description...' style={{ width: '67%', display: 'block' }} onChange={e => this.setState({ description: e.target.value })} />
-            <button style={{ float: 'right' }} onClick={this.clear}>Clear</button>
-            <button onClick={this.add}>Add</button>
+            <ParameterControls params={CameraSnapshotControls.Params} values={this.state} onEnter={this.add} onChange={p => this.setState({ [p.name]: p.value } as any)}  />
+
+            <div className='msp-btn-row-group'>
+                <button className='msp-btn msp-btn-block msp-form-control' onClick={this.add}>Add</button>
+                <button className='msp-btn msp-btn-block msp-form-control' onClick={this.clear}>Clear</button>
+            </div>
         </div>;
     }
 }
@@ -56,11 +64,12 @@ class CameraSnapshotList extends PluginComponent<{ }, { }> {
     }
 
     render() {
-        return <ul style={{ listStyle: 'none' }}>
+        return <ul style={{ listStyle: 'none' }} className='msp-state-list'>
             {this.plugin.state.cameraSnapshots.entries.valueSeq().map(e =><li key={e!.id}>
-                <button onClick={this.apply(e!.id)}>Set</button>
-                &nbsp;{e!.name} <small>{e!.description}</small>
-                <button onClick={this.remove(e!.id)} style={{ float: 'right' }}>X</button>
+                <button className='msp-btn msp-btn-block msp-form-control' onClick={this.apply(e!.id)}>{e!.name || e!.timestamp} <small>{e!.description}</small></button>
+                <button onClick={this.remove(e!.id)} style={{ float: 'right' }} className='msp-btn msp-btn-link msp-state-list-remove-button'>
+                    <span className='msp-icon msp-icon-remove' />
+                </button>
             </li>)}
         </ul>;
     }

+ 2 - 1
src/mol-plugin/ui/controls/parameters.tsx

@@ -121,8 +121,10 @@ export class TextControl extends SimpleParam<PD.Text> {
     }
 
     renderControl() {
+        const placeholder = this.props.param.label || camelCaseToWords(this.props.name);
         return <input type='text'
             value={this.props.value || ''}
+            placeholder={placeholder}
             onChange={this.onChange}
             onKeyPress={this.props.onEnter ? this.onKeyPress : void 0}
             disabled={this.props.isDisabled}
@@ -227,7 +229,6 @@ export class GroupControl extends React.PureComponent<ParamProps<PD.Group<any>>,
         const params = this.props.param.params;
         const label = this.props.param.label || camelCaseToWords(this.props.name);
 
-        // TODO toggle panel
         return <div className='msp-control-group-wrapper'>
             <div className='msp-control-group-header'>
                 <button className='msp-btn msp-btn-block' onClick={this.toggleExpanded}>

+ 7 - 8
src/mol-plugin/ui/plugin.tsx

@@ -37,7 +37,7 @@ export class Plugin extends React.Component<{ plugin: PluginContext }, {}> {
                     <div className='msp-layout-hide-top'>
                         {this.region('main', <ViewportWrapper />)}
                         {this.region('left', <State />)}
-                        {this.region('right', <div className='msp-scrollable-container'>
+                        {this.region('right', <div className='msp-scrollable-container msp-right-controls'>
                             <CurrentObject />
                             <Controls />
                             <CameraSnapshots />
@@ -105,7 +105,7 @@ export class Log extends PluginComponent<{}, { entries: List<LogEntry> }> {
     }
 
     render() {
-        return <div ref={this.wrapper} style={{ position: 'absolute', top: '0', right: '0', bottom: '0', left: '0', padding: '10px', overflowY: 'scroll' }}>
+        return <div ref={this.wrapper} style={{ position: 'absolute', top: '0', right: '0', bottom: '0', left: '0', overflowY: 'scroll' }}>
             <ul style={{ listStyle: 'none' }}>
                 {this.state.entries.map((e, i) => <li key={i} style={{ borderBottom: '1px solid #999', padding: '3px' }}>
                     [{e!.type}] [{formatTime(e!.timestamp)}] {e!.message}
@@ -146,15 +146,14 @@ export class CurrentObject extends PluginComponent {
         const actions = type
             ? current.state.actions.fromType(type)
             : []
-        return <div>
-            <hr />
-            <h3>{obj.obj ? obj.obj.label : ref}</h3>
+        return <>
+            <div className='msp-section-header'>
+                {obj.obj ? obj.obj.label : ref}
+            </div>
             <UpdateTransformContol state={current.state} transform={transform} />
-            <hr />
-            <h3>Create</h3>
             {
                 actions.map((act, i) => <ApplyActionContol plugin={this.plugin} key={`${act.id}`} state={current.state} action={act} nodeRef={ref} />)
             }
-        </div>;
+        </>;
     }
 }

+ 28 - 19
src/mol-plugin/ui/state.tsx

@@ -10,6 +10,8 @@ import { PluginComponent } from './base';
 import { shallowEqual } from 'mol-util';
 import { List } from 'immutable';
 import { LogEntry } from 'mol-util/log-entry';
+import { ParamDefinition as PD } from 'mol-util/param-definition';
+import { ParameterControls } from './controls/parameters';
 
 export class StateSnapshots extends PluginComponent<{ }, { serverUrl: string }> {
     state = { serverUrl: 'http://webchem.ncbr.muni.cz/molstar-state' }
@@ -18,9 +20,8 @@ export class StateSnapshots extends PluginComponent<{ }, { serverUrl: string }>
 
     render() {
         return <div>
-            <h3>State Snapshots</h3>
+            <div className='msp-section-header'>State Snapshots</div>
             <StateSnapshotControls serverUrl={this.state.serverUrl} serverChanged={this.updateServerUrl} />
-            <b>Local</b>
             <LocalStateSnapshotList />
             <RemoteStateSnapshotList serverUrl={this.state.serverUrl} />
         </div>;
@@ -30,6 +31,12 @@ export class StateSnapshots extends PluginComponent<{ }, { serverUrl: string }>
 class StateSnapshotControls extends PluginComponent<{ 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 = {
+        name: PD.Text(),
+        description: PD.Text(),
+        serverUrl: PD.Text('http://webchem.ncbr.muni.cz/molstar-state')
+    }
+
     add = () => {
         PluginCommands.State.Snapshots.Add.dispatch(this.plugin, { name: this.state.name, description: this.state.description });
         this.setState({ name: '', description: '' })
@@ -51,15 +58,16 @@ class StateSnapshotControls extends PluginComponent<{ serverUrl: string, serverC
 
     render() {
         return <div>
-            <input type='text' value={this.state.name} placeholder='Name...' style={{ width: '33%', display: 'block', float: 'left' }} onChange={e => this.setState({ name: e.target.value })} />
-            <input type='text' value={this.state.description} placeholder='Description...' style={{ width: '67%', display: 'block' }} onChange={e => this.setState({ description: e.target.value })} />
-            <input type='text' value={this.state.serverUrl} placeholder='Server URL...' style={{ width: '100%', display: 'block' }} onChange={e => {
-                this.setState({ serverUrl: e.target.value });
-                this.props.serverChanged(e.target.value);
-            }} />
-            <button style={{ float: 'right' }} onClick={this.clear}>Clear</button>
-            <button onClick={this.add}>Add Local</button>
-            <button onClick={this.upload} disabled={this.state.isUploading}>Upload</button>
+            <ParameterControls params={StateSnapshotControls.Params} values={this.state} onEnter={this.add} onChange={p => {
+                this.setState({ [p.name]: p.value } as any);
+                if (p.name === 'serverUrl') this.props.serverChanged(p.value);
+            }}/>
+
+            <div className='msp-btn-row-group'>
+                <button className='msp-btn msp-btn-block msp-form-control' onClick={this.add}>Add Local</button>
+                <button className='msp-btn msp-btn-block msp-form-control' onClick={this.upload} disabled={this.state.isUploading}>Upload</button>
+                <button className='msp-btn msp-btn-block msp-form-control' onClick={this.clear}>Clear</button>
+            </div>
         </div>;
     }
 }
@@ -80,11 +88,12 @@ class LocalStateSnapshotList extends PluginComponent<{ }, { }> {
     }
 
     render() {
-        return <ul style={{ listStyle: 'none' }}>
+        return <ul style={{ listStyle: 'none' }} className='msp-state-list'>
             {this.plugin.state.snapshots.entries.valueSeq().map(e =><li key={e!.id}>
-                <button onClick={this.apply(e!.id)}>Set</button>
-                &nbsp;{e!.name} <small>{e!.description}</small>
-                <button onClick={this.remove(e!.id)} style={{ float: 'right' }}>X</button>
+                <button className='msp-btn msp-btn-block msp-form-control' onClick={this.apply(e!.id)}>{e!.name || e!.timestamp} <small>{e!.description}</small></button>
+                <button onClick={this.remove(e!.id)} style={{ float: 'right' }} className='msp-btn msp-btn-link msp-state-list-remove-button'>
+                    <span className='msp-icon msp-icon-remove' />
+                </button>
             </li>)}
         </ul>;
     }
@@ -117,11 +126,11 @@ class RemoteStateSnapshotList extends PluginComponent<{ serverUrl: string }, { e
 
     render() {
         return <div>
-            <b>Remote</b> <button onClick={this.refresh} disabled={this.state.isFetching}>Refresh</button>
-            <ul style={{ listStyle: 'none' }}>
+            <button title='Click to Refresh' style={{fontWeight: 'bold'}} className='msp-btn msp-btn-block msp-form-control' onClick={this.refresh} disabled={this.state.isFetching}>↻ Remote Snapshots</button>
+
+            <ul style={{ listStyle: 'none' }} className='msp-state-list'>
                 {this.state.entries.valueSeq().map(e =><li key={e!.id}>
-                    <button onClick={this.fetch(e!.url)} disabled={this.state.isFetching}>Fetch</button>
-                    &nbsp;{e!.name} <small>{e!.description}</small>
+                    <button className='msp-btn msp-btn-block msp-form-control' onClick={this.fetch(e!.url)}>{e!.name || e!.timestamp} <small>{e!.description}</small></button>
                 </li>)}
             </ul>
         </div>;

+ 1 - 1
src/mol-plugin/ui/state/apply-action.tsx

@@ -43,7 +43,7 @@ class ApplyActionContol extends TransformContolBase<ApplyActionContol.Props, App
     getInfo() { return this._getInfo(this.props.nodeRef, this.props.state.transforms.get(this.props.nodeRef).version); }
     getHeader() { return this.props.action.definition.display; }
     getHeaderFallback() { return this.props.action.id; }
-    isBusy() { return !!this.state.error || this.state.busy; }
+    canApply() { return !this.state.error && !this.state.busy; }
     applyText() { return 'Apply'; }
 
     private _getInfo = memoizeOne((t: Transform.Ref, v: string) => StateTransformParameters.infoFromAction(this.plugin, this.props.state, this.props.action, this.props.nodeRef));

+ 26 - 15
src/mol-plugin/ui/state/common.tsx

@@ -85,7 +85,8 @@ namespace TransformContolBase {
         params: any,
         error?: string,
         busy: boolean,
-        isInitial: boolean
+        isInitial: boolean,
+        isCollapsed?: boolean
     }
 }
 
@@ -94,7 +95,7 @@ abstract class TransformContolBase<P, S extends TransformContolBase.State> exten
     abstract getInfo(): StateTransformParameters.Props['info'];
     abstract getHeader(): Transformer.Definition['display'];
     abstract getHeaderFallback(): string;
-    abstract isBusy(): boolean;
+    abstract canApply(): boolean;
     abstract applyText(): string;
     abstract state: S;
 
@@ -134,25 +135,35 @@ abstract class TransformContolBase<P, S extends TransformContolBase.State> exten
         this.setState({ params, isInitial: PD.areEqual(info.params, params, info.initialValues), error: void 0 });
     }
 
+    toggleExpanded = () => {
+        this.setState({ isCollapsed: !this.state.isCollapsed });
+    }
+
     render() {
         const info = this.getInfo();
-        if (info.isEmpty) return <div>Nothing to update</div>;
+        if (info.isEmpty) return null;
 
         const display = this.getHeader();
 
-        return <div>
-            <div style={{ borderBottom: '1px solid #999', marginBottom: '5px' }}>
-                <button onClick={this.setDefault} disabled={this.state.busy} style={{ float: 'right'}} title='Set default params'>↻</button>
-                <h3>{(display && display.name) || this.getHeaderFallback()}</h3>
-            </div>
-
-            <StateTransformParameters info={info} events={this.events} params={this.state.params} isDisabled={this.state.busy} />
-
-            <div style={{ textAlign: 'right' }}>
-                <span style={{ color: 'red' }}>{this.state.error}</span>
-                {this.state.isInitial ? void 0 : <button title='Refresh params' onClick={this.refresh} disabled={this.state.busy}>↶</button>}
-                <button onClick={this.apply} disabled={this.isBusy()}>{this.applyText()}</button>
+        return <div className='msp-transform-wrapper'>
+            <div className='msp-transform-header'>
+                <button className='msp-btn msp-btn-block' onClick={this.toggleExpanded}>{(display && display.name) || this.getHeaderFallback()}</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>}
             </div>
+            {!this.state.isCollapsed && <>
+                <StateTransformParameters 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-refresh msp-form-control' title='Refresh params' onClick={this.refresh} disabled={this.state.busy || this.state.isInitial}>
+                        ↶ Reset
+                    </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()}>
+                            {this.applyText()}
+                        </button>
+                    </div>
+                </div>
+            </>}
         </div>
     }
 }

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

@@ -30,8 +30,8 @@ class UpdateTransformContol extends TransformContolBase<UpdateTransformContol.Pr
     getInfo() { return this._getInfo(this.props.transform); }
     getHeader() { return this.props.transform.transformer.definition.display; }
     getHeaderFallback() { return this.props.transform.transformer.definition.name; }
-    isBusy() { return !!this.state.error || this.state.busy || this.state.isInitial; }
-    applyText() { return 'Update'; }
+    canApply() { return !this.state.error && !this.state.busy && !this.state.isInitial; }
+    applyText() { return this.canApply() ? 'Update' : 'Nothing to Update'; }
 
     private _getInfo = memoizeOne((t: Transform) => StateTransformParameters.infoFromTransform(this.plugin, this.props.state, this.props.transform));