diff --git a/src/mol-plugin/behavior/dynamic/representation.ts b/src/mol-plugin/behavior/dynamic/representation.ts index d9d211670397911053a2d16bad52bcb63a717951..77f7f6d946bbca42695e5b7eea3ee2875d3f9f18 100644 --- a/src/mol-plugin/behavior/dynamic/representation.ts +++ b/src/mol-plugin/behavior/dynamic/representation.ts @@ -7,6 +7,8 @@ import { PluginBehavior } from '../behavior'; import { EmptyLoci, Loci, areLociEqual } from 'mol-model/loci'; import { MarkerAction } from 'mol-geo/geometry/marker-data'; +import { labelFirst } from 'mol-theme/label'; +import { PluginContext } from 'mol-plugin/context'; export const HighlightLoci = PluginBehavior.create({ name: 'representation-highlight-loci', @@ -25,7 +27,7 @@ export const HighlightLoci = PluginBehavior.create({ }); } }, - display: { name: 'Highlight Loci on Canvas', group: 'Data' } + display: { name: 'Highlight Loci on Canvas', group: 'Representation' } }); export const SelectLoci = PluginBehavior.create({ @@ -38,5 +40,16 @@ export const SelectLoci = PluginBehavior.create({ }); } }, - display: { name: 'Select Loci on Canvas', group: 'Data' } + display: { name: 'Select Loci on Canvas', group: 'Representation' } +}); + +export const DefaultLociLabelProvider = PluginBehavior.create({ + name: 'default-loci-label-provider', + ctor: class implements PluginBehavior<undefined> { + private f = labelFirst; + register(): void { this.ctx.lociLabels.addProvider(this.f); } + unregister() { this.ctx.lociLabels.removeProvider(this.f); } + constructor(protected ctx: PluginContext) { } + }, + display: { name: 'Provide Default Loci Label', group: 'Representation' } }); \ No newline at end of file diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index 1280f0262421a4ef00102cf5105ea06db1159d5f..fb6cf4760b2fc90af3680a87570b23dc2d3a9a60 100644 --- a/src/mol-plugin/context.ts +++ b/src/mol-plugin/context.ts @@ -22,6 +22,7 @@ import { PluginSpec } from './spec'; import { PluginState } from './state'; import { TaskManager } from './util/task-manager'; import { Color } from 'mol-util/color'; +import { LociLabelEntry, LociLabelManager } from './util/loci-label-manager'; export class PluginContext { private disposed = false; @@ -47,9 +48,14 @@ export class PluginContext { snapshots: this.state.snapshots.events, }, log: this.ev<LogEntry>(), - task: this.tasks.events + task: this.tasks.events, + labels: { + highlight: this.ev<{ entries: ReadonlyArray<LociLabelEntry> }>() + } }; + readonly lociLabels: LociLabelManager; + readonly structureReprensentation = { registry: new StructureRepresentationRegistry(), themeCtx: { colorThemeRegistry: new ColorTheme.Registry(), sizeThemeRegistry: new SizeTheme.Registry() } as ThemeRegistryContext @@ -145,6 +151,8 @@ export class PluginContext { this.initBehaviors(); this.initDataActions(); + + this.lociLabels = new LociLabelManager(this); } // settings = ; diff --git a/src/mol-plugin/index.ts b/src/mol-plugin/index.ts index 33ddd654ae94f920bfda011c00dc1f084ff766c7..f21c16e9fb9b1992666c738c6e92d8e10e1522cc 100644 --- a/src/mol-plugin/index.ts +++ b/src/mol-plugin/index.ts @@ -13,6 +13,7 @@ import { PluginSpec } from './spec'; import { CreateStructureFromPDBe } from './state/actions/basic'; import { StateTransforms } from './state/transforms'; import { PluginBehaviors } from './behavior'; +import { LogEntry } from 'mol-util/log-entry'; function getParam(name: string, regex: string): string { let r = new RegExp(`${name}=(${regex})[&]?`, 'i'); @@ -31,7 +32,8 @@ const DefaultSpec: PluginSpec = { ], behaviors: [ PluginSpec.Behavior(PluginBehaviors.Representation.HighlightLoci), - PluginSpec.Behavior(PluginBehaviors.Representation.SelectLoci) + PluginSpec.Behavior(PluginBehaviors.Representation.SelectLoci), + PluginSpec.Behavior(PluginBehaviors.Representation.DefaultLociLabelProvider) ] } @@ -39,19 +41,18 @@ export function createPlugin(target: HTMLElement): PluginContext { const ctx = new PluginContext(DefaultSpec); ReactDOM.render(React.createElement(Plugin, { plugin: ctx }), target); - try { - trySetSnapshot(ctx); - } catch (e) { - console.warn('Failed to load snapshot', e); - } + trySetSnapshot(ctx); return ctx; } -function trySetSnapshot(ctx: PluginContext) { - const snapshotUrl = getParam('snapshot-url', `[^&]+`); - if (!snapshotUrl) return; - // const data = JSON.parse(atob(snapshot)); - // setTimeout(() => ctx.state.setSnapshot(data), 250); - PluginCommands.State.Snapshots.Fetch.dispatch(ctx, { url: snapshotUrl }) +async function trySetSnapshot(ctx: PluginContext) { + try { + const snapshotUrl = getParam('snapshot-url', `[^&]+`); + if (!snapshotUrl) return; + await PluginCommands.State.Snapshots.Fetch.dispatch(ctx, { url: snapshotUrl }) + } catch (e) { + ctx.log(LogEntry.error('Failed to load snapshot.')); + console.warn('Failed to load snapshot', e); + } } \ No newline at end of file diff --git a/src/mol-plugin/skin/base/components/temp.scss b/src/mol-plugin/skin/base/components/temp.scss index 506dc4899112a82cdec5c964373bc9b57c8e2abb..7dbe81f208e5a833b4044c24fb773f2b55f84a31 100644 --- a/src/mol-plugin/skin/base/components/temp.scss +++ b/src/mol-plugin/skin/base/components/temp.scss @@ -104,4 +104,14 @@ .msp-tree-children { margin-left: $control-spacing; +} + +.msp-log-list { + list-style: none; + + > li { + background: $msp-form-control-background; + margin-bottom: 1px; + padding: 3px 6px; + } } \ No newline at end of file diff --git a/src/mol-plugin/ui/controls.tsx b/src/mol-plugin/ui/controls.tsx index b3e2426fd3d4ce49f3877e2c60a77488cd05ef48..ac3330b70dfe374eb44afa13e6455cc75639265c 100644 --- a/src/mol-plugin/ui/controls.tsx +++ b/src/mol-plugin/ui/controls.tsx @@ -8,6 +8,7 @@ import * as React from 'react'; import { PluginCommands } from 'mol-plugin/command'; import { UpdateTrajectory } from 'mol-plugin/state/actions/basic'; import { PluginComponent } from './base'; +import { LociLabelEntry } from 'mol-plugin/util/loci-label-manager'; export class Controls extends PluginComponent<{ }, { }> { render() { @@ -34,4 +35,18 @@ export class TrajectoryControls extends PluginComponent { })}>â–ş</button><br /> </div> } +} + +export class LociLabelControl extends PluginComponent<{}, { entries: ReadonlyArray<LociLabelEntry> }> { + state = { entries: [] } + + componentDidMount() { + this.subscribe(this.plugin.events.labels.highlight, e => this.setState({ entries: e.entries })); + } + + render() { + return <div> + {this.state.entries.map((e, i) => <div key={'' + i}>{e}</div>)} + </div> + } } \ No newline at end of file diff --git a/src/mol-plugin/ui/plugin.tsx b/src/mol-plugin/ui/plugin.tsx index 4e8154c03f51f4c123e953bc88218b922a96a528..3d91ea82467227d25b02e7eb5cc93d079e0a7e50 100644 --- a/src/mol-plugin/ui/plugin.tsx +++ b/src/mol-plugin/ui/plugin.tsx @@ -8,7 +8,7 @@ import * as React from 'react'; import { PluginContext } from '../context'; import { StateTree } from './state-tree'; import { Viewport, ViewportControls } from './viewport'; -import { Controls, TrajectoryControls } from './controls'; +import { Controls, TrajectoryControls, LociLabelControl } from './controls'; import { PluginComponent, PluginReactContext } from './base'; import { CameraSnapshots } from './camera'; import { StateSnapshots } from './state'; @@ -62,6 +62,9 @@ export class ViewportWrapper extends PluginComponent { <div style={{ position: 'absolute', left: '10px', bottom: '10px' }}> <BackgroundTaskProgress /> </div> + <div style={{ position: 'absolute', right: '10px', bottom: '10px' }}> + <LociLabelControl /> + </div> </>; } } @@ -107,10 +110,10 @@ 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', 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} + return <div ref={this.wrapper} style={{ position: 'absolute', top: '0', right: '0', bottom: '0', left: '0', overflowY: 'auto' }}> + <ul style={{ listStyle: 'none' }} className='msp-log-list'> + {this.state.entries.map((e, i) => <li key={i}> + <b>[{formatTime(e!.timestamp)} | {e!.type}]</b> {e!.message} </li>)} </ul> </div>; diff --git a/src/mol-plugin/util/loci-label-manager.ts b/src/mol-plugin/util/loci-label-manager.ts new file mode 100644 index 0000000000000000000000000000000000000000..a747b640894b9229b2549e2e1b6cfb9985285ca1 --- /dev/null +++ b/src/mol-plugin/util/loci-label-manager.ts @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { PluginContext } from 'mol-plugin/context'; +import { Loci } from 'mol-model/loci'; +import { Representation } from 'mol-repr/representation'; + +export type LociLabelEntry = JSX.Element | string +export type LociLabelProvider = (info: Loci, repr?: Representation<any>) => LociLabelEntry | undefined + +export class LociLabelManager { + providers: LociLabelProvider[] = []; + + addProvider(provider: LociLabelProvider) { + this.providers.push(provider); + } + + removeProvider(provider: LociLabelProvider) { + this.providers = this.providers.filter(p => p !== provider); + // Event.Interactivity.Highlight.dispatch(this.ctx, []); + } + + private empty: any[] = []; + private getInfo(loci: Loci, repr?: Representation<any>) { + if (!loci || loci.kind === 'empty-loci') return this.empty; + const info: LociLabelEntry[] = []; + for (let p of this.providers) { + const e = p(loci, repr); + if (e) info.push(e); + } + return info; + } + + constructor(public ctx: PluginContext) { + ctx.behaviors.canvas.highlightLoci.subscribe(ev => ctx.events.labels.highlight.next({ entries: this.getInfo(ev.loci, ev.repr) })); + } +} \ No newline at end of file