pdbe-molstar
Version:
Molstar implementation for PDBe
226 lines (225 loc) • 12.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SymmetryAnnotationControls = void 0;
exports.isAssemblySymmetryAnnotationApplicable = isAssemblySymmetryAnnotationApplicable;
const tslib_1 = require("tslib");
const jsx_runtime_1 = require("react/jsx-runtime");
const behavior_1 = require("molstar/lib/extensions/assembly-symmetry/behavior");
const prop_1 = require("molstar/lib/extensions/assembly-symmetry/prop");
const base_1 = require("molstar/lib/mol-plugin-ui/base");
const mol_state_1 = require("molstar/lib/mol-state");
const mol_task_1 = require("molstar/lib/mol-task");
const param_definition_1 = require("molstar/lib/mol-util/param-definition");
const annotation_row_controls_1 = require("./annotation-row-controls");
const DefaultSymmetryParams = {
/** State of the visibility button */
on: param_definition_1.ParamDefinition.Boolean(false, { isHidden: true }),
/** Index of the currently selected symmetry (in case there a more symmetries for an assembly), regardless of whether visibility is on of off */
symmetryIndex: param_definition_1.ParamDefinition.Select(0, [[0, 'Auto']]),
/** `true` if visibility is on and symmetry data have been retrieved and contain at least one non-trivial symmetry. */
hasSymmetries: param_definition_1.ParamDefinition.Boolean(false, { isHidden: true }),
};
const DefaultSymmetryControlsState = {
params: DefaultSymmetryParams,
values: param_definition_1.ParamDefinition.getDefaultValues(DefaultSymmetryParams),
};
/** UI controls for showing Assembly Symmetry annotations (a row within Annotations section) */
class SymmetryAnnotationControls extends base_1.PurePluginUIComponent {
constructor() {
super(...arguments);
this.state = DefaultSymmetryControlsState;
this.currentStructureId = undefined;
}
componentDidMount() {
// Reset state when the pivot structure changes
this.subscribe(this.plugin.managers.structure.hierarchy.behaviors.selection, c => {
var _a;
const structureObj = (_a = c.structures[0]) === null || _a === void 0 ? void 0 : _a.cell.obj;
const structureId = structureObj === null || structureObj === void 0 ? void 0 : structureObj.id;
if (structureId !== this.currentStructureId) {
this.currentStructureId = structureId;
this.syncParams();
}
});
// Synchronize params when AssemblySymmetry3D changes
this.subscribe(this.plugin.state.events.cell.stateUpdated, e => {
if (e.cell.transform.transformer === behavior_1.AssemblySymmetry3D) {
this.syncParams();
}
});
// Synchronize params when AssemblySymmetry3D is removed
this.subscribe(this.plugin.state.events.cell.removed, e => {
this.syncParams();
});
}
/** Synchronize parameters and values in UI with real parameters currently applied in `AssemblySymmetryProvider` */
syncParams() {
if (this.hasAssemblySymmetry3D()) {
const hasSymmetries = this.hasSymmetriesAvailable();
const realParams = this.getRealParams();
const realValues = this.getRealValues();
const options = hasSymmetries ?
realParams.symmetryIndex.options.filter(([index, label]) => index >= 0) // Removing the 'off' option (having index -1)
: [[0, 'None']];
const params = Object.assign(Object.assign({}, this.state.params), { symmetryIndex: param_definition_1.ParamDefinition.Select(0, options) });
const values = (realValues.symmetryIndex >= 0) ? {
on: true,
symmetryIndex: realValues.symmetryIndex,
hasSymmetries,
} : {
on: false,
symmetryIndex: this.state.values.symmetryIndex,
hasSymmetries,
};
this.setState({ params, values });
}
else {
this.setState({
params: DefaultSymmetryControlsState.params,
values: DefaultSymmetryControlsState.values,
});
}
}
/** Return `true` if symmetry data have been retrieved and contain at least one non-trivial symmetry. */
hasSymmetriesAvailable() {
var _a, _b;
const structure = (_b = (_a = this.getPivotStructure()) === null || _a === void 0 ? void 0 : _a.cell.obj) === null || _b === void 0 ? void 0 : _b.data;
const symmetryData = structure && prop_1.AssemblySymmetryDataProvider.get(structure).value;
return symmetryData !== undefined && symmetryData.filter(sym => sym.symbol !== 'C1').length > 0;
}
/** Get the first loaded structure, if any. */
getPivotStructure() {
return getPivotStructure(this.plugin);
}
/** Get parameters currently applied in `AssemblySymmetryProvider` */
getRealParams() {
var _a, _b;
const structure = (_b = (_a = this.getPivotStructure()) === null || _a === void 0 ? void 0 : _a.cell.obj) === null || _b === void 0 ? void 0 : _b.data;
let params;
if (structure) {
params = prop_1.AssemblySymmetryProvider.getParams(structure);
}
else {
params = prop_1.AssemblySymmetryProvider.defaultParams;
}
return params;
}
/** Get parameter values currently applied in `AssemblySymmetryProvider` */
getRealValues() {
var _a, _b;
const structure = (_b = (_a = this.getPivotStructure()) === null || _a === void 0 ? void 0 : _a.cell.obj) === null || _b === void 0 ? void 0 : _b.data;
if (structure) {
return prop_1.AssemblySymmetryProvider.props(structure);
}
else {
return Object.assign(Object.assign({}, param_definition_1.ParamDefinition.getDefaultValues(prop_1.AssemblySymmetryProvider.defaultParams)), { symmetryIndex: -1 });
}
}
/** Return `true` if an `AssemblySymmetry3D` node existing in the */
hasAssemblySymmetry3D() {
const struct = this.getPivotStructure();
const state = struct === null || struct === void 0 ? void 0 : struct.cell.parent;
return state !== undefined && !!mol_state_1.StateSelection.findTagInSubtree(state.tree, struct.cell.transform.ref, prop_1.AssemblySymmetryData.Tag.Representation);
}
/** Run changes needed to set visibility on or off, and set UI accordingly */
apply(applied) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
if (applied) {
if (this.hasAssemblySymmetry3D()) {
yield this.changeParamValues(Object.assign(Object.assign({}, this.state.values), { on: true }));
}
else {
yield this.initSymmetry();
}
this.setState(old => ({ values: Object.assign(Object.assign({}, old.values), { on: true }) }));
}
else {
if (this.hasAssemblySymmetry3D()) {
yield this.changeParamValues(Object.assign(Object.assign({}, this.state.values), { on: false }));
}
else {
// no action needed
}
this.setState(old => ({ values: Object.assign(Object.assign({}, old.values), { on: false }) }));
}
});
}
/** Run changes needed to change parameter values, and set UI accordingly */
changeParamValues(values) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
var _a;
const struct = this.getPivotStructure();
if (!struct)
return;
const currValues = this.getRealValues();
const newValues = Object.assign(Object.assign({}, currValues), { symmetryIndex: values.on ? values.symmetryIndex : -1 });
if (struct.properties) {
const b = this.plugin.state.data.build();
b.to(struct.properties.cell).update(old => {
old.properties[prop_1.AssemblySymmetryProvider.descriptor.name] = newValues;
});
yield b.commit();
}
else {
const pd = this.plugin.customStructureProperties.getParams((_a = struct.cell.obj) === null || _a === void 0 ? void 0 : _a.data);
const params = param_definition_1.ParamDefinition.getDefaultValues(pd);
params.properties[prop_1.AssemblySymmetryProvider.descriptor.name] = newValues;
yield this.plugin.builders.structure.insertStructureProperties(struct.cell, params);
}
if (!values.on) { // if values.on, parameters will be updated via subscription
this.setState({ values });
}
});
}
/** Try to retrieve symmetry data and create `AssemblySymmetry3D` representation */
initSymmetry(initialSymmetryIndex) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
yield mol_task_1.Task.create('Initialize Assembly Symmetry', (ctx) => tslib_1.__awaiter(this, void 0, void 0, function* () {
var _a;
const struct = this.getPivotStructure();
const data = (_a = struct === null || struct === void 0 ? void 0 : struct.cell.obj) === null || _a === void 0 ? void 0 : _a.data;
if (!data)
return;
try {
const propCtx = { runtime: ctx, assetManager: this.plugin.managers.asset };
const config = (0, behavior_1.getAssemblySymmetryConfig)(this.plugin);
const symmetryDataProps = {
serverType: config.DefaultServerType,
serverUrl: config.DefaultServerUrl,
};
yield prop_1.AssemblySymmetryDataProvider.attach(propCtx, data, symmetryDataProps);
const assemblySymmetryData = prop_1.AssemblySymmetryDataProvider.get(data).value;
const symmetryIndex = initialSymmetryIndex !== null && initialSymmetryIndex !== void 0 ? initialSymmetryIndex : (assemblySymmetryData ? prop_1.AssemblySymmetryData.firstNonC1(assemblySymmetryData) : -1);
const symmetryProps = Object.assign(Object.assign({}, symmetryDataProps), { symmetryIndex });
yield prop_1.AssemblySymmetryProvider.attach(propCtx, data, symmetryProps);
}
catch (e) {
this.plugin.log.error(`Assembly Symmetry: ${e}`);
return;
}
yield (0, behavior_1.tryCreateAssemblySymmetry)(this.plugin, struct.cell);
})).run();
});
}
render() {
const notAvailable = this.state.values.on && !this.state.values.hasSymmetries;
const shortTitle = 'Assembly Symmetry' + (notAvailable ? ' [Not Available]' : '');
const title = 'Assembly Symmetry' + (notAvailable ? ' [Not Available]\nSymmetry information for this assembly is not available' : '');
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsx)(annotation_row_controls_1.AnnotationRowControls, { shortTitle: shortTitle, title: title, applied: this.state.values.on, onChangeApplied: applied => this.apply(applied), params: this.state.params, values: this.state.values, onChangeValues: v => this.changeParamValues(v), errorMessage: !this.state.values.on ? 'First activate annotation to show options' : notAvailable ? 'Symmetry information not available' : undefined }) });
}
}
exports.SymmetryAnnotationControls = SymmetryAnnotationControls;
/** Get the first loaded structure, if any. */
function getPivotStructure(plugin) {
return plugin.managers.structure.hierarchy.selection.structures[0];
}
function isAssemblySymmetryAnnotationApplicable(plugin) {
var _a, _b, _c;
const struct = getPivotStructure(plugin);
const isAssembly = ((_b = (_a = struct === null || struct === void 0 ? void 0 : struct.cell.obj) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.units[0].conformation.operator.assembly) !== undefined;
return isAssembly && prop_1.AssemblySymmetryData.isApplicable((_c = struct === null || struct === void 0 ? void 0 : struct.cell.obj) === null || _c === void 0 ? void 0 : _c.data);
// It would be nice to disable the default `AssemblySymmetry.isApplicable` behavior
// (i.e. hiding Assembly Symmetry controls for non-biological assemblies, e.g. 1smv assembly 3)
// by `AssemblySymmetry.isApplicable = struct => struct?.units[0].conformation.operator.assembly !== undefined;`
// But we cannot easily override the `fetch` function which calls the original `isApplicable`.
}