molstar
Version:
A comprehensive macromolecular library.
338 lines (337 loc) • 21.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VolumeSourceControls = exports.VolumeStreamingControls = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const volume_1 = require("../../mol-model/volume");
const volume_representation_params_1 = require("../../mol-plugin-state/helpers/volume-representation-params");
const hierarchy_1 = require("../../mol-plugin-state/manager/structure/hierarchy");
const hierarchy_2 = require("../../mol-plugin-state/manager/volume/hierarchy");
const transforms_1 = require("../../mol-plugin-state/transforms");
const representation_1 = require("../../mol-plugin/behavior/dynamic/representation");
const behavior_1 = require("../../mol-plugin/behavior/dynamic/volume-streaming/behavior");
const transformers_1 = require("../../mol-plugin/behavior/dynamic/volume-streaming/transformers");
const commands_1 = require("../../mol-plugin/commands");
const mol_state_1 = require("../../mol-state");
const color_1 = require("../../mol-util/color");
const memoize_1 = require("../../mol-util/memoize");
const param_definition_1 = require("../../mol-util/param-definition");
const base_1 = require("../base");
const action_menu_1 = require("../controls/action-menu");
const color_2 = require("../controls/color");
const common_1 = require("../controls/common");
const icons_1 = require("../controls/icons");
const parameters_1 = require("../controls/parameters");
const apply_action_1 = require("../state/apply-action");
const update_transform_1 = require("../state/update-transform");
const help_1 = require("../viewport/help");
const rxjs_1 = require("rxjs");
class VolumeStreamingControls extends base_1.CollapsableControls {
defaultState() {
return {
header: 'Volume Streaming',
isCollapsed: false,
isBusy: false,
isHidden: true,
brand: { accent: 'cyan', svg: icons_1.BlurOnSvg }
};
}
componentDidMount() {
// TODO: do not hide this but instead show some help text??
this.subscribe(this.plugin.managers.structure.hierarchy.behaviors.selection, () => {
this.setState({
isHidden: !this.canEnable(),
description: hierarchy_1.StructureHierarchyManager.getSelectedStructuresDescription(this.plugin)
});
});
this.subscribe(this.plugin.state.events.cell.stateUpdated, e => {
if (mol_state_1.StateTransform.hasTag(e.cell.transform, behavior_1.VolumeStreaming.RootTag))
this.forceUpdate();
});
this.subscribe(this.plugin.behaviors.state.isBusy, v => {
this.setState({ isBusy: v });
});
}
get pivot() {
return this.plugin.managers.structure.hierarchy.selection.structures[0];
}
canEnable() {
var _a, _b;
const { selection } = this.plugin.managers.structure.hierarchy;
if (selection.structures.length !== 1)
return false;
const pivot = this.pivot.cell;
if (!pivot.obj)
return false;
return !!((_b = (_a = transformers_1.InitVolumeStreaming.definition).isApplicable) === null || _b === void 0 ? void 0 : _b.call(_a, pivot.obj, pivot.transform, this.plugin));
}
renderEnable() {
var _a, _b;
const pivot = this.pivot;
if (!pivot.cell.parent)
return null;
const root = mol_state_1.StateSelection.findTagInSubtree(pivot.cell.parent.tree, this.pivot.cell.transform.ref, behavior_1.VolumeStreaming.RootTag);
const rootCell = root && pivot.cell.parent.cells.get(root);
const simpleApply = rootCell && rootCell.status === 'error'
? { header: !!rootCell.errorText && ((_a = rootCell.errorText) === null || _a === void 0 ? void 0 : _a.includes('404')) ? 'No Density Data Available' : 'Error Enabling', icon: icons_1.ErrorSvg, title: rootCell.errorText }
: rootCell && ((_b = rootCell.obj) === null || _b === void 0 ? void 0 : _b.data.entries.length) === 0
? { header: 'Error Enabling', icon: icons_1.ErrorSvg, title: 'No Entry for Streaming Found' }
: { header: 'Enable', icon: icons_1.CheckSvg, title: 'Enable' };
return (0, jsx_runtime_1.jsx)(apply_action_1.ApplyActionControl, { state: pivot.cell.parent, action: transformers_1.InitVolumeStreaming, initiallyCollapsed: true, nodeRef: pivot.cell.transform.ref, simpleApply: simpleApply });
}
renderParams() {
var _a, _b, _c, _d, _e;
const pivot = this.pivot;
if (!pivot.cell.parent)
return null;
const bindings = ((_b = (_a = pivot.volumeStreaming) === null || _a === void 0 ? void 0 : _a.cell.transform.params) === null || _b === void 0 ? void 0 : _b.entry.params.view.name) === 'selection-box' && ((_e = (_d = (_c = this.plugin.state.behaviors.cells.get(representation_1.FocusLoci.id)) === null || _c === void 0 ? void 0 : _c.params) === null || _d === void 0 ? void 0 : _d.values) === null || _e === void 0 ? void 0 : _e.bindings);
return (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(update_transform_1.UpdateTransformControl, { state: pivot.cell.parent, transform: pivot.volumeStreaming.cell.transform, customHeader: 'none', noMargin: true }), bindings && (0, jsx_runtime_1.jsx)(common_1.ExpandGroup, { header: 'Controls Help', children: (0, jsx_runtime_1.jsx)(help_1.BindingsHelp, { bindings: bindings }) })] });
}
renderControls() {
const pivot = this.pivot;
if (!pivot)
return null;
if (!pivot.volumeStreaming)
return this.renderEnable();
return this.renderParams();
}
}
exports.VolumeStreamingControls = VolumeStreamingControls;
class VolumeSourceControls extends base_1.CollapsableControls {
constructor() {
super(...arguments);
this.item = (ref) => {
var _a;
const selected = this.plugin.managers.volume.hierarchy.selection;
const label = ((_a = ref.cell.obj) === null || _a === void 0 ? void 0 : _a.label) || 'Volume';
const item = {
kind: 'item',
label: (ref.kind === 'lazy-volume' ? 'Load ' : '') + (label || ref.kind),
selected: selected === ref,
value: ref
};
return item;
};
this.selectCurrent = (item) => {
this.toggleHierarchy();
if (!item)
return;
const current = item.value;
if (current.kind === 'volume') {
this.plugin.managers.volume.hierarchy.setCurrent(current);
}
else {
this.lazyLoad(current.cell);
}
};
this.selectAdd = (item) => {
if (!item)
return;
this.setState({ show: void 0 });
item.value();
};
this.toggleHierarchy = () => this.setState({ show: this.state.show !== 'hierarchy' ? 'hierarchy' : void 0 });
this.toggleAddRepr = () => this.setState({ show: this.state.show !== 'add-repr' ? 'add-repr' : void 0 });
this.toggleVisibility = () => {
var _a, _b;
const mng = this.plugin.managers.volume.hierarchy;
const { current } = mng;
const globalVisibility = !((_b = (_a = current.volumes[0]) === null || _a === void 0 ? void 0 : _a.representations[0]) === null || _b === void 0 ? void 0 : _b.cell.state.isHidden);
this.plugin.managers.volume.hierarchy.toggleVisibility(current.volumes.flatMap(v => v.representations), globalVisibility ? 'hide' : 'show');
};
}
defaultState() {
return {
header: 'Volume',
isCollapsed: false,
isBusy: false,
isHidden: true,
brand: { accent: 'purple', svg: icons_1.BlurOnSvg }
};
}
componentDidMount() {
this.subscribe(this.plugin.managers.volume.hierarchy.behaviors.selection, sel => {
this.setState({ isHidden: sel.hierarchy.volumes.length === 0 && sel.hierarchy.lazyVolumes.length === 0 });
});
this.subscribe(this.plugin.behaviors.state.isBusy, v => {
this.setState({ isBusy: v });
});
}
get hierarchyItems() {
const mng = this.plugin.managers.volume.hierarchy;
const { current } = mng;
const ret = [];
for (const ref of current.volumes) {
ret.push(this.item(ref));
}
for (const ref of current.lazyVolumes) {
ret.push(this.item(ref));
}
return ret;
}
get addActions() {
const mng = this.plugin.managers.volume.hierarchy;
const current = mng.selection;
const ret = [
...hierarchy_2.VolumeHierarchyManager.getRepresentationTypes(this.plugin, current)
.map(t => action_menu_1.ActionMenu.Item(t[1], () => mng.addRepresentation(current, t[0])))
];
return ret;
}
get isEmpty() {
const { volumes, lazyVolumes } = this.plugin.managers.volume.hierarchy.current;
return volumes.length === 0 && lazyVolumes.length === 0;
}
get label() {
var _a;
if (this.state.loadingLabel)
return `Loading ${this.state.loadingLabel}...`;
const selected = this.plugin.managers.volume.hierarchy.selection;
if (!selected)
return 'Nothing Selected';
return ((_a = selected === null || selected === void 0 ? void 0 : selected.cell.obj) === null || _a === void 0 ? void 0 : _a.label) || 'Volume';
}
async lazyLoad(cell) {
const { url, isBinary, format, entryId, isovalues } = cell.obj.data;
this.setState({ isBusy: true, loadingLabel: cell.obj.label });
try {
const plugin = this.plugin;
await plugin.dataTransaction(async () => {
var _a, _b, _c, _d;
const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } });
const parsed = await plugin.dataFormats.get(format).parse(plugin, data, { entryId });
const firstVolume = (parsed.volume || parsed.volumes[0]);
if (!(firstVolume === null || firstVolume === void 0 ? void 0 : firstVolume.isOk))
throw new Error('Failed to parse any volume.');
const repr = plugin.build();
for (const iso of isovalues) {
repr
.to((_c = (_a = parsed.volumes) === null || _a === void 0 ? void 0 : _a[(_b = iso.volumeIndex) !== null && _b !== void 0 ? _b : 0]) !== null && _c !== void 0 ? _c : parsed.volume)
.apply(transforms_1.StateTransforms.Representation.VolumeRepresentation3D, (0, volume_representation_params_1.createVolumeRepresentationParams)(this.plugin, firstVolume.data, {
type: 'isosurface',
typeParams: { alpha: (_d = iso.alpha) !== null && _d !== void 0 ? _d : 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } },
color: 'uniform',
colorParams: { value: iso.color }
}));
}
await repr.commit();
await plugin.build().delete(cell).commit();
});
}
finally {
this.setState({ isBusy: false, loadingLabel: void 0 });
}
}
renderControls() {
const disabled = this.state.isBusy || this.isEmpty;
const label = this.label;
const selected = this.plugin.managers.volume.hierarchy.selection;
const mng = this.plugin.managers.volume.hierarchy;
const { current } = mng;
return (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: 'msp-flex-row', style: { marginTop: '1px' }, children: [(0, jsx_runtime_1.jsx)(common_1.Button, { noOverflow: true, flex: true, onClick: this.toggleHierarchy, disabled: disabled, title: label, children: label }), !this.isEmpty && selected && (0, jsx_runtime_1.jsx)(common_1.IconButton, { svg: icons_1.AddSvg, onClick: this.toggleAddRepr, title: 'Apply a structure presets to the current hierarchy.', toggleState: this.state.show === 'add-repr', disabled: disabled }), !this.isEmpty && (0, jsx_runtime_1.jsx)(common_1.IconButton, { svg: icons_1.VisibilityOutlinedSvg, onClick: this.toggleVisibility, toggleState: false, title: 'Toggle visibility of all volumes.', disabled: disabled })] }), this.state.show === 'hierarchy' && (0, jsx_runtime_1.jsx)(action_menu_1.ActionMenu, { items: this.hierarchyItems, onSelect: this.selectCurrent }), this.state.show === 'add-repr' && (0, jsx_runtime_1.jsx)(action_menu_1.ActionMenu, { items: this.addActions, onSelect: this.selectAdd }), current.volumes.length > 0 && (0, jsx_runtime_1.jsx)("div", { style: { marginTop: '6px' }, children: current.volumes.map((volume) => (0, jsx_runtime_1.jsx)(VolumeEntryControls, { volume: volume }, volume.cell.transform.ref)) })] });
}
}
exports.VolumeSourceControls = VolumeSourceControls;
function VolumeEntryControls({ volume }) {
var _a, _b;
return (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: 'msp-control-group-header', style: { marginTop: '1px' }, children: (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)("b", { children: (_b = (_a = volume.cell.obj) === null || _a === void 0 ? void 0 : _a.label) !== null && _b !== void 0 ? _b : 'n/a' }) }) }), volume.representations.map(r => (0, jsx_runtime_1.jsx)(VolumeRepresentationControls, { volume: volume, representation: r }, r.cell.transform.ref))] });
}
class VolumeRepresentationControls extends base_1.PurePluginUIComponent {
constructor() {
super(...arguments);
this.state = { action: void 0 };
this.updateIsoValueEvent = new rxjs_1.Subject();
this.remove = () => this.plugin.managers.volume.hierarchy.remove([this.props.representation], true);
this.toggleVisible = (e) => {
e.preventDefault();
e.currentTarget.blur();
this.plugin.managers.volume.hierarchy.toggleVisibility([this.props.representation]);
};
this.toggleColor = () => {
this.setState({ action: this.state.action === 'select-color' ? undefined : 'select-color' });
};
this.toggleUpdate = () => this.setState({ action: this.state.action === 'update' ? void 0 : 'update' });
this.highlight = (e) => {
e.preventDefault();
if (!this.props.representation.cell.parent)
return;
commands_1.PluginCommands.Interactivity.Object.Highlight(this.plugin, { state: this.props.representation.cell.parent, ref: this.props.representation.cell.transform.ref });
};
this.clearHighlight = (e) => {
e.preventDefault();
commands_1.PluginCommands.Interactivity.ClearHighlights(this.plugin);
};
this.focus = () => {
var _a;
const repr = this.props.representation;
const lociList = (_a = repr.cell.obj) === null || _a === void 0 ? void 0 : _a.data.repr.getAllLoci();
if (repr.cell.state.isHidden)
this.plugin.managers.volume.hierarchy.toggleVisibility([this.props.representation], 'show');
if (lociList)
this.plugin.managers.camera.focusLoci(lociList, { extraRadius: 1 });
};
this.updateColor = ({ value }) => {
const t = this.props.representation.cell.transform;
return this.plugin.build().to(t.ref).update({
...t.params,
colorTheme: {
name: 'uniform',
params: { value }
},
}).commit();
};
this.requestIsoValueUpdate = (values) => {
this.updateIsoValueEvent.next(values);
};
this.updateIsoValue = (values) => {
var _a, _b;
const t = this.props.representation.cell.transform;
return this.plugin.build().to(t.ref).update({
...t.params,
type: {
...(_a = t.params) === null || _a === void 0 ? void 0 : _a.type,
params: {
...(_b = t.params) === null || _b === void 0 ? void 0 : _b.type.params,
isoValue: values.isoValue
}
}
}).commit();
};
this.isoValueParams = (0, memoize_1.memoizeLatest)((ref, type) => {
var _a, _b;
const repr = this.props.representation.cell;
const params = repr.transform.params;
if ((params === null || params === void 0 ? void 0 : params.type.name) !== 'isosurface')
return undefined;
return { isoValue: volume_1.Volume.createIsoValueParam(params.type.params.isoValue, (_b = (_a = this.props.volume.cell.obj) === null || _a === void 0 ? void 0 : _a.data.grid) === null || _b === void 0 ? void 0 : _b.stats) };
});
}
componentDidMount() {
this.subscribe(this.plugin.state.events.cell.stateUpdated, e => {
if (mol_state_1.State.ObjectEvent.isCell(e, this.props.representation.cell))
this.forceUpdate();
});
this.subscribe(this.updateIsoValueEvent.pipe((0, rxjs_1.throttleTime)(100, undefined, { leading: false, trailing: true })), this.updateIsoValue);
}
get color() {
var _a, _b;
const repr = this.props.representation.cell;
const isUniform = ((_a = repr.transform.params) === null || _a === void 0 ? void 0 : _a.colorTheme.name) === 'uniform';
if (!isUniform)
return void 0;
return (_b = repr.transform.params) === null || _b === void 0 ? void 0 : _b.colorTheme.params.value;
}
get isIsoSurface() {
var _a;
const repr = this.props.representation.cell;
return ((_a = repr.transform.params) === null || _a === void 0 ? void 0 : _a.type.name) === 'isosurface';
}
render() {
var _a, _b, _c;
const repr = this.props.representation.cell;
const params = repr.transform.params;
const color = this.color;
const isoParams = this.isoValueParams(repr.transform.ref, params === null || params === void 0 ? void 0 : params.type.name);
return (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: 'msp-flex-row', children: [color !== void 0 && (0, jsx_runtime_1.jsx)(common_1.Button, { style: { backgroundColor: color_1.Color.toStyle(color), minWidth: 32, width: 32 }, onClick: this.toggleColor }), (0, jsx_runtime_1.jsxs)(common_1.Button, { noOverflow: true, className: 'msp-control-button-label', title: `${(_a = repr.obj) === null || _a === void 0 ? void 0 : _a.label}. Click to focus.`, onClick: this.focus, onMouseEnter: this.highlight, onMouseLeave: this.clearHighlight, style: { textAlign: 'left' }, children: [(_b = repr.obj) === null || _b === void 0 ? void 0 : _b.label, (0, jsx_runtime_1.jsx)("small", { className: 'msp-25-lower-contrast-text', style: { float: 'right' }, children: (_c = repr.obj) === null || _c === void 0 ? void 0 : _c.description })] }), (0, jsx_runtime_1.jsx)(common_1.IconButton, { svg: repr.state.isHidden ? icons_1.VisibilityOffOutlinedSvg : icons_1.VisibilityOutlinedSvg, toggleState: false, onClick: this.toggleVisible, title: `${repr.state.isHidden ? 'Show' : 'Hide'} component`, small: true, className: 'msp-form-control', flex: true }), (0, jsx_runtime_1.jsx)(common_1.IconButton, { svg: icons_1.DeleteOutlinedSvg, onClick: this.remove, title: 'Remove', small: true }), (0, jsx_runtime_1.jsx)(common_1.IconButton, { svg: icons_1.MoreHorizSvg, onClick: this.toggleUpdate, title: 'Actions', toggleState: this.state.action === 'update' })] }), this.state.action === 'update' && !!repr.parent && (0, jsx_runtime_1.jsx)("div", { style: { marginBottom: '6px' }, className: 'msp-accent-offset', children: (0, jsx_runtime_1.jsxs)("div", { children: [!!isoParams && (0, jsx_runtime_1.jsx)("div", { style: { marginBottom: '1px' }, children: (0, jsx_runtime_1.jsx)(parameters_1.ParameterControls, { params: isoParams, values: { isoValue: params === null || params === void 0 ? void 0 : params.type.params.isoValue }, onChangeValues: this.requestIsoValueUpdate }) }), (0, jsx_runtime_1.jsx)(update_transform_1.UpdateTransformControl, { state: repr.parent, transform: repr.transform, customHeader: 'none', noMargin: true })] }) }), this.state.action === 'select-color' && color !== void 0 && (0, jsx_runtime_1.jsx)("div", { style: { marginBottom: '6px', marginTop: 1 }, className: 'msp-accent-offset', children: (0, jsx_runtime_1.jsx)(common_1.ControlGroup, { header: 'Select Color', initialExpanded: true, hideExpander: true, hideOffset: true, onHeaderClick: this.toggleColor, topRightIcon: icons_1.CloseSvg, noTopMargin: true, childrenClassName: 'msp-viewport-controls-panel-controls', children: (0, jsx_runtime_1.jsx)(color_2.CombinedColorControl, { param: VolumeColorParam, value: this.color, onChange: this.updateColor, name: 'color', hideNameRow: true }) }) })] });
}
}
const VolumeColorParam = param_definition_1.ParamDefinition.Color((0, color_1.Color)(0x121212));