UNPKG

molstar

Version:

A comprehensive macromolecular library.

240 lines (239 loc) 12.5 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import * as React from 'react'; import { formatTime } from '../mol-util'; import { PluginReactContext, PluginUIComponent } from './base'; import { AnimationViewportControls, DefaultStructureTools, LociLabels, StateSnapshotViewportControls, TrajectoryViewportControls, SelectionViewportControls, ViewportSnapshotDescription } from './controls'; import { LeftPanelControls } from './left-panel'; import { SequenceView } from './sequence'; import { BackgroundTaskProgress, OverlayTaskProgress } from './task'; import { Toasts } from './toast'; import { Viewport, ViewportControls } from './viewport'; import { PluginCommands } from '../mol-plugin/commands'; import { OpenFiles } from '../mol-plugin-state/actions/file'; import { Asset } from '../mol-util/assets'; import { BehaviorSubject } from 'rxjs'; import { useBehavior } from './hooks/use-behavior'; export function Plugin({ plugin }) { if (plugin.isInitialized) { return _jsx(PluginReactContext.Provider, { value: plugin, children: _jsx(Layout, {}) }); } return _jsx(PluginInitWrapper, { plugin: plugin }); } function PluginInitWrapper({ plugin }) { const [state, setState] = React.useState({ kind: 'pending' }); React.useEffect(() => { setState({ kind: 'pending' }); let mounted = true; plugin.initialized.then(() => { if (mounted) setState({ kind: 'initialized' }); }).catch(err => { if (mounted) setState({ kind: 'error', message: `${err}` }); }); return () => { mounted = false; }; }, [plugin]); if (state.kind === 'pending') return null; if (state.kind === 'error') { return _jsx("div", { className: 'msp-plugin', children: _jsxs("div", { className: 'msp-plugin-init-error', children: ["Initialization error: ", state.message] }) }); } return _jsx(PluginReactContext.Provider, { value: plugin, children: _jsx(Layout, {}) }); } export class PluginContextContainer extends React.Component { render() { return _jsx(PluginReactContext.Provider, { value: this.props.plugin, children: _jsx("div", { className: 'msp-plugin', children: this.props.children }) }); } } class Layout extends PluginUIComponent { constructor() { super(...arguments); this.onDrop = (ev) => { ev.preventDefault(); const files = []; 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; const file = ev.dataTransfer.items[i].getAsFile(); if (file) files.push(file); } } else { for (let i = 0; i < ev.dataTransfer.files.length; i++) { const file = ev.dataTransfer.files[i]; if (file) files.push(file); } } const sessions = files.filter(f => { const fn = f.name.toLowerCase(); return fn.endsWith('.molx') || fn.endsWith('.molj'); }); if (sessions.length > 0) { PluginCommands.State.Snapshots.OpenFile(this.plugin, { file: sessions[0] }); } else { this.plugin.runTask(this.plugin.state.data.applyAction(OpenFiles, { files: files.map(f => Asset.File(f)), format: { name: 'auto', params: {} }, visuals: true })); } }; this.onDragOver = (ev) => { ev.preventDefault(); }; this.showDragOverlay = new BehaviorSubject(false); this.onDragEnter = (ev) => { let hasFile = false; if (ev.dataTransfer.items && ev.dataTransfer.items.length > 0) { for (let i = 0; i < ev.dataTransfer.items.length; i++) { if (ev.dataTransfer.items[i].kind !== 'file') continue; hasFile = true; break; } } else { for (let i = 0; i < ev.dataTransfer.types.length; i++) { if (ev.dataTransfer.types[i] !== 'Files') continue; hasFile = true; break; } } if (hasFile) { this.showDragOverlay.next(true); } }; } componentDidMount() { this.subscribe(this.plugin.layout.events.updated, () => this.forceUpdate()); } region(kind, Element) { return _jsx("div", { className: `msp-layout-region msp-layout-${kind}`, children: _jsx("div", { className: 'msp-layout-static', children: Element ? _jsx(Element, {}) : null }) }); } get layoutVisibilityClassName() { var _a, _b; const layout = this.plugin.layout.state; const controls = (_b = (_a = this.plugin.spec.components) === null || _a === void 0 ? void 0 : _a.controls) !== null && _b !== void 0 ? _b : {}; const classList = []; 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 = ['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(' '); } render() { var _a, _b, _c, _d, _e, _f, _g; const layout = this.plugin.layout.state; const controls = ((_a = this.plugin.spec.components) === null || _a === void 0 ? void 0 : _a.controls) || {}; const viewport = ((_c = (_b = this.plugin.spec.components) === null || _b === void 0 ? void 0 : _b.viewport) === null || _c === void 0 ? void 0 : _c.view) || DefaultViewport; const sequenceView = ((_e = (_d = this.plugin.spec.components) === null || _d === void 0 ? void 0 : _d.sequenceViewer) === null || _e === void 0 ? void 0 : _e.view) || SequenceView; return _jsx("div", { className: 'msp-plugin', children: _jsxs("div", { className: this.layoutClassName, onDragEnter: this.onDragEnter, children: [_jsxs("div", { className: this.layoutVisibilityClassName, children: [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)] }), !((_f = this.plugin.spec.components) === null || _f === void 0 ? void 0 : _f.hideTaskOverlay) && _jsx(OverlayTaskProgress, {}), !((_g = this.plugin.spec.components) === null || _g === void 0 ? void 0 : _g.disableDragOverlay) && _jsx(DragOverlay, { plugin: this.plugin, showDragOverlay: this.showDragOverlay })] }) }); } } function dropFiles(ev, plugin, showDragOverlay) { ev.preventDefault(); ev.stopPropagation(); showDragOverlay.next(false); const files = []; 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; const file = ev.dataTransfer.items[i].getAsFile(); if (file) files.push(file); } } else { for (let i = 0; i < ev.dataTransfer.files.length; i++) { const file = ev.dataTransfer.files[i]; if (file) files.push(file); } } plugin.managers.dragAndDrop.handle(files); } function DragOverlay({ plugin, showDragOverlay }) { const show = useBehavior(showDragOverlay); const preventDrag = (e) => { e.dataTransfer.dropEffect = 'copy'; e.preventDefault(); e.stopPropagation(); }; return _jsx("div", { className: 'msp-drag-drop-overlay', style: { display: show ? 'flex' : 'none' }, onDragEnter: preventDrag, onDragOver: preventDrag, onDragLeave: () => showDragOverlay.next(false), onDrop: e => dropFiles(e, plugin, showDragOverlay), children: "Load File(s)" }); } export class ControlsWrapper extends PluginUIComponent { render() { var _a; const StructureTools = ((_a = this.plugin.spec.components) === null || _a === void 0 ? void 0 : _a.structureTools) || DefaultStructureTools; return _jsx("div", { className: 'msp-scrollable-container', children: _jsx(StructureTools, {}) }); } } export class DefaultViewport extends PluginUIComponent { render() { var _a, _b, _c, _d, _e, _f; const VPControls = ((_b = (_a = this.plugin.spec.components) === null || _a === void 0 ? void 0 : _a.viewport) === null || _b === void 0 ? void 0 : _b.controls) || ViewportControls; const SVPControls = ((_d = (_c = this.plugin.spec.components) === null || _c === void 0 ? void 0 : _c.selectionTools) === null || _d === void 0 ? void 0 : _d.controls) || SelectionViewportControls; const SnapshotDescription = ((_f = (_e = this.plugin.spec.components) === null || _e === void 0 ? void 0 : _e.viewport) === null || _f === void 0 ? void 0 : _f.snapshotDescription) || ViewportSnapshotDescription; return _jsxs(_Fragment, { children: [_jsx(Viewport, {}), _jsxs("div", { className: 'msp-viewport-top-left-controls', children: [_jsx(AnimationViewportControls, {}), _jsx(TrajectoryViewportControls, {}), _jsx(StateSnapshotViewportControls, {}), _jsx(SnapshotDescription, {})] }), _jsx(SVPControls, {}), _jsx(VPControls, {}), _jsx(BackgroundTaskProgress, {}), _jsxs("div", { className: 'msp-highlight-toast-wrapper', children: [_jsx(LociLabels, {}), _jsx(Toasts, {})] })] }); } } export class Log extends PluginUIComponent { constructor() { super(...arguments); this.wrapper = React.createRef(); this.state = { entries: this.plugin.log.entries }; } componentDidMount() { this.subscribe(this.plugin.events.log, () => this.setState({ entries: this.plugin.log.entries })); } componentDidUpdate() { this.scrollToBottom(); } 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 = []; for (let i = Math.max(0, l - maxEntries), o = 0; i < l; i++) { const e = xs.get(i); entries.push(_jsxs("li", { children: [_jsx("div", { className: 'msp-log-entry-badge msp-log-entry-' + e.type }), _jsx("div", { className: 'msp-log-timestamp', children: formatTime(e.timestamp) }), _jsx("div", { className: 'msp-log-entry', children: e.message })] }, o++)); } return _jsx("div", { ref: this.wrapper, className: 'msp-log', style: { position: 'absolute', top: '0', right: '0', bottom: '0', left: '0', overflowY: 'auto' }, children: _jsx("ul", { className: 'msp-list-unstyled', children: entries }) }); } }