UNPKG

pdbe-molstar

Version:
120 lines (119 loc) 7.92 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.VerticalTabbedPanel = VerticalTabbedPanel; exports.LeftPanel = LeftPanel; const tslib_1 = require("tslib"); const jsx_runtime_1 = require("react/jsx-runtime"); const base_1 = require("molstar/lib/mol-plugin-ui/base"); const common_1 = require("molstar/lib/mol-plugin-ui/controls/common"); const commands_1 = require("molstar/lib/mol-plugin/commands"); const sleep_1 = require("molstar/lib/mol-util/sleep"); /** Convert `Icon` to a React functional element. `Icon` can be either React element (class or functional) or a string with icon path (in 24x24 viewBox) */ function resolveIcon(Icon) { if (typeof Icon === 'string') { return function _Icon() { return (0, jsx_runtime_1.jsx)("svg", { width: '24px', height: '24px', viewBox: '0 0 24 24', children: (0, jsx_runtime_1.jsx)("path", { d: Icon }) }); }; } else { return function _Icon() { return (0, jsx_runtime_1.jsx)(Icon, {}); }; } } /** Special tab id value, meaning no tab is open */ const NO_TAB = 'none'; /** Return a React component showing a panel with tab icons on the left and tab content of the open tab on the right */ function VerticalTabbedPanel(options) { var _a, _b; const tabs = [...(_a = options.tabsTop) !== null && _a !== void 0 ? _a : [], ...(_b = options.tabsBottom) !== null && _b !== void 0 ? _b : []]; if (tabs.some(tab => tab.id === NO_TAB)) throw new Error(`Cannot use '${NO_TAB}' as tab id because it is reserved.`); return class _GenericLeftPanelControls extends base_1.PluginUIComponent { constructor() { var _a, _b, _c, _d; super(...arguments); this.boundBehavior = (_a = options.boundBehavior) === null || _a === void 0 ? void 0 : _a.call(options, this.plugin); this.state = { tab: (_d = (_b = options.defaultTab) !== null && _b !== void 0 ? _b : (_c = this.boundBehavior) === null || _c === void 0 ? void 0 : _c.value) !== null && _d !== void 0 ? _d : NO_TAB, dirtyTabs: [], }; this.setTab = (tab) => tslib_1.__awaiter(this, void 0, void 0, function* () { if (tab === this.state.tab) return; // important to avoid infinite loop when state is bound to `boundBehavior` this.setDirtyTab(tab, false); this.setState({ tab }, () => { var _a, _b; (_a = this.boundBehavior) === null || _a === void 0 ? void 0 : _a.next(tab); (_b = options.onTabChange) === null || _b === void 0 ? void 0 : _b.call(options, this.plugin, tab); }); }); this.toggleTab = (tab) => { if (tab === this.state.tab) { this.setTab(NO_TAB); } else { this.setTab(tab); } }; this.setDirtyTab = (tab, dirty) => { if (dirty && !this.state.dirtyTabs.includes(tab)) { this.setState({ dirtyTabs: [...this.state.dirtyTabs, tab] }); // Add to dirty tabs } else if (!dirty && this.state.dirtyTabs.includes(tab)) { this.setState({ dirtyTabs: this.state.dirtyTabs.filter(t => t !== tab) }); // Remove from dirty tabs } }; } componentDidMount() { var _a, _b; if (this.boundBehavior) { if (this.boundBehavior.value !== this.state.tab) { this.boundBehavior.next(this.state.tab); } this.subscribe(this.boundBehavior, tab => this.setTab(tab)); } for (const tab of tabs) { const dirtySubject = (_a = tab.dirtyOn) === null || _a === void 0 ? void 0 : _a.call(tab, this.plugin); if (dirtySubject) { this.subscribe(dirtySubject, isDirty => { this.setDirtyTab(tab.id, !!isDirty && this.state.tab !== tab.id); }); } } (_b = options.onTabChange) === null || _b === void 0 ? void 0 : _b.call(options, this.plugin, this.state.tab); } render() { var _a, _b; const currentTabId = this.state.tab; const currentTab = currentTabId ? tabs.find(t => t.id === currentTabId) : undefined; const CurrentTabHeader = currentTab === null || currentTab === void 0 ? void 0 : currentTab.header; const CurrentTabBody = currentTab === null || currentTab === void 0 ? void 0 : currentTab.body; const iconForTab = (tab) => { if (tab.showWhen && !tab.showWhen(this.plugin)) return null; return (0, jsx_runtime_1.jsx)(common_1.IconButton, { title: tab.title, svg: resolveIcon(tab.icon), toggleState: tab.id === currentTabId, onClick: () => this.toggleTab(tab.id), transparent: true, style: { position: 'relative' }, extraContent: this.state.dirtyTabs.includes(tab.id) ? (0, jsx_runtime_1.jsx)("div", { className: 'msp-left-panel-controls-button-data-dirty' }) : undefined }, tab.id); }; return (0, jsx_runtime_1.jsxs)("div", { className: 'msp-left-panel-controls', children: [(0, jsx_runtime_1.jsxs)("div", { className: 'msp-left-panel-controls-buttons', children: [(_a = options.tabsTop) === null || _a === void 0 ? void 0 : _a.map(iconForTab), (0, jsx_runtime_1.jsx)("div", { className: 'msp-left-panel-controls-buttons-bottom', children: (_b = options.tabsBottom) === null || _b === void 0 ? void 0 : _b.map(iconForTab) })] }), currentTab && (0, jsx_runtime_1.jsxs)("div", { className: 'msp-scrollable-container', children: [CurrentTabHeader && (0, jsx_runtime_1.jsx)(CurrentTabHeader, {}) || (0, jsx_runtime_1.jsx)(common_1.SectionHeader, { icon: resolveIcon(currentTab.icon), title: currentTab.title }), CurrentTabBody && (0, jsx_runtime_1.jsx)(CurrentTabBody, {})] })] }); } }; } /** Set left panel state in Molstar layout to 'full' (if expanded) or 'collapsed' (if not expanded). * Do not change the state if it is 'hidden'. */ function adjustLeftPanelState(plugin, expanded) { return tslib_1.__awaiter(this, void 0, void 0, function* () { yield (0, sleep_1.sleep)(0); // this ensures PluginCommands.Layout.Update runs after componentDidMount, without this the panel will not collapse when defaultTab is none (not sure why) if (expanded && plugin.layout.state.regionState.left === 'collapsed') { yield commands_1.PluginCommands.Layout.Update(plugin, { state: { regionState: Object.assign(Object.assign({}, plugin.layout.state.regionState), { left: 'full' }) } }); } if (!expanded && plugin.layout.state.regionState.left === 'full') { yield commands_1.PluginCommands.Layout.Update(plugin, { state: { regionState: Object.assign(Object.assign({}, plugin.layout.state.regionState), { left: 'collapsed' }) } }); } }); } /** Like `VerticalTabbedPanel` but is bound to plugin.behaviors.layout.leftPanelTabName and plugin.layout.state.regionState (ensures left panel collapsing/expanding) */ function LeftPanel(options) { return VerticalTabbedPanel(Object.assign(Object.assign({}, options), { boundBehavior: plugin => plugin.behaviors.layout.leftPanelTabName, onTabChange: (plugin, tab) => { var _a, _b, _c; const tabPresent = !!(((_a = options.tabsTop) === null || _a === void 0 ? void 0 : _a.some(t => t.id === tab)) || ((_b = options.tabsBottom) === null || _b === void 0 ? void 0 : _b.some(t => t.id === tab))); adjustLeftPanelState(plugin, tabPresent); (_c = options.onTabChange) === null || _c === void 0 ? void 0 : _c.call(options, plugin, tab); } })); }