UNPKG

pdbe-molstar

Version:
163 lines (162 loc) 11.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.StateGalleryControls = void 0; exports.StateGalleryManagerControls = StateGalleryManagerControls; exports.StateGalleryTitleBox = StateGalleryTitleBox; 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 icons_1 = require("molstar/lib/mol-plugin-ui/controls/icons"); const parameters_1 = require("molstar/lib/mol-plugin-ui/controls/parameters"); const param_definition_1 = require("molstar/lib/mol-util/param-definition"); const react_1 = tslib_1.__importStar(require("react")); const helpers_1 = require("../../helpers"); const icons_2 = require("../../ui/icons"); const behavior_1 = require("./behavior"); const manager_1 = require("./manager"); /** Parameter definition for ParameterControls part of "3D State Gallery" section */ const Params = { entryId: param_definition_1.ParamDefinition.Text(undefined, { label: 'Entry ID' }), }; /** "3D State Gallery" section in Structure Tools (right panel) */ class StateGalleryControls extends base_1.CollapsableControls { constructor() { super(...arguments); this.onChangeValues = (values) => { this.setState({ entryId: values.entryId }); }; /** Load entry given by `this.state.entryId` */ this.load = () => tslib_1.__awaiter(this, void 0, void 0, function* () { if (this.loadDisabled()) return; this.setState({ isLoading: true, description: undefined }); const manager = yield manager_1.StateGalleryManager.create(this.plugin, this.state.entryId); this.setState({ manager, isLoading: false, description: this.state.entryId.toUpperCase() }); }); this.loadDisabled = () => { var _a; return !this.state.entryId || this.state.entryId === ((_a = this.state.manager) === null || _a === void 0 ? void 0 : _a.entryId) || this.state.isBusy || this.state.isLoading; }; } defaultState() { var _a, _b; const existingManager = (_a = (0, behavior_1.StateGalleryCustomState)(this.plugin).manager) === null || _a === void 0 ? void 0 : _a.value; return { // CollapsableControls state header: '3D State Gallery', brand: { accent: 'green', svg: icons_2.CollectionsOutlinedSvg }, isCollapsed: false, // Own state entryId: (_b = existingManager === null || existingManager === void 0 ? void 0 : existingManager.entryId) !== null && _b !== void 0 ? _b : '', manager: existingManager, isLoading: false, isBusy: false, description: existingManager === null || existingManager === void 0 ? void 0 : existingManager.entryId.toUpperCase(), }; } componentDidMount() { this.subscribe(this.plugin.managers.structure.hierarchy.behaviors.selection, sel => { var _a; if (this.state.entryId === '' && sel.structures.length > 0) { const id = (_a = sel.structures[0].cell.obj) === null || _a === void 0 ? void 0 : _a.data.model.entryId; if (id) { this.setState({ entryId: id.toLowerCase() }); } } }); this.subscribe(this.plugin.behaviors.state.isBusy, isBusy => this.setState({ isBusy })); } get values() { return { entryId: this.state.entryId, }; } renderControls() { return (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(parameters_1.ParameterControls, { params: Params, values: this.values, onChangeValues: this.onChangeValues, onEnter: this.load }), (!this.state.manager || this.state.manager.entryId !== this.state.entryId) && (0, jsx_runtime_1.jsx)(common_1.Button, { icon: this.state.isLoading ? undefined : icons_1.CheckSvg, title: 'Load', disabled: this.loadDisabled(), onClick: this.load, className: 'msp-btn-block', children: this.state.isLoading ? 'Loading...' : 'Load' }), this.state.manager && (0, jsx_runtime_1.jsx)(StateGalleryManagerControls, { manager: this.state.manager })] }); } } exports.StateGalleryControls = StateGalleryControls; /** Part of "3D State Gallery" section related to a specific entry */ function StateGalleryManagerControls(props) { var _a; const images = props.manager.images; const nImages = images.length; const categories = react_1.default.useMemo(() => (0, helpers_1.groupElements)(images, img => { var _a; return (_a = img.category) !== null && _a !== void 0 ? _a : 'Miscellaneous'; }), [images]); const [selected, setSelected] = react_1.default.useState(undefined); const [status, setStatus] = react_1.default.useState('ready'); const keyDownTargetRef = react_1.default.useRef(null); const handleKeyDown = (e) => tslib_1.__awaiter(this, void 0, void 0, function* () { if (e.code === 'ArrowLeft') yield props.manager.loadPrevious(); if (e.code === 'ArrowRight') yield props.manager.loadNext(); }); react_1.default.useEffect(() => { var _a; if (images.length > 0 && props.manager.events.requestedImage.value === undefined) { props.manager.load(images[0]); } (_a = keyDownTargetRef.current) === null || _a === void 0 ? void 0 : _a.focus(); const subs = [ props.manager.events.status.subscribe(status => setStatus(status)), props.manager.events.requestedImage.subscribe(img => setSelected(img)), ]; return () => subs.forEach(sub => sub.unsubscribe()); }, [props.manager]); if (nImages === 0) { return (0, jsx_runtime_1.jsxs)("div", { style: { margin: 8 }, children: ["No data available for ", props.manager.entryId, "."] }); } return (0, jsx_runtime_1.jsxs)("div", { className: 'pdbemolstar-state-gallery-controls', onKeyDown: handleKeyDown, tabIndex: -1, ref: keyDownTargetRef, children: [(0, jsx_runtime_1.jsx)(common_1.ExpandGroup, { header: 'States', initiallyExpanded: true, children: (0, jsx_runtime_1.jsx)("div", { style: { marginBottom: 8 }, children: categories.groups.map(cat => { var _a; return (0, jsx_runtime_1.jsx)(common_1.ExpandGroup, { header: cat, initiallyExpanded: true, children: (_a = categories.members.get(cat)) === null || _a === void 0 ? void 0 : _a.map(img => (0, jsx_runtime_1.jsx)(ImageButton, { img: img, isSelected: img === selected, status: status, onClick: () => props.manager.load(img) }, img.filename)) }, cat); }) }) }, 'states'), (0, jsx_runtime_1.jsx)(common_1.ExpandGroup, { header: 'Description', initiallyExpanded: true, children: (0, jsx_runtime_1.jsx)("div", { className: 'pdbemolstar-state-gallery-legend', children: (0, jsx_runtime_1.jsx)("div", { dangerouslySetInnerHTML: { __html: (_a = selected === null || selected === void 0 ? void 0 : selected.description) !== null && _a !== void 0 ? _a : '' } }) }) }, 'description')] }); } /** Button with image title */ function ImageButton(props) { var _a; const { img, isSelected, status, onClick } = props; const icon = !isSelected ? icons_2.EmptyIconSvg : (status === 'loading') ? icons_2.HourglassBottomSvg : (status === 'error') ? icons_1.ErrorSvg : icons_1.CheckSvg; const tooltip = imageTooltip(img, isSelected ? status : 'ready'); return (0, jsx_runtime_1.jsxs)(common_1.Button, { className: 'msp-action-menu-button pdbemolstar-state-gallery-state-button', icon: icon, onClick: onClick, title: tooltip, style: { fontWeight: isSelected ? 'bold' : undefined }, children: [(_a = img.title) !== null && _a !== void 0 ? _a : img.filename, img.subtitle && (0, jsx_runtime_1.jsxs)("small", { children: ["\u2002 ", img.subtitle] })] }); } /** Box in viewport with image title and arrows to move between images (3D states) */ function StateGalleryTitleBox() { var _a; const plugin = react_1.default.useContext(base_1.PluginReactContext); const [image, setImage] = react_1.default.useState(undefined); const [manager, setManager] = react_1.default.useState(undefined); const [status, setStatus] = react_1.default.useState('ready'); const loadingCounter = (0, react_1.useRef)(0); react_1.default.useEffect(() => { var _a, _b, _c; const customState = (0, behavior_1.StateGalleryCustomState)(plugin); const subs = [ (_a = customState.requestedImage) === null || _a === void 0 ? void 0 : _a.subscribe(img => setImage(img)), (_b = customState.manager) === null || _b === void 0 ? void 0 : _b.subscribe(mgr => setManager(mgr)), (_c = customState.status) === null || _c === void 0 ? void 0 : _c.subscribe(status => { const counter = ++loadingCounter.current; if (status === 'loading') { setTimeout(() => { if (loadingCounter.current === counter) setStatus('loading'); }, 250); } else { setStatus(status); } }), ]; return () => subs.forEach(sub => sub === null || sub === void 0 ? void 0 : sub.unsubscribe()); }, [plugin]); if (image === undefined) return null; return (0, jsx_runtime_1.jsxs)("div", { className: 'pdbemolstar-state-gallery-title-box', children: [manager && (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(common_1.Button, { className: 'msp-btn-icon', title: 'Previous state', icon: icons_2.ChevronLeftSvg, onClick: () => manager.loadPrevious() }) }), (0, jsx_runtime_1.jsxs)("div", { className: 'pdbemolstar-state-gallery-title', title: imageTooltip(image, status), children: [(0, jsx_runtime_1.jsx)("div", { className: 'pdbemolstar-state-gallery-title-icon', children: (0, jsx_runtime_1.jsx)(icons_1.Icon, { svg: status === 'error' ? icons_1.ErrorSvg : status === 'loading' ? icons_2.HourglassBottomSvg : icons_2.EmptyIconSvg }) }), (0, jsx_runtime_1.jsxs)("div", { className: 'pdbemolstar-state-gallery-title-text', children: [(_a = image.title) !== null && _a !== void 0 ? _a : image.filename, (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("small", { children: image.subtitle })] })] }), manager && (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(common_1.Button, { className: 'msp-btn-icon', title: 'Next state', icon: icons_2.ChevronRightSvg, onClick: () => manager === null || manager === void 0 ? void 0 : manager.loadNext() }) })] }); } /** Return tooltip text for an image */ function imageTooltip(img, status) { var _a; const tooltip = (status === 'error' ? '[Failed to load] \n' : status === 'loading' ? '[Loading] \n' : '') + ((_a = img.title) !== null && _a !== void 0 ? _a : img.filename) + (img.subtitle ? `: ${img.subtitle}` : ''); return tooltip; }