/** * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal * @author Alexander Rose */ import { List } from 'immutable'; import * as React from 'react'; import { PluginContext } from '../mol-plugin/context'; import { formatTime } from '../mol-util'; import { LogEntry } from '../mol-util/log-entry'; import { PluginReactContext, PluginUIComponent } from './base'; import { AnimationViewportControls, DefaultStructureTools, LociLabels, StateSnapshotViewportControls, TrajectoryViewportControls, SelectionViewportControls } from './controls'; import { LeftPanelControls } from './left-panel'; import { SequenceView } from './sequence'; import { BackgroundTaskProgress } from './task'; import { Toasts } from './toast'; import { Viewport, ViewportControls } from './viewport'; import { PluginCommands } from '../mol-plugin/commands'; export class Plugin extends React.Component<{ plugin: PluginContext }, {}> { region(kind: 'left' | 'right' | 'bottom' | 'main', element: JSX.Element) { return
{element}
; } render() { return ; } } export class PluginContextContainer extends React.Component<{ plugin: PluginContext }> { render() { return
{this.props.children}
; } } type RegionKind = 'top' | 'left' | 'right' | 'bottom' | 'main' class Layout extends PluginUIComponent { componentDidMount() { this.subscribe(this.plugin.layout.events.updated, () => this.forceUpdate()); } region(kind: RegionKind, Element?: React.ComponentClass) { return
{Element ? : null}
; } get layoutVisibilityClassName() { const layout = this.plugin.layout.state; const controls = (this.plugin.spec.layout && this.plugin.spec.layout.controls) || {}; const classList: string[] = []; if (controls.top === 'none' || !layout.showControls || layout.regionState.top === 'hidden') { classList.push('msp-layout-hide-top'); } if (controls.left === 'none' || !layout.showControls || layout.regionState.left === 'hidden') { classList.push('msp-layout-hide-left'); } else if (layout.regionState.left === 'collapsed') { classList.push('msp-layout-collapse-left'); } if (controls.right === 'none' || !layout.showControls || layout.regionState.right === 'hidden') { classList.push('msp-layout-hide-right'); } if (controls.bottom === 'none' || !layout.showControls || layout.regionState.bottom === 'hidden') { classList.push('msp-layout-hide-bottom'); } return classList.join(' '); } get layoutClassName() { const layout = this.plugin.layout.state; const classList: string[] = ['msp-plugin-content']; if (layout.isExpanded) { classList.push('msp-layout-expanded'); } else { classList.push('msp-layout-standard', `msp-layout-standard-${layout.controlsDisplay}`); } return classList.join(' '); } onDrop = (ev: React.DragEvent) => { ev.preventDefault(); let file: File | undefined | null; if (ev.dataTransfer.items) { // Use DataTransferItemList interface to access the file(s) for (let i = 0; i < ev.dataTransfer.items.length; i++) { if (ev.dataTransfer.items[i].kind !== 'file') continue; file = ev.dataTransfer.items[i].getAsFile(); break; } } else { file = ev.dataTransfer.files[0]; } if (!file) return; const fn = file?.name.toLowerCase() || ''; if (fn.endsWith('molx') || fn.endsWith('molj')) { PluginCommands.State.Snapshots.OpenFile(this.plugin, { file }); } } onDragOver = (ev: React.DragEvent) => { ev.preventDefault(); } render() { const layout = this.plugin.layout.state; const controls = this.plugin.spec.layout?.controls || {}; const viewport = this.plugin.spec.components?.viewport?.view || DefaultViewport; return
{this.region('main', viewport)} {layout.showControls && controls.top !== 'none' && this.region('top', controls.top || SequenceView)} {layout.showControls && controls.left !== 'none' && this.region('left', controls.left || LeftPanelControls)} {layout.showControls && controls.right !== 'none' && this.region('right', controls.right || ControlsWrapper)} {layout.showControls && controls.bottom !== 'none' && this.region('bottom', controls.bottom || Log)}
; } } export class ControlsWrapper extends PluginUIComponent { render() { const StructureTools = this.plugin.spec.components?.structureTools || DefaultStructureTools; return
{/* */}
; } } export class DefaultViewport extends PluginUIComponent { render() { const VPControls = this.plugin.spec.components?.viewport?.controls || ViewportControls; return <>
; } } export class Log extends PluginUIComponent<{}, { entries: List }> { private wrapper = React.createRef(); componentDidMount() { this.subscribe(this.plugin.events.log, () => this.setState({ entries: this.plugin.log.entries })); } componentDidUpdate() { this.scrollToBottom(); } state = { entries: this.plugin.log.entries }; private scrollToBottom() { const log = this.wrapper.current; if (log) log.scrollTop = log.scrollHeight - log.clientHeight - 1; } render() { // TODO: ability to show full log // showing more entries dramatically slows animations. const maxEntries = 10; const xs = this.state.entries, l = xs.size; const entries: JSX.Element[] = []; for (let i = Math.max(0, l - maxEntries), o = 0; i < l; i++) { const e = xs.get(i); entries.push(
  • {formatTime(e!.timestamp)}
    {e!.message}
  • ); } return
      {entries}
    ; } }