molstar
Version:
A comprehensive macromolecular library.
219 lines (218 loc) • 10.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.StructureFocusControls = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
/**
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
const int_1 = require("../../mol-data/int");
const structure_1 = require("../../mol-model/structure");
const representation_1 = require("../../mol-plugin/behavior/dynamic/representation");
const label_1 = require("../../mol-theme/label");
const binding_1 = require("../../mol-util/binding");
const memoize_1 = require("../../mol-util/memoize");
const base_1 = require("../base");
const action_menu_1 = require("../controls/action-menu");
const common_1 = require("../controls/common");
const icons_1 = require("../controls/icons");
function addSymmetryGroupEntries(entries, location, unitSymmetryGroup, granularity) {
const idx = int_1.SortedArray.indexOf(location.unit.elements, location.element);
const base = structure_1.StructureElement.Loci(location.structure, [
{ unit: location.unit, indices: int_1.OrderedSet.ofSingleton(idx) }
]);
const extended = granularity === 'residue'
? structure_1.StructureElement.Loci.extendToWholeResidues(base)
: structure_1.StructureElement.Loci.extendToWholeChains(base);
const name = structure_1.StructureProperties.entity.pdbx_description(location).join(', ');
for (const u of unitSymmetryGroup.units) {
const loci = structure_1.StructureElement.Loci(extended.structure, [
{ unit: u, indices: extended.elements[0].indices }
]);
let label = (0, label_1.lociLabel)(loci, { reverse: true, hidePrefix: true, htmlStyling: false, granularity });
if (!label)
label = (0, label_1.lociLabel)(loci, { hidePrefix: false, htmlStyling: false });
if (unitSymmetryGroup.units.length > 1) {
label += ` | ${loci.elements[0].unit.conformation.operator.name}`;
}
const item = { label, category: name, loci };
if (entries.has(name))
entries.get(name).push(item);
else
entries.set(name, [item]);
}
}
function getFocusEntries(structure) {
const entityEntries = new Map();
const l = structure_1.StructureElement.Location.create(structure);
for (const ug of structure.unitSymmetryGroups) {
if (!structure_1.Unit.isAtomic(ug.units[0]))
continue;
l.unit = ug.units[0];
l.element = ug.elements[0];
const isMultiChain = structure_1.Unit.Traits.is(l.unit.traits, structure_1.Unit.Trait.MultiChain);
const entityType = structure_1.StructureProperties.entity.type(l);
const isNonPolymer = entityType === 'non-polymer';
const isBranched = entityType === 'branched';
const isBirdMolecule = !!structure_1.StructureProperties.entity.prd_id(l);
if (isBirdMolecule) {
addSymmetryGroupEntries(entityEntries, l, ug, 'chain');
}
else if (isNonPolymer && !isMultiChain) {
addSymmetryGroupEntries(entityEntries, l, ug, 'residue');
}
else if (isBranched || (isNonPolymer && isMultiChain)) {
const u = l.unit;
const { index: residueIndex } = u.model.atomicHierarchy.residueAtomSegments;
let prev = -1;
for (let i = 0, il = u.elements.length; i < il; ++i) {
const eI = u.elements[i];
const rI = residueIndex[eI];
if (rI !== prev) {
l.element = eI;
addSymmetryGroupEntries(entityEntries, l, ug, 'residue');
prev = rI;
}
}
}
}
const entries = [];
entityEntries.forEach((e, name) => {
if (e.length === 1) {
entries.push({ label: `${name}: ${e[0].label}`, loci: e[0].loci });
}
else if (e.length < 2000) {
entries.push(...e);
}
});
return entries;
}
class StructureFocusControls extends base_1.PluginUIComponent {
constructor() {
super(...arguments);
this.state = { isBusy: false, showAction: false };
this.getSelectionItems = (0, memoize_1.memoizeLatest)((structures) => {
var _a;
const presetItems = [];
for (const s of structures) {
const d = (_a = s.cell.obj) === null || _a === void 0 ? void 0 : _a.data;
if (d) {
const entries = getFocusEntries(d);
if (entries.length > 0) {
presetItems.push([
action_menu_1.ActionMenu.Header(d.label, { description: d.label }),
...action_menu_1.ActionMenu.createItems(entries, {
label: f => f.label,
category: f => f.category,
description: f => f.label
})
]);
}
}
}
return presetItems;
});
this.selectAction = (item, e) => {
if (!item || !this.state.showAction) {
this.setState({ showAction: false });
return;
}
const f = item.value;
if (e === null || e === void 0 ? void 0 : e.shiftKey) {
this.plugin.managers.structure.focus.addFromLoci(f.loci);
}
else {
this.plugin.managers.structure.focus.set(f);
}
this.focusCamera();
};
this.toggleAction = () => this.setState({ showAction: !this.state.showAction });
this.focusCamera = () => {
const { current } = this.plugin.managers.structure.focus;
if (current)
this.plugin.managers.camera.focusLoci(current.loci);
};
this.clear = () => {
this.plugin.managers.structure.focus.clear();
this.plugin.managers.camera.reset();
};
this.highlightCurrent = () => {
const { current } = this.plugin.managers.structure.focus;
if (current)
this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci: current.loci }, false);
};
this.clearHighlights = () => {
this.plugin.managers.interactivity.lociHighlights.clearHighlights();
};
}
componentDidMount() {
this.subscribe(this.plugin.managers.structure.focus.behaviors.current, c => {
// clear the memo cache
this.getSelectionItems([]);
this.forceUpdate();
});
this.subscribe(this.plugin.managers.structure.focus.events.historyUpdated, c => {
this.forceUpdate();
});
this.subscribe(this.plugin.behaviors.state.isBusy, v => {
this.setState({ isBusy: v, showAction: false });
});
}
get isDisabled() {
return this.state.isBusy || this.plugin.managers.structure.hierarchy.selection.structures.length === 0;
}
get actionItems() {
const historyItems = [];
const { history } = this.plugin.managers.structure.focus;
if (history.length > 0) {
historyItems.push([
action_menu_1.ActionMenu.Header('History', { description: 'Previously focused on items.' }),
...action_menu_1.ActionMenu.createItems(history, {
label: f => f.label,
description: f => {
return f.category && f.label !== f.category
? `${f.category} | ${f.label}`
: f.label;
}
})
]);
}
const presetItems = this.getSelectionItems(this.plugin.managers.structure.hierarchy.selection.structures);
if (presetItems.length === 1) {
const item = presetItems[0];
const header = item[0];
header.initiallyExpanded = true;
}
const items = [];
if (presetItems.length > 0)
items.push(...presetItems);
if (historyItems.length > 0)
items.push(...historyItems);
return items;
}
getToggleBindingLabel() {
var _a;
const t = this.plugin.state.behaviors.transforms.get(representation_1.FocusLoci.id);
if (!t)
return '';
const binding = (_a = t.params) === null || _a === void 0 ? void 0 : _a.bindings.clickFocus;
if (!binding || binding_1.Binding.isEmpty(binding))
return '';
return binding_1.Binding.formatTriggers(binding);
}
render() {
const { current } = this.plugin.managers.structure.focus;
const label = (current === null || current === void 0 ? void 0 : current.label) || 'Nothing Focused';
let title = 'Click to Center Camera';
if (!current) {
title = 'Select focus using the menu';
const binding = this.getToggleBindingLabel();
if (binding) {
title += `\nor use '${binding}' on element`;
}
}
return (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: 'msp-flex-row', children: [(0, jsx_runtime_1.jsx)(common_1.Button, { noOverflow: true, onClick: this.focusCamera, title: title, onMouseEnter: this.highlightCurrent, onMouseLeave: this.clearHighlights, disabled: this.isDisabled || !current, style: { textAlignLast: current ? 'left' : void 0 }, children: label }), current && (0, jsx_runtime_1.jsx)(common_1.IconButton, { svg: icons_1.CancelOutlinedSvg, onClick: this.clear, title: 'Clear', className: 'msp-form-control', flex: true, disabled: this.isDisabled }), (0, jsx_runtime_1.jsx)(common_1.ToggleButton, { icon: icons_1.CenterFocusStrongSvg, title: 'Select a focus target to center on an show its surroundings. Hold shift to focus on multiple targets.', toggle: this.toggleAction, isSelected: this.state.showAction, disabled: this.isDisabled, style: { flex: '0 0 40px', padding: 0 } })] }), this.state.showAction && (0, jsx_runtime_1.jsx)(action_menu_1.ActionMenu, { items: this.actionItems, onSelect: this.selectAction })] });
}
}
exports.StructureFocusControls = StructureFocusControls;