UNPKG

molstar

Version:

A comprehensive macromolecular library.

154 lines (153 loc) 6.89 kB
/** * Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Adam Midlik <midlik@gmail.com> * @author David Sehnal <david.sehnal@gmail.com> */ import { StructureRepresentation3D } from '../../mol-plugin-state/transforms/representation'; import { StateTree } from '../../mol-state'; import { UUID } from '../../mol-util'; import { stringHash } from './helpers/utils'; import { dfs } from './tree/generic/tree-utils'; export function loadTreeVirtual(plugin, tree, loadingActions, context, options) { var _a; const updateRoot = UpdateTarget.create(plugin, (_a = options === null || options === void 0 ? void 0 : options.replaceExisting) !== null && _a !== void 0 ? _a : false); loadTreeInUpdate(updateRoot, tree, loadingActions, context, options); const stateTree = updateRoot.update.getTree(); const stateSnapshot = { tree: StateTree.toJSON(stateTree) }; const pluginStateSnapshot = { id: UUID.create22(), data: stateSnapshot }; return pluginStateSnapshot; } function loadTreeInUpdate(updateRoot, tree, loadingActions, context, options) { var _a; const mapping = new Map(); if (options === null || options === void 0 ? void 0 : options.replaceExisting) { UpdateTarget.deleteChildren(updateRoot); } const extensionContexts = ((_a = options === null || options === void 0 ? void 0 : options.extensions) !== null && _a !== void 0 ? _a : []).map(ext => ({ ext, extCtx: ext.createExtensionContext(tree, context) })); const mvsRefMap = new Map(); dfs(tree, (node, parent) => { const kind = node.kind; let msNode; const updateParent = parent ? mapping.get(parent) : updateRoot; const action = loadingActions[kind]; if (action) { if (updateParent) { msNode = action(updateParent, node, context); if (msNode && node.ref) { UpdateTarget.tag(msNode, mvsRefTags(node.ref)); mvsRefMap.set(node.ref, msNode.selector.ref); } mapping.set(node, msNode); } else { console.warn(`No target found for this "${node.kind}" node`); return; } } if (updateParent) { for (const { ext, extCtx } of extensionContexts) { ext.action(msNode !== null && msNode !== void 0 ? msNode : updateParent, node, context, extCtx); } } }); for (const target of updateRoot.targetManager.allTargets) { UpdateTarget.dependsOn(target, mvsRefMap); } extensionContexts.forEach(e => { var _a, _b; return (_b = (_a = e.ext).disposeExtensionContext) === null || _b === void 0 ? void 0 : _b.call(_a, e.extCtx, tree, context); }); } export const UpdateTarget = { /** Create a new update, with `selector` pointing to the root. */ create(plugin, replaceExisting) { const update = plugin.build(); const msTarget = update.toRoot(); return { update, selector: msTarget.selector, targetManager: new TargetManager(plugin, replaceExisting), mvsDependencyRefs: new Set() }; }, /** Add a child node to `target.selector`, return a new `UpdateTarget` pointing to the new child. */ apply(target, transformer, params, options) { var _a, _b; let refSuffix = transformer.id; if (transformer.id === StructureRepresentation3D.id) { const reprType = (_b = (_a = params === null || params === void 0 ? void 0 : params.type) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : ''; refSuffix += `:${reprType}`; } const ref = target.targetManager.getChildRef(target.selector, refSuffix); const apply = target.update.to(target.selector).apply(transformer, params, { ...options, ref }); const result = { ...target, selector: apply.selector, mvsDependencyRefs: new Set(), transformer, transformParams: params }; target.targetManager.allTargets.push(result); return result; }, setMvsDependencies(target, refs) { refs.forEach(ref => target.mvsDependencyRefs.add(ref)); return target; }, dependsOn(target, mapping) { if (!target.mvsDependencyRefs.size) return target; const dependsOn = Array.from(target.mvsDependencyRefs).map(d => mapping.get(d)).filter(d => d); if (!dependsOn.length) return target; target.update.to(target.selector).dependsOn(dependsOn); return target; }, /** Add tags to `target.selector` */ tag(target, tags) { if (tags.length > 0) { target.update.to(target.selector).tag(tags); } return target; }, /** Delete all children of `target.selector`. */ deleteChildren(target) { const children = target.update.currentTree.children.get(target.selector.ref); children.forEach(child => target.update.delete(child)); return target; }, /** Commit all changes done in the current update. */ commit(target) { return target.update.commit(); }, }; /** Manages transform refs in a deterministic way. Uses refs like !mvs:3ce3664304d32c5d:0 */ class TargetManager { constructor(plugin, replaceExisting) { /** For each hash (e.g. 3ce3664304d32c5d), store the number of already used refs with that hash. */ this._counter = {}; this.allTargets = []; if (!replaceExisting) { plugin.state.data.cells.forEach(cell => { var _a; const ref = cell.transform.ref; if (ref.startsWith('!mvs:')) { const [_, hash, idNumber] = ref.split(':'); const nextIdNumber = parseInt(idNumber) + 1; if (nextIdNumber > ((_a = this._counter[hash]) !== null && _a !== void 0 ? _a : 0)) { this._counter[hash] = nextIdNumber; } } }); } } /** Return ref for a new node with given `hash`; update the counter accordingly. */ nextRef(hash) { var _a; var _b; (_a = (_b = this._counter)[hash]) !== null && _a !== void 0 ? _a : (_b[hash] = 0); const idNumber = this._counter[hash]++; return `!mvs:${hash}:${idNumber}`; } /** Return ref for a new node based on parent and desired suffix. */ getChildRef(parent, suffix) { const hashBase = parent.ref.replace(/^!mvs:/, '') + ':' + suffix; const hash = stringHash(hashBase); const result = this.nextRef(hash); return result; } } /** Create node tags based of MVS node.ref */ export function mvsRefTags(mvsNodeRef) { if (mvsNodeRef === undefined) return []; else return [`mvs-ref:${mvsNodeRef}`]; }