UNPKG

molstar

Version:

A comprehensive macromolecular library.

915 lines (914 loc) 61.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.EntityNode = exports.GroupNode = exports.EntityControls = exports.FocusInfo = exports.SelectionInfo = exports.ModelInfo = void 0; exports.MesoMarkdownAnchor = MesoMarkdownAnchor; exports.MesoViewportSnapshotDescription = MesoViewportSnapshotDescription; const tslib_1 = require("tslib"); const jsx_runtime_1 = require("react/jsx-runtime"); /** * Copyright (c) 2022-2024 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ const base_1 = require("../../../mol-plugin-ui/base"); const common_1 = require("../../../mol-plugin-ui/controls/common"); const icons_1 = require("../../../mol-plugin-ui/controls/icons"); const commands_1 = require("../../../mol-plugin/commands"); const mol_state_1 = require("../../../mol-state"); const parameters_1 = require("../../../mol-plugin-ui/controls/parameters"); const param_definition_1 = require("../../../mol-util/param-definition"); const clip_1 = require("../../../mol-util/clip"); const color_1 = require("../../../mol-util/color"); const color_2 = require("../../../mol-plugin-ui/controls/color"); const marker_action_1 = require("../../../mol-util/marker-action"); const loci_1 = require("../../../mol-model/loci"); const mol_util_1 = require("../../../mol-util"); const state_1 = require("../data/state"); const react_1 = tslib_1.__importStar(require("react")); const element_1 = require("../../../mol-model/structure/structure/element"); const structure_1 = require("../../../mol-model/structure"); const geometry_1 = require("../../../mol-math/geometry"); const camera_1 = require("../behavior/camera"); const react_markdown_1 = tslib_1.__importDefault(require("react-markdown")); const rxjs_1 = require("rxjs"); const states_1 = require("./states"); function centerLoci(plugin, loci, durationMs = 250) { const { canvas3d } = plugin; if (!canvas3d) return; const sphere = loci_1.Loci.getBoundingSphere(loci) || (0, geometry_1.Sphere3D)(); const snapshot = canvas3d.camera.getCenter(sphere.center); canvas3d.requestCameraReset({ durationMs, snapshot }); } class ModelInfo extends base_1.PluginUIComponent { constructor() { super(...arguments); this.state = { isDisabled: false, }; } componentDidMount() { this.subscribe(this.plugin.state.data.behaviors.isUpdating, v => { this.setState({ isDisabled: v }); }); this.subscribe(this.plugin.state.events.cell.stateUpdated, e => { if (!this.state.isDisabled && state_1.MesoscaleState.has(this.plugin) && state_1.MesoscaleState.ref(this.plugin) === e.ref) { this.forceUpdate(); } }); } get info() { if (!state_1.MesoscaleState.has(this.plugin)) return; const state = state_1.MesoscaleState.get(this.plugin); if (!state.description && !state.link) return; return { selectionDescription: state.focusInfo, description: state.description, link: state.link, }; } render() { const info = this.info; return info && (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsxs)("div", { id: 'modelinfo', className: 'msp-help-text', children: [(0, jsx_runtime_1.jsx)("div", { children: info.description }), (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)("a", { href: info.link, target: '_blank', children: "Source" }) })] }) }); } } exports.ModelInfo = ModelInfo; const SelectionStyleParam = param_definition_1.ParamDefinition.Select('color+outline', param_definition_1.ParamDefinition.objectToOptions({ 'color+outline': 'Color & Outline', 'color': 'Color', 'outline': 'Outline' })); class SelectionInfo extends base_1.PluginUIComponent { constructor() { super(...arguments); this.state = { isDisabled: false, }; } componentDidMount() { this.subscribe(this.plugin.state.data.behaviors.isUpdating, v => { this.setState({ isDisabled: v }); }); this.subscribe(this.plugin.managers.structure.selection.events.changed, e => { if (!this.state.isDisabled) { this.forceUpdate(); } }); } get info() { const infos = []; this.plugin.managers.structure.selection.entries.forEach((e, k) => { var _a; if (element_1.StructureElement.Loci.is(e.selection) && !element_1.StructureElement.Loci.isEmpty(e.selection)) { const cell = this.plugin.helpers.substructureParent.get(e.selection.structure); const { entities } = e.selection.structure.model; const description = entities.data.pdbx_description.value(0)[0] || 'model'; infos.push({ description: description, label: ((_a = cell === null || cell === void 0 ? void 0 : cell.obj) === null || _a === void 0 ? void 0 : _a.label) || 'Unknown', key: k, }); } }); return infos; } find(label) { state_1.MesoscaleState.set(this.plugin, { filter: `"${label}"` }); if (label) (0, state_1.expandAllGroups)(this.plugin); } ; remove(key) { const e = this.plugin.managers.structure.selection.entries.get(key); if (!e) return; const loci = structure_1.Structure.toStructureElementLoci(e.selection.structure); this.plugin.managers.interactivity.lociSelects.deselect({ loci }, false); } center(key) { const e = this.plugin.managers.structure.selection.entries.get(key); if (!e) return; const loci = structure_1.Structure.toStructureElementLoci(e.selection.structure); centerLoci(this.plugin, loci); const cell = this.plugin.helpers.substructureParent.get(loci.structure); const d = (0, state_1.getCellDescription)(cell); // '### ' + cell?.obj?.label + '\n\n' + cell?.obj?.description; state_1.MesoscaleState.set(this.plugin, { focusInfo: `${d}` }); } get selection() { const info = this.info; const help_selection = (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { children: ["Use ", (0, jsx_runtime_1.jsx)("i", { children: "ctrl+left" }), " to select entities, either on the 3D canvas or in the tree below"] }), (0, jsx_runtime_1.jsxs)("div", { children: ["Use ", (0, jsx_runtime_1.jsx)("i", { children: "shift+left" }), " to select individual chain on the 3D canvas"] })] }); if (!info.length) return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsx)("div", { id: 'seleinfo', className: 'msp-help-text', children: help_selection }) }); return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsxs)("div", { id: 'seleinfo', children: [info.map((entry, index) => { const label = (0, jsx_runtime_1.jsx)(common_1.Button, { className: `msp-btn-tree-label`, noOverflow: true, disabled: this.state.isDisabled, onClick: () => this.center(entry.key), children: (0, jsx_runtime_1.jsx)("span", { title: entry.label, children: entry.label }) }); const find = (0, jsx_runtime_1.jsx)(common_1.IconButton, { svg: icons_1.SearchSvg, toggleState: false, disabled: this.state.isDisabled, small: true, onClick: () => this.find(entry.label) }); const remove = (0, jsx_runtime_1.jsx)(common_1.IconButton, { svg: icons_1.CloseSvg, toggleState: false, disabled: this.state.isDisabled, onClick: () => this.remove(entry.key) }); return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsxs)("div", { className: `msp-flex-row`, style: { margin: `1px 5px 1px ${1 * 10 + 5}px` }, children: [label, find, remove] }, index) }); }), ";"] }) }); } get style() { var _a; const p = (_a = this.plugin.canvas3d) === null || _a === void 0 ? void 0 : _a.props; if (!p) return; if (p.renderer.dimStrength === 1 && p.marking.enabled) return 'color+outline'; if (p.renderer.dimStrength === 1) return 'color'; if (p.marking.enabled) return 'outline'; } setStyle(value) { var _a, _b, _c, _d; if (value.includes('color') && value.includes('outline')) { (_a = this.plugin.canvas3d) === null || _a === void 0 ? void 0 : _a.setProps({ renderer: { dimStrength: 1, }, marking: { enabled: true } }); } else if (value.includes('color')) { (_b = this.plugin.canvas3d) === null || _b === void 0 ? void 0 : _b.setProps({ renderer: { dimStrength: 1, }, marking: { enabled: false } }); } else if (value.includes('outline')) { (_c = this.plugin.canvas3d) === null || _c === void 0 ? void 0 : _c.setProps({ renderer: { dimStrength: 0, selectStrength: 0.3, }, marking: { enabled: true } }); } else { (_d = this.plugin.canvas3d) === null || _d === void 0 ? void 0 : _d.setProps({ renderer: { dimStrength: 0, selectStrength: 0, }, marking: { enabled: false } }); } this.forceUpdate(); } renderStyle() { const style = this.style || ''; return (0, jsx_runtime_1.jsx)("div", { id: 'selestyle', style: { margin: '5px', marginBottom: '10px' }, children: (0, jsx_runtime_1.jsx)(parameters_1.SelectControl, { name: 'Style', param: SelectionStyleParam, value: style, onChange: (e) => { this.setStyle(e.value); } }) }); } render() { return (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [this.renderStyle(), this.selection] }); } } exports.SelectionInfo = SelectionInfo; function MesoMarkdownAnchor({ href, children, element }) { const plugin = react_1.default.useContext(base_1.PluginReactContext); if (!href) return element; // Decode the href to handle encoded spaces and other characters const decodedHref = href ? decodeURIComponent(href) : ''; const handleHover = (e) => { var _a, _b, _c, _d, _e, _f; e.preventDefault(); if (decodedHref.startsWith('i')) { (_a = plugin.canvas3d) === null || _a === void 0 ? void 0 : _a.mark({ loci: loci_1.EveryLoci }, marker_action_1.MarkerAction.RemoveHighlight); const query_names = decodedHref.substring(1).split(','); for (const query_name of query_names) { const entities = (0, state_1.getEveryEntity)(plugin, query_name); for (const r of entities) { const repr = (_b = r.obj) === null || _b === void 0 ? void 0 : _b.data.repr; if (repr) { (_c = plugin.canvas3d) === null || _c === void 0 ? void 0 : _c.mark({ repr, loci: loci_1.EveryLoci }, marker_action_1.MarkerAction.Highlight); } } } } else if (decodedHref.startsWith('g')) { (_d = plugin.canvas3d) === null || _d === void 0 ? void 0 : _d.mark({ loci: loci_1.EveryLoci }, marker_action_1.MarkerAction.RemoveHighlight); const qindex = decodedHref.indexOf('.'); const query = decodedHref.substring(1, qindex) + ':'; const query_names = decodedHref.substring(qindex + 1).split(','); for (const query_name of query_names) { const e = (0, state_1.getAllEntities)(plugin, query + query_name); for (const r of e) { const repr = (_e = r.obj) === null || _e === void 0 ? void 0 : _e.data.repr; if (repr) { (_f = plugin.canvas3d) === null || _f === void 0 ? void 0 : _f.mark({ repr, loci: loci_1.EveryLoci }, marker_action_1.MarkerAction.Highlight); } } } } }; const handleLeave = (e) => { var _a; // Implement your hover off logic here // Example: Perform an action if the href starts with 'h' if (decodedHref.startsWith('i') || decodedHref.startsWith('g')) { // Example hover off action e.preventDefault(); (_a = plugin.canvas3d) === null || _a === void 0 ? void 0 : _a.mark({ loci: loci_1.EveryLoci }, marker_action_1.MarkerAction.RemoveHighlight); } }; const handleClick = (e) => { var _a, _b, _c, _d, _e, _f, _g, _h; e.preventDefault(); if (href.startsWith('#')) { plugin.managers.snapshot.applyKey(decodedHref.substring(1)); } else if (decodedHref.startsWith('i')) { plugin.managers.interactivity.lociSelects.deselectAll(); (_a = plugin.canvas3d) === null || _a === void 0 ? void 0 : _a.mark({ loci: loci_1.EveryLoci }, marker_action_1.MarkerAction.RemoveHighlight); const query_names = decodedHref.substring(1).split(','); for (const query_name of query_names) { const entities = (0, state_1.getFilteredEntities)(plugin, '', query_name); for (const r of entities) { const repr = (_b = r.obj) === null || _b === void 0 ? void 0 : _b.data.repr; if (repr) { (_c = plugin.canvas3d) === null || _c === void 0 ? void 0 : _c.mark({ repr, loci: loci_1.EveryLoci }, marker_action_1.MarkerAction.Highlight); } const cell = r; if (!(((_d = cell === null || cell === void 0 ? void 0 : cell.obj) === null || _d === void 0 ? void 0 : _d.data.sourceData) instanceof structure_1.Structure)) { return; } const loci = structure_1.Structure.toStructureElementLoci(cell.obj.data.sourceData); plugin.managers.interactivity.lociSelects.toggle({ loci }, false); } } } else if (decodedHref.startsWith('g')) { plugin.managers.interactivity.lociSelects.deselectAll(); (_e = plugin.canvas3d) === null || _e === void 0 ? void 0 : _e.mark({ loci: loci_1.EveryLoci }, marker_action_1.MarkerAction.RemoveHighlight); const qindex = decodedHref.indexOf('.'); const query = decodedHref.substring(1, qindex) + ':'; const query_names = decodedHref.substring(qindex + 1).split(','); for (const query_name of query_names) { const entities = (0, state_1.getAllEntities)(plugin, query + query_name); for (const r of entities) { const repr = (_f = r.obj) === null || _f === void 0 ? void 0 : _f.data.repr; if (repr) { (_g = plugin.canvas3d) === null || _g === void 0 ? void 0 : _g.mark({ repr, loci: loci_1.EveryLoci }, marker_action_1.MarkerAction.Highlight); } const cell = r; if (!(((_h = cell === null || cell === void 0 ? void 0 : cell.obj) === null || _h === void 0 ? void 0 : _h.data.sourceData) instanceof structure_1.Structure)) return; const loci = structure_1.Structure.toStructureElementLoci(cell.obj.data.sourceData); plugin.managers.interactivity.lociSelects.toggle({ loci }, false); } } } else { // open the link in a new tab window.open(decodedHref, '_blank'); } }; if (decodedHref[0] === '#') { return (0, jsx_runtime_1.jsx)("a", { href: decodedHref[0], onMouseOver: handleHover, onClick: handleClick, children: children }); } if (decodedHref[0] === 'i' || decodedHref[0] === 'g') { return (0, jsx_runtime_1.jsx)("a", { href: decodedHref[0], onMouseLeave: handleLeave, onMouseOver: handleHover, onClick: handleClick, children: children }); } if (decodedHref[0] === 'h') { return (0, jsx_runtime_1.jsx)("a", { href: decodedHref[0], onClick: handleClick, rel: 'noopener noreferrer', children: children }); } return element; } function MesoViewportSnapshotDescription() { var _a; let tSize = 14; const plugin = react_1.default.useContext(base_1.PluginReactContext); if (state_1.MesoscaleState.has(plugin)) { const state = state_1.MesoscaleState.get(plugin); tSize = state.textSizeDescription; } const [_, setV] = react_1.default.useState(0); const [isShown, setIsShown] = (0, react_1.useState)(true); const [textSize, setTextSize] = (0, react_1.useState)(tSize); const toggleVisibility = () => { setIsShown(!isShown); }; const increaseTextSize = () => { setTextSize(prevSize => Math.min(prevSize + 2, 50)); // Increase the text size by 2px, but not above 50px }; const decreaseTextSize = () => { setTextSize(prevSize => Math.max(prevSize - 2, 2)); // Decrease the text size by 2px, but not below 2px }; react_1.default.useEffect(() => { const sub = plugin.managers.snapshot.events.changed.subscribe(() => setV(v => v + 1)); return () => sub.unsubscribe(); }, [plugin]); const current = plugin.managers.snapshot.state.current; if (!current) return null; const e = plugin.managers.snapshot.getEntry(current); if (!((_a = e === null || e === void 0 ? void 0 : e.description) === null || _a === void 0 ? void 0 : _a.trim())) return null; if (state_1.MesoscaleState.has(plugin)) { state_1.MesoscaleState.set(plugin, { textSizeDescription: textSize }); } const showInfo = (0, jsx_runtime_1.jsx)(common_1.IconButton, { svg: isShown ? icons_1.TooltipTextSvg : icons_1.TooltipTextOutlineSvg, flex: '20px', onClick: toggleVisibility, title: isShown ? 'Hide Description' : 'Show Description' }); const increasePoliceSize = (0, jsx_runtime_1.jsx)(common_1.IconButton, { svg: icons_1.PlusBoxSvg, flex: '20px', onClick: increaseTextSize, title: 'Bigger Text' }); const decreasePoliceSize = (0, jsx_runtime_1.jsx)(common_1.IconButton, { svg: icons_1.MinusBoxSvg, flex: '20px', onClick: decreaseTextSize, title: 'Smaller Text' }); return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { id: 'snapinfoctrl', className: "msp-state-snapshot-viewport-controls", style: { marginRight: '30px' }, children: [showInfo, increasePoliceSize, decreasePoliceSize] }), (0, jsx_runtime_1.jsx)("div", { id: 'snapinfo', className: `msp-snapshot-description-me ${isShown ? 'shown' : 'hidden'}`, style: { fontSize: `${textSize}px` }, children: e.descriptionFormat === 'plaintext' && e.description || (0, jsx_runtime_1.jsx)(react_markdown_1.default, { skipHtml: false, components: { a: MesoMarkdownAnchor }, children: e.description }) })] })); } class FocusInfo extends base_1.PluginUIComponent { componentDidMount() { this.subscribe((0, rxjs_1.combineLatest)([ this.plugin.state.data.behaviors.isUpdating, this.plugin.managers.structure.selection.events.changed ]), ([isUpdating]) => { if (!isUpdating) this.forceUpdate(); }); } get info() { let focusInfo = ''; if (state_1.MesoscaleState.has(this.plugin)) { const state = state_1.MesoscaleState.get(this.plugin); if (state.focusInfo) focusInfo = state.focusInfo; } return focusInfo; } render() { const focusInfo = this.info; const description = (focusInfo !== '') ? (0, jsx_runtime_1.jsx)(react_markdown_1.default, { skipHtml: true, components: { a: MesoMarkdownAnchor }, children: focusInfo }) : ''; return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsx)("div", { id: 'focusinfo', className: 'msp-help-text', children: description }) }); } } exports.FocusInfo = FocusInfo; class EntityControls extends base_1.PluginUIComponent { constructor() { super(...arguments); this.filterRef = react_1.default.createRef(); this.prevFilter = ''; this.filterFocus = false; this.state = { isDisabled: false, }; this.setGroupBy = (value) => { this.roots.forEach((c, i) => { if (c.state.isHidden && value === i || !c.state.isHidden && value !== i) { commands_1.PluginCommands.State.ToggleVisibility(this.plugin, { state: c.parent, ref: c.transform.ref }); } }); }; this.setFilter = (value) => { this.filterFocus = true; const filter = value.trim().replace(/\s+/gi, ' '); state_1.MesoscaleState.set(this.plugin, { filter }); if (filter) (0, state_1.expandAllGroups)(this.plugin); }; this.setGraphics = (graphics) => { state_1.MesoscaleState.set(this.plugin, { graphics }); this.plugin.customState.graphicsMode = graphics; if (graphics === 'custom') return; const update = this.plugin.state.data.build(); const { lodLevels, approximate, alphaThickness } = (0, state_1.getGraphicsModeProps)(graphics); for (const r of (0, state_1.getAllEntities)(this.plugin)) { update.to(r).update(old => { if (old.type) { old.type.params.lodLevels = lodLevels; old.type.params.approximate = approximate; old.type.params.alphaThickness = alphaThickness; } }); } for (const g of (0, state_1.getAllGroups)(this.plugin)) { update.to(g).update(old => { old.lod.lodLevels = lodLevels; old.lod.approximate = approximate; }); } update.commit(); (0, state_1.setGraphicsCanvas3DProps)(this.plugin, graphics); }; } componentDidMount() { this.subscribe(this.plugin.state.events.object.created, e => { this.forceUpdate(); }); this.subscribe(this.plugin.state.events.object.removed, e => { this.forceUpdate(); }); this.subscribe(this.plugin.state.data.behaviors.isUpdating, v => { this.setState({ isDisabled: v }); }); this.subscribe(this.plugin.state.events.cell.stateUpdated, e => { if (!this.state.isDisabled && this.roots.some(r => e.cell === r) || (state_1.MesoscaleState.has(this.plugin) && state_1.MesoscaleState.ref(this.plugin) === e.ref)) { this.forceUpdate(); } }); } componentDidUpdate() { var _a; const filter = this.filter; if (this.filterFocus) { (_a = this.filterRef.current) === null || _a === void 0 ? void 0 : _a.focus(); this.prevFilter = filter; } } get roots() { return (0, state_1.getRoots)(this.plugin); } get groupBy() { const roots = this.roots; for (let i = 0, il = roots.length; i < il; ++i) { if (!roots[i].state.isHidden) return i; } return 0; } get filter() { return state_1.MesoscaleState.has(this.plugin) ? state_1.MesoscaleState.get(this.plugin).filter : ''; } get graphics() { const customState = this.plugin.customState; return state_1.MesoscaleState.has(this.plugin) ? state_1.MesoscaleState.get(this.plugin).graphics : customState.graphicsMode; } renderGraphics() { const graphics = this.graphics; return (0, jsx_runtime_1.jsx)("div", { id: 'graphicsquality', style: { margin: '5px', marginBottom: '10px' }, children: (0, jsx_runtime_1.jsx)(parameters_1.SelectControl, { name: 'Graphics', param: state_1.MesoscaleStateParams.graphics, value: `${graphics}`, onChange: (e) => { this.setGraphics(e.value); } }) }); } render() { const roots = this.roots; if (roots.length === 0 || !state_1.MesoscaleState.has(this.plugin)) { return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: this.renderGraphics() }); } const disabled = this.state.isDisabled; const groupBy = this.groupBy; const options = []; roots.forEach((c, i) => { options.push([`${i}`, c.obj.label]); }); const groupParam = param_definition_1.ParamDefinition.Select(options[0][0], options); const root = roots.length === 1 ? roots[0] : roots[groupBy]; const filter = this.filter; return (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [this.renderGraphics(), (0, jsx_runtime_1.jsxs)("div", { id: 'searchtree', className: `msp-flex-row msp-control-row`, style: { margin: '5px', marginBottom: '10px' }, children: [(0, jsx_runtime_1.jsx)("input", { type: 'text', ref: this.filterRef, value: filter, placeholder: 'Search', onChange: e => this.setFilter(e.target.value), disabled: disabled, onBlur: () => this.filterFocus = false }), (0, jsx_runtime_1.jsx)(common_1.IconButton, { svg: icons_1.CloseSvg, toggleState: false, disabled: disabled, onClick: () => this.setFilter('') })] }), options.length > 1 && (0, jsx_runtime_1.jsx)("div", { id: 'grouptree', style: { margin: '5px', marginBottom: '10px' }, children: (0, jsx_runtime_1.jsx)(parameters_1.SelectControl, { name: 'Group By', param: groupParam, value: `${groupBy}`, onChange: (e) => { this.setGroupBy(parseInt(e.value)); } }) }), (0, jsx_runtime_1.jsx)("div", { id: 'tree', style: { position: 'relative', overflowY: 'auto', borderBottom: '1px solid #000', maxHeight: '600px' }, children: (0, jsx_runtime_1.jsx)(GroupNode, { filter: filter, cell: root, depth: 0 }) })] }); } } exports.EntityControls = EntityControls; class Node extends base_1.PluginUIComponent { is(e) { return e.ref === this.ref && e.state === this.props.cell.parent; } get ref() { return this.props.cell.transform.ref; } get cell() { return this.props.cell; } get roots() { return (0, state_1.getRoots)(this.plugin); } componentDidMount() { this.subscribe(this.plugin.state.data.behaviors.isUpdating, v => { this.setState({ isDisabled: v }); }); this.subscribe(this.plugin.state.events.cell.stateUpdated, e => { if (!this.state.isDisabled && this.is(e)) { this.forceUpdate(); } }); } } class GroupNode extends Node { constructor() { super(...arguments); this.state = { isCollapsed: !!this.props.cell.state.isCollapsed, action: undefined, isDisabled: false, }; this.toggleExpanded = (e) => { commands_1.PluginCommands.State.ToggleExpanded(this.plugin, { state: this.cell.parent, ref: this.ref }); }; this.toggleColor = (e) => { this.setState({ action: this.state.action === 'color' ? undefined : 'color' }); }; this.toggleClip = () => { this.setState({ action: this.state.action === 'clip' ? undefined : 'clip' }); }; this.toggleRoot = () => { this.setState({ action: this.state.action === 'root' ? undefined : 'root' }); }; this.showInfo = (e) => { e.preventDefault(); const d = (0, state_1.getCellDescription)(this.cell); // '### ' + this.cell?.obj?.label + '\n\n' + this.cell?.obj?.description; state_1.MesoscaleState.set(this.plugin, { focusInfo: `${d}` }); }; this.highlight = (e) => { var _a, _b, _c; e.preventDefault(); (_a = this.plugin.canvas3d) === null || _a === void 0 ? void 0 : _a.mark({ loci: loci_1.EveryLoci }, marker_action_1.MarkerAction.RemoveHighlight); for (const r of this.allFilteredEntities) { const repr = (_b = r.obj) === null || _b === void 0 ? void 0 : _b.data.repr; if (repr) { (_c = this.plugin.canvas3d) === null || _c === void 0 ? void 0 : _c.mark({ repr, loci: loci_1.EveryLoci }, marker_action_1.MarkerAction.Highlight); } } }; this.clearHighlight = (e) => { var _a; e.preventDefault(); (_a = this.plugin.canvas3d) === null || _a === void 0 ? void 0 : _a.mark({ loci: loci_1.EveryLoci }, marker_action_1.MarkerAction.RemoveHighlight); e.currentTarget.blur(); }; this.toggleVisible = (e) => { commands_1.PluginCommands.State.ToggleVisibility(this.plugin, { state: this.cell.parent, ref: this.ref }); const isHidden = this.cell.state.isHidden; for (const r of this.allFilteredEntities) { this.plugin.state.data.updateCellState(r.transform.ref, { isHidden }); } this.plugin.build().to(this.ref).update(old => { old.hidden = isHidden; }).commit(); }; this.updateColor = (values) => { const update = this.plugin.state.data.build(); const { value, illustrative, type, lightness, alpha, emissive } = values; const entities = this.filteredEntities; let groupColors = []; if (type === 'generate') { groupColors = (0, state_1.getDistinctGroupColors)(entities.length, value, values.variability, values.shift); } for (let i = 0; i < entities.length; ++i) { const c = type === 'generate' ? groupColors[i] : value; update.to(entities[i]).update(old => { if (old.type) { if (illustrative) { old.colorTheme = { name: 'illustrative', params: { style: { name: 'uniform', params: { value: c, lightness } } } }; } else { old.colorTheme = { name: 'uniform', params: { value: c, lightness } }; } old.type.params.alpha = alpha; old.type.params.xrayShaded = alpha < 1 ? 'inverted' : false; old.type.params.emissive = emissive; } else { old.coloring.params.color = c; old.coloring.params.lightness = lightness; old.alpha = alpha; old.xrayShaded = alpha < 1 ? true : false; old.emissive = emissive; } }); } update.to(this.ref).update(old => { old.color = values; }); for (const r of this.roots) { update.to(r).update(old => { old.color.type = 'custom'; }); } update.commit(); }; this.updateRoot = async (values) => { var _a, _b; await (0, state_1.updateColors)(this.plugin, values, (_a = this.cell.params) === null || _a === void 0 ? void 0 : _a.values.tag, this.props.filter); const update = this.plugin.state.data.build(); for (const r of this.roots) { if (r !== this.cell) { update.to(r).update(old => { old.color.type = 'custom'; }); const others = (0, state_1.getAllLeafGroups)(this.plugin, (_b = r.params) === null || _b === void 0 ? void 0 : _b.values.tag); for (const o of others) { update.to(o).update(old => { old.color.type = 'custom'; }); } } } update.to(this.ref).update(old => { old.color = values; }); update.commit(); }; this.updateClip = (values) => { const update = this.plugin.state.data.build(); const clipObjects = (0, state_1.getClipObjects)(values, this.plugin.canvas3d.boundingSphere); for (const r of this.allFilteredEntities) { update.to(r).update(old => { if (old.type) { old.type.params.clip.objects = clipObjects; } else { old.clip.objects = clipObjects; } }); } for (const g of this.allGroups) { update.to(g).update(old => { old.clip = values; }); } update.commit(); }; this.updateLod = (values) => { state_1.MesoscaleState.set(this.plugin, { graphics: 'custom' }); this.plugin.customState.graphicsMode = 'custom'; const update = this.plugin.state.data.build(); for (const r of this.allFilteredEntities) { update.to(r).update(old => { if (old.type) { old.type.params.lodLevels = values.lodLevels; old.type.params.cellSize = values.cellSize; old.type.params.batchSize = values.batchSize; old.type.params.approximate = values.approximate; } }); } for (const g of this.allGroups) { update.to(g).update(old => { old.lod = values; }); } update.commit(); }; this.update = (props) => { this.plugin.state.data.build().to(this.ref).update(props); }; } get groups() { var _a; return (0, state_1.getGroups)(this.plugin, (_a = this.cell.params) === null || _a === void 0 ? void 0 : _a.values.tag); } get allGroups() { var _a; const allGroups = (0, state_1.getAllGroups)(this.plugin, (_a = this.cell.params) === null || _a === void 0 ? void 0 : _a.values.tag); allGroups.push(this.cell); return allGroups; } get entities() { var _a; return (0, state_1.getEntities)(this.plugin, (_a = this.cell.params) === null || _a === void 0 ? void 0 : _a.values.tag); } get filteredEntities() { var _a; return (0, state_1.getFilteredEntities)(this.plugin, (_a = this.cell.params) === null || _a === void 0 ? void 0 : _a.values.tag, this.props.filter); } get allEntities() { var _a; return (0, state_1.getAllEntities)(this.plugin, (_a = this.cell.params) === null || _a === void 0 ? void 0 : _a.values.tag); } get allFilteredEntities() { var _a; return (0, state_1.getAllFilteredEntities)(this.plugin, (_a = this.cell.params) === null || _a === void 0 ? void 0 : _a.values.tag, this.props.filter); } renderColor() { var _a, _b, _c; const color = (_a = this.cell.params) === null || _a === void 0 ? void 0 : _a.values.color; if (((_b = this.cell.params) === null || _b === void 0 ? void 0 : _b.values.color.type) === 'uniform') { const style = { backgroundColor: color_1.Color.toStyle(color.value), minWidth: 32, width: 32, borderRight: `6px solid ${color_1.Color.toStyle(color_1.Color.lighten(color.value, color.lightness))}` }; return (0, jsx_runtime_1.jsx)(common_1.Button, { style: style, onClick: this.toggleColor }); } else if (((_c = this.cell.params) === null || _c === void 0 ? void 0 : _c.values.color.type) === 'generate') { const style = { minWidth: 32, width: 32, borderRight: `6px solid ${color_1.Color.toStyle(color_1.Color.lighten(color.value, color.lightness))}` }; return (0, jsx_runtime_1.jsx)(common_1.IconButton, { style: style, svg: icons_1.BrushSvg, toggleState: false, small: true, onClick: this.toggleColor }); } else { return (0, jsx_runtime_1.jsx)(common_1.IconButton, { svg: icons_1.BrushSvg, toggleState: false, small: true, onClick: this.toggleColor }); } } render() { var _a, _b, _c, _d, _e; if (this.allFilteredEntities.length === 0) return; const state = this.cell.state; const disabled = false; const groupLabel = this.cell.obj.label; const depth = this.props.depth; const colorValue = (_a = this.cell.params) === null || _a === void 0 ? void 0 : _a.values.color; const rootValue = (_b = this.cell.params) === null || _b === void 0 ? void 0 : _b.values.color; const clipValue = (_c = this.cell.params) === null || _c === void 0 ? void 0 : _c.values.clip; const lodValue = (_d = this.cell.params) === null || _d === void 0 ? void 0 : _d.values.lod; const isRoot = (_e = this.cell.params) === null || _e === void 0 ? void 0 : _e.values.root; const groups = this.groups; const entities = this.entities; const label = (0, jsx_runtime_1.jsx)(common_1.Button, { className: `msp-btn-tree-label`, noOverflow: true, disabled: disabled, onMouseEnter: this.highlight, onMouseLeave: this.clearHighlight, onClick: this.showInfo, children: (0, jsx_runtime_1.jsx)("span", { title: groupLabel, children: groupLabel }) }); const expand = (0, jsx_runtime_1.jsx)(common_1.IconButton, { svg: state.isCollapsed ? icons_1.ArrowRightSvg : icons_1.ArrowDropDownSvg, flex: '20px', disabled: disabled, onClick: this.toggleExpanded, transparent: true, className: 'msp-no-hover-outline', style: { visibility: groups.length > 0 || entities.length > 0 ? 'visible' : 'hidden' } }); const color = (entities.length > 0 && !isRoot) && this.renderColor(); const root = (isRoot && this.allGroups.length > 1) && (0, jsx_runtime_1.jsx)(common_1.IconButton, { svg: icons_1.BrushSvg, toggleState: false, disabled: disabled, small: true, onClick: this.toggleRoot }); const clip = (0, jsx_runtime_1.jsx)(common_1.IconButton, { svg: icons_1.ContentCutSvg, toggleState: false, disabled: disabled, small: true, onClick: this.toggleClip }); const visibility = (0, jsx_runtime_1.jsx)(common_1.IconButton, { svg: state.isHidden ? icons_1.VisibilityOffOutlinedSvg : icons_1.VisibilityOutlinedSvg, toggleState: false, disabled: disabled, small: true, onClick: this.toggleVisible }); const loadColorButton = (depth === 0) && (0, jsx_runtime_1.jsx)(states_1.ColorLoaderControls, { plugin: this.plugin }); return (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: `msp-flex-row`, style: { margin: `1px 5px 1px ${depth * 10 + 5}px` }, children: [expand, label, root || color, loadColorButton, clip, visibility] }), this.state.action === 'color' && (0, jsx_runtime_1.jsx)("div", { style: { marginRight: 5 }, className: 'msp-accent-offset', children: (0, jsx_runtime_1.jsx)(common_1.ControlGroup, { header: 'Color', initialExpanded: true, hideExpander: true, hideOffset: true, onHeaderClick: this.toggleColor, topRightIcon: icons_1.CloseSvg, noTopMargin: true, childrenClassName: 'msp-viewport-controls-panel-controls', children: (0, jsx_runtime_1.jsx)(parameters_1.ParameterControls, { params: state_1.ColorParams, values: colorValue, onChangeValues: this.updateColor }) }) }), this.state.action === 'clip' && (0, jsx_runtime_1.jsx)("div", { style: { marginRight: 5 }, className: 'msp-accent-offset', children: (0, jsx_runtime_1.jsxs)(common_1.ControlGroup, { header: 'Clip', initialExpanded: true, hideExpander: true, hideOffset: true, onHeaderClick: this.toggleClip, topRightIcon: icons_1.CloseSvg, noTopMargin: true, childrenClassName: 'msp-viewport-controls-panel-controls', children: [(0, jsx_runtime_1.jsx)(parameters_1.ParameterControls, { params: state_1.SimpleClipParams, values: clipValue, onChangeValues: this.updateClip }), (0, jsx_runtime_1.jsx)(parameters_1.ParameterControls, { params: state_1.LodParams, values: lodValue, onChangeValues: this.updateLod })] }) }), this.state.action === 'root' && (0, jsx_runtime_1.jsx)("div", { style: { marginRight: 5 }, className: 'msp-accent-offset', children: (0, jsx_runtime_1.jsx)(common_1.ControlGroup, { header: 'Color', initialExpanded: true, hideExpander: true, hideOffset: true, onHeaderClick: this.toggleRoot, topRightIcon: icons_1.CloseSvg, noTopMargin: true, childrenClassName: 'msp-viewport-controls-panel-controls', children: (0, jsx_runtime_1.jsx)(parameters_1.ParameterControls, { params: state_1.RootParams, values: rootValue, onChangeValues: this.updateRoot }) }) }), (!state.isCollapsed) && (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [groups.map(c => { return (0, jsx_runtime_1.jsx)(GroupNode, { filter: this.props.filter, cell: c, depth: depth + 1 }, c.transform.ref); }), this.filteredEntities.map(c => { return (0, jsx_runtime_1.jsx)(EntityNode, { cell: c, depth: depth + 1 }, c.transform.ref); })] })] }); } } exports.GroupNode = GroupNode; class EntityNode extends Node { constructor() { super(...arguments); this.state = { action: undefined, isDisabled: false, }; this.clipMapping = (0, state_1.createClipMapping)(this); this.toggleVisible = (e) => { e.preventDefault(); commands_1.PluginCommands.State.ToggleVisibility(this.plugin, { state: this.props.cell.parent, ref: this.ref }); e.currentTarget.blur(); }; this.toggleColor = (e) => { var _a; if (e === null || e === void 0 ? void 0 : e.ctrlKey) { this.updateLightness({ lightness: ((_a = this.lightnessValue) === null || _a === void 0 ? void 0 : _a.lightness) ? 0 : state_1.DimLightness }); e.preventDefault(); } else { this.setState({ action: this.state.action === 'color' ? undefined : 'color' }); } }; this.toggleClip = () => { this.setState({ action: this.state.action === 'clip' ? undefined : 'clip' }); }; this.highlight = (e) => { var _a, _b, _c, _d; e.preventDefault(); (_a = this.plugin.canvas3d) === null || _a === void 0 ? void 0 : _a.mark({ loci: loci_1.EveryLoci }, marker_action_1.MarkerAction.RemoveHighlight); const repr = (_c = (_b = this.cell) === null || _b === void 0 ? void 0 : _b.obj) === null || _c === void 0 ? void 0 : _c.data.repr; if (repr) { (_d = this.plugin.canvas3d) === null || _d === void 0 ? void 0 : _d.mark({ repr, loci: loci_1.EveryLoci }, marker_action_1.MarkerAction.Highlight); } e.currentTarget.blur(); }; this.clearHighlight = (e) => { var _a; e.preventDefault(); (_a = this.plugin.canvas3d) === null || _a === void 0 ? void 0 : _a.mark({ loci: loci_1.EveryLoci }, marker_action_1.MarkerAction.RemoveHighlight); e.currentTarget.blur(); }; this.toggleSelect = (e) => { var _a; e.preventDefault(); const cell = this.cell; if (!(((_a = cell === null || cell === void 0 ? void 0 : cell.obj) === null || _a === void 0 ? void 0 : _a.data.sourceData) instanceof structure_1.Structure)) return; const loci = structure_1.Structure.toStructureElementLoci(cell.obj.data.sourceData); this.plugin.managers.interactivity.lociSelects.toggle({ loci }, false); }; this.center = (e) => { var _a; e.preventDefault(); const cell = this.cell; if (!(((_a = cell === null || cell === void 0 ? void 0 : cell.obj) === null || _a === void 0 ? void 0 : _a.data.sourceData) instanceof structure_1.Structure)) return; const loci = structure_1.Structure.toStructureElementLoci(cell.obj.data.sourceData); centerLoci(this.plugin, loci); }; this.handleClick = (e) => { var _a, _b, _c, _d, _e, _f, _g; if (e.ctrlKey) { this.toggleSelect(e); } else { const d = (0, state_1.getEntityDescription)(this.plugin, this.cell); if (((_b = (_a = this.cell) === null || _a === void 0 ? void 0 : _a.obj) === null || _b === void 0 ? void 0 : _b.data.sourceData.state.models.length) !== 0) { const repr = (_d = (_c = this.cell) === null || _c === void 0 ? void 0 : _c.obj) === null || _d === void 0 ? void 0 : _d.data.repr; if (repr) { // for fiber need to think how to handle. const aloci = repr.getAllLoci()[0]; const locis = loci_1.Loci.normalize(aloci, 'chainInstances'); const nChain = aloci.structure.state.unitSymmetryGroups.length; let index = state_1.MesoscaleState.get(this.plugin).index + 1; if (index * nChain >= locis.elements.length) index = 0; const elems = locis.elements.slice(index * nChain, ((index + 1) * nChain)); // end index is not included const loci = element_1.StructureElement.Loci(aloci.structure, elems); const sphere = loci_1.Loci.getBoundingSphere(loci) || (0, geometry_1.Sphere3D)(); const state = this.plugin.state.behaviors; const selections = state.select(mol_state_1.StateSelection.Generators.ofTransformer(camera_1.MesoFocusLoci)); const params = selections.length === 1 ? (_e = selections[0].obj) === null || _e === void 0 ? void 0 : _e.data.params : undefined; if (!params.centerOnly) { this.plugin.managers.camera.focusSphere(sphere, params); } else { const snapshot = (_f = this.plugin.canvas3d) === null || _f === void 0 ? void 0 : _f.camera.getCenter(sphere.center); (_g = this.plugin.canvas3d) === null || _g === void 0 ? void 0 : _g.requestCameraReset({ durationMs: params.durationMs, snapshot }); } state_1.MesoscaleState.set(this.plugin, { index: index, focusInfo: `${d}` }); } } else { this.center(e); state_1.MesoscaleState.set(this.plugin, { focusInfo: `${d}` }); } } }; this.updateColor = ({ value }) => { const update = this.plugin.state.data.build(); for (const g of this.groups) { update.to(g.transform.ref).update(old => { old.color.type = 'custom'; }); } for (const r of this.roots) { update.to(r).update(old => { old.color.type = 'custom'; }); } update.to(this.ref).update(old => { if (old.colorTheme) { if (old.colorTheme.name === 'illustrative') { old.colorTheme.params.style.params.value = value; } else { old.colorTheme.params.value = value; } } else if (old.coloring) { old.coloring.params.color = value; } }); update.commit(); }; this.updateIllustrative = (values) => { return this.plugin.build().to(this.ref).update(old => { if (old.colorTheme) { if (old.colorTheme.name !== 'illustrative' && values.illustrative) { old.colorTheme = { name: 'illustrative', params: { style: { name: 'uniform', params: { value: old.colorTheme.params.value, lightness: old.colorTheme.params.lightness } } } }; } else if (old.colorTheme.name === 'illustrative' && !values.illustrative) { old.colorTheme = { name: 'uniform', params: { value: old.colorTheme.params.style.params.value, lightness: old.colorTheme.params.style.params.lightness } }; } } }).commit(); }; this.updateLightness = (values) => { return this.plugin.build().to(this.ref).update(old => { if (old.colorTheme) { if (old.colorTheme.name === 'illustrative') {