UNPKG

molstar

Version:

A comprehensive macromolecular library.

972 lines 58 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "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> */ import { PluginReactContext, PluginUIComponent } from '../../../mol-plugin-ui/base'; import { Button, ControlGroup, IconButton } from '../../../mol-plugin-ui/controls/common'; import { ArrowDropDownSvg, ArrowRightSvg, CloseSvg, VisibilityOffOutlinedSvg, VisibilityOutlinedSvg, ContentCutSvg, BrushSvg, SearchSvg, TooltipTextSvg, TooltipTextOutlineSvg, PlusBoxSvg, MinusBoxSvg } from '../../../mol-plugin-ui/controls/icons'; import { PluginCommands } from '../../../mol-plugin/commands'; import { StateSelection } from '../../../mol-state'; import { ParameterControls, ParameterMappingControl, SelectControl } from '../../../mol-plugin-ui/controls/parameters'; import { ParamDefinition as PD } from '../../../mol-util/param-definition'; import { Clip } from '../../../mol-util/clip'; import { Color } from '../../../mol-util/color'; import { CombinedColorControl } from '../../../mol-plugin-ui/controls/color'; import { MarkerAction } from '../../../mol-util/marker-action'; import { EveryLoci, Loci } from '../../../mol-model/loci'; import { deepEqual } from '../../../mol-util'; import { ColorValueParam, ColorParams, DimLightness, LightnessParams, LodParams, MesoscaleGroup, OpacityParams, SimpleClipParams, createClipMapping, getClipObjects, getDistinctGroupColors, RootParams, MesoscaleState, getRoots, getAllGroups, getAllLeafGroups, getFilteredEntities, getAllFilteredEntities, getGroups, getEntities, getAllEntities, getEntityLabel, updateColors, getGraphicsModeProps, MesoscaleStateParams, setGraphicsCanvas3DProps, PatternParams, expandAllGroups, EmissiveParams, IllustrativeParams, getCellDescription, getEntityDescription, getEveryEntity } from '../data/state'; import React, { useState } from 'react'; import { StructureElement } from '../../../mol-model/structure/structure/element'; import { Structure } from '../../../mol-model/structure'; import { Sphere3D } from '../../../mol-math/geometry'; import { MesoFocusLoci } from '../behavior/camera'; import Markdown from 'react-markdown'; import { combineLatest } from 'rxjs'; import { ColorLoaderControls } from './states'; function centerLoci(plugin, loci, durationMs = 250) { const { canvas3d } = plugin; if (!canvas3d) return; const sphere = Loci.getBoundingSphere(loci) || Sphere3D(); const snapshot = canvas3d.camera.getCenter(sphere.center); canvas3d.requestCameraReset({ durationMs, snapshot }); } export class ModelInfo extends 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 && MesoscaleState.has(this.plugin) && MesoscaleState.ref(this.plugin) === e.ref) { this.forceUpdate(); } }); } get info() { if (!MesoscaleState.has(this.plugin)) return; const state = 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 && _jsx(_Fragment, { children: _jsxs("div", { id: 'modelinfo', className: 'msp-help-text', children: [_jsx("div", { children: info.description }), _jsx("div", { children: _jsx("a", { href: info.link, target: '_blank', children: "Source" }) })] }) }); } } const SelectionStyleParam = PD.Select('color+outline', PD.objectToOptions({ 'color+outline': 'Color & Outline', 'color': 'Color', 'outline': 'Outline' })); export class SelectionInfo extends 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 (StructureElement.Loci.is(e.selection) && !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) { MesoscaleState.set(this.plugin, { filter: `"${label}"` }); if (label) expandAllGroups(this.plugin); } ; remove(key) { const e = this.plugin.managers.structure.selection.entries.get(key); if (!e) return; const loci = 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.toStructureElementLoci(e.selection.structure); centerLoci(this.plugin, loci); const cell = this.plugin.helpers.substructureParent.get(loci.structure); const d = getCellDescription(cell); // '### ' + cell?.obj?.label + '\n\n' + cell?.obj?.description; MesoscaleState.set(this.plugin, { focusInfo: `${d}` }); } get selection() { const info = this.info; const help_selection = _jsxs(_Fragment, { children: [_jsxs("div", { children: ["Use ", _jsx("i", { children: "ctrl+left" }), " to select entities, either on the 3D canvas or in the tree below"] }), _jsxs("div", { children: ["Use ", _jsx("i", { children: "shift+left" }), " to select individual chain on the 3D canvas"] })] }); if (!info.length) return _jsx(_Fragment, { children: _jsx("div", { id: 'seleinfo', className: 'msp-help-text', children: help_selection }) }); return _jsx(_Fragment, { children: _jsxs("div", { id: 'seleinfo', children: [info.map((entry, index) => { const label = _jsx(Button, { className: `msp-btn-tree-label`, noOverflow: true, disabled: this.state.isDisabled, onClick: () => this.center(entry.key), children: _jsx("span", { title: entry.label, children: entry.label }) }); const find = _jsx(IconButton, { svg: SearchSvg, toggleState: false, disabled: this.state.isDisabled, small: true, onClick: () => this.find(entry.label) }); const remove = _jsx(IconButton, { svg: CloseSvg, toggleState: false, disabled: this.state.isDisabled, onClick: () => this.remove(entry.key) }); return _jsx(_Fragment, { children: _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 _jsx("div", { id: 'selestyle', style: { margin: '5px', marginBottom: '10px' }, children: _jsx(SelectControl, { name: 'Style', param: SelectionStyleParam, value: style, onChange: (e) => { this.setStyle(e.value); } }) }); } render() { return _jsxs(_Fragment, { children: [this.renderStyle(), this.selection] }); } } export function MesoMarkdownAnchor({ href, children, element }) { const plugin = React.useContext(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: EveryLoci }, MarkerAction.RemoveHighlight); const query_names = decodedHref.substring(1).split(','); for (const query_name of query_names) { const entities = 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: EveryLoci }, MarkerAction.Highlight); } } } } else if (decodedHref.startsWith('g')) { (_d = plugin.canvas3d) === null || _d === void 0 ? void 0 : _d.mark({ loci: EveryLoci }, 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 = 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: EveryLoci }, 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: EveryLoci }, 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: EveryLoci }, MarkerAction.RemoveHighlight); const query_names = decodedHref.substring(1).split(','); for (const query_name of query_names) { const entities = 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: EveryLoci }, 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)) { return; } const loci = 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: EveryLoci }, 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 = 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: EveryLoci }, 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)) return; const loci = 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 _jsx("a", { href: decodedHref[0], onMouseOver: handleHover, onClick: handleClick, children: children }); } if (decodedHref[0] === 'i' || decodedHref[0] === 'g') { return _jsx("a", { href: decodedHref[0], onMouseLeave: handleLeave, onMouseOver: handleHover, onClick: handleClick, children: children }); } if (decodedHref[0] === 'h') { return _jsx("a", { href: decodedHref[0], onClick: handleClick, rel: 'noopener noreferrer', children: children }); } return element; } export function MesoViewportSnapshotDescription() { var _a; let tSize = 14; const plugin = React.useContext(PluginReactContext); if (MesoscaleState.has(plugin)) { const state = MesoscaleState.get(plugin); tSize = state.textSizeDescription; } const [_, setV] = React.useState(0); const [isShown, setIsShown] = useState(true); const [textSize, setTextSize] = 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.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 (MesoscaleState.has(plugin)) { MesoscaleState.set(plugin, { textSizeDescription: textSize }); } const showInfo = _jsx(IconButton, { svg: isShown ? TooltipTextSvg : TooltipTextOutlineSvg, flex: '20px', onClick: toggleVisibility, title: isShown ? 'Hide Description' : 'Show Description' }); const increasePoliceSize = _jsx(IconButton, { svg: PlusBoxSvg, flex: '20px', onClick: increaseTextSize, title: 'Bigger Text' }); const decreasePoliceSize = _jsx(IconButton, { svg: MinusBoxSvg, flex: '20px', onClick: decreaseTextSize, title: 'Smaller Text' }); return (_jsxs(_Fragment, { children: [_jsxs("div", { id: 'snapinfoctrl', className: "msp-state-snapshot-viewport-controls", style: { marginRight: '30px' }, children: [showInfo, increasePoliceSize, decreasePoliceSize] }), _jsx("div", { id: 'snapinfo', className: `msp-snapshot-description-me ${isShown ? 'shown' : 'hidden'}`, style: { fontSize: `${textSize}px` }, children: e.descriptionFormat === 'plaintext' && e.description || _jsx(Markdown, { skipHtml: false, components: { a: MesoMarkdownAnchor }, children: e.description }) })] })); } export class FocusInfo extends PluginUIComponent { componentDidMount() { this.subscribe(combineLatest([ this.plugin.state.data.behaviors.isUpdating, this.plugin.managers.structure.selection.events.changed ]), ([isUpdating]) => { if (!isUpdating) this.forceUpdate(); }); } get info() { let focusInfo = ''; if (MesoscaleState.has(this.plugin)) { const state = MesoscaleState.get(this.plugin); if (state.focusInfo) focusInfo = state.focusInfo; } return focusInfo; } render() { const focusInfo = this.info; const description = (focusInfo !== '') ? _jsx(Markdown, { skipHtml: true, components: { a: MesoMarkdownAnchor }, children: focusInfo }) : ''; return _jsx(_Fragment, { children: _jsx("div", { id: 'focusinfo', className: 'msp-help-text', children: description }) }); } } export class EntityControls extends PluginUIComponent { constructor() { super(...arguments); this.filterRef = React.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) { 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, ' '); MesoscaleState.set(this.plugin, { filter }); if (filter) expandAllGroups(this.plugin); }; this.setGraphics = (graphics) => { 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 } = getGraphicsModeProps(graphics); for (const r of 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 getAllGroups(this.plugin)) { update.to(g).update(old => { old.lod.lodLevels = lodLevels; old.lod.approximate = approximate; }); } update.commit(); 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) || (MesoscaleState.has(this.plugin) && 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 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 MesoscaleState.has(this.plugin) ? MesoscaleState.get(this.plugin).filter : ''; } get graphics() { const customState = this.plugin.customState; return MesoscaleState.has(this.plugin) ? MesoscaleState.get(this.plugin).graphics : customState.graphicsMode; } renderGraphics() { const graphics = this.graphics; return _jsx("div", { id: 'graphicsquality', style: { margin: '5px', marginBottom: '10px' }, children: _jsx(SelectControl, { name: 'Graphics', param: MesoscaleStateParams.graphics, value: `${graphics}`, onChange: (e) => { this.setGraphics(e.value); } }) }); } render() { const roots = this.roots; if (roots.length === 0 || !MesoscaleState.has(this.plugin)) { return _jsx(_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 = PD.Select(options[0][0], options); const root = roots.length === 1 ? roots[0] : roots[groupBy]; const filter = this.filter; return _jsxs(_Fragment, { children: [this.renderGraphics(), _jsxs("div", { id: 'searchtree', className: `msp-flex-row msp-control-row`, style: { margin: '5px', marginBottom: '10px' }, children: [_jsx("input", { type: 'text', ref: this.filterRef, value: filter, placeholder: 'Search', onChange: e => this.setFilter(e.target.value), disabled: disabled, onBlur: () => this.filterFocus = false }), _jsx(IconButton, { svg: CloseSvg, toggleState: false, disabled: disabled, onClick: () => this.setFilter('') })] }), options.length > 1 && _jsx("div", { id: 'grouptree', style: { margin: '5px', marginBottom: '10px' }, children: _jsx(SelectControl, { name: 'Group By', param: groupParam, value: `${groupBy}`, onChange: (e) => { this.setGroupBy(parseInt(e.value)); } }) }), _jsx("div", { id: 'tree', style: { position: 'relative', overflowY: 'auto', borderBottom: '1px solid #000', maxHeight: '600px' }, children: _jsx(GroupNode, { filter: filter, cell: root, depth: 0 }) })] }); } } class Node extends 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 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(); } }); } } export class GroupNode extends Node { constructor() { super(...arguments); this.state = { isCollapsed: !!this.props.cell.state.isCollapsed, action: undefined, isDisabled: false, }; this.toggleExpanded = (e) => { 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 = getCellDescription(this.cell); // '### ' + this.cell?.obj?.label + '\n\n' + this.cell?.obj?.description; 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: EveryLoci }, 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: EveryLoci }, MarkerAction.Highlight); } } }; this.clearHighlight = (e) => { var _a; e.preventDefault(); (_a = this.plugin.canvas3d) === null || _a === void 0 ? void 0 : _a.mark({ loci: EveryLoci }, MarkerAction.RemoveHighlight); e.currentTarget.blur(); }; this.toggleVisible = (e) => { 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 = 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 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 = 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 = 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) => { 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 getGroups(this.plugin, (_a = this.cell.params) === null || _a === void 0 ? void 0 : _a.values.tag); } get allGroups() { var _a; const allGroups = 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 getEntities(this.plugin, (_a = this.cell.params) === null || _a === void 0 ? void 0 : _a.values.tag); } get filteredEntities() { var _a; return getFilteredEntities(this.plugin, (_a = this.cell.params) === null || _a === void 0 ? void 0 : _a.values.tag, this.props.filter); } get allEntities() { var _a; return getAllEntities(this.plugin, (_a = this.cell.params) === null || _a === void 0 ? void 0 : _a.values.tag); } get allFilteredEntities() { var _a; return 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.toStyle(color.value), minWidth: 32, width: 32, borderRight: `6px solid ${Color.toStyle(Color.lighten(color.value, color.lightness))}` }; return _jsx(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.toStyle(Color.lighten(color.value, color.lightness))}` }; return _jsx(IconButton, { style: style, svg: BrushSvg, toggleState: false, small: true, onClick: this.toggleColor }); } else { return _jsx(IconButton, { svg: 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 = _jsx(Button, { className: `msp-btn-tree-label`, noOverflow: true, disabled: disabled, onMouseEnter: this.highlight, onMouseLeave: this.clearHighlight, onClick: this.showInfo, children: _jsx("span", { title: groupLabel, children: groupLabel }) }); const expand = _jsx(IconButton, { svg: state.isCollapsed ? ArrowRightSvg : 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) && _jsx(IconButton, { svg: BrushSvg, toggleState: false, disabled: disabled, small: true, onClick: this.toggleRoot }); const clip = _jsx(IconButton, { svg: ContentCutSvg, toggleState: false, disabled: disabled, small: true, onClick: this.toggleClip }); const visibility = _jsx(IconButton, { svg: state.isHidden ? VisibilityOffOutlinedSvg : VisibilityOutlinedSvg, toggleState: false, disabled: disabled, small: true, onClick: this.toggleVisible }); const loadColorButton = (depth === 0) && _jsx(ColorLoaderControls, { plugin: this.plugin }); return _jsxs(_Fragment, { children: [_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' && _jsx("div", { style: { marginRight: 5 }, className: 'msp-accent-offset', children: _jsx(ControlGroup, { header: 'Color', initialExpanded: true, hideExpander: true, hideOffset: true, onHeaderClick: this.toggleColor, topRightIcon: CloseSvg, noTopMargin: true, childrenClassName: 'msp-viewport-controls-panel-controls', children: _jsx(ParameterControls, { params: ColorParams, values: colorValue, onChangeValues: this.updateColor }) }) }), this.state.action === 'clip' && _jsx("div", { style: { marginRight: 5 }, className: 'msp-accent-offset', children: _jsxs(ControlGroup, { header: 'Clip', initialExpanded: true, hideExpander: true, hideOffset: true, onHeaderClick: this.toggleClip, topRightIcon: CloseSvg, noTopMargin: true, childrenClassName: 'msp-viewport-controls-panel-controls', children: [_jsx(ParameterControls, { params: SimpleClipParams, values: clipValue, onChangeValues: this.updateClip }), _jsx(ParameterControls, { params: LodParams, values: lodValue, onChangeValues: this.updateLod })] }) }), this.state.action === 'root' && _jsx("div", { style: { marginRight: 5 }, className: 'msp-accent-offset', children: _jsx(ControlGroup, { header: 'Color', initialExpanded: true, hideExpander: true, hideOffset: true, onHeaderClick: this.toggleRoot, topRightIcon: CloseSvg, noTopMargin: true, childrenClassName: 'msp-viewport-controls-panel-controls', children: _jsx(ParameterControls, { params: RootParams, values: rootValue, onChangeValues: this.updateRoot }) }) }), (!state.isCollapsed) && _jsxs(_Fragment, { children: [groups.map(c => { return _jsx(GroupNode, { filter: this.props.filter, cell: c, depth: depth + 1 }, c.transform.ref); }), this.filteredEntities.map(c => { return _jsx(EntityNode, { cell: c, depth: depth + 1 }, c.transform.ref); })] })] }); } } export class EntityNode extends Node { constructor() { super(...arguments); this.state = { action: undefined, isDisabled: false, }; this.clipMapping = createClipMapping(this); this.toggleVisible = (e) => { e.preventDefault(); 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 : 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: EveryLoci }, 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: EveryLoci }, 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: EveryLoci }, 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)) return; const loci = 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)) return; const loci = 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 = 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.normalize(aloci, 'chainInstances'); const nChain = aloci.structure.state.unitSymmetryGroups.length; let index = 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 = StructureElement.Loci(aloci.structure, elems); const sphere = Loci.getBoundingSphere(loci) || Sphere3D(); const state = this.plugin.state.behaviors; const selections = state.select(StateSelection.Generators.ofTransformer(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 }); } MesoscaleState.set(this.plugin, { index: index, focusInfo: `${d}` }); } } else { this.center(e); 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') { old.colorTheme.params.style.params.lightness = values.lightness; } else { old.colorTheme.params.lightness = values.lightness; } } else if (old.coloring) { old.coloring.params.lightness = values.lightness; } }).commit(); }; this.updateOpacity = (values) => { return this.plugin.build().to(this.ref).update(old => { if (old.type) { old.type.params.alpha = values.alpha; old.type.params.xrayShaded = values.alpha < 1 ? 'inverted' : false; } else { old.alpha = values.alpha; old.xrayShaded = values.alpha < 1 ? true : false; } }).commit(); }; this.updateEmissive = (values) => { return this.plugin.build().to(this.ref).update(old => { if (old.type) { old.type.params.emissive = values.emissive; } else { old.emissive = values.emissive; } }).commit(); }; this.updateClip = (props) => { const params = this.cell.transform.params; const clip = params.type ? params.type.params.clip : params.clip; if (!PD.areEqual(Clip.Params, clip, props)) { this.plugin.build().to(this.ref).update(old => { if (old.type) { old.type.params.clip = props; } else { old.clip = props; } }).commit(); } }; this.updateLod = (values) => { const params = this.cell.transform.params; if (!params.type) return; MesoscaleState.set(this.plugin, { graphics: 'custom' }); this.plugin.customState.graphicsMode = 'custom'; if (!deepEqual(params.type.params.lodLevels, values.lodLevels) || params.type.params.cellSize !== values.cellSize || params.type.params.batchSize !== values.batchSize || params.type.params.approximate !== values.approximate) { this.plugin.build().to(this.ref).update(old => { old.type.params.lodLevels = values.lodLevels; old.type.params.cellSize = values.cellSize; old.type.params.batchSize = values.batchSize; old.type.params.approximate = values.approximate; }).commit(); } }; this.updatePattern = (values) => { return this.plugin.build().to(this.ref).update(old => { if (!old.type) { old.bumpAmplitude = values.amplitude; old.bumpFrequency = values.frequency / 10; } }).c