pdbe-molstar
Version:
Molstar implementation for PDBe
740 lines • 64.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PDBeMolstarPlugin = void 0;
const tslib_1 = require("tslib");
const behavior_1 = require("molstar/lib/extensions/assembly-symmetry/behavior");
const geo_export_1 = require("molstar/lib/extensions/geo-export");
const behavior_2 = require("molstar/lib/extensions/model-archive/quality-assessment/behavior");
const mp4_export_1 = require("molstar/lib/extensions/mp4-export");
const behavior_3 = require("molstar/lib/extensions/mvs/behavior");
const custom_tooltips_prop_1 = require("molstar/lib/extensions/mvs/components/custom-tooltips-prop");
const load_1 = require("molstar/lib/extensions/mvs/load");
const mvs_data_1 = require("molstar/lib/extensions/mvs/mvs-data");
const pdbe_1 = require("molstar/lib/extensions/pdbe");
const linear_algebra_1 = require("molstar/lib/mol-math/linear-algebra");
const loci_1 = require("molstar/lib/mol-model/loci");
const structure_1 = require("molstar/lib/mol-model/structure");
const assembly_unwind_1 = require("molstar/lib/mol-plugin-state/animation/built-in/assembly-unwind");
const camera_rock_1 = require("molstar/lib/mol-plugin-state/animation/built-in/camera-rock");
const camera_spin_1 = require("molstar/lib/mol-plugin-state/animation/built-in/camera-spin");
const model_index_1 = require("molstar/lib/mol-plugin-state/animation/built-in/model-index");
const spin_structure_1 = require("molstar/lib/mol-plugin-state/animation/built-in/spin-structure");
const state_interpolation_1 = require("molstar/lib/mol-plugin-state/animation/built-in/state-interpolation");
const state_snapshots_1 = require("molstar/lib/mol-plugin-state/animation/built-in/state-snapshots");
const structure_overpaint_1 = require("molstar/lib/mol-plugin-state/helpers/structure-overpaint");
const structure_representation_params_1 = require("molstar/lib/mol-plugin-state/helpers/structure-representation-params");
const objects_1 = require("molstar/lib/mol-plugin-state/objects");
const transforms_1 = require("molstar/lib/mol-plugin-state/transforms");
const model_1 = require("molstar/lib/mol-plugin-state/transforms/model");
const representation_1 = require("molstar/lib/mol-plugin-state/transforms/representation");
const index_1 = require("molstar/lib/mol-plugin-ui/index");
const react18_1 = require("molstar/lib/mol-plugin-ui/react18");
const camera_1 = require("molstar/lib/mol-plugin/behavior/dynamic/camera");
const representation_2 = require("molstar/lib/mol-plugin/behavior/dynamic/representation");
const structure_focus_representation_1 = require("molstar/lib/mol-plugin/behavior/dynamic/selection/structure-focus-representation");
const transformers_1 = require("molstar/lib/mol-plugin/behavior/dynamic/volume-streaming/transformers");
const commands_1 = require("molstar/lib/mol-plugin/commands");
const config_1 = require("molstar/lib/mol-plugin/config");
const spec_1 = require("molstar/lib/mol-plugin/spec");
const mol_state_1 = require("molstar/lib/mol-state");
const element_symbol_1 = require("molstar/lib/mol-theme/color/element-symbol");
const assets_1 = require("molstar/lib/mol-util/assets");
const color_1 = require("molstar/lib/mol-util/color/color");
const rx_event_helper_1 = require("molstar/lib/mol-util/rx-event-helper");
const custom_events_1 = require("./custom-events");
const behavior_4 = require("./domain-annotations/behavior");
const Complexes = tslib_1.__importStar(require("./extensions/complexes"));
const Foldseek = tslib_1.__importStar(require("./extensions/foldseek"));
const Interactions = tslib_1.__importStar(require("./extensions/interactions"));
const behavior_5 = require("./extensions/state-gallery/behavior");
const manager_1 = require("./extensions/state-gallery/manager");
const ui_1 = require("./extensions/state-gallery/ui");
const helpers_1 = require("./helpers");
const plugin_custom_state_1 = require("./plugin-custom-state");
const spec_2 = require("./spec");
const spec_from_html_1 = require("./spec-from-html");
const subscribe_events_1 = require("./subscribe-events");
const superposition_1 = require("./superposition");
const superposition_focus_representation_1 = require("./superposition-focus-representation");
const pdbe_left_panel_1 = require("./ui/left-panel/pdbe-left-panel");
const pdbe_structure_controls_1 = require("./ui/pdbe-structure-controls");
const pdbe_viewport_1 = require("./ui/pdbe-viewport");
const pdbe_viewport_controls_1 = require("./ui/pdbe-viewport-controls");
const components_1 = require("./ui/split-ui/components");
const split_ui_1 = require("./ui/split-ui/split-ui");
class PDBeMolstarPlugin {
constructor() {
/** Helper for creating events (e.g. RxJS Subjects) */
this._ev = rx_event_helper_1.RxEventHelper.create();
this.events = {
loadComplete: this._ev(),
};
this.assemblyRef = '';
this.isHighlightColorUpdated = false;
this.isSelectedColorUpdated = false;
/** Keeps track of representations added by `.visual.select` for each structure. */
this.addedReprs = {};
/** Maps structure IDs (assigned when loading) to cell refs. */
this.structureRefMap = new Map();
/** Helper methods related to canvas and layout */
this.canvas = {
/** Set canvas background color. */
setBgColor: (color) => tslib_1.__awaiter(this, void 0, void 0, function* () {
if (color === undefined || color === null)
return;
yield this.canvas.applySettings({ color });
}),
/** Set controls panel visibility. Without `isVisible` parameter, toggle controls panel visibility. */
toggleControls: (isVisible) => {
if (typeof isVisible === 'undefined')
isVisible = !this.plugin.layout.state.showControls;
commands_1.PluginCommands.Layout.Update(this.plugin, { state: { showControls: isVisible } });
},
/** Set full-screen mode on or off. Without `isExpanded` parameter, toggle full-screen mode. */
toggleExpanded: (isExpanded) => {
if (typeof isExpanded === 'undefined')
isExpanded = !this.plugin.layout.state.isExpanded;
commands_1.PluginCommands.Layout.Update(this.plugin, { state: { isExpanded: isExpanded } });
},
applySettings: (settings) => tslib_1.__awaiter(this, void 0, void 0, function* () {
if (!settings)
return;
if (!this.plugin.canvas3d)
return;
const renderer = Object.assign({}, this.plugin.canvas3d.props.renderer);
if (settings.color) {
renderer.backgroundColor = (0, helpers_1.normalizeColor)(settings.color);
}
if (settings.lighting) {
renderer.style = { name: settings.lighting }; // I don't think this does anything and I don't see how it could ever have worked
}
yield commands_1.PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { renderer } });
}),
};
/** Helper methods related to 3D visuals */
this.visual = {
/** Change the visibility of individual entity visuals */
visibility: (data) => tslib_1.__awaiter(this, void 0, void 0, function* () {
var _a;
if (!data)
return;
for (const visual in data) {
const requiredVisibility = data[visual];
if (requiredVisibility === undefined)
continue;
const tags = (_a = helpers_1.StructureComponentTags[visual]) !== null && _a !== void 0 ? _a : [];
for (const tag of tags) {
const componentRef = mol_state_1.StateSelection.findTagInSubtree(this.plugin.state.data.tree, mol_state_1.StateTransform.RootRef, tag);
if (componentRef) {
const compVisual = this.plugin.state.data.select(componentRef)[0];
if (compVisual && compVisual.obj) {
const currentlyVisible = !(compVisual.state && compVisual.state.isHidden);
if (currentlyVisible !== requiredVisibility) {
yield commands_1.PluginCommands.State.ToggleVisibility(this.plugin, { state: this.state, ref: componentRef });
}
}
}
}
}
}),
/** Change the visibility of a structure.
* `structureNumberOrId` is either index (numbered from 1!) or the ID that was provided when loading the structure.
* If `visibility` is undefined, toggle current visibility state. */
structureVisibility: (structureNumberOrId, visibility) => tslib_1.__awaiter(this, void 0, void 0, function* () {
const struct = this.getStructure(structureNumberOrId);
if (!struct) {
console.error(`Cannot change visibility of structure ${structureNumberOrId}: structure not found.`);
return;
}
const currentVisibility = !struct.cell.state.isHidden;
if (visibility !== currentVisibility) {
yield commands_1.PluginCommands.State.ToggleVisibility(this.plugin, { state: this.state, ref: struct.cell.transform.ref });
}
}),
/** With `isSpinning` parameter, switch visual rotation on or off. Without `isSpinning` parameter, toggle rotation. If `resetCamera`, also reset the camera zoom. */
toggleSpin: (isSpinning, resetCamera) => tslib_1.__awaiter(this, void 0, void 0, function* () {
if (!this.plugin.canvas3d)
return;
const trackball = this.plugin.canvas3d.props.trackball;
let toggleSpinParam = trackball.animate.name === 'spin' ? { name: 'off', params: {} } : { name: 'spin', params: { speed: 1 } };
if (typeof isSpinning !== 'undefined') {
toggleSpinParam = { name: 'off', params: {} };
if (isSpinning)
toggleSpinParam = { name: 'spin', params: { speed: 1 } };
}
yield commands_1.PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { trackball: Object.assign(Object.assign({}, trackball), { animate: toggleSpinParam }) } });
if (resetCamera)
yield commands_1.PluginCommands.Camera.Reset(this.plugin, {});
}),
/** Focus (zoom) on the part of the structure defined by `selection`.
* If `selection` contains more items, focus on the union of those.
* `structureNumberOrId` is either index (numbered from 1!) or the ID that was provided when loading the structure;
* if not provided, will use the last added structure. */
focus: (selection, structureNumberOrId) => tslib_1.__awaiter(this, void 0, void 0, function* () {
const loci = this.getLociForParams(selection, structureNumberOrId);
this.plugin.managers.camera.focusLoci(loci);
}),
/** Apply interactive focus behavior on the part of the structure defined by `data` (i.e. emulate user clicking on a substructure).
* This includes behaviors like "show non-covalent interactions" or "show streamed volume data"
* but not camera adjustment (call `.visual.focus` for that).
* If `data` contains more items, focus on the union of those.
* If `structureId` or `structureNumber` is provided, use the specified structure (numbered from 1!);
* otherwise use the last added structure. */
interactivityFocus: (params) => tslib_1.__awaiter(this, void 0, void 0, function* () {
var _a;
const structureNumberOrId = (_a = params.structureId) !== null && _a !== void 0 ? _a : params.structureNumber;
const loci = this.getLociForParams(params.data, structureNumberOrId);
this.plugin.managers.structure.focus.setFromLoci(loci);
}),
/** Trigger highlight on the part of the structure defined by `data`
* (this will look the same as when the user hovers over a part of the structure).
* If `focus`, also zoom on the highlighted part.
* If `structureId` or `structureNumber` is provided, use the specified structure (numbered from 1!); otherwise use the last added structure. */
highlight: (params) => tslib_1.__awaiter(this, void 0, void 0, function* () {
var _a;
const loci = this.getLociForParams(params.data, (_a = params.structureId) !== null && _a !== void 0 ? _a : params.structureNumber);
if (loci_1.Loci.isEmpty(loci))
return;
if (params.color) {
yield this.visual.setColor({ highlight: params.color });
}
this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci });
if (params.focus)
this.plugin.managers.camera.focusLoci(loci);
}),
/** Remove any current highlight and reset the highlight color to its default value. */
clearHighlight: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci: loci_1.EmptyLoci });
if (this.isHighlightColorUpdated) {
yield this.visual.reset({ highlightColor: true });
}
}),
/** Color the parts of the structure defined by `data`. Color the rest of the structure in `nonSelectedColor` if provided.
* If any items in `data` contain `focus`, zoom to the union of these items.
* If any items in `data` contain `sideChain` or `representation`, add extra representations to them (colored in `representationColor` if provided).
* If `structureNumber` is provided, apply to the specified structure (numbered from 1!); otherwise apply to all loaded structures.
* Remove any previously added coloring and extra representations, unless `keepColors` and/or `keepRepresentations` is set. */
select: (params) => tslib_1.__awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d;
const structureNumberOrId = (_a = params.structureId) !== null && _a !== void 0 ? _a : params.structureNumber;
yield this.visual.clearSelection(structureNumberOrId, { keepColors: params.keepColors, keepRepresentations: params.keepRepresentations });
// Structure list to apply selection
const structures = this.getStructures(structureNumberOrId);
// Filter selection items that apply added representations
const addedReprParams = {};
for (const param of params.data) {
const repr = (_b = param.representation) !== null && _b !== void 0 ? _b : (param.sideChain ? 'ball-and-stick' : undefined);
if (repr) {
((_c = addedReprParams[repr]) !== null && _c !== void 0 ? _c : (addedReprParams[repr] = [])).push(param);
}
}
const DefaultSelectColor = color_1.Color.fromRgb(255, 112, 3);
const focusLoci = [];
for (const struct of structures) {
const selections = this.getSelections(params.data, struct.number);
for (const selection of selections) {
if (selection.param.focus) {
focusLoci.push(selection.loci);
}
}
// Apply color to the main representation (color:undefined (or omitted) means apply default color, color:null means do not apply color)
const overpaintLayers = selections
.filter(s => s.param.color !== null)
.map(s => ({
bundle: s.bundle,
color: s.param.color ? (0, helpers_1.normalizeColor)(s.param.color) : DefaultSelectColor,
clear: false,
}));
if (params.nonSelectedColor) {
const wholeStructBundle = this.getBundle([{}], struct.number);
if (wholeStructBundle) {
overpaintLayers.unshift({
bundle: wholeStructBundle,
color: (0, helpers_1.normalizeColor)(params.nonSelectedColor),
clear: false,
});
}
}
yield (0, helpers_1.applyOverpaint)(this.plugin, struct.structureRef, overpaintLayers);
// Add extra representations
for (const repr in addedReprParams) {
const bundle = this.getBundle(addedReprParams[repr], struct.number);
if (!bundle)
continue;
const overpaintLayers = selections.filter(s => s.param.representationColor).map(s => ({
bundle: s.bundle,
color: (0, helpers_1.normalizeColor)(s.param.representationColor),
clear: false,
}));
yield this.plugin.build()
.to(struct.structureRef.cell)
.apply(model_1.StructureComponent, { type: { name: 'bundle', params: bundle }, label: repr }, { tags: helpers_1.Tags.AddedComponent })
.apply(representation_1.StructureRepresentation3D, (0, structure_representation_params_1.createStructureRepresentationParams)(this.plugin, (_d = struct.structureRef.cell.obj) === null || _d === void 0 ? void 0 : _d.data, { type: repr }))
.apply(transforms_1.StateTransforms.Representation.OverpaintStructureRepresentation3DFromBundle, { layers: overpaintLayers }, { tags: helpers_1.Tags.Overpaint })
.commit();
// Track that reprs have been added (for later clearSelection)
this.addedReprs[struct.number] = true;
}
}
// Apply focus
if (focusLoci.length > 0) {
this.plugin.managers.camera.focusLoci(focusLoci);
}
}),
/** Remove any coloring and extra representations previously added by the `select` method.
* `structureNumberOrId` is either index (numbered from 1!) or the ID that was provided when loading the structure;
* if not provided, will apply to all loaded structures.
* If `keepColors`, current residue coloring is preserved. If `keepRepresentations`, current added representations are preserved. */
clearSelection: (structureNumberOrId, options) => tslib_1.__awaiter(this, void 0, void 0, function* () {
// Structure list to apply to
const structures = this.getStructures(structureNumberOrId);
for (const struct of structures) {
// Remove overpaint
if (!(options === null || options === void 0 ? void 0 : options.keepColors)) {
const componentsToClear = struct.structureRef.components.filter(c => { var _a; return !((_a = c.cell.transform.tags) === null || _a === void 0 ? void 0 : _a.includes(helpers_1.Tags.AddedComponent)); });
yield (0, structure_overpaint_1.clearStructureOverpaint)(this.plugin, componentsToClear);
}
// Remove added reprs
if (!(options === null || options === void 0 ? void 0 : options.keepRepresentations)) {
if (this.addedReprs[struct.number]) {
const componentsToDelete = struct.structureRef.components.filter(comp => { var _a; return (_a = comp.cell.transform.tags) === null || _a === void 0 ? void 0 : _a.includes(helpers_1.Tags.AddedComponent); });
const update = this.plugin.build();
for (const comp of componentsToDelete) {
update.delete(comp.cell.transform.ref);
}
yield update.commit();
delete this.addedReprs[struct.number];
}
}
}
}),
/** Add interactive tooltips to parts of the structure. The added tooltips will be shown on a separate line in the tooltip box.
* Repeated call to this function removes any previously added tooltips.
* `structureNumber` counts from 1; if not provided, tooltips will be applied to all loaded structures.
* Example: `await this.visual.tooltips({ data: [{ struct_asym_id: 'A', tooltip: 'Chain A' }, { struct_asym_id: 'B', tooltip: 'Chain B' }] });`. */
tooltips: (params) => tslib_1.__awaiter(this, void 0, void 0, function* () {
var _a;
// Structure list to apply tooltips
const structures = this.getStructures((_a = params.structureId) !== null && _a !== void 0 ? _a : params.structureNumber);
for (const struct of structures) {
const selections = this.getSelections(params.data, struct.number);
const customTooltipProps = {
tooltips: selections.map(s => { var _a; return ({ text: (_a = s.param.tooltip) !== null && _a !== void 0 ? _a : '', selector: { name: 'bundle', params: s.bundle } }); }),
};
const structRef = struct.structureRef.cell.transform.ref;
let customPropsCells = this.plugin.state.data.select(mol_state_1.StateSelection.Generators.ofTransformer(model_1.CustomStructureProperties, structRef));
if (customPropsCells.length === 0) {
yield this.plugin.build().to(structRef).apply(model_1.CustomStructureProperties).commit();
customPropsCells = this.plugin.state.data.select(mol_state_1.StateSelection.Generators.ofTransformer(model_1.CustomStructureProperties, structRef));
}
yield this.plugin.build().to(customPropsCells[0]).update(old => ({
properties: Object.assign(Object.assign({}, old.properties), { [custom_tooltips_prop_1.CustomTooltipsProvider.descriptor.name]: customTooltipProps }),
autoAttach: old.autoAttach.includes(custom_tooltips_prop_1.CustomTooltipsProvider.descriptor.name) ?
old.autoAttach
: [...old.autoAttach, custom_tooltips_prop_1.CustomTooltipsProvider.descriptor.name],
})).commit();
}
}),
/** Remove any custom tooltips added by the `tooltips` method. */
clearTooltips: (structureNumberOrId) => tslib_1.__awaiter(this, void 0, void 0, function* () {
yield this.visual.tooltips({ data: [], structureId: structureNumberOrId });
}),
/** Set highlight and/or selection color.
* Highlight color is used when the user hovers over a part of the structure or when applying the `highlight` method.
* Selection color is used when creating selections with Selection Mode (the mouse cursor icon) and is not related to the color used by the `select` method. */
setColor: (params) => tslib_1.__awaiter(this, void 0, void 0, function* () {
if (!this.plugin.canvas3d)
return;
if (!params.highlight && !params.select)
return;
const renderer = Object.assign({}, this.plugin.canvas3d.props.renderer);
const marking = Object.assign({}, this.plugin.canvas3d.props.marking);
if (params.highlight) {
renderer.highlightColor = (0, helpers_1.normalizeColor)(params.highlight);
marking.highlightEdgeColor = color_1.Color.darken((0, helpers_1.normalizeColor)(params.highlight), 1);
this.isHighlightColorUpdated = true;
}
if (params.select) {
renderer.selectColor = (0, helpers_1.normalizeColor)(params.select);
marking.selectEdgeColor = color_1.Color.darken((0, helpers_1.normalizeColor)(params.select), 1);
this.isSelectedColorUpdated = true;
}
yield commands_1.PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { renderer, marking } });
}),
/** Reset various settings to defaults:
* `camera` resets camera position (i.e. zooms on the whole scene).
* `theme` resets color theme for visual representations.
* `highlightColor` and `selectColor` reset colors previously set by the `setColor` method. */
reset: (params) => tslib_1.__awaiter(this, void 0, void 0, function* () {
if (params.camera)
yield commands_1.PluginCommands.Camera.Reset(this.plugin, { durationMs: 250 });
if (params.theme) {
const defaultTheme = { color: this.defaultColorTheme() };
const componentGroups = this.plugin.managers.structure.hierarchy.currentComponentGroups;
for (const compGrp of componentGroups) {
yield this.plugin.managers.structure.component.updateRepresentationsTheme(compGrp, defaultTheme);
}
}
if (params.highlightColor || params.selectColor) {
if (!this.plugin.canvas3d)
return;
const renderer = Object.assign({}, this.plugin.canvas3d.props.renderer);
const marking = Object.assign({}, this.plugin.canvas3d.props.marking);
if (params.highlightColor) {
renderer.highlightColor = this.defaultRendererProps.highlightColor;
marking.highlightEdgeColor = this.defaultMarkingProps.highlightEdgeColor;
this.isHighlightColorUpdated = false;
}
if (params.selectColor) {
renderer.selectColor = this.defaultRendererProps.selectColor;
marking.selectEdgeColor = this.defaultMarkingProps.selectEdgeColor;
this.isSelectedColorUpdated = false;
}
yield commands_1.PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { renderer, marking } });
}
}),
setViewDirection: (view, options) => tslib_1.__awaiter(this, void 0, void 0, function* () {
var _a;
const visibleSphere = (_a = this.plugin.canvas3d) === null || _a === void 0 ? void 0 : _a.boundingSphereVisible;
if (!visibleSphere)
return;
const rot = (0, helpers_1.getRotationMat4)(view);
const snapshot = this.plugin.canvas3d.camera.getFocus(visibleSphere.center, visibleSphere.radius);
// Need to force direction and up after getFocus (getFocus sometimes flips direction/up)
const newDir = linear_algebra_1.Vec3.create(0, 0, -1);
linear_algebra_1.Vec3.transformMat4(newDir, newDir, rot);
linear_algebra_1.Vec3.scale(newDir, newDir, linear_algebra_1.Vec3.distance(snapshot.position, snapshot.target));
snapshot.position = linear_algebra_1.Vec3.sub(newDir, snapshot.target, newDir);
const newUp = linear_algebra_1.Vec3.create(0, 1, 0);
snapshot.up = linear_algebra_1.Vec3.transformMat4(newUp, newUp, rot);
yield commands_1.PluginCommands.Camera.SetSnapshot(this.plugin, { snapshot, durationMs: options === null || options === void 0 ? void 0 : options.durationMs });
}),
/** Change parameters of the plugin instance.
* Can be used to load a different structure.
* If `fullLoad`, remove currently loaded structure before loading the new one;
* otherwise add the new structure to existing structures. */
update: (options, fullLoad) => tslib_1.__awaiter(this, void 0, void 0, function* () {
console.debug('Updating PDBeMolstarPlugin instance with options:', options);
// Validate options
if (!options) {
console.error('Missing `options` argument to `PDBeMolstarPlugin.visual.update');
return false;
}
const validationIssues = (0, spec_2.validateInitParams)(options);
if (validationIssues) {
console.error('Invalid PDBeMolstarPlugin options:', options);
return false;
}
this.initParams = (0, helpers_1.addDefaults)(options, spec_2.DefaultParams);
if (!this.initParams.moleculeId && !this.initParams.customData)
return false;
if (this.initParams.customData && this.initParams.customData.url && !this.initParams.customData.format)
return false;
(0, plugin_custom_state_1.PluginCustomState)(this.plugin).initParams = this.initParams;
// Update layout
commands_1.PluginCommands.Layout.Update(this.plugin, { state: (0, helpers_1.pluginLayoutStateFromInitParams)(this.initParams) });
// Show/hide buttons in the viewport control panel
this.plugin.config.set(config_1.PluginConfig.Viewport.ShowScreenshotControls, !this.initParams.hideCanvasControls.includes('screenshot'));
this.plugin.config.set(config_1.PluginConfig.Viewport.ShowExpand, !this.initParams.hideCanvasControls.includes('expand'));
this.plugin.config.set(config_1.PluginConfig.Viewport.ShowControls, !this.initParams.hideCanvasControls.includes('controlToggle'));
this.plugin.config.set(config_1.PluginConfig.Viewport.ShowSettings, !this.initParams.hideCanvasControls.includes('controlInfo'));
this.plugin.config.set(config_1.PluginConfig.Viewport.ShowSelectionMode, !this.initParams.hideCanvasControls.includes('selection'));
this.plugin.config.set(config_1.PluginConfig.Viewport.ShowAnimation, !this.initParams.hideCanvasControls.includes('animation'));
this.plugin.config.set(config_1.PluginConfig.Viewport.ShowTrajectoryControls, !this.initParams.hideCanvasControls.includes('trajectory'));
// Set background colour
if (this.initParams.bgColor || this.initParams.lighting) {
yield this.canvas.applySettings({ color: this.initParams.bgColor, lighting: this.initParams.lighting });
}
// Load Molecule CIF or coordQuery and Parse
const dataSource = this.getMoleculeSrcUrl();
if (dataSource) {
yield this.load({ url: dataSource.url, format: dataSource.format, assemblyId: this.initParams.assemblyId, isBinary: dataSource.isBinary }, fullLoad);
}
return true;
}),
};
}
/** Extract InitParams from attributes of an HTML element */
static initParamsFromHtmlAttributes(element) {
return (0, spec_from_html_1.initParamsFromHtmlAttributes)(element);
}
render(target, options) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
var _a, _b;
console.debug('Rendering PDBeMolstarPlugin instance with options:', options);
// Validate options
if (!options) {
console.error('Missing `options` argument to `PDBeMolstarPlugin.render');
return;
}
const validationIssues = (0, spec_2.validateInitParams)(options);
if (validationIssues) {
console.error('Invalid PDBeMolstarPlugin options:', options);
return;
}
this.initParams = (0, helpers_1.addDefaults)(options, spec_2.DefaultParams);
// Set PDBe Plugin Spec
const pdbePluginSpec = (0, spec_2.DefaultPluginUISpec)();
(_a = pdbePluginSpec.config) !== null && _a !== void 0 ? _a : (pdbePluginSpec.config = []);
pdbePluginSpec.behaviors.push(spec_1.PluginSpec.Behavior(behavior_3.MolViewSpec));
pdbePluginSpec.behaviors.push(spec_1.PluginSpec.Behavior(pdbe_1.PDBeStructureQualityReport, { autoAttach: false, showTooltip: false }));
pdbePluginSpec.behaviors.push(spec_1.PluginSpec.Behavior(behavior_4.PDBeDomainAnnotations, { autoAttach: false, showTooltip: false }));
pdbePluginSpec.behaviors.push(spec_1.PluginSpec.Behavior(behavior_1.AssemblySymmetry));
pdbePluginSpec.config.push([behavior_1.AssemblySymmetryConfig.DefaultServerType, 'pdbe'], [behavior_1.AssemblySymmetryConfig.DefaultServerUrl, 'https://www.ebi.ac.uk/pdbe/aggregated-api/pdb/symmetry'], [behavior_1.AssemblySymmetryConfig.ApplyColors, false]);
if (this.initParams.galleryView) {
pdbePluginSpec.behaviors.push(spec_1.PluginSpec.Behavior(behavior_5.StateGallery));
}
if (!this.initParams.ligandView && !this.initParams.superposition && this.initParams.selectInteraction) {
pdbePluginSpec.behaviors.push(spec_1.PluginSpec.Behavior(structure_focus_representation_1.StructureFocusRepresentation));
}
if (this.initParams.superposition) {
pdbePluginSpec.behaviors.push(spec_1.PluginSpec.Behavior(superposition_focus_representation_1.SuperpositionFocusRepresentation), spec_1.PluginSpec.Behavior(behavior_2.MAQualityAssessment, { autoAttach: true, showTooltip: true }));
}
pdbePluginSpec.layout = {
initial: (0, helpers_1.pluginLayoutStateFromInitParams)(this.initParams),
};
pdbePluginSpec.components = {
controls: {
left: this.initParams.tabs === 'pdbe' ? pdbe_left_panel_1.PDBeLeftPanelControls : pdbe_left_panel_1.DefaultLeftPanelControls,
},
viewport: {
controls: pdbe_viewport_controls_1.PDBeViewportControls,
view: pdbe_viewport_1.PDBeViewport_NoFullscreen,
},
remoteState: 'none',
structureTools: this.initParams.superposition ? pdbe_structure_controls_1.PDBeSuperpositionStructureTools
: this.initParams.ligandView ? pdbe_structure_controls_1.PDBeLigandViewStructureTools
: this.initParams.galleryView ? ui_1.StateGalleryControls
: pdbe_structure_controls_1.PDBeStructureTools,
};
if (this.initParams.alphafoldView) {
pdbePluginSpec.behaviors.push(spec_1.PluginSpec.Behavior(behavior_2.MAQualityAssessment, { autoAttach: true, showTooltip: true }));
}
pdbePluginSpec.config.push([config_1.PluginConfig.Structure.DefaultRepresentationPresetParams, {
theme: {
globalName: (this.initParams.alphafoldView) ? 'plddt-confidence' : undefined,
carbonColor: { name: 'element-symbol', params: {} },
focus: {
name: 'element-symbol',
params: { carbonColor: { name: 'element-symbol', params: {} } },
},
},
}]);
element_symbol_1.ElementSymbolColorThemeParams.carbonColor.defaultValue = { name: 'element-symbol', params: {} };
// Add animation props
if (!this.initParams.ligandView && !this.initParams.superposition) {
pdbePluginSpec.animations = [model_index_1.AnimateModelIndex, camera_spin_1.AnimateCameraSpin, camera_rock_1.AnimateCameraRock, state_snapshots_1.AnimateStateSnapshots, assembly_unwind_1.AnimateAssemblyUnwind, spin_structure_1.AnimateStructureSpin, state_interpolation_1.AnimateStateInterpolation];
pdbePluginSpec.behaviors.push(spec_1.PluginSpec.Behavior(mp4_export_1.Mp4Export));
pdbePluginSpec.behaviors.push(spec_1.PluginSpec.Behavior(geo_export_1.GeometryExport));
}
if (this.initParams.hideCanvasControls.includes('screenshot'))
pdbePluginSpec.config.push([config_1.PluginConfig.Viewport.ShowScreenshotControls, false]);
if (this.initParams.hideCanvasControls.includes('expand'))
pdbePluginSpec.config.push([config_1.PluginConfig.Viewport.ShowExpand, false]);
if (this.initParams.hideCanvasControls.includes('controlToggle'))
pdbePluginSpec.config.push([config_1.PluginConfig.Viewport.ShowControls, false]);
if (this.initParams.hideCanvasControls.includes('controlInfo'))
pdbePluginSpec.config.push([config_1.PluginConfig.Viewport.ShowSettings, false]);
if (this.initParams.hideCanvasControls.includes('selection'))
pdbePluginSpec.config.push([config_1.PluginConfig.Viewport.ShowSelectionMode, false]);
if (this.initParams.hideCanvasControls.includes('animation'))
pdbePluginSpec.config.push([config_1.PluginConfig.Viewport.ShowAnimation, false]);
if (this.initParams.hideCanvasControls.includes('trajectory'))
pdbePluginSpec.config.push([config_1.PluginConfig.Viewport.ShowTrajectoryControls, false]);
// override default event bindings
if (this.initParams.selectBindings) {
pdbePluginSpec.behaviors.push(spec_1.PluginSpec.Behavior(representation_2.SelectLoci, { bindings: this.initParams.selectBindings }));
}
if (this.initParams.focusBindings) {
pdbePluginSpec.behaviors.push(spec_1.PluginSpec.Behavior(camera_1.FocusLoci, { bindings: this.initParams.focusBindings }));
}
// Create/ Initialise Plugin
const onBeforeUIRender = (plugin) => tslib_1.__awaiter(this, void 0, void 0, function* () {
// This needs to run after the plugin is created but before the UI is rendered
(0, plugin_custom_state_1.PluginCustomState)(plugin).initParams = Object.assign({}, this.initParams);
(0, plugin_custom_state_1.PluginCustomState)(plugin).events = {
segmentUpdate: this._ev(),
superpositionInit: this._ev(),
isBusy: this._ev(),
};
if (this.initParams.galleryView && this.initParams.moleculeId) {
try {
yield manager_1.StateGalleryManager.create(plugin, this.initParams.moleculeId); // sets extension custom state
}
catch (err) {
console.error(`Failed to initialize StateGalleryManager for entry ${this.initParams.moleculeId}.\n${err}`);
}
}
});
if (Array.isArray(target)) {
this.targetElement = (0, split_ui_1.resolveHTMLElement)(target[0].target);
this.plugin = yield (0, split_ui_1.createPluginSplitUI)({
layout: target,
spec: pdbePluginSpec,
onBeforeUIRender,
});
for (const comp of target) {
try {
(0, split_ui_1.resolveHTMLElement)(comp.target).viewerInstance = this;
}
catch ( /* some of the target elements missing, ignore */_c) { /* some of the target elements missing, ignore */ }
}
}
else {
this.targetElement = (0, split_ui_1.resolveHTMLElement)(target);
this.plugin = yield (0, index_1.createPluginUI)({ target: this.targetElement, spec: pdbePluginSpec, onBeforeUIRender, render: react18_1.renderReact18 });
this.targetElement.viewerInstance = this;
}
// Set background colour
if (this.initParams.bgColor || this.initParams.lighting) {
this.canvas.applySettings({ color: this.initParams.bgColor, lighting: this.initParams.lighting });
}
// Set selection granularity
if (this.initParams.granularity) {
this.plugin.managers.interactivity.setProps({ granularity: this.initParams.granularity });
}
// Set default highlight and selection colors
if (this.initParams.highlightColor || this.initParams.selectColor) {
this.visual.setColor({ highlight: this.initParams.highlightColor, select: this.initParams.selectColor });
}
// Save renderer defaults
this.defaultRendererProps = Object.assign({}, this.plugin.canvas3d.props.renderer);
this.defaultMarkingProps = Object.assign({}, this.plugin.canvas3d.props.marking);
if (this.initParams.superposition) {
// Set left panel tab
this.plugin.behaviors.layout.leftPanelTabName.next('segments');
// Initialise superposition
(0, superposition_1.initSuperposition)(this.plugin, this.events.loadComplete);
}
else if (this.initParams.galleryView) {
// Do nothing, StateGalleryManager initialized in onBeforeUIRender
}
else {
// Load Molecule CIF or coordQuery and Parse
const dataSource = this.getMoleculeSrcUrl();
if (dataSource) {
this.load({
url: dataSource.url,
format: dataSource.format,
assemblyId: this.initParams.assemblyId,
isBinary: dataSource.isBinary,
progressMessage: `Loading ${(_b = this.initParams.moleculeId) !== null && _b !== void 0 ? _b : ''} ...`,
id: PDBeMolstarPlugin.MAIN_STRUCTURE_ID,
});
}
// Subscribe to events from other PDB Component
if (this.initParams.subscribeEvents) {
(0, subscribe_events_1.subscribeToComponentEvents)(this);
}
// Emit events for other PDB Components
custom_events_1.CustomEvents.add(this.plugin, this.targetElement);
}
});
}
getMoleculeSrcUrl() {
if (this.initParams.customData) {
let { url, format, binary } = this.initParams.customData;
if (!url || !format) {
throw new Error(`Provide all custom data parameters`);
}
if (format === 'cif' || format === 'bcif')
format = 'mmcif';
// Validate supported format
const supportedFormats = ['mmcif', 'pdb', 'sdf'];
if (!supportedFormats.includes(format)) {
throw new Error(`${format} not supported.`);
}
return {
url: url,
format: format,
isBinary: binary,
};
}
if (this.initParams.moleculeId) {
const request = { pdbId: this.initParams.moleculeId, queryType: 'full', queryParams: {} };
if (this.initParams.ligandView) {
request.queryType = 'residueSurroundings';
request.queryParams['data_source'] = 'pdb-h';
if (!this.initParams.ligandView.label_comp_id_list) {
request.queryParams['label_comp_id'] = this.initParams.ligandView.label_comp_id;
request.queryParams['auth_seq_id'] = this.initParams.ligandView.auth_seq_id;
request.queryParams['auth_asym_id'] = this.initParams.ligandView.auth_asym_id;
}
}
return {
url: (0, helpers_1.getStructureUrl)(this.initParams, request),
format: 'mmcif',
isBinary: this.initParams.encoding === 'bcif',
};
}
throw new Error(`Mandatory parameters missing! (customData or moleculeId must be defined)`);
}
get state() {
return this.plugin.state.data;
}
createLigandStructure(isBranched) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
var _a, e_1, _b, _c;
var _d, _e;
if (this.assemblyRef === '')
return;
try {
for (var _f = true, _g = tslib_1.__asyncValues(this.plugin.managers.structure.hierarchy.currentComponentGroups), _h; _h = yield _g.next(), _a = _h.done, !_a; _f = true) {
_c = _h.value;
_f = false;
const comp = _c;
yield commands_1.PluginCommands.State.RemoveObject(this.plugin, { state: comp[0].cell.parent, ref: comp[0].cell.transform.ref, removeParentGhosts: true });
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (!_f && !_a && (_b = _g.return)) yield _b.call(_g);
}
finally { if (e_1) throw e_1.error; }
}
const structure = this.state.select(this.assemblyRef)[0];
let ligandQuery;
if (isBranched) {
ligandQuery = helpers_1.LigandView.branchedQuery((_d = this.initParams.ligandView) === null || _d === void 0 ? void 0 : _d.label_comp_id_list);
}
else {
ligandQuery = helpers_1.LigandView.query(this.initParams.ligandView);
}
const ligandVis = yield this.plugin.builders.structure.tryCreateComponentFromExpression(structure, ligandQuery.core, 'pivot', { label: 'Ligand' });
if (ligandVis)
yield this.plugin.builders.structure.representation.addRepresentation(ligandVis, { type: 'ball-and-stick', color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } }, size: 'uniform', sizeParams: { value: 2.5 } }, { tag: 'ligand-vis' });
const ligandSurr = yield this.plugin.builders.structure.tryCreateComponentFromExpression(structure, ligandQuery.surroundings, 'rest', { label: 'Surroundings' });
if (ligandSurr)
yield this.plugin.builders.structure.representation.addRepresentation(ligandSurr, { type: 'ball-and-stick', color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } }, size: 'uniform', sizeParams: { value: 0.8 } });
// Focus ligand
const ligRef = mol_state_1.StateSelection.findTagInSubtree(this.plugin.state.data.tree, mol_state_1.StateTransform.RootRef, 'ligand-vis');
if (!ligRef)
return;
const cell = this.plugin.state.data.cells.get(ligRef);
if (cell === null || cell === void 0 ? void 0 : cell.obj) {
const repr = cell.obj.data.repr;
const ligLoci = repr.getAllLoci()[0]; // getAllLoci returns multiple copies of the same loci (one per representation visual)
this.plugin.managers.structure.focus.setFromLoci(ligLoci);
// focus-add is not handled in camera behavior, doing it here
const current = (_e = this.plugin.managers.structure.focus.current) === null || _e === void 0 ? void 0 : _e.loci;
if (current)
this.plugin.managers.camera.focusLoci(current);
}
});
}
load(_a) {
return tslib_1.__awaiter(this, arguments, void 0, function* ({ url, format = 'mmcif', isBinary = false, assemblyId = '', progressMessage, id }, fullLoad = true) {
yield (0, helpers_1.runWithProgressMessage)(this.plugin, progressMessage, () => tslib_1.__awaiter(this, void 0, void 0, function* () {
var _a, _b;
let success = false;
try {
(_a = (0, plugin_custom_state_1.PluginCustomState)(this.plugin).events) === null || _a === void 0 ? void 0 : _a.isBusy.next(true);
if (fullLoad)
yield this.clear();
const isHetView = this.initParams.ligandView ? true : false;
let downloadOptions = undefined;
let isBranchedView = false;
if (this.initParams.ligandView && this.initParams.ligandView.label_comp_id_list) {
isBranchedView = true;
downloadOptions = { body: JSON.stringify(this.initParams.ligandView.label_comp_id_list), headers: [['Content-type', 'app