UNPKG

molstar

Version:

A comprehensive macromolecular library.

270 lines (269 loc) 11.2 kB
/** * Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Adam Midlik <midlik@gmail.com> */ import { StateTree } from '../tree/immutable'; import { StateObjectCell, StateObjectSelector, StateObjectRef } from '../object'; import { StateTransform } from '../transform'; import { produce } from 'immer'; export { StateBuilder }; var StateBuilder; (function (StateBuilder) { function buildTree(state) { if (!state.state || state.state.tree === state.editInfo.sourceTree) { return state.tree.asImmutable(); } // The tree has changed in the meantime, we need to reapply the changes! const tree = state.state.tree.asTransient(); for (const a of state.actions) { switch (a.kind) { case 'add': tree.add(a.transform); break; case 'update': tree.setParams(a.ref, a.params); break; case 'delete': tree.remove(a.ref); break; case 'insert': { const children = tree.children.get(a.ref).toArray(); tree.add(a.transform); for (const c of children) { tree.changeParent(c, a.transform.ref); } break; } } } state.editInfo.sourceTree = state.tree; return tree.asImmutable(); } function is(obj) { return !!obj && typeof obj.getTree === 'function'; } StateBuilder.is = is; function isTo(obj) { return !!obj && typeof obj.getTree === 'function' && typeof obj.ref === 'string'; } StateBuilder.isTo = isTo; // type ToFromCell<C extends StateObjectCell> = C extends StateObjectCell<infer A, StateTransform<infer T extends StateTransformer>> ? To<A, any>: never class Root { get editInfo() { return this.state.editInfo; } get currentTree() { return this.state.tree; } to(refOrCellOrSelector) { const ref = typeof refOrCellOrSelector === 'string' ? refOrCellOrSelector : StateObjectCell.is(refOrCellOrSelector) ? refOrCellOrSelector.transform.ref : refOrCellOrSelector.ref; return new To(this.state, ref, this); } toRoot() { return new To(this.state, this.state.tree.root.ref, this); } delete(obj) { const ref = StateObjectRef.resolveRef(obj); if (!ref || !this.state.tree.transforms.has(ref)) return this; this.editInfo.count++; this.state.tree.remove(ref); this.state.actions.push({ kind: 'delete', ref }); return this; } getTree() { return buildTree(this.state); } commit(options) { if (!this.state.state) throw new Error('Cannot commit template tree'); return this.state.state.runTask(this.state.state.updateTree(this, options)); } constructor(tree, state) { this.state = { state, tree: tree.asTransient(), actions: [], editInfo: { applied: false, sourceTree: tree, count: 0, lastUpdate: void 0 } }; } } StateBuilder.Root = Root; class To { get editInfo() { return this.state.editInfo; } get selector() { return new StateObjectSelector(this.ref, this.state.state); } getApplyRoot() { return StateTree.getDecoratorRoot(this.state.tree, this.ref); } /** * Apply the transformed to the parent node * If no params are specified (params <- undefined), default params are lazily resolved. */ apply(tr, params, options) { if (tr.definition.isDecorator) { return this.insert(tr, params, options); } const applyRoot = this.getApplyRoot(); const t = tr.apply(applyRoot, params, options); this.state.tree.add(t); this.editInfo.count++; this.editInfo.lastUpdate = t.ref; this.state.actions.push({ kind: 'add', transform: t }); return new To(this.state, t.ref, this.root); } /** * If the ref is present, the transform is applied. * Otherwise a transform with the specifed ref is created. */ applyOrUpdate(ref, tr, params, options) { if (this.state.tree.transforms.has(ref)) { const to = this.to(ref); if (params) to.update(params); return to; } else { return this.apply(tr, params, { ...options, ref }); } } /** * Apply the transformed to the parent node * If no params are specified (params <- undefined), default params are lazily resolved. * The transformer cannot be a decorator to be able to use this. */ applyOrUpdateTagged(tags, tr, params, options) { if (tr.definition.isDecorator) { throw new Error(`Can't use applyOrUpdateTagged on decorator transformers.`); } const applyRoot = this.getApplyRoot(); const children = this.state.tree.children.get(applyRoot).values(); while (true) { const child = children.next(); if (child.done) break; const tr = this.state.tree.transforms.get(child.value); if (tr && StateTransform.hasTags(tr, tags)) { const to = this.to(child.value); to.updateTagged(params, stringArrayUnion(tr.tags, tags, options && options.tags)); return to; } } const t = tr.apply(applyRoot, params, { ...options, tags: stringArrayUnion(tags, options && options.tags) }); this.state.tree.add(t); this.editInfo.count++; this.editInfo.lastUpdate = t.ref; this.state.actions.push({ kind: 'add', transform: t }); return new To(this.state, t.ref, this.root); } /** * A helper to greate a group-like state object and keep the current type. */ group(tr, params, options) { return this.apply(tr, params, options); } /** * Inserts a new transform that does not change the object type and move the original children to it. */ insert(tr, params, options) { // cache the children const children = this.state.tree.children.get(this.ref).toArray(); // add the new node const t = tr.apply(this.ref, params, options); this.state.tree.add(t); // move the original children to the new node for (const c of children) { this.state.tree.changeParent(c, t.ref); } this.editInfo.count++; this.editInfo.lastUpdate = t.ref; this.state.actions.push({ kind: 'insert', ref: this.ref, transform: t }); return new To(this.state, t.ref, this.root); } updateTagged(params, tags) { if (this.state.tree.setParams(this.ref, params) || this.state.tree.setTags(this.ref, tags)) { this.editInfo.count++; this.editInfo.lastUpdate = this.ref; this.state.actions.push({ kind: 'update', ref: this.ref, params }); } } update(paramsOrTransformer, provider) { let params; if (provider) { const old = this.state.tree.transforms.get(this.ref); params = produce(old.params, provider); } else { params = typeof paramsOrTransformer === 'function' ? produce(this.state.tree.transforms.get(this.ref).params, paramsOrTransformer) : paramsOrTransformer; } if (this.state.tree.setParams(this.ref, params)) { this.editInfo.count++; this.editInfo.lastUpdate = this.ref; this.state.actions.push({ kind: 'update', ref: this.ref, params }); } return this.root; } updateState(state) { const transform = this.state.tree.transforms.get(this.ref); if (StateTransform.isStateChange(transform.state, state)) { this.state.tree.assignState(this.ref, state); this.editInfo.count++; this.editInfo.lastUpdate = this.ref; if (!this.state.actions.find(a => a.kind === 'update' && a.ref === this.ref)) { this.state.actions.push({ kind: 'update', ref: this.ref, params: transform.params }); } } } /** Add tags to the current node */ tag(tags) { const transform = this.state.tree.transforms.get(this.ref); this.updateTagged(transform.params, stringArrayUnion(transform.tags, tags)); return this; } /** Add dependsOn to the current node */ dependsOn(dependsOn) { const transform = this.state.tree.transforms.get(this.ref); if (this.state.tree.setDependsOn(this.ref, stringArrayUnion(transform.dependsOn, dependsOn))) { this.editInfo.count++; this.editInfo.lastUpdate = this.ref; this.state.actions.push({ kind: 'update', ref: this.ref, params: transform.params }); } } to(ref) { return this.root.to(ref); } toRoot() { return this.root.toRoot(); } delete(ref) { return this.root.delete(ref); } getTree() { return buildTree(this.state); } /** Returns selector to this node. */ commit(options) { if (!this.state.state) throw new Error('Cannot commit template tree'); return this.state.state.runTask(this.state.state.updateTree(this, options)); } constructor(state, ref, root) { this.state = state; this.root = root; this.ref = ref; if (!this.state.tree.transforms.has(ref)) { throw new Error(`Could not find node '${ref}'.`); } } } StateBuilder.To = To; })(StateBuilder || (StateBuilder = {})); function stringArrayUnion(...arrays) { let set = void 0; const ret = []; for (const xs of arrays) { if (!xs) continue; if (!set) set = new Set(); if (typeof xs === 'string') { if (set.has(xs)) continue; set.add(xs); ret.push(xs); } else { for (const x of xs) { if (set.has(x)) continue; set.add(x); ret.push(x); } } } return ret; }