pdbe-molstar
Version:
Molstar implementation for PDBe
120 lines (119 loc) • 7.92 kB
JavaScript
"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);
} }));
}