From 575ca97e383462c6ba549a3f05c8e0aa54e26878 Mon Sep 17 00:00:00 2001 From: Cameron Gilchrist Date: Sat, 30 May 2026 15:42:31 +0900 Subject: [PATCH 1/7] initial molstar integration, removal of NGL --- frontend/Alignment.vue | 68 +- frontend/AlignmentPanel.vue | 545 ++- frontend/FoldDiscoSearch.vue | 130 +- frontend/InterfaceAlignmentPanel.vue | 503 ++- frontend/LigandMotifSelection.vue | 33 +- frontend/MDI.js | 3 + frontend/MSA.vue | 101 + frontend/ResidueSpan.vue | 105 +- frontend/ResultFoldDisco.vue | 1 + frontend/SelectToSendPanel.vue | 9 +- frontend/StructureViewer.vue | 633 --- frontend/StructureViewerInterface.vue | 808 ---- frontend/StructureViewerMSA.vue | 856 +--- frontend/StructureViewerMixin.vue | 118 - frontend/StructureViewerMotif.vue | 516 +-- frontend/StructureViewerThumbnail.vue | 646 --- frontend/StructureViewerToolbar.vue | 28 +- frontend/TopHitEntries.vue | 11 +- frontend/TopHits.vue | 36 +- frontend/Utilities.js | 150 +- frontend/alignmentPanelUtils.js | 71 + frontend/molstar/StructureViewer.vue | 365 ++ frontend/molstar/StructureViewerThumbnail.vue | 463 ++ frontend/molstar/folddiscoResult.js | 458 ++ frontend/molstar/foldmasonResult.js | 646 +++ frontend/molstar/foldseekArrows.js | 185 + frontend/molstar/foldseekData.js | 181 + frontend/molstar/foldseekResult.js | 632 +++ frontend/molstar/foldseekSelections.js | 337 ++ frontend/molstar/foldseekSuperposition.js | 124 + frontend/molstar/foldseekUtilities.js | 61 + frontend/molstar/viewerHelpers.js | 90 + frontend/webpack.frontend.config.js | 3 +- package-lock.json | 4021 +++++++++++++++-- package.json | 2 +- 35 files changed, 8700 insertions(+), 4239 deletions(-) delete mode 100644 frontend/StructureViewer.vue delete mode 100644 frontend/StructureViewerInterface.vue delete mode 100644 frontend/StructureViewerMixin.vue delete mode 100644 frontend/StructureViewerThumbnail.vue create mode 100644 frontend/alignmentPanelUtils.js create mode 100644 frontend/molstar/StructureViewer.vue create mode 100644 frontend/molstar/StructureViewerThumbnail.vue create mode 100644 frontend/molstar/folddiscoResult.js create mode 100644 frontend/molstar/foldmasonResult.js create mode 100644 frontend/molstar/foldseekArrows.js create mode 100644 frontend/molstar/foldseekData.js create mode 100644 frontend/molstar/foldseekResult.js create mode 100644 frontend/molstar/foldseekSelections.js create mode 100644 frontend/molstar/foldseekSuperposition.js create mode 100644 frontend/molstar/foldseekUtilities.js create mode 100644 frontend/molstar/viewerHelpers.js diff --git a/frontend/Alignment.vue b/frontend/Alignment.vue index 0d8bae10..fdb6d25d 100644 --- a/frontend/Alignment.vue +++ b/frontend/Alignment.vue @@ -5,6 +5,16 @@ Q {{padNumber(getQueryRowStartPos(i), (Math.max(alignment.qStartPos, alignment.dbStartPos) + alignment.alnLength+"").length, ' ')}} {{alignment.qAln.substring((i-1)*lineLen, (i-1)*lineLen+lineLen)}}T {{padNumber(getTargetRowStartPos(i), (Math.max(alignment.qStartPos, alignment.dbStartPos) + alignment.alnLength+"").length, ' ')}} {{alignment.dbAln.substring((i-1)*lineLen, (i-1)*lineLen+lineLen)}} @@ -55,15 +71,28 @@ export default { 'targetMap', 'alnIndex', 'highlights', + 'queryHighlights', + 'interfaceHighlights', + 'queryInterfaceHighlights', + 'hover', 'colorscheme' ], components: { ResidueSpan }, methods: { - getSelectionStart(i) { - return (i > 0 && i <= this.highlights.length) ? this.highlights[i-1][0] : 0; + getSelectionStart(i, side) { + const highlights = side === 'query' ? this.queryHighlights : this.highlights; + return (i > 0 && i <= highlights.length) ? highlights[i-1][0] : 0; }, - getSelectionEnd(i) { - return (i > 0 && i <= this.highlights.length) ? this.highlights[i-1][1] : 0; + getSelectionEnd(i, side) { + const highlights = side === 'query' ? this.queryHighlights : this.highlights; + return (i > 0 && i <= highlights.length) ? highlights[i-1][1] : 0; + }, + getHoverOffset(i, side) { + return this.hover?.side === side && this.hover?.lineNo === i ? this.hover.offset : null; + }, + getInterfaceRanges(i, side) { + const highlights = side === 'query' ? this.queryInterfaceHighlights : this.interfaceHighlights; + return (i > 0 && highlights && i <= highlights.length) ? highlights[i - 1] : []; }, // Get the index of a given residue in the alignment @@ -89,11 +118,23 @@ export default { padNumber(nr, n, str){ return Array(n - String(nr).length + 1).join(str || '0') + nr }, - onSelectStart(event, alnIndex, lineNo) { - this.$emit('residueSelectStart', event, alnIndex, lineNo); + onSelectStart(event, alnIndex, lineNo, side) { + this.$emit('residueSelectStart', event, alnIndex, lineNo, side); + }, + onPointerUp(event, alnIndex, lineNo, side) { + this.$emit('residuePointerUp', event, alnIndex, lineNo, side); + }, + onPointerDown(event, alnIndex, lineNo, side) { + this.$emit('residuePointerDown', event, alnIndex, lineNo, side); + }, + onPointerMove(event, alnIndex, lineNo, side) { + this.$emit('residuePointerMove', event, alnIndex, lineNo, side); + }, + onPointerLeave(event, alnIndex, lineNo, side) { + this.$emit('residuePointerLeave', event, alnIndex, lineNo, side); }, - onPointerUp(event, alnIndex, lineNo) { - this.$emit('residuePointerUp', event, alnIndex, lineNo); + onClickHighlight(event, alnIndex, lineNo, side) { + this.$emit('residueClickHighlight', event, alnIndex, lineNo, side); } }, } @@ -111,7 +152,8 @@ export default { .inselection, .inselection * { user-select: none; } -.inselection span.target, span.target * { +.inselection span.query, .inselection span.query *, +.inselection span.target, .inselection span.target * { user-select: text !important; } .alignment-wrapper-inner .line { diff --git a/frontend/AlignmentPanel.vue b/frontend/AlignmentPanel.vue index edda4b18..27cd6473 100644 --- a/frontend/AlignmentPanel.vue +++ b/frontend/AlignmentPanel.vue @@ -30,7 +30,7 @@ - Select target residues to highlight their structure.
+ Select query or target residues to highlight their structure.
Click on highlighted sequences to dehighlight the corresponding chain.
@@ -45,72 +45,86 @@ :queryMap="queryMaps[index]" :targetMap="targetMaps[index]" :highlights="highlights[index]" + :queryHighlights="queryHighlights[index]" + :hover="getAlignmentHover(index)" :colorscheme="colorscheme" ref="alignments" @residueSelectStart="onResidueSelectStart" + @residuePointerDown="onResiduePointerDown" @residuePointerUp="onResiduePointerUp" + @residuePointerMove="onResiduePointerMove" + @residuePointerLeave="onResiduePointerLeave" + @residueClickHighlight="onResidueClickHighlight" />
- + class="structure-panel" + :scene="foldseekResult" + :scene-input="structureSceneInput" + :toolbar="structureToolbar" + bg-color-light="white" + bg-color-dark="#1E1E1E" + @makeImage="handleMakeImage" + @makeCIF="handleMakeCIF" + @toggleQuery="handleToggleQuery" + @toggleTarget="handleToggleTarget" + @toggleArrows="handleToggleArrows" + @sceneEvent="handleStructureSceneEvent" + @sceneState="handleStructureSceneState" + > + +
@@ -300,8 +590,36 @@ export default { .alignment-structure-wrapper { min-width:450px; + width: 500px; + height: 400px; + flex: 0 0 500px; margin: 0; margin-bottom: auto; + overflow: hidden; +} + +.structure-panel { + width: 100%; + height: 100%; + margin: 0 auto; +} + +.tmscore-panel { + position: absolute; + width: 100%; + top: 0; + left: 0; + z-index: 2; + pointer-events: none; +} + +.tmscore-panel .left-cell { + text-align: right; + padding-right: 0.35em; +} + +.tmscore-panel .right-cell { + text-align: left; } @media screen and (max-width: 960px) { @@ -335,9 +653,14 @@ span.selected { box-shadow: 0 0 .4em .1em rgba(0, 255, 255, 0.5); cursor: pointer; } +span.hovered { + border-radius: 4px; + background-color: rgba(255, 64, 255, 0.16); + box-shadow: 0 0 .3em .08em rgba(255, 64, 255, 0.55); +} /* TODO Some sort of banding thing here? */ /* .alignment-wrapper-inner:nth-child(odd) span.selected { background-color: rgba(0, 255, 100, 0.1); box-shadow: 0 0 .4em .1em rgba(0, 255, 100, 0.5); } */ - \ No newline at end of file + diff --git a/frontend/FoldDiscoSearch.vue b/frontend/FoldDiscoSearch.vue index 819c0934..50fcff7c 100644 --- a/frontend/FoldDiscoSearch.vue +++ b/frontend/FoldDiscoSearch.vue @@ -184,24 +184,128 @@ import { StorageWrapper, HistoryMixin } from './lib/HistoryMixin.js'; import { BlobDatabase } from './lib/BlobDatabase.js'; import Databases from './Databases.vue'; import QueryTextarea from "./QueryTextarea.vue"; -import {autoLoad} from 'ngl'; import MotifSelection from "./MotifSelection.vue"; import LigandMotifSelection from "./LigandMotifSelection.vue"; const db = BlobDatabase(); const storage = new StorageWrapper("folddisco"); -async function getStructure(data) { - var ext = 'pdb'; - if (data[0] == "#" || data.startsWith("data_")) { - ext = 'cif'; - data = data.replaceAll("_chem_comp.", "_chem_comp_SKIP_HACK."); - data = data.replaceAll("_struct_conf.", "_struct_conf_SKIP_HACK."); - data = data.replaceAll("_struct_conn.", "_struct_conn_SKIP_HACK."); +function makeStructureFromAtoms(atoms) { + const residues = new Map(); + for (const atom of atoms) { + const key = `${atom.chainname}:${atom.resno}:${atom.resname}:${atom.record}`; + if (!residues.has(key)) { + residues.set(key, { + chainname: atom.chainname, + resno: atom.resno, + resname: atom.resname, + record: atom.record, + atoms: [], + isPolymer: () => atom.record === 'ATOM', + isWater: () => ['HOH', 'WAT', 'H2O'].includes(atom.resname), + }); + } + residues.get(key).atoms.push(atom); + } + return { + residues: Array.from(residues.values()), + atoms, + eachResidue(callback) { + this.residues.forEach(callback); + }, + }; +} + +function parsePdbStructure(data) { + const atoms = []; + for (const line of String(data || '').split('\n')) { + const record = line.slice(0, 6).trim(); + if (record !== 'ATOM' && record !== 'HETATM') continue; + const atom = { + index: atoms.length, + record, + atomname: line.slice(12, 16).trim(), + resname: line.slice(17, 20).trim(), + chainname: line.slice(21, 22).trim() || '_', + resno: Number.parseInt(line.slice(22, 26).trim(), 10), + x: Number.parseFloat(line.slice(30, 38).trim()), + y: Number.parseFloat(line.slice(38, 46).trim()), + z: Number.parseFloat(line.slice(46, 54).trim()), + }; + if (!Number.isFinite(atom.resno)) continue; + atoms.push(atom); + } + return makeStructureFromAtoms(atoms); +} + +function tokenizeCif(text) { + const tokens = []; + const re = /'(?:[^']|'')*'|"(?:[^"]|"")*"|\S+/g; + for (const line of String(text || '').split('\n')) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) continue; + let match; + while ((match = re.exec(line))) { + let token = match[0]; + if ((token[0] === "'" && token[token.length - 1] === "'") + || (token[0] === '"' && token[token.length - 1] === '"')) { + token = token.slice(1, -1); + } + tokens.push(token); + } } - var blob = new Blob([data], { type: 'text/plain' }); - var promise = autoLoad(blob, {ext : ext, name: 'query', firstModelOnly: true}); - return promise.then(structure => structure.getStructure()); + return tokens; +} + +function parseCifStructure(data) { + const tokens = tokenizeCif(data); + const atoms = []; + for (let i = 0; i < tokens.length; i++) { + if (tokens[i] !== 'loop_') continue; + const headers = []; + i++; + while (i < tokens.length && tokens[i].startsWith('_')) { + headers.push(tokens[i]); + i++; + } + if (!headers.some(header => header.startsWith('_atom_site.'))) { + while (i < tokens.length && tokens[i] !== 'loop_') i++; + i--; + continue; + } + const h = Object.fromEntries(headers.map((header, index) => [header, index])); + const width = headers.length; + while (i + width <= tokens.length && tokens[i] !== 'loop_') { + if (tokens[i]?.startsWith('_')) break; + const row = tokens.slice(i, i + width); + const record = row[h['_atom_site.group_PDB']] || 'ATOM'; + const resno = Number.parseInt(row[h['_atom_site.auth_seq_id']] || row[h['_atom_site.label_seq_id']], 10); + const atom = { + index: atoms.length, + record, + atomname: row[h['_atom_site.auth_atom_id']] || row[h['_atom_site.label_atom_id']] || '', + resname: row[h['_atom_site.auth_comp_id']] || row[h['_atom_site.label_comp_id']] || '', + chainname: row[h['_atom_site.auth_asym_id']] || row[h['_atom_site.label_asym_id']] || '_', + resno, + x: Number.parseFloat(row[h['_atom_site.Cartn_x']]), + y: Number.parseFloat(row[h['_atom_site.Cartn_y']]), + z: Number.parseFloat(row[h['_atom_site.Cartn_z']]), + }; + if (Number.isFinite(atom.resno) && Number.isFinite(atom.x) && Number.isFinite(atom.y) && Number.isFinite(atom.z)) { + atoms.push(atom); + } + i += width; + } + i--; + } + return makeStructureFromAtoms(atoms); +} + +function parseStructure(data) { + const text = String(data || '').trimStart(); + return text[0] === '#' || text.startsWith('data_') + ? parseCifStructure(text) + : parsePdbStructure(text); } function setDefaultMotif(structure) { @@ -211,7 +315,7 @@ function setDefaultMotif(structure) { var motifList = new Set(); structure.eachResidue(r => { - motifList.add(`${r.chainname}${r.resno}`); + if (r.isPolymer()) motifList.add(`${r.chainname}${r.resno}`); }); return Array.from(motifList).join(','); } @@ -342,7 +446,7 @@ export default { async query(value) { let prev = await db.getItem('folddisco.query') db.setItem('folddisco.query', value); - this.queryStructure = await getStructure(this.query); + this.queryStructure = parseStructure(this.query); if (prev && prev == value) { return; } diff --git a/frontend/InterfaceAlignmentPanel.vue b/frontend/InterfaceAlignmentPanel.vue index 91db1c63..2921edc8 100644 --- a/frontend/InterfaceAlignmentPanel.vue +++ b/frontend/InterfaceAlignmentPanel.vue @@ -1,73 +1,44 @@
-
@@ -75,213 +46,308 @@ - - diff --git a/frontend/LigandMotifSelection.vue b/frontend/LigandMotifSelection.vue index de84721a..3fd5ddb1 100644 --- a/frontend/LigandMotifSelection.vue +++ b/frontend/LigandMotifSelection.vue @@ -61,7 +61,6 @@ - - - - \ No newline at end of file diff --git a/frontend/StructureViewerInterface.vue b/frontend/StructureViewerInterface.vue deleted file mode 100644 index ccbe01aa..00000000 --- a/frontend/StructureViewerInterface.vue +++ /dev/null @@ -1,808 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/frontend/StructureViewerMSA.vue b/frontend/StructureViewerMSA.vue index 4f95a8a5..7d5c4768 100644 --- a/frontend/StructureViewerMSA.vue +++ b/frontend/StructureViewerMSA.vue @@ -1,140 +1,50 @@ diff --git a/frontend/StructureViewerMixin.vue b/frontend/StructureViewerMixin.vue deleted file mode 100644 index 38fae2f4..00000000 --- a/frontend/StructureViewerMixin.vue +++ /dev/null @@ -1,118 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/StructureViewerMotif.vue b/frontend/StructureViewerMotif.vue index dce7b57c..fee90078 100644 --- a/frontend/StructureViewerMotif.vue +++ b/frontend/StructureViewerMotif.vue @@ -1,448 +1,164 @@ diff --git a/frontend/StructureViewerThumbnail.vue b/frontend/StructureViewerThumbnail.vue deleted file mode 100644 index 318bd31e..00000000 --- a/frontend/StructureViewerThumbnail.vue +++ /dev/null @@ -1,646 +0,0 @@ - - - - - diff --git a/frontend/StructureViewerToolbar.vue b/frontend/StructureViewerToolbar.vue index 95cdb7ef..f9662493 100644 --- a/frontend/StructureViewerToolbar.vue +++ b/frontend/StructureViewerToolbar.vue @@ -2,13 +2,13 @@
- {{ $MDI.SavePDB }} -  Save PDB + {{ structureFileIcon }} +  {{ structureFileButtonLabel }} {{ $MDI.AxisZRotateCounterclockwise }} @@ -91,6 +90,7 @@ export default { showArrows: { type: Boolean, default: false }, isFullscreen: { type: Boolean, default: false }, isSpinning: { type: Boolean, default: true }, + disableCIFButton: { default: null }, disablePDBButton: { type: Boolean, default: false }, disableSpinButton: { type: Boolean, default: false }, disableImageButton: { type: Boolean, default: false }, @@ -99,8 +99,19 @@ export default { disableArrowButton: { type: Boolean, default: false }, disableResetButton: { type: Boolean, default: false }, disableFullscreenButton: { type: Boolean, default: false }, + cifButtonLabel: { type: String, default: null }, + pdbButtonLabel: { type: String, default: 'Save PDB' }, }, computed: { + structureFileButtonDisabled: function() { + return this.disableCIFButton !== null ? this.disableCIFButton : this.disablePDBButton; + }, + structureFileButtonLabel: function() { + return this.cifButtonLabel || this.pdbButtonLabel; + }, + structureFileIcon: function() { + return this.cifButtonLabel ? this.$MDI.SaveCIF : this.$MDI.SavePDB; + }, toolbarIconProps: function() { return (this.isFullscreen) ? { 'right': true @@ -122,7 +133,8 @@ export default { handleClickSpin() { this.$emit("toggleSpin"); }, - handleClickMakePDB() { + handleClickMakeCIF() { + this.$emit("makeCIF"); this.$emit("makePDB"); }, handleClickMakeImage() { @@ -158,4 +170,4 @@ export default { z-index: 1; left: 0; } - \ No newline at end of file + diff --git a/frontend/TopHitEntries.vue b/frontend/TopHitEntries.vue index 150670ef..67210dd4 100644 --- a/frontend/TopHitEntries.vue +++ b/frontend/TopHitEntries.vue @@ -20,7 +20,7 @@ + + diff --git a/frontend/molstar/StructureViewerThumbnail.vue b/frontend/molstar/StructureViewerThumbnail.vue new file mode 100644 index 00000000..6d663d5f --- /dev/null +++ b/frontend/molstar/StructureViewerThumbnail.vue @@ -0,0 +1,463 @@ + + + + + diff --git a/frontend/molstar/folddiscoResult.js b/frontend/molstar/folddiscoResult.js new file mode 100644 index 00000000..6619f89a --- /dev/null +++ b/frontend/molstar/folddiscoResult.js @@ -0,0 +1,458 @@ +import { OrderedSet } from 'molstar/lib/mol-data/int'; +import { Mat4 } from 'molstar/lib/mol-math/linear-algebra'; +import { QueryContext, StructureElement, StructureProperties, StructureSelection } from 'molstar/lib/mol-model/structure'; +import { to_mmCIF } from 'molstar/lib/mol-model/structure/export/mmcif'; +import { StateTransforms } from 'molstar/lib/mol-plugin-state/transforms'; +import { compile } from 'molstar/lib/mol-script/runtime/query/compiler'; +import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder'; +import { Color } from 'molstar/lib/mol-util/color'; +import { mockPDB } from './foldseekUtilities.js'; + +const QueryColor = 0x1e88e5; +const TargetColor = 0xffc107; +const QueryLightColor = 0xa5cff5; +const TargetLightColor = 0xffe699; +const MotifRadius = 0.28; + +function processStructure(raw) { + const text = raw?.trimStart?.() || ''; + const format = text[0] === '#' || text.startsWith('data_') ? 'mmcif' : 'pdb'; + return { + // Folddisco PDB ATOM rows are whitespace-token valid, but Mol* reads + // coordinates from fixed columns. Re-emit strict columns before parsing. + data: format === 'pdb' + ? normalizePdbText(text) + : text, + format, + }; +} + +function normalizePdbText(text) { + return text + .split(/\r?\n/) + .map(normalizePdbLine) + .filter(Boolean) + .join('\n'); +} + +function normalizePdbLine(line) { + const recordLine = line.trimStart(); + if (recordLine.startsWith('ATOM') || recordLine.startsWith('HETATM')) { + const atom = parseAtomLine(recordLine); + return atom ? formatPdbAtomLine(atom) : null; + } + if (recordLine.startsWith('TER') || recordLine.startsWith('MODEL') || recordLine.startsWith('ENDMDL') || recordLine.startsWith('END')) { + return recordLine.padEnd(80, ' '); + } + if (recordLine.startsWith('HELIX') || recordLine.startsWith('SHEET') || recordLine.startsWith('SEQRES')) { + return recordLine.padEnd(80, ' '); + } + return null; +} + +function parseAtomLine(line) { + const parts = line.trim().split(/\s+/); + if (parts.length < 9) return null; + const hasChain = !/^-?\d+$/.test(parts[4]); + const coordOffset = hasChain ? 6 : 5; + const atom = { + record: parts[0] === 'HETATM' ? 'HETATM' : 'ATOM', + serial: Number.parseInt(parts[1], 10), + name: parts[2], + altLoc: '', + resName: parts[3], + chain: hasChain ? parts[4] : 'A', + resSeq: Number.parseInt(hasChain ? parts[5] : parts[4], 10), + iCode: '', + x: Number.parseFloat(parts[coordOffset]), + y: Number.parseFloat(parts[coordOffset + 1]), + z: Number.parseFloat(parts[coordOffset + 2]), + occupancy: Number.parseFloat(parts[coordOffset + 3]), + bFactor: Number.parseFloat(parts[coordOffset + 4]), + element: parts[coordOffset + 5] || '', + charge: parts[coordOffset + 6] || '', + }; + if (!atom.name || !atom.resName || !Number.isFinite(atom.resSeq) || !coordinatesAreFinite(atom)) return null; + return atom; +} + +function coordinatesAreFinite(atom) { + return Number.isFinite(atom.x) && Number.isFinite(atom.y) && Number.isFinite(atom.z); +} + +function formatPdbAtomLine(atom) { + const serial = Number.isFinite(atom.serial) ? atom.serial : 1; + const occupancy = Number.isFinite(atom.occupancy) ? atom.occupancy : 1; + const bFactor = Number.isFinite(atom.bFactor) ? atom.bFactor : 0; + const element = (atom.element || atom.name.replace(/[^A-Za-z]/g, '').slice(0, 2)).toUpperCase(); + return [ + atom.record.padEnd(6).slice(0, 6), + String(serial).padStart(5).slice(-5), + ' ', + formatAtomName(atom.name, element), + (atom.altLoc || ' ').slice(0, 1), + atom.resName.padStart(3).slice(-3), + ' ', + (atom.chain || 'A').slice(0, 1), + String(atom.resSeq).padStart(4).slice(-4), + (atom.iCode || ' ').slice(0, 1), + ' ', + atom.x.toFixed(3).padStart(8), + atom.y.toFixed(3).padStart(8), + atom.z.toFixed(3).padStart(8), + occupancy.toFixed(2).padStart(6), + bFactor.toFixed(2).padStart(6), + ' ', + element.padStart(2).slice(-2), + (atom.charge || ' ').padStart(2).slice(-2), + ].join(''); +} + +function formatAtomName(name, element) { + const value = String(name || '').slice(0, 4); + return element.length === 1 && value.length < 4 + ? ` ${value.padEnd(3)}` + : value.padStart(4); +} + +async function loadStructure(plugin, source, label) { + const raw = await plugin.builders.data.rawData( + { data: source.data, label }, + { state: { isGhost: true } }, + ); + const trajectory = await plugin.builders.structure.parseTrajectory(raw, source.format); + const model = await plugin.builders.structure.createModel(trajectory); + return plugin.builders.structure.createStructure(model); +} + +function transformParams(alignment) { + const t = String(alignment?.tmat || '').split(',').map(Number); + const u = String(alignment?.umat || '').split(',').map(Number); + if (t.length < 3 || u.length < 9 || t.some(Number.isNaN) || u.some(Number.isNaN)) return null; + return { + t, + u: [ + [u[0], u[1], u[2]], + [u[3], u[4], u[5]], + [u[6], u[7], u[8]], + ], + }; +} + +function makeMat4({ t, u }) { + return Mat4.ofRows([ + [u[0][0], u[0][1], u[0][2], t[0]], + [u[1][0], u[1][1], u[1][2], t[1]], + [u[2][0], u[2][1], u[2][2], t[2]], + [0, 0, 0, 1], + ]); +} + +async function transformStructure(plugin, structure, params) { + if (!params) return structure; + return plugin.state.data.build() + .to(structure.ref) + .insert(StateTransforms.Model.TransformStructureConformation, { + transform: { + name: 'matrix', + params: { data: makeMat4(params), transpose: false }, + }, + }) + .commit(); +} + +function representationTypeParams(type, alpha = 1) { + if (type === 'ball-and-stick') { + return { + alpha, + sizeFactor: MotifRadius, + sizeAspectRatio: 0.7, + aromaticBonds: true, + material: { metalness: 0, roughness: 0.5, bumpiness: 0.1 }, + }; + } + return { + alpha, + quality: 'higher', + sizeFactor: 0.2, + helixProfile: 'rounded', + nucleicProfile: 'rounded', + linearSegments: 10, + radialSegments: 16, + material: { metalness: 0, roughness: 0.55, bumpiness: 0.1 }, + }; +} + +async function addRepresentation(plugin, structure, label, expression, color, alpha = 1, type = 'cartoon') { + if (!expression) return null; + const component = await plugin.builders.structure.tryCreateComponentFromExpression( + structure, + expression, + label, + { label }, + ); + if (!component) return null; + + return plugin.builders.structure.representation.addRepresentation(component, { + type, + color: 'uniform', + colorParams: { value: Color(color) }, + typeParams: representationTypeParams(type, alpha), + }); +} + +function parseMotif(value = '') { + const residues = []; + const chains = new Set(); + const seen = new Set(); + for (const raw of String(value).split(',')) { + const item = raw.trim(); + if (!item || item === '_') continue; + const [chain, numeric] = splitAlphaNum(item); + const residue = Number(numeric); + if (!chain || !Number.isFinite(residue)) continue; + const key = `${chain}:${residue}`; + if (seen.has(key)) continue; + seen.add(key); + residues.push({ chain, residue }); + chains.add(chain); + } + return { residues, chains: Array.from(chains) }; +} + +function splitAlphaNum(str) { + const value = String(str || ''); + const len = value.length; + let i = 0; + while (i < len) { + const cc = value.charCodeAt(i); + if (cc >= 48 && cc <= 57) break; + i++; + } + let j = i; + while (j < len) { + const cc = value.charCodeAt(j); + if (cc < 48 || cc > 57) break; + j++; + } + return [value.slice(0, i), value.slice(i, j)]; +} + +function atomGroupExpression(chain, residue) { + const { or } = MS.core.logic; + const { eq } = MS.core.rel; + const { macromolecular } = MS.struct.atomProperty; + return MS.struct.generator.atomGroups({ + 'chain-test': or([ + eq([macromolecular.auth_asym_id(), chain]), + eq([macromolecular.label_asym_id(), chain]), + ]), + 'residue-test': or([ + eq([macromolecular.auth_seq_id(), residue]), + eq([macromolecular.label_seq_id(), residue]), + ]), + }); +} + +function chainExpression(chain) { + const { or } = MS.core.logic; + const { eq } = MS.core.rel; + const { macromolecular } = MS.struct.atomProperty; + return MS.struct.generator.atomGroups({ + 'chain-test': or([ + eq([macromolecular.auth_asym_id(), chain]), + eq([macromolecular.label_asym_id(), chain]), + ]), + }); +} + +function mergeExpressions(expressions) { + const valid = expressions.filter(Boolean); + if (valid.length === 0) return null; + if (valid.length === 1) return valid[0]; + return MS.struct.combinator.merge(valid.map(expression => MS.struct.modifier.union([expression]))); +} + +function motifExpression(motif) { + return mergeExpressions(parseMotif(motif).residues.map(({ chain, residue }) => atomGroupExpression(chain, residue))); +} + +function chainSetExpression(motif) { + return mergeExpressions(parseMotif(motif).chains.map(chainExpression)); +} + +function nonChainSetExpression(motif) { + const chains = chainSetExpression(motif); + if (!chains) return null; + return MS.struct.modifier.exceptBy({ + 0: MS.struct.generator.all(), + by: chains, + }); +} + +async function deleteReprGroup(plugin, state) { + for (const ref of state.reprRefs || []) { + await plugin.state.data.build().delete(ref).commit(); + } + state.reprRefs = []; +} + +async function addRepr(plugin, state, structure, label, expression, color, alpha, type) { + const repr = await addRepresentation(plugin, structure, label, expression, color, alpha, type); + const ref = repr?.ref || repr?.cell?.transform?.ref; + if (ref) state.reprRefs.push(ref); +} + +function targetMotifSource(alignment) { + if (!alignment?.tCa) return null; + return { + data: mockPDB(alignment.tCa, '', 'A'), + format: 'pdb', + label: 'targetMotifStructure', + }; +} + +async function applyRepresentations(plugin, state, input) { + const key = `${input.showQuery}:${input.showTarget}:${input.alignment?.queryresidues}:${input.alignment?.targetresidues}`; + if (state.reprKey === key) return; + state.reprKey = key; + await deleteReprGroup(plugin, state); + + const queryMotif = motifExpression(input.alignment?.queryresidues); + const targetMotif = state.targetMotif ? MS.struct.generator.all() : motifExpression(input.alignment?.targetresidues); + const queryChains = chainSetExpression(input.alignment?.queryresidues); + const targetChains = chainSetExpression(input.alignment?.targetresidues); + + if (input.showQuery === 2) { + await addRepr(plugin, state, state.query, 'query-rest', nonChainSetExpression(input.alignment?.queryresidues), QueryLightColor, 0.3, 'cartoon'); + } + if (input.showQuery >= 1) { + await addRepr(plugin, state, state.query, 'query-matched', queryChains, QueryLightColor, 0.8, 'cartoon'); + } + await addRepr(plugin, state, state.query, 'query-motif', queryMotif, QueryColor, 1, 'ball-and-stick'); + + if (input.showTarget === 2) { + await addRepr(plugin, state, state.target, 'target-rest', nonChainSetExpression(input.alignment?.targetresidues), TargetLightColor, 0.3, 'cartoon'); + } + if (input.showTarget >= 1) { + await addRepr(plugin, state, state.target, 'target-matched', targetChains, TargetLightColor, 0.8, 'cartoon'); + } + await addRepr(plugin, state, state.targetMotif || state.target, 'target-motif', targetMotif, TargetColor, 1, 'ball-and-stick'); +} + +async function rebuildScene(plugin, state, input) { + await plugin.clear(); + state.query = null; + state.target = null; + state.targetMotif = null; + state.reprRefs = []; + state.reprKey = null; + + state.query = await loadStructure(plugin, processStructure(input.queryPdb), 'queryStructure'); + const targetSource = processStructure(input.targetPdb); + const targetTransform = transformParams(input.alignment); + state.target = await loadStructure(plugin, targetSource, 'targetStructure'); + state.target = await transformStructure(plugin, state.target, targetTransform); + const motifSource = targetMotifSource(input.alignment); + if (motifSource) { + state.targetMotif = await loadStructure(plugin, motifSource, 'targetMotifStructure'); + state.targetMotif = await transformStructure(plugin, state.targetMotif, targetTransform); + } + await applyRepresentations(plugin, state, input); + const loci = lociFromExpression(state.query, motifExpression(input.alignment?.queryresidues)); + if (loci) plugin.managers.camera.focusLoci(loci, { durationMs: 100, extraRadius: 10, minRadius: 5 }); + else plugin.managers.camera.reset(); +} + +function lociFromExpression(structureRef, expression) { + const structure = structureRef?.cell?.obj?.data; + if (!structure || !expression) return null; + const query = compile(expression); + const selection = query(new QueryContext(structure)); + const loci = StructureSelection.toLociWithSourceUnits(selection); + return StructureElement.Loci.isEmpty(loci) ? null : loci; +} + +function structureEvent(state, current, type) { + if (!StructureElement.Loci.is(current?.loci) || StructureElement.Loci.isEmpty(current.loci)) { + return { type, hasLoci: false }; + } + const root = current.loci.structure?.root || current.loci.structure; + const side = state.query?.cell?.obj?.data?.root === root || state.query?.cell?.obj?.data === root + ? 'query' + : 'target'; + const entry = current.loci.elements?.[0]; + if (!entry) return { type, side, hasLoci: true }; + const loc = StructureElement.Location.create(current.loci.structure); + loc.unit = entry.unit; + loc.element = OrderedSet.getAt(entry.indices, 0); + return { + type, + side, + hasLoci: true, + chain: StructureProperties.chain.auth_asym_id(loc) || StructureProperties.chain.label_asym_id(loc) || '', + residue: StructureProperties.residue.auth_seq_id(loc) || StructureProperties.residue.label_seq_id(loc), + residueName: StructureProperties.residue.label_comp_id(loc) || StructureProperties.residue.auth_comp_id(loc) || '', + }; +} + +function setCurrentHover(plugin, current) { + const loci = current?.loci; + if (!StructureElement.Loci.is(loci) || StructureElement.Loci.isEmpty(loci)) { + plugin.managers.interactivity.lociHighlights.clearHighlights(); + return; + } + plugin.managers.interactivity.lociHighlights.highlightOnly({ loci }, false); +} + +function makeCIF(state) { + const blocks = []; + const query = state.query?.cell?.obj?.data; + const target = state.target?.cell?.obj?.data; + if (query) blocks.push(to_mmCIF('query', query, false, { copyAllCategories: false })); + if (target) blocks.push(to_mmCIF('target', target, false, { copyAllCategories: false })); + return blocks.join('\n'); +} + +export const folddiscoResult = { + async mount() { + return {}; + }, + + async update(plugin, state, input) { + const key = `${input.alignment?.dbkey || input.alignment?.target}:${input.alignment?.queryresidues}:${input.alignment?.targetresidues}`; + if (state.loadKey !== key) { + state.loadKey = key; + state.queryPdbSource = input.queryPdb; + state.targetPdbSource = input.targetPdb; + await rebuildScene(plugin, state, input); + return; + } + if (state.queryPdbSource !== input.queryPdb || state.targetPdbSource !== input.targetPdb) { + state.queryPdbSource = input.queryPdb; + state.targetPdbSource = input.targetPdb; + await rebuildScene(plugin, state, input); + return; + } + await applyRepresentations(plugin, state, input); + }, + + onHover(plugin, state, input, event) { + setCurrentHover(plugin, event?.current); + return structureEvent(state, event?.current, 'structure-hover'); + }, + + resetView(plugin, state, input) { + const loci = lociFromExpression(state.query, motifExpression(input.alignment?.queryresidues)); + if (loci) plugin.managers.camera.focusLoci(loci, { durationMs: 250, extraRadius: 10, minRadius: 5 }); + else plugin.managers.camera.reset(); + }, + + async makeCIF(plugin, state) { + return makeCIF(state); + }, + + async dispose(plugin) { + await plugin?.clear?.(); + }, +}; diff --git a/frontend/molstar/foldmasonResult.js b/frontend/molstar/foldmasonResult.js new file mode 100644 index 00000000..84ef60e5 --- /dev/null +++ b/frontend/molstar/foldmasonResult.js @@ -0,0 +1,646 @@ +import { OrderedSet } from 'molstar/lib/mol-data/int'; +import { Mat4 } from 'molstar/lib/mol-math/linear-algebra'; +import { QueryContext, StructureElement, StructureProperties, StructureSelection } from 'molstar/lib/mol-model/structure'; +import { to_mmCIF } from 'molstar/lib/mol-model/structure/export/mmcif'; +import { StateTransforms } from 'molstar/lib/mol-plugin-state/transforms'; +import { compile } from 'molstar/lib/mol-script/runtime/query/compiler'; +import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder'; +import { Color } from 'molstar/lib/mol-util/color'; +import { tmalign, parseMatrix as parseTMMatrix } from 'tmalign-wasm'; +import { pulchra } from 'pulchra-wasm'; +import { mockPDB } from './foldseekUtilities.js'; + +const ReferenceColor = 0x1e88e5; +const RegularColor = 0xffc107; +const MaskColor = 0x666666; +const oneToThree = { + A: 'ALA', R: 'ARG', N: 'ASN', D: 'ASP', C: 'CYS', + E: 'GLU', Q: 'GLN', G: 'GLY', H: 'HIS', I: 'ILE', + L: 'LEU', K: 'LYS', M: 'MET', F: 'PHE', P: 'PRO', + S: 'SER', T: 'THR', W: 'TRP', Y: 'TYR', V: 'VAL', + U: 'SEC', O: 'PYL', X: 'ALA', +}; +const threeToOne = {}; +for (const [one, three] of Object.entries(oneToThree)) { + if (!threeToOne[three]) threeToOne[three] = one; +} + +async function loadStructure(plugin, source) { + const raw = await plugin.builders.data.rawData( + { data: source.data, label: source.label }, + { state: { isGhost: true } }, + ); + const trajectory = await plugin.builders.structure.parseTrajectory(raw, source.format || 'pdb'); + const model = await plugin.builders.structure.createModel(trajectory); + return plugin.builders.structure.createStructure(model); +} + +async function transformStructure(plugin, structure, matrix) { + const transformed = await plugin.state.data.build() + .to(structure) + .apply(StateTransforms.Model.TransformStructureConformation, { + transform: { name: 'matrix', params: { data: matrix, transpose: false } }, + }, { tags: 'foldmason-transform' }) + .commit(); + return transformed; +} + +function representationTypeParams(type, alpha) { + return { + quality: 'higher', + sizeFactor: 0.25, + visuals: ['polymer-trace', 'polymer-gap'], + helixProfile: 'rounded', + nucleicProfile: 'rounded', + linearSegments: 10, + radialSegments: 16, + alpha, + material: { + metalness: 0, + roughness: 0.55, + bumpiness: 0.1, + }, + }; +} + +async function addRepresentation(plugin, structure, label, color, expression, alpha = 1, type = 'cartoon') { + if (!expression) return null; + const component = await plugin.builders.structure.tryCreateComponentFromExpression( + structure, + expression, + label, + { label }, + ); + if (!component) return null; + + const representation = await plugin.builders.structure.representation.addRepresentation(component, { + type, + color: 'uniform', + colorParams: { value: Color(color) }, + typeParams: representationTypeParams(type, alpha), + }); + return { component, representation }; +} + +async function addStructure(plugin, state, input, index) { + const entry = input.entries[index]; + const pdb = await structurePDB(entry); + const source = { data: pdb, format: 'pdb', label: `key-${index}` }; + let structure = await loadStructure(plugin, source); + + if (index !== input.reference && state.referenceStructure) { + const matrix = await superpositionMatrix(input.entries[input.reference], entry); + if (matrix) structure = await transformStructure(plugin, structure, matrix); + } + + const color = index === input.reference ? ReferenceColor : RegularColor; + const item = { + index, + entry, + structure, + base: await addRepresentation(plugin, structure, `foldmason-${index}`, color, MS.struct.generator.all()), + mask: null, + }; + item.mask = await addRepresentation(plugin, structure, `foldmason-${index}-mask`, MaskColor, maskExpression(entry, input.mask)); + state.structures.set(index, item); + if (index === input.reference) state.referenceStructure = structure; +} + +async function deleteRepresentation(plugin, item) { + if (item?.component?.ref) { + await plugin.state.data.build().delete(item.component.ref).commit(); + } +} + +async function removeStructure(plugin, state, index) { + const item = state.structures.get(index); + if (!item) return; + if (item.structure?.ref) { + await plugin.state.data.build().delete(item.structure.ref).commit(); + } + state.structures.delete(index); +} + +async function rebuildScene(plugin, state, input) { + await plugin.clear(); + state.entries = input.entries; + state.reference = input.reference; + state.mask = input.mask; + state.structures = new Map(); + state.referenceStructure = null; + state.focusKey = null; + + if (!input.entries?.length || input.reference < 0) return; + if (input.selection.includes(input.reference)) { + await addStructure(plugin, state, input, input.reference); + } + for (const index of input.selection) { + if (index !== input.reference) await addStructure(plugin, state, input, index); + } + plugin.managers.camera.reset(); +} + +async function updateStructures(plugin, state, input) { + if ( + state.entries !== input.entries + || state.reference !== input.reference + || !state.structures + ) { + await rebuildScene(plugin, state, input); + return; + } + + const next = new Set(input.selection); + for (const index of Array.from(state.structures.keys())) { + if (!next.has(index)) await removeStructure(plugin, state, index); + } + for (const index of input.selection) { + if (!state.structures.has(index)) await addStructure(plugin, state, input, index); + } + if (state.mask !== input.mask) { + state.mask = input.mask; + await updateMasks(plugin, state, input); + } +} + +async function updateMasks(plugin, state, input) { + for (const item of state.structures.values()) { + await deleteRepresentation(plugin, item.mask); + item.mask = await addRepresentation( + plugin, + item.structure, + `foldmason-${item.index}-mask`, + MaskColor, + maskExpression(item.entry, input.mask), + ); + } +} + +async function setSelection(plugin, state, input) { + const loci = lociForColumns(state, input.selectedColumns || []); + plugin.managers.interactivity.lociSelects.deselectAll(); + for (const item of loci) { + plugin.managers.interactivity.lociSelects.select({ loci: item }, false); + } +} + +async function setPreview(plugin, state, input) { + const column = input.previewColumn; + if (!Number.isInteger(column) || column < 0) { + plugin.managers.interactivity.lociHighlights.clearHighlights(); + return; + } + if (Number.isInteger(input.previewStructureIndex) && input.previewStructureIndex >= 0) { + const item = state.structures.get(input.previewStructureIndex); + const loci = item ? lociForItem(item, [column]) : null; + if (loci) { + plugin.managers.interactivity.lociHighlights.highlightOnly({ loci }, false); + } else { + plugin.managers.interactivity.lociHighlights.clearHighlights(); + } + return; + } + const loci = lociForColumns(state, [column]); + if (loci.length === 0) { + plugin.managers.interactivity.lociHighlights.clearHighlights(); + return; + } + plugin.managers.interactivity.lociHighlights.highlightOnly({ loci: loci[0] }, false); +} + +async function setFocus(plugin, state, input) { + const focus = input.focusColumn; + const key = Number.isInteger(focus) ? `${focus}:${input.focusToken || 0}` : null; + if (state.focusKey === key) return; + state.focusKey = key; + if (!Number.isInteger(focus) || focus < 0) return; + + const reference = state.structures.get(input.reference); + if (!reference) return; + const loci = lociForItem(reference, [focus]); + if (!loci) return; + plugin.managers.camera.focusLoci(loci, { + durationMs: 250, + extraRadius: 8, + minRadius: 4, + }); +} + +function lociForColumns(state, columns) { + const loci = []; + for (const item of state.structures?.values?.() || []) { + const itemLoci = lociForItem(item, columns); + if (itemLoci) loci.push(itemLoci); + } + return loci; +} + +function lociForItem(item, columns) { + const expression = columnExpression(item.entry, columns); + if (!expression) return null; + const structure = item.structure?.cell?.obj?.data; + if (!structure) return null; + const query = compile(expression); + const selection = query(new QueryContext(structure)); + const loci = StructureSelection.toLociWithSourceUnits(selection); + return StructureElement.Loci.isEmpty(loci) ? null : loci; +} + +function columnExpression(entry, columns) { + return mergeExpressions(columns.map(column => residueExpressionForColumn(entry, column))); +} + +function maskExpression(entry, mask = []) { + const maskedColumns = []; + for (let i = 0; i < entry.aa.length; i++) { + if (mask[i] === 0) maskedColumns.push(i); + } + return columnExpression(entry, maskedColumns); +} + +function residueExpressionForColumn(entry, column) { + if (!Number.isInteger(column) || entry.aa?.[column] === '-') return null; + const residueIndex = residueIndexForColumn(entry.aa, column); + if (residueIndex < 1) return null; + const chain = entry.chains?.[residueIndex] || 'A'; + const residue = entry.resns?.[residueIndex] || residueIndex; + return mergeExpressions([ + atomGroupExpression(chain, residue), + atomGroupExpression(null, residueIndex), + ]); +} + +function atomGroupExpression(chain, residue) { + const { or } = MS.core.logic; + const { eq } = MS.core.rel; + const { macromolecular } = MS.struct.atomProperty; + const tests = { + 'residue-test': or([ + eq([macromolecular.auth_seq_id(), residue]), + eq([macromolecular.label_seq_id(), residue]), + ]), + }; + if (chain) { + tests['chain-test'] = or([ + eq([macromolecular.auth_asym_id(), chain]), + eq([macromolecular.label_asym_id(), chain]), + ]); + } + return MS.struct.generator.atomGroups(tests); +} + +function mergeExpressions(expressions) { + const valid = expressions.filter(Boolean); + if (valid.length === 0) return null; + if (valid.length === 1) return valid[0]; + return MS.struct.combinator.merge(valid.map(expression => MS.struct.modifier.union([expression]))); +} + +function structureEvent(state, current, type) { + if (!StructureElement.Loci.is(current?.loci) || StructureElement.Loci.isEmpty(current.loci)) { + return { type, column: -1, hasLoci: false }; + } + const item = itemForLoci(state, current.loci); + if (!item) return { type, column: -1, hasLoci: true, residue: null }; + + const residue = residueFromLoci(current.loci); + if (!residue) { + return { + type, + index: item.index, + column: -1, + hasLoci: true, + residue: null, + name: item.entry.name, + }; + } + + const column = columnForResidue(item.entry, residue.chain, residue.residue); + const residueInfo = residueInfoForColumn(item.entry, column) || { + chain: residue.chain, + residue: residue.residue, + oneLetter: residue.oneLetter, + threeLetter: residue.threeLetter, + }; + return { + type, + index: item.index, + column, + hasLoci: true, + name: item.entry.name, + residue: residueInfo, + }; +} + +function itemForLoci(state, loci) { + const root = loci.structure?.root || loci.structure; + for (const item of state.structures?.values?.() || []) { + const structure = item.structure?.cell?.obj?.data; + if (structure === loci.structure || structure === root || structure?.root === root) return item; + } + return null; +} + +function residueFromLoci(loci) { + const entry = loci.elements?.[0]; + if (!entry || OrderedSet.size(entry.indices) === 0) return null; + const loc = StructureElement.Location.create(loci.structure); + loc.unit = entry.unit; + loc.element = OrderedSet.getAt(entry.indices, 0); + const threeLetter = StructureProperties.residue.label_comp_id(loc) + || StructureProperties.residue.auth_comp_id(loc) + || ''; + return { + chain: StructureProperties.chain.auth_asym_id(loc) || StructureProperties.chain.label_asym_id(loc) || '', + residue: StructureProperties.residue.auth_seq_id(loc) || StructureProperties.residue.label_seq_id(loc), + oneLetter: threeToOne[threeLetter] || '', + threeLetter, + }; +} + +function setCurrentHover(plugin, current) { + const loci = current?.loci; + if (!StructureElement.Loci.is(loci) || StructureElement.Loci.isEmpty(loci)) { + plugin.managers.interactivity.lociHighlights.clearHighlights(); + return; + } + plugin.managers.interactivity.lociHighlights.highlightOnly({ loci }, false); +} + +function focusCurrent(plugin, current) { + const loci = current?.loci; + if (!StructureElement.Loci.is(loci) || StructureElement.Loci.isEmpty(loci)) return; + plugin.managers.camera.focusLoci(loci, { + durationMs: 250, + extraRadius: 6, + minRadius: 3, + }); +} + +function columnForResidue(entry, chain, residue) { + let residueIndex = -1; + const hasChain = typeof chain === 'string' && chain.trim() !== ''; + for (let i = 1; i < entry.resns.length; i++) { + if ((!hasChain || (entry.chains?.[i] || 'A') === chain) && entry.resns[i] === residue) { + residueIndex = i; + break; + } + } + if (residueIndex < 1) return -1; + + let seen = 0; + for (let column = 0; column < entry.aa.length; column++) { + if (entry.aa[column] === '-') continue; + seen++; + if (seen === residueIndex) return column; + } + return -1; +} + +function residueInfoForColumn(entry, column) { + if (!Number.isInteger(column) || column < 0 || entry.aa?.[column] === '-') return null; + const residueIndex = residueIndexForColumn(entry.aa, column); + if (residueIndex < 1) return null; + const oneLetter = entry.aa[column] || ''; + return { + chain: entry.chains?.[residueIndex] || 'A', + residue: entry.resns?.[residueIndex] || residueIndex, + oneLetter, + threeLetter: oneToThree[oneLetter] || '', + }; +} + +function residueIndexForColumn(seq, column) { + let residueIndex = 0; + for (let i = 0; i <= column && i < seq.length; i++) { + if (seq[i] !== '-') residueIndex++; + } + return residueIndex; +} + +async function superpositionMatrix(reference, target) { + const alignment = mockAlignment(reference.aa, target.aa); + const fasta = `>target\n${alignment.dbAln}\n\n>query\n${alignment.qAln}`; + const referencePDB = subPDB(mockPDB(reference.ca, reference.aa.replace(/-/g, ''), 'A'), alignment.qStartPos, alignment.qEndPos); + const targetPDB = subPDB(mockPDB(target.ca, target.aa.replace(/-/g, ''), 'A'), alignment.dbStartPos, alignment.dbEndPos); + const { matrix } = await tmalign(targetPDB, referencePDB, fasta); + const { t, u } = parseTMMatrix(matrix); + return makeMat4(t, u); +} + +function makeMat4(t, u) { + return Mat4.ofRows([ + [u[0][0], u[0][1], u[0][2], t[0]], + [u[1][0], u[1][1], u[1][2], t[1]], + [u[2][0], u[2][1], u[2][2], t[2]], + [0, 0, 0, 1], + ]); +} + +function mockAlignment(one, two) { + const res = { backtrace: '', qAln: '', dbAln: '' }; + let started = false; + let m = 0; + let qr = 0; + let tr = 0; + let qBuffer = ''; + let tBuffer = ''; + while (m < one.length) { + const qc = one[m]; + const tc = two[m]; + if (qc === '-' && tc === '-') { + // skip + } else if (qc === '-') { + if (started) { + res.backtrace += 'D'; + qBuffer += qc; + tBuffer += tc; + } + tr++; + } else if (tc === '-') { + if (started) { + res.backtrace += 'I'; + qBuffer += qc; + tBuffer += tc; + } + qr++; + } else { + if (started) { + res.qAln += qBuffer; + res.dbAln += tBuffer; + qBuffer = ''; + tBuffer = ''; + } else { + started = true; + res.qStartPos = qr; + res.dbStartPos = tr; + } + res.backtrace += 'M'; + qBuffer += qc; + tBuffer += tc; + res.qEndPos = qr; + res.dbEndPos = tr; + qr++; + tr++; + } + m++; + } + res.qStartPos++; + res.dbStartPos++; + res.qSeq = one.replace(/-/g, ''); + res.tSeq = two.replace(/-/g, ''); + return res; +} + +function subPDB(pdb, start, end) { + return pdb.split(/\r?\n/) + .filter((line) => { + if (!line.startsWith('ATOM') && !line.startsWith('HETATM')) return false; + const residue = Number.parseInt(line.slice(22, 26), 10); + return residue >= start && residue <= end; + }) + .join('\n'); +} + +async function structurePDB(entry) { + const mock = mockPDB(entry.ca, entry.aa.replace(/-/g, ''), 'A'); + try { + if (!entry.suffix) return await pulchra(mock); + + const decoded = decodeMultimer(mock, entry.suffix); + const chains = storeChains(decoded); + const chunks = []; + for (const split of splitMultimer(decoded)) { + chunks.push(await pulchra(split)); + } + return revertChainInfo(mergeMultimer(chunks), chains); + } catch (e) { + return mock; + } +} + +function decodeMultimer(pdb, suffix) { + if (!suffix) return pdb; + const chainInfos = suffix.split('-').map((s) => { + const [chain, end, offset] = s.split('_'); + return { chain, end: Number(end), offset: Number(offset) }; + }); + + let index = 0; + const out = []; + for (const line of pdb.split('\n')) { + if (!line.startsWith('ATOM')) continue; + const residue = Number(line.slice(22, 26)); + const info = chainInfos[index] || chainInfos[0]; + out.push( + line.slice(0, 21) + + info.chain + + (residue - info.offset).toString().padStart(4, ' ') + + line.slice(26), + ); + if (residue === info.end) { + index++; + out.push('TER'); + } + } + out.push('END'); + return out.join('\n'); +} + +function splitMultimer(pdb) { + return pdb.split('\nTER\n').slice(0, -1).map(s => `${s}\nTER\nEND`); +} + +function mergeMultimer(chunks) { + let serial = 1; + const merged = chunks.map(chunk => chunk.split('\nEND')[0]).join('\n') + '\nEND'; + const out = []; + for (const line of merged.split('\n')) { + if (line.startsWith('ATOM')) { + out.push(line.slice(0, 6) + serial.toString().padStart(5, ' ') + line.slice(11)); + serial++; + } else if (line.startsWith('TER') || line.startsWith('END')) { + out.push(line); + } + } + return out.join('\n'); +} + +function storeChains(pdb) { + const chains = []; + let chain = ''; + for (const line of pdb.split('\n')) { + if (line.startsWith('ATOM')) { + chain = line.charAt(21); + } else if (line.startsWith('TER')) { + chains.push(chain); + } + } + if (chains.length === 0) chains.push(chain); + return chains; +} + +function revertChainInfo(pdb, chains) { + if (!chains.length || chains[0] === '') return pdb; + const out = []; + let index = 0; + for (let line of pdb.split('\n')) { + if (line.startsWith('ATOM')) { + line = line.slice(0, 21) + chains[index] + line.slice(22); + } else if (line.startsWith('TER')) { + index++; + } + out.push(line); + } + return out.join('\n'); +} + +function makeCIF(state) { + const blocks = []; + for (const item of state.structures?.values?.() || []) { + const structure = item.structure?.cell?.obj?.data; + if (structure) blocks.push(to_mmCIF(`structure_${item.index}`, structure, false, { copyAllCategories: false })); + } + return blocks.join('\n'); +} + +export const foldmasonResult = { + async mount() { + return {}; + }, + + async update(plugin, state, input) { + await updateStructures(plugin, state, input); + await setSelection(plugin, state, input); + await setPreview(plugin, state, input); + await setFocus(plugin, state, input); + }, + + onHover(plugin, state, input, event) { + setCurrentHover(plugin, event?.current); + return structureEvent(state, event?.current, 'structure-hover'); + }, + + onClick(plugin, state, input, event) { + focusCurrent(plugin, event?.current); + return structureEvent(state, event?.current, 'structure-click'); + }, + + resetView(plugin, state, input) { + const reference = state.structures?.get?.(input.reference); + const loci = reference ? lociForItem(reference, input.selectedColumns || []) : null; + if (loci) { + plugin.managers.camera.focusLoci(loci, { durationMs: 250, extraRadius: 10, minRadius: 5 }); + } else { + plugin.managers.camera.reset(); + } + }, + + async makeCIF(plugin, state) { + return makeCIF(state); + }, + + async dispose(plugin) { + await plugin?.clear?.(); + }, +}; diff --git a/frontend/molstar/foldseekArrows.js b/frontend/molstar/foldseekArrows.js new file mode 100644 index 00000000..f97a56da --- /dev/null +++ b/frontend/molstar/foldseekArrows.js @@ -0,0 +1,185 @@ +import { Color } from 'molstar/lib/mol-util/color'; +import { OrderedSet } from 'molstar/lib/mol-data/int'; +import { Vec3 } from 'molstar/lib/mol-math/linear-algebra'; +import { StructureElement, StructureProperties, Unit } from 'molstar/lib/mol-model/structure'; +import { StateTransforms } from 'molstar/lib/mol-plugin-state/transforms'; +import { PluginStateObject } from 'molstar/lib/mol-plugin-state/objects'; +import { StateTransformer } from 'molstar/lib/mol-state'; +import { Task } from 'molstar/lib/mol-task'; +import { Shape } from 'molstar/lib/mol-model/shape'; +import { Mesh } from 'molstar/lib/mol-geo/geometry/mesh/mesh'; +import { MeshBuilder } from 'molstar/lib/mol-geo/geometry/mesh/mesh-builder'; +import { addCylinder } from 'molstar/lib/mol-geo/geometry/mesh/builder/cylinder'; +import { Sphere3D } from 'molstar/lib/mol-math/geometry'; +import { ParamDefinition as PD } from 'molstar/lib/mol-util/param-definition'; +import { getChainName } from './foldseekUtilities.js'; + +const FoldseekTransform = StateTransformer.builderFactory('foldseek'); +const FoldseekArrowsShape = FoldseekTransform({ + name: 'alignment-arrows-shape', + display: { name: 'Alignment Arrows' }, + from: PluginStateObject.Root, + to: PluginStateObject.Shape.Provider, + params: { + pairs: PD.Value([]), + color: PD.Color(Color(0x00ffff)), + }, +})({ + apply({ params }) { + return Task.create('Alignment Arrows', async () => new PluginStateObject.Shape.Provider({ + label: 'Alignment Arrows', + data: params, + params: Mesh.Params, + geometryUtils: Mesh.Utils, + getShape: (_, data, __, mesh) => Shape.create( + 'Alignment Arrows', + data, + createArrowMesh(data.pairs, mesh), + () => data.color, + () => 1, + () => 'Aligned residue pair', + ), + }, { label: 'Alignment Arrows' })); + }, +}); + +export async function setArrows(plugin, state, input) { + if (input?.structureMode === 'interface' || !input?.showArrows || !state.query || !state.target) { + if (state.arrows?.ref) { + await plugin.state.data.build().delete(state.arrows.ref).commit(); + state.arrows = null; + } + return; + } + + if (state.arrows?.ref) return; + + const pairs = alignmentArrowPairs(state, input); + if (pairs.length === 0) return; + + state.arrows = await plugin.state.data.build() + .toRoot() + .apply(FoldseekArrowsShape, { + pairs, + color: Color(input.arrowColor || 0x00ffff), + }, { + tags: ['foldseek-arrows'], + state: { isCollapsed: true }, + }) + .apply(StateTransforms.Representation.ShapeRepresentation3D, {}, { + tags: ['foldseek-arrows-representation'], + }) + .commit(); +} + +function createArrowMesh(pairs, oldMesh) { + const state = MeshBuilder.createState(Math.max(256, pairs.length * 64), Math.max(128, pairs.length * 32), oldMesh); + const center = Vec3(); + let radius = 1; + + pairs.forEach(([startRaw, endRaw], i) => { + const start = Vec3.create(startRaw[0], startRaw[1], startRaw[2]); + const end = Vec3.create(endRaw[0], endRaw[1], endRaw[2]); + const length = Vec3.distance(start, end); + if (!Number.isFinite(length) || length < 0.5) return; + + state.currentGroup = i; + addCylinder(state, start, end, 1, { + radiusTop: 0.12, + radiusBottom: 0.12, + radialSegments: 16, + }); + + Vec3.add(center, center, start); + Vec3.add(center, center, end); + }); + + if (pairs.length > 0) { + Vec3.scale(center, center, 1 / (pairs.length * 2)); + radius = pairs.reduce((max, [start, end]) => Math.max( + max, + Vec3.distance(center, start), + Vec3.distance(center, end), + ), 1); + } + + const mesh = MeshBuilder.getMesh(state); + mesh.setBoundingSphere(Sphere3D.create(center, radius)); + return mesh; +} + +function getMatchingResiduePairs(alignment) { + const pairs = []; + let queryPos = alignment.qStartPos; + let targetPos = alignment.dbStartPos; + + for (let i = 0; i < alignment.qAln.length; i++) { + const hasQuery = alignment.qAln[i] !== '-'; + const hasTarget = alignment.dbAln[i] !== '-'; + if (hasQuery && hasTarget) { + pairs.push({ query: queryPos, target: targetPos }); + } + if (hasQuery) queryPos++; + if (hasTarget) targetPos++; + } + + return pairs; +} + +function caOrdinalCoordinateMap(structure) { + const map = new Map(); + if (!structure) return map; + + const loc = StructureElement.Location.create(structure); + const chainCounts = new Map(); + for (const unit of structure.units) { + if (!Unit.isAtomic(unit)) continue; + loc.unit = unit; + + const { elements } = unit; + const size = OrderedSet.size(elements); + for (let i = 0; i < size; i++) { + loc.element = OrderedSet.getAt(elements, i); + if (StructureProperties.atom.label_atom_id(loc) !== 'CA') continue; + + const authChain = StructureProperties.chain.auth_asym_id(loc); + const labelChain = StructureProperties.chain.label_asym_id(loc); + const coordinate = [ + StructureProperties.atom.x(loc), + StructureProperties.atom.y(loc), + StructureProperties.atom.z(loc), + ]; + + for (const chain of new Set([authChain, labelChain].filter(Boolean))) { + const ordinal = (chainCounts.get(chain) || 0) + 1; + chainCounts.set(chain, ordinal); + map.set(`${chain}:${ordinal}`, coordinate); + } + } + } + + return map; +} + +function alignmentArrowPairs(state, input) { + const queryStructure = state.query?.structure?.cell?.obj?.data; + const targetStructure = state.target?.structure?.cell?.obj?.data; + if (!queryStructure || !targetStructure) return []; + + const queryCoords = caOrdinalCoordinateMap(queryStructure); + const targetCoords = caOrdinalCoordinateMap(targetStructure); + const pairs = []; + + for (const alignment of input?.alignments || []) { + const queryChain = getChainName(alignment.query); + const targetChain = getChainName(alignment.target); + + for (const match of getMatchingResiduePairs(alignment)) { + const query = queryCoords.get(`${queryChain}:${match.query}`); + const target = targetCoords.get(`${targetChain}:${match.target}`); + if (query && target) pairs.push([query, target]); + } + } + + return pairs; +} diff --git a/frontend/molstar/foldseekData.js b/frontend/molstar/foldseekData.js new file mode 100644 index 00000000..5d3469a1 --- /dev/null +++ b/frontend/molstar/foldseekData.js @@ -0,0 +1,181 @@ +import { Mat4 } from 'molstar/lib/mol-math/linear-algebra'; +import { pulchra } from 'pulchra-wasm'; +import { getAccession, getChainName, mockPDB } from './foldseekUtilities.js'; + +export async function prepareFoldseekStructureInput(ctx) { + if (!ctx.alignments?.length || (ctx.structureMode !== 'interface' && typeof ctx.alignments[0].tCa === 'undefined')) { + return emptyInput(); + } + const query = await buildQuery(ctx); + const target = ctx.structureMode === 'interface' + ? await buildInterfaceTarget(ctx) + : await buildTarget(ctx); + const targetTransform = computeMultimerTransform(ctx.alignments); + return { + query: query.source, + target: target.source, + targetTransform, + alignments: ctx.alignments, + hasQuery: Boolean(query.source), + structureMode: ctx.structureMode || 'alignment', + interfaceCutoff: ctx.interfaceCutoff || 10, + }; +} + +function emptyInput() { + return { + query: null, + target: null, + targetTransform: null, + hasQuery: false, + structureMode: 'alignment', + interfaceCutoff: 10, + }; +} + +async function buildQuery(ctx) { + const data = await loadQueryData(ctx); + if (!data) { + return { source: null, pdb: '' }; + } + + const format = detectFormat(data); + return { + source: { data, format, label: 'query' }, + pdb: format === 'pdb' ? data : '', + }; +} + +async function loadQueryData(ctx) { + const query = ctx.hits?.queries?.[0]; + if (!query) return ''; + + if (ctx.isLocal) { + if (query.hasOwnProperty('pdb')) return JSON.parse(query.pdb); + return mockPDB(query.qCa, query.sequence, 'A'); + } + + if (ctx.route.params.ticket.startsWith('user-')) { + if (query.hasOwnProperty('pdb')) return JSON.parse(query.pdb); + const localData = ctx.root.userData[ctx.route.params.entry]; + return mockPDB(localData.queries[0].qCa, localData.queries[0].sequence, 'A'); + } + + try { + const request = await ctx.axios.get(`api/result/${ctx.route.params.ticket}/query`); + return request.data; + } catch (e) { + return ''; + } +} + +async function buildTarget(ctx) { + const targets = []; + let lastIdx = null; + let remoteData = null; + let remoteOffset = 0; + + for (const alignment of ctx.alignments) { + const chain = getChainName(alignment.target); + let tSeq = alignment.tSeq; + let tCa = alignment.tCa; + + if (Number.isInteger(alignment.tCa) && Number.isInteger(alignment.tSeq)) { + const idx = alignment.tCa; + if (idx !== lastIdx) { + const response = await ctx.axios.get( + `api/result/${ctx.route.params.ticket}/${ctx.route.params.entry}?format=brief&index=${idx}&database=${alignment.db}`, + ); + remoteData = response.data; + remoteOffset = 0; + lastIdx = idx; + } + if (Array.isArray(remoteData) && remoteData[remoteOffset]) { + tSeq = remoteData[remoteOffset].tSeq; + tCa = remoteData[remoteOffset].tCa; + remoteOffset += 1; + } + } + + if (!tSeq || !tCa) continue; + + const mock = mockPDB(tCa, tSeq, chain); + try { + targets.push(applyChainId(await pulchra(mock), chain)); + } catch (e) { + targets.push(mock); + } + } + + const pdb = mergePdbChunks(targets); + return { + source: pdb ? { data: pdb, format: 'pdb', label: 'target' } : null, + pdb, + }; +} + +async function buildInterfaceTarget(ctx) { + const chunks = []; + const seen = new Set(); + + for (const alignment of ctx.alignments) { + if (!alignment?.target || !alignment?.db) continue; + const accession = getAccession(alignment.target); + const key = `${alignment.db}:${accession}`; + if (seen.has(key)) continue; + seen.add(key); + + const response = await ctx.axios.get( + `api/result/interface/${ctx.route.params.ticket}?database=${encodeURIComponent(alignment.db)}&id=${encodeURIComponent(alignment.target)}`, + { + headers: { Accept: 'text/plain' }, + transformResponse: [(data) => data], + }, + ); + chunks.push(response.data); + } + + const pdb = mergePdbChunks(chunks); + return { + source: pdb ? { data: pdb, format: 'pdb', label: 'target' } : null, + pdb, + }; +} + +function computeMultimerTransform(alignments) { + const first = alignments[0]; + if (!first?.complexu || !first?.complext) return null; + const u = Array.isArray(first.complexu) ? first.complexu : String(first.complexu).split(',').map(Number); + const t = Array.isArray(first.complext) ? first.complext : String(first.complext).split(',').map(Number); + + return Mat4.ofRows([ + [u[0], u[1], u[2], t[0]], + [u[3], u[4], u[5], t[1]], + [u[6], u[7], u[8], t[2]], + [0, 0, 0, 1], + ]); +} + +function extractAtomLines(pdb) { + if (!pdb) return []; + return pdb.split(/\r?\n/).filter(line => line.startsWith('ATOM') || line.startsWith('HETATM')); +} + +function applyChainId(pdb, chain) { + if (!pdb || !chain) return pdb; + return pdb.split('\n').map((line) => { + if (!line.startsWith('ATOM') && !line.startsWith('HETATM')) return line; + return `${line.slice(0, 21)}${chain}${line.slice(22)}`; + }).join('\n'); +} + +function mergePdbChunks(chunks) { + return chunks + .flatMap(chunk => extractAtomLines(chunk)) + .join('\n'); +} + +function detectFormat(data) { + const trimmed = (data || '').trimStart(); + return trimmed.startsWith('#') || trimmed.startsWith('data_') ? 'mmcif' : 'pdb'; +} diff --git a/frontend/molstar/foldseekResult.js b/frontend/molstar/foldseekResult.js new file mode 100644 index 00000000..692a2dac --- /dev/null +++ b/frontend/molstar/foldseekResult.js @@ -0,0 +1,632 @@ +import { OrderedSet } from 'molstar/lib/mol-data/int'; +import { QueryContext, StructureElement, StructureProperties, StructureSelection } from 'molstar/lib/mol-model/structure'; +import { to_mmCIF } from 'molstar/lib/mol-model/structure/export/mmcif'; +import { compile } from 'molstar/lib/mol-script/runtime/query/compiler'; +import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder'; +import { setSubtreeVisibility } from 'molstar/lib/mol-plugin/behavior/static/state'; +import { Color } from 'molstar/lib/mol-util/color'; +import { + alignmentRegions, + atomGroupExpression, + backgroundExpressionForMode, + baseSelectionExpressionForMode, + chainColorEntries, + computeInterfaceRegions, + interfaceExpressionForChain, + mergeSelectionExpressions, + selectionExpressionForChains, + structureSelectionExpression, +} from './foldseekSelections.js'; +import { superposeTargetWithFoldseekAlignment, transformStructure } from './foldseekSuperposition.js'; +import { setArrows } from './foldseekArrows.js'; + +const QueryChainSurfaceColors = [ + 0x991999, + 0x00bfbf, + 0xe9967a, + 0x009e73, + 0xf0e442, + 0x0072b2, + 0xd55e00, + 0xcc79a7, +]; +const InterfacePalettes = { + query: [0x1e88e5, 0xe53935, 0x991999, 0x00bfbf], + target: [0xffc107, 0x43a047, 0xe9967a, 0x009e73], +}; + +async function buildStructureRepresentations(plugin, state, structure, side, input, color, alignedColor) { + if (input?.structureMode === 'interface') { + return buildInterfaceStructureRepresentations(plugin, state, structure, side, input); + } + + const mode = input[`show${capitalize(side)}`] || 0; + const type = input[`${side}Representation`] || 'cartoon'; + const aligned = await addRepresentation( + plugin, + structure, + `${side}-aligned`, + type, + alignedColor, + baseSelectionExpressionForMode(state, input, side, 0), + representationOverrides(input, side), + ); + const stateEntry = { + structure, + type, + mode, + aligned, + background: null, + surfaces: await addChainSurfaces(plugin, structure, side, input), + baseColor: color, + alignedColor, + }; + await setBackgroundRepresentation(plugin, state, stateEntry, input, side, mode); + setSurfaceVisibility(plugin, stateEntry, input, side, mode); + return stateEntry; +} + +async function applyVisibility(plugin, state, input) { + const queryMode = input?.showQuery || 0; + const targetMode = input?.showTarget || 0; + + if (state.query) { + await setSelectionMode(plugin, state, state.query, input, 'query', queryMode); + } + if (state.target) { + await setSelectionMode(plugin, state, state.target, input, 'target', targetMode); + } + await setArrows(plugin, state, input); +} + +function representationTypeParams(type, overrides = {}) { + if (type === 'cartoon') { + return { + quality: 'higher', + ignoreLight: false, + sizeFactor: 0.25, + visuals: ['polymer-trace', 'polymer-gap'], + helixProfile: 'rounded', + nucleicProfile: 'rounded', + linearSegments: 10, + radialSegments: 16, + material: { + metalness: 0, + roughness: 0.55, + bumpiness: 0.1, + }, + ...overrides, + }; + } + + return { + quality: 'higher', + ignoreLight: false, + ...overrides, + }; +} + +async function addRepresentation(plugin, structure, label, type, color, expression = MS.struct.generator.all(), typeParams = {}) { + const component = await plugin.builders.structure.tryCreateComponentFromExpression( + structure, + expression, + label, + { label }, + ); + if (!component) return null; + + const representation = await plugin.builders.structure.representation.addRepresentation(component, { + type, + color: 'uniform', + colorParams: { value: Color(color) }, + typeParams: representationTypeParams(type, typeParams), + }, { + tag: `${label}-representation`, + }); + return { component, representation }; +} + +async function addChainSurfaces(plugin, structure, side, input) { + if (input?.structureMode !== 'multimer' || side !== 'query') return []; + + const entries = querySurfaceEntries(input.alignments); + + const surfaces = []; + for (const entry of entries) { + const surface = await addRepresentation( + plugin, + structure, + `${side}-${entry.chain}-${entry.level || 'surface'}`, + 'gaussian-surface', + entry.color, + entry.expression, + { + alpha: input.chainSurfaceAlpha || 0.18, + visuals: ['gaussian-surface-mesh'], + smoothness: 1.5, + ignoreHydrogens: true, + material: { + metalness: 0, + roughness: 0.35, + bumpiness: 0, + }, + }, + ); + if (surface) surfaces.push({ ...surface, level: entry.level || 'base' }); + } + return surfaces; +} + +function querySurfaceEntries(alignments) { + const byChain = new Map(); + for (const region of alignmentRegions(alignments, 'query')) { + if (!byChain.has(region.chain)) byChain.set(region.chain, []); + byChain.get(region.chain).push(atomGroupExpression(region.chain, region.start, region.end)); + } + const entries = []; + let i = 0; + for (const [chain, expressions] of byChain.entries()) { + const alignedExpression = mergeSelectionExpressions(expressions); + const color = QueryChainSurfaceColors[i % QueryChainSurfaceColors.length]; + entries.push({ + chain, + level: 'aligned', + color, + expression: alignedExpression, + }); + entries.push({ + chain, + level: 'unaligned', + color, + expression: MS.struct.modifier.exceptBy({ + 0: atomGroupExpression(chain), + by: alignedExpression, + }), + }); + i += 1; + } + + const queryChains = selectionExpressionForChains(alignments, 'query'); + if (queryChains) { + entries.push({ + chain: 'other', + level: 'other', + color: 0xcccccc, + expression: MS.struct.modifier.exceptBy({ + 0: MS.struct.generator.all(), + by: queryChains, + }), + }); + } + return entries; +} + +async function buildInterfaceStructureRepresentations(plugin, state, structure, side, input) { + const mode = input[`show${capitalize(side)}`] || 0; + const type = input[`${side}Representation`] || 'cartoon'; + const aligned = []; + + for (const { chain, color } of chainColorEntries(input, side, InterfacePalettes)) { + const expression = interfaceExpressionForChain(state, side, chain) || atomGroupExpression(chain); + const representation = await addRepresentation( + plugin, + structure, + `${side}-${chain}-interface`, + type, + color, + expression, + ); + if (representation) aligned.push({ ...representation, chain, color }); + } + + const stateEntry = { + structure, + type, + mode, + aligned, + background: null, + surfaces: [], + baseColor: null, + alignedColor: null, + }; + await setInterfaceBackgroundRepresentation(plugin, state, stateEntry, input, side, mode); + return stateEntry; +} + +function capitalize(value) { + return value.charAt(0).toUpperCase() + value.slice(1); +} + +async function setSelectionMode(plugin, state, stateEntry, input, side, mode) { + if (!stateEntry || stateEntry.mode === mode) return; + stateEntry.mode = mode; + if (input?.structureMode === 'interface') { + await setInterfaceBackgroundRepresentation(plugin, state, stateEntry, input, side, mode); + } else { + await setBackgroundRepresentation(plugin, state, stateEntry, input, side, mode); + } + setSurfaceVisibility(plugin, stateEntry, input, side, mode); + plugin.managers.camera.reset(); +} + +async function setBackgroundRepresentation(plugin, state, stateEntry, input, side, mode) { + const expression = backgroundExpressionForMode(state, input, side, mode); + await deleteRepresentations(plugin, stateEntry.background); + stateEntry.background = null; + + if (!expression) { + return; + } + + stateEntry.background = await addRepresentation( + plugin, + stateEntry.structure, + `${side}-background`, + stateEntry.type, + stateEntry.baseColor, + expression, + representationOverrides(input, side), + ); +} + +async function setInterfaceBackgroundRepresentation(plugin, state, stateEntry, input, side, mode) { + await deleteRepresentations(plugin, stateEntry.background); + stateEntry.background = null; + if (mode === 0) return; + + const alpha = mode === 1 ? 0.28 : 1; + const background = []; + for (const { chain, color } of chainColorEntries(input, side, InterfacePalettes)) { + const chainExpression = atomGroupExpression(chain); + const interfaceExpression = interfaceExpressionForChain(state, side, chain); + const expression = interfaceExpression + ? MS.struct.modifier.exceptBy({ 0: chainExpression, by: interfaceExpression }) + : chainExpression; + const representation = await addRepresentation( + plugin, + stateEntry.structure, + `${side}-${chain}-context`, + stateEntry.type, + color, + expression, + { alpha }, + ); + if (representation) background.push({ ...representation, chain, color }); + } + stateEntry.background = background; +} + +async function deleteRepresentations(plugin, representations) { + const items = Array.isArray(representations) + ? representations + : (representations ? [representations] : []); + for (const item of items) { + if (item?.component?.ref) { + await plugin.state.data.build().delete(item.component.ref).commit(); + } + } +} + +function representationOverrides(input, side) { + const alpha = input?.[`${side}Alpha`]; + return Number.isFinite(alpha) ? { alpha } : {}; +} + +function setSurfaceVisibility(plugin, stateEntry, input, side, mode) { + if (input?.structureMode !== 'multimer' || side !== 'query') return; + + for (const surface of stateEntry.surfaces || []) { + const ref = surface.component?.ref; + if (!ref) continue; + + const visible = surface.level === 'aligned' + || (surface.level === 'unaligned' && mode >= 1) + || (surface.level === 'other' && mode >= 2); + setSubtreeVisibility(plugin.state.data, ref, !visible); + } +} + +async function loadStructure(plugin, source) { + const raw = await plugin.builders.data.rawData( + { data: source.data, label: source.label }, + { state: { isGhost: true } }, + ); + const trajectory = await plugin.builders.structure.parseTrajectory(raw, source.format); + const model = await plugin.builders.structure.createModel(trajectory); + return plugin.builders.structure.createStructure(model); +} + +function needsBaseRebuild(state, input) { + return state.querySource !== input?.query + || state.targetSource !== input?.target + || state.targetTransform !== input?.targetTransform + || state.alignments !== input?.alignments + || state.structureMode !== input?.structureMode; +} + +function resetSceneState(state, input) { + state.querySource = input?.query || null; + state.targetSource = input?.target || null; + state.targetTransform = input?.targetTransform || null; + state.alignments = input?.alignments || null; + state.structureMode = input?.structureMode || 'alignment'; + state.query = null; + state.target = null; + state.arrows = null; + state.tmAlignResults = null; + state.focusKey = null; + state.interfaceRegions = { query: [], target: [] }; + state.interfaceResidueMap = { query: [], target: [] }; +} + +function lociFromExpression(state, expression, side = 'target') { + if (!state[side] || !expression) return null; + const structure = state[side].structure?.cell?.obj?.data; + if (!structure) return null; + + const query = compile(expression); + const selection = query(new QueryContext(structure)); + const loci = StructureSelection.toLociWithSourceUnits(selection); + return StructureElement.Loci.isEmpty(loci) ? null : loci; +} + +function structureEventFromInteraction(state, current, type) { + const picked = pickedComponentInfo(state, current?.loci) || pickedRepresentationInfo(state, current?.repr); + const event = structureEventFromLoci(state, current?.loci, type); + if (!event.side && picked?.side) event.side = picked.side; + if (!event.chain && picked?.chain) event.chain = picked.chain; + return event; +} + +function pickedRepresentationInfo(state, repr) { + if (!repr) return null; + for (const { side, item } of structureRepresentationItems(state)) { + if (representationObject(item) === repr) return { side, chain: item.chain }; + } + return null; +} + +function pickedComponentInfo(state, loci) { + if (!StructureElement.Loci.is(loci) || StructureElement.Loci.isEmpty(loci)) return null; + for (const { side, item } of structureRepresentationItems(state)) { + if (componentStructure(item) === loci.structure) return { side, chain: item.chain }; + } + return null; +} + +function* structureRepresentationItems(state) { + for (const side of ['query', 'target']) { + const entry = state[side]; + if (!entry) continue; + for (const item of representationItems(entry.aligned)) { + yield { side, item }; + } + for (const item of representationItems(entry.background)) { + yield { side, item }; + } + } +} + +function representationItems(value) { + return Array.isArray(value) ? value : (value ? [value] : []); +} + +function representationObject(item) { + return item?.representation?.cell?.obj?.data?.repr + || item?.representation?.obj?.data?.repr + || item?.representation?.data?.repr + || null; +} + +function componentStructure(item) { + return item?.component?.cell?.obj?.data + || item?.component?.obj?.data + || item?.component?.data + || null; +} + +function structureEventFromLoci(state, loci, type) { + if (!StructureElement.Loci.is(loci) || StructureElement.Loci.isEmpty(loci)) { + return { type, residue: null }; + } + + const lociStructure = loci.structure; + const side = ['query', 'target'].find((name) => { + const structure = state[name]?.structure?.cell?.obj?.data; + return structuresMatch(lociStructure, structure); + }); + if (!side) return { type, residue: null }; + + const entry = loci.elements?.[0]; + if (!entry || OrderedSet.size(entry.indices) === 0) { + return { type, residue: null }; + } + + const loc = StructureElement.Location.create(lociStructure); + loc.unit = entry.unit; + loc.element = OrderedSet.getAt(entry.indices, 0); + + const authResidue = StructureProperties.residue.auth_seq_id(loc); + const labelResidue = StructureProperties.residue.label_seq_id(loc); + + return { + type, + side, + chain: StructureProperties.chain.auth_asym_id(loc) || StructureProperties.chain.label_asym_id(loc), + residue: labelResidue || authResidue, + authResidue, + labelResidue, + residues: Array.from(new Set([labelResidue, authResidue].filter(Number.isFinite))), + }; +} + +function structuresMatch(a, b) { + if (!a || !b) return false; + if (a === b) return true; + if (a.root && a.root === b) return true; + if (a.root && b.root && a.root === b.root) return true; + return false; +} + +async function setStructureSelection(plugin, state, input) { + const selections = input?.highlightSelections || []; + if (selections.length === 0) { + plugin.managers.interactivity.lociSelects.deselectAll(); + return; + } + + const lociList = []; + for (const side of ['query', 'target']) { + if (!state[side]) continue; + const expression = mergeSelectionExpressions( + selections + .filter(selection => (selection.side || 'target') === side) + .map(selection => structureSelectionExpression(state, input, selection)), + ); + const loci = lociFromExpression(state, expression, side); + if (loci) lociList.push(loci); + } + + if (lociList.length === 0) { + plugin.managers.interactivity.lociSelects.deselectAll(); + return; + } + + plugin.managers.interactivity.lociSelects.deselectAll(); + for (const loci of lociList) { + plugin.managers.interactivity.lociSelects.select({ loci }, false); + } +} + +async function setStructureHover(plugin, state, input) { + const loci = lociFromInputSelection(state, input, input?.hoverSelection); + if (!loci) { + plugin.managers.interactivity.lociHighlights.clearHighlights(); + return; + } + + plugin.managers.interactivity.lociHighlights.highlightOnly({ loci }, false); +} + +async function setTargetFocus(plugin, state, input) { + const selection = input?.focusSelection; + const key = selection + ? `${selection.side || 'target'}:${selection.index}:${selection.start}:${selection.length}:${selection.token || 0}` + : null; + if (state.focusKey === key) return; + state.focusKey = key; + if (!selection) return; + + const loci = lociFromInputSelection(state, input, selection); + if (!loci) return; + + plugin.managers.camera.focusLoci(loci, { + durationMs: 250, + extraRadius: 6, + minRadius: 3, + }); +} + +function lociFromInputSelection(state, input, selection) { + const side = selection?.side || 'target'; + if (!state[side]) return null; + return lociFromExpression(state, structureSelectionExpression(state, input, selection), side); +} + +async function rebuildBaseScene(plugin, state, input) { + await plugin.clear(); + resetSceneState(state, input); + if (!input) return; + + let target = input.target ? await loadStructure(plugin, input.target) : null; + const query = input.query ? await loadStructure(plugin, input.query) : null; + + if (target && input.targetTransform) { + target = await transformStructure(plugin, target, input.targetTransform); + } else if (target && query) { + const superposition = await superposeTargetWithFoldseekAlignment(plugin, target, query, input.alignments); + target = superposition.structure; + state.tmAlignResults = superposition.results; + } + + const interfaceData = computeInterfaceRegions(query, target, input); + state.interfaceRegions = interfaceData.regions; + state.interfaceResidueMap = interfaceData.residueMap; + + if (target) { + state.target = await buildStructureRepresentations( + plugin, + state, + target, + 'target', + input, + input.targetUnalignedColor || 0xffe699, + input.targetColor || 0xffc107, + ); + } + + if (query) { + state.query = await buildStructureRepresentations( + plugin, + state, + query, + 'query', + input, + input.queryUnalignedColor || 0xa5cff5, + input.queryColor || 0x1e88e5, + ); + } + + plugin.managers.camera.reset(); +} + +function makeCIF(state) { + const queryStructure = state.query?.structure?.cell?.obj?.data; + const targetStructure = state.target?.structure?.cell?.obj?.data; + if (!queryStructure && !targetStructure) return ''; + + const blocks = []; + if (queryStructure) { + blocks.push(to_mmCIF('query', queryStructure, false, { copyAllCategories: false })); + } + if (targetStructure) { + blocks.push(to_mmCIF('target', targetStructure, false, { copyAllCategories: false })); + } + return blocks.join('\n'); +} + +export const foldseekResult = { + async mount() { + return {}; + }, + + async update(plugin, state, input) { + if (needsBaseRebuild(state, input)) { + await rebuildBaseScene(plugin, state, input); + } + await applyVisibility(plugin, state, input); + await setStructureSelection(plugin, state, input); + await setStructureHover(plugin, state, input); + await setTargetFocus(plugin, state, input); + }, + + onHover(plugin, state, input, event) { + return structureEventFromInteraction(state, event?.current, 'structure-hover'); + }, + + onClick(plugin, state, input, event) { + return structureEventFromInteraction(state, event?.current, 'structure-click'); + }, + + resetView(plugin) { + plugin.managers.camera.reset(); + }, + + async makeCIF(plugin, state, input) { + return makeCIF(state, input); + }, + + async dispose(plugin) { + await plugin?.clear?.(); + }, +}; diff --git a/frontend/molstar/foldseekSelections.js b/frontend/molstar/foldseekSelections.js new file mode 100644 index 00000000..460d0b5d --- /dev/null +++ b/frontend/molstar/foldseekSelections.js @@ -0,0 +1,337 @@ +import { OrderedSet } from 'molstar/lib/mol-data/int'; +import { StructureElement, StructureProperties, Unit } from 'molstar/lib/mol-model/structure'; +import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder'; +import { getChainName } from './foldseekUtilities.js'; + +export function rangesByChain(alignments, side) { + const ranges = new Map(); + for (const { chain, start, end } of alignmentRegions(alignments, side)) { + if (!ranges.has(chain)) ranges.set(chain, []); + ranges.get(chain).push([start, end]); + } + return ranges; +} + +function chainsBySide(alignments, side) { + return Array.from(new Set(alignmentRegions(alignments, side).map(({ chain }) => chain))); +} + +function selectionExpressionForRanges(alignments, side) { + return mergeSelectionExpressions( + alignmentRegions(alignments, side).map(({ chain, start, end }) => atomGroupExpression(chain, start, end)), + ); +} + +export function alignmentRegions(alignments, side) { + const regions = []; + for (const alignment of alignments || []) { + const name = side === 'query' ? alignment.query : alignment.target; + const start = side === 'query' ? alignment.qStartPos : alignment.dbStartPos; + const end = side === 'query' ? alignment.qEndPos : alignment.dbEndPos; + if (!Number.isFinite(start) || !Number.isFinite(end)) continue; + regions.push({ + chain: getChainName(name), + start: Math.min(start, end), + end: Math.max(start, end), + }); + } + return regions; +} + +export function selectionExpressionForChains(alignments, side) { + return mergeSelectionExpressions(chainsBySide(alignments, side).map(chain => atomGroupExpression(chain))); +} + +export function chainColorEntries(input, side, palettes) { + const palette = side === 'query' ? palettes.query : palettes.target; + const seen = new Set(); + const entries = []; + for (const { chain } of alignmentRegions(input.alignments, side)) { + if (seen.has(chain)) continue; + seen.add(chain); + entries.push({ + chain, + color: palette[entries.length % palette.length], + }); + } + return entries; +} + +export function interfaceExpressionForChain(state, side, chain) { + const expressions = (state.interfaceRegions?.[side] || []) + .filter(region => region.chain === chain) + .map(({ start, end }) => atomGroupExpression(chain, start, end)); + return mergeSelectionExpressions(expressions); +} + +export function baseSelectionExpressionForMode(state, input, side, mode) { + if (input?.structureMode === 'interface' && mode === 0) { + return selectionExpressionForInterface(state, side) + || selectionExpressionForChains(input.alignments, side) + || MS.struct.generator.all(); + } + return selectionExpressionForRanges(input.alignments, side) || MS.struct.generator.all(); +} + +function selectionExpressionForMode(state, input, side, mode) { + if (mode === 0) { + return baseSelectionExpressionForMode(state, input, side, mode); + } + if (mode === 1) { + return selectionExpressionForChains(input.alignments, side) || MS.struct.generator.all(); + } + return MS.struct.generator.all(); +} + +export function backgroundExpressionForMode(state, input, side, mode) { + if (mode === 0) return null; + + const visible = selectionExpressionForMode(state, input, side, mode); + const base = baseSelectionExpressionForMode(state, input, side, 0); + if (!base) return visible; + + return MS.struct.modifier.exceptBy({ + 0: visible, + by: base, + }); +} + +export function structureSelectionExpression(state, input, selection) { + const side = selection?.side || 'target'; + const alignment = input?.alignments?.[selection?.index]; + if (!alignment || !Number.isFinite(selection.start) || !Number.isFinite(selection.length)) return null; + const start = selection.start; + const end = selection.start + Math.max(0, selection.length - 1); + if (!Number.isFinite(end) || end < start) return null; + const chain = getChainName(alignment[side]); + + if (input?.structureMode === 'interface') { + const residueMap = (state.interfaceResidueMap?.[side] || []).filter(entry => entry.chain === chain); + let residues = residueMap.filter(entry => entry.alignmentResidue >= start && entry.alignmentResidue <= end); + if (residues.length === 0) { + residues = residueMap.filter(entry => entry.authResidue >= start && entry.authResidue <= end); + } + return atomGroupExpressionForResidueMap(chain, residues); + } + + return atomGroupExpression(chain, start, end); +} + +export function atomGroupExpression(chain, start, end) { + const { and, or } = MS.core.logic; + const { eq, gre, lte } = MS.core.rel; + const { macromolecular } = MS.struct.atomProperty; + const tests = { + 'chain-test': or([ + eq([macromolecular.auth_asym_id(), chain]), + eq([macromolecular.label_asym_id(), chain]), + ]), + }; + if (Number.isFinite(start) || Number.isFinite(end)) { + tests['residue-test'] = or([ + residueRangeTest(macromolecular.auth_seq_id(), start, end), + residueRangeTest(macromolecular.label_seq_id(), start, end), + ]); + } + return MS.struct.generator.atomGroups(tests); +} + +export function mergeSelectionExpressions(expressions) { + const valid = expressions.filter(Boolean); + if (valid.length === 0) return null; + if (valid.length === 1) return valid[0]; + return MS.struct.combinator.merge(valid.map(expression => MS.struct.modifier.union([expression]))); +} + +function residueRanges(residues) { + if (residues.length === 0) return []; + const ranges = []; + let start = residues[0]; + let prev = start; + for (let i = 1; i < residues.length; i++) { + if (residues[i] === prev + 1) { + prev = residues[i]; + continue; + } + ranges.push([start, prev]); + start = residues[i]; + prev = residues[i]; + } + ranges.push([start, prev]); + return ranges; +} + +export function computeInterfaceRegions(query, target, input) { + if (input?.structureMode !== 'interface') { + return { + regions: { query: [], target: [] }, + residueMap: { query: [], target: [] }, + }; + } + const queryChains = chainsBySide(input.alignments, 'query'); + const targetChains = chainsBySide(input.alignments, 'target'); + const queryInterface = interfaceRegions(query, queryChains, input.interfaceCutoff); + const targetInterface = interfaceRegions(target, targetChains, input.interfaceCutoff); + return { + regions: { + query: queryInterface.regions, + target: targetInterface.regions, + }, + residueMap: { + query: structureResidueMap(query, queryChains), + target: structureResidueMap(target, targetChains), + }, + }; +} + +function selectionExpressionForInterface(state, side) { + return mergeSelectionExpressions( + (state.interfaceRegions?.[side] || []).map(({ chain, start, end }) => atomGroupExpression(chain, start, end)), + ); +} + +function atomGroupExpressionForResidueMap(chain, residues) { + if (!residues.length) return null; + + const labelResidues = residues + .map(entry => entry.labelResidue) + .filter(Number.isFinite) + .sort((a, b) => a - b); + const authResidues = residues + .filter(entry => !Number.isFinite(entry.labelResidue)) + .map(entry => entry.authResidue) + .filter(Number.isFinite) + .sort((a, b) => a - b); + const expressions = [ + ...residueRanges(labelResidues).map(([start, end]) => atomGroupExpressionForProperty(chain, MS.struct.atomProperty.macromolecular.label_seq_id(), start, end)), + ...residueRanges(authResidues).map(([start, end]) => atomGroupExpressionForProperty(chain, MS.struct.atomProperty.macromolecular.auth_seq_id(), start, end)), + ]; + return mergeSelectionExpressions(expressions); +} + +function atomGroupExpressionForProperty(chain, residueProperty, start, end) { + const { or } = MS.core.logic; + const { eq } = MS.core.rel; + const { macromolecular } = MS.struct.atomProperty; + return MS.struct.generator.atomGroups({ + 'chain-test': or([ + eq([macromolecular.auth_asym_id(), chain]), + eq([macromolecular.label_asym_id(), chain]), + ]), + 'residue-test': residueRangeTest(residueProperty, start, end), + }); +} + +function residueRangeTest(property, start, end) { + const { and } = MS.core.logic; + const { gre, lte } = MS.core.rel; + const tests = []; + if (Number.isFinite(start)) tests.push(gre([property, start])); + if (Number.isFinite(end)) tests.push(lte([property, end])); + return tests.length === 1 ? tests[0] : and(tests); +} + +function interfaceRegions(structureRef, chains, cutoff = 10) { + const structure = structureRef?.cell?.obj?.data; + if (!structure) return { regions: [] }; + + const chainSet = new Set(chains); + const residuesByChain = new Map(); + const loc = StructureElement.Location.create(structure); + + for (const unit of structure.units) { + if (!Unit.isAtomic(unit)) continue; + loc.unit = unit; + const size = OrderedSet.size(unit.elements); + for (let i = 0; i < size; i++) { + loc.element = OrderedSet.getAt(unit.elements, i); + if (StructureProperties.atom.label_atom_id(loc) !== 'CA') continue; + + const authChain = StructureProperties.chain.auth_asym_id(loc); + const labelChain = StructureProperties.chain.label_asym_id(loc); + const chain = chainSet.has(authChain) ? authChain : (chainSet.has(labelChain) ? labelChain : null); + if (!chain) continue; + + if (!residuesByChain.has(chain)) residuesByChain.set(chain, []); + residuesByChain.get(chain).push({ + residue: StructureProperties.residue.label_seq_id(loc) || StructureProperties.residue.auth_seq_id(loc), + x: StructureProperties.atom.x(loc), + y: StructureProperties.atom.y(loc), + z: StructureProperties.atom.z(loc), + }); + } + } + + const cutoffSq = cutoff * cutoff; + const hits = new Map(Array.from(chainSet).map(chain => [chain, new Set()])); + for (const [chain, residues] of residuesByChain.entries()) { + const otherResidues = []; + for (const [otherChain, entries] of residuesByChain.entries()) { + if (otherChain !== chain) otherResidues.push(...entries); + } + + for (const residue of residues) { + if (otherResidues.some(other => squaredDistance(residue, other) <= cutoffSq)) { + hits.get(chain)?.add(residue.residue); + } + } + } + + const regions = []; + for (const [chain, residues] of hits.entries()) { + const ordered = Array.from(residues).sort((a, b) => a - b); + for (const [start, end] of residueRanges(ordered)) { + regions.push({ chain, start, end }); + } + } + return { regions }; +} + +function structureResidueMap(structureRef, chains) { + const structure = structureRef?.cell?.obj?.data; + if (!structure) return []; + + const chainSet = new Set(chains); + const residuesByChain = new Map(Array.from(chainSet).map(chain => [chain, new Map()])); + const loc = StructureElement.Location.create(structure); + + for (const unit of structure.units) { + if (!Unit.isAtomic(unit)) continue; + loc.unit = unit; + const size = OrderedSet.size(unit.elements); + for (let i = 0; i < size; i++) { + loc.element = OrderedSet.getAt(unit.elements, i); + if (StructureProperties.atom.label_atom_id(loc) !== 'CA') continue; + + const authChain = StructureProperties.chain.auth_asym_id(loc); + const labelChain = StructureProperties.chain.label_asym_id(loc); + const chain = chainSet.has(authChain) ? authChain : (chainSet.has(labelChain) ? labelChain : null); + if (!chain) continue; + + const authResidue = StructureProperties.residue.auth_seq_id(loc); + const labelResidue = StructureProperties.residue.label_seq_id(loc); + const alignmentResidue = labelResidue || authResidue; + if (Number.isFinite(alignmentResidue)) { + residuesByChain.get(chain)?.set(alignmentResidue, { + chain, + alignmentResidue, + labelResidue, + authResidue, + }); + } + } + } + + const residueMap = []; + for (const residues of residuesByChain.values()) { + residueMap.push(...Array.from(residues.values()).sort((a, b) => a.alignmentResidue - b.alignmentResidue)); + } + return residueMap; +} + +function squaredDistance(a, b) { + const dx = a.x - b.x; + const dy = a.y - b.y; + const dz = a.z - b.z; + return dx * dx + dy * dy + dz * dz; +} diff --git a/frontend/molstar/foldseekSuperposition.js b/frontend/molstar/foldseekSuperposition.js new file mode 100644 index 00000000..c49577a8 --- /dev/null +++ b/frontend/molstar/foldseekSuperposition.js @@ -0,0 +1,124 @@ +import { OrderedSet } from 'molstar/lib/mol-data/int'; +import { Mat4 } from 'molstar/lib/mol-math/linear-algebra'; +import { StructureElement, StructureProperties, Unit } from 'molstar/lib/mol-model/structure'; +import { StateTransforms } from 'molstar/lib/mol-plugin-state/transforms'; +import { tmalign, parse as parseTMOutput, parseMatrix as parseTMMatrix } from 'tmalign-wasm'; +import { rangesByChain } from './foldseekSelections.js'; + +export async function transformStructure(plugin, structure, matrix) { + return plugin.state.data.build() + .to(structure.ref) + .insert(StateTransforms.Model.TransformStructureConformation, { + transform: { + name: 'matrix', + params: { data: matrix, transpose: false }, + }, + }) + .commit(); +} + +export async function superposeTargetWithFoldseekAlignment(plugin, target, query, alignments) { + if (!alignments?.length || !alignments[0]?.qAln || !alignments[0]?.dbAln) { + return { structure: target, results: null }; + } + + const queryPdb = makeAlignedCaPdb(query, rangesByChain(alignments, 'query')); + const targetPdb = makeAlignedCaPdb(target, rangesByChain(alignments, 'target')); + if (!queryPdb || !targetPdb) { + return { structure: target, results: null }; + } + + const alnFasta = `>target\n${alignments[0].dbAln}\n\n>query\n${alignments[0].qAln}`; + const tm = await tmalign(targetPdb, queryPdb, alnFasta); + const { t, u } = parseTMMatrix(tm.matrix); + + return { + structure: await transformStructure(plugin, target, makeMat4(t, u)), + results: parseTMOutput(tm.output), + }; +} + +function makeAlignedCaPdb(structureRef, ranges) { + const structure = structureRef?.cell?.obj?.data; + if (!structure || ranges.size === 0) return ''; + + const rows = []; + const loc = StructureElement.Location.create(structure); + let serial = 1; + + for (const unit of structure.units) { + if (!Unit.isAtomic(unit)) continue; + loc.unit = unit; + + const { elements } = unit; + const size = OrderedSet.size(elements); + for (let i = 0; i < size; i++) { + loc.element = OrderedSet.getAt(elements, i); + + const atomName = StructureProperties.atom.label_atom_id(loc); + if (atomName !== 'CA') continue; + + const chain = StructureProperties.chain.auth_asym_id(loc) || StructureProperties.chain.label_asym_id(loc) || 'A'; + const chainRanges = ranges.get(chain) || (ranges.size === 1 ? ranges.values().next().value : null); + if (!chainRanges) continue; + + const resno = StructureProperties.residue.auth_seq_id(loc) || StructureProperties.residue.label_seq_id(loc); + if (!chainRanges.some(([start, end]) => resno >= start && resno <= end)) continue; + + rows.push(atomToPdbRow({ + serial, + atomName, + resName: StructureProperties.atom.auth_comp_id(loc) || StructureProperties.atom.label_comp_id(loc) || 'ALA', + chain, + resno, + x: StructureProperties.atom.x(loc), + y: StructureProperties.atom.y(loc), + z: StructureProperties.atom.z(loc), + })); + serial += 1; + } + } + + return rows.join('\n'); +} + +function atomToPdbRow(atom) { + const atomName = formatAtomName(atom.atomName, atom.element || atom.atomName?.[0] || 'C'); + return [ + (atom.group || 'ATOM').padEnd(6).slice(0, 6), + String(atom.serial).padStart(5), + ' ', + atomName, + atom.altId || ' ', + atom.resName.slice(0, 3).padStart(3), + ' ', + String(atom.chain).slice(0, 1).padStart(1), + String(atom.resno).padStart(4), + atom.insCode || ' ', + ' ', + atom.x.toFixed(3).padStart(8), + atom.y.toFixed(3).padStart(8), + atom.z.toFixed(3).padStart(8), + (Number.isFinite(atom.occupancy) ? atom.occupancy : 1).toFixed(2).padStart(6), + (Number.isFinite(atom.bFactor) ? atom.bFactor : 0).toFixed(2).padStart(6), + ' ', + String(atom.element || '').trim().slice(0, 2).padStart(2), + ' ', + ].join(''); +} + +function formatAtomName(atomName, element) { + const name = String(atomName || '').slice(0, 4); + if (name.length >= 4) return name; + const symbol = String(element || '').trim(); + return symbol.length <= 1 ? ` ${name.padEnd(3)}` : name.padEnd(4); +} + +function makeMat4(t, u) { + return Mat4.ofRows([ + [u[0][0], u[0][1], u[0][2], t[0]], + [u[1][0], u[1][1], u[1][2], t[1]], + [u[2][0], u[2][1], u[2][2], t[2]], + [0, 0, 0, 1], + ]); +} diff --git a/frontend/molstar/foldseekUtilities.js b/frontend/molstar/foldseekUtilities.js new file mode 100644 index 00000000..c9ef4eb6 --- /dev/null +++ b/frontend/molstar/foldseekUtilities.js @@ -0,0 +1,61 @@ +const oneToThree = { + A: 'ALA', + R: 'ARG', + N: 'ASN', + D: 'ASP', + C: 'CYS', + E: 'GLU', + Q: 'GLN', + G: 'GLY', + H: 'HIS', + I: 'ILE', + L: 'LEU', + K: 'LYS', + M: 'MET', + F: 'PHE', + P: 'PRO', + S: 'SER', + T: 'THR', + W: 'TRP', + Y: 'TYR', + V: 'VAL', + U: 'SEC', + O: 'PYL', + X: 'ALA', +}; + +export function getChainName(name) { + if (!name || /_v[0-9]+$/.test(name)) return 'A'; + const pos = name.lastIndexOf('_'); + return pos !== -1 ? name.substring(pos + 1, pos + 2) : 'A'; +} + +export function getAccession(name) { + if (!name || /_v[0-9]+$/.test(name)) return name; + const pos = name.lastIndexOf('_'); + return pos !== -1 ? name.substring(0, pos) : name; +} + +export function mockPDB(ca, seq, chain) { + const atoms = ca.split(','); + const pdb = []; + let j = 1; + for (let i = 0; i < atoms.length; i += 3, j++) { + const [x, y, z] = atoms.slice(i, i + 3).map(element => Number.parseFloat(element)); + const residue = seq !== '' && atoms.length / 3 === seq.length ? seq[i / 3] : 'A'; + pdb.push( + 'ATOM ' + + j.toString().padStart(5) + + ' CA ' + + (oneToThree[residue] || 'ALA') + + chain.toString().padStart(2) + + j.toString().padStart(4) + + ' ' + + x.toFixed(3).padStart(8) + + y.toFixed(3).padStart(8) + + z.toFixed(3).padStart(8) + + ' 1.00 0.00 C ', + ); + } + return pdb.join('\n'); +} diff --git a/frontend/molstar/viewerHelpers.js b/frontend/molstar/viewerHelpers.js new file mode 100644 index 00000000..d2915f3a --- /dev/null +++ b/frontend/molstar/viewerHelpers.js @@ -0,0 +1,90 @@ +import { PluginContext } from 'molstar/lib/mol-plugin/context'; +import { PluginConfig } from 'molstar/lib/mol-plugin/config'; +import { Color } from 'molstar/lib/mol-util/color'; + +export const DEFAULT_SPIN_SPEED = 0.05; + +export function createMolstarPlugin() { + return new PluginContext({ + actions: [], + behaviors: [], + animations: [], + config: [ + [PluginConfig.General.Transparency, 'blended'], + ], + }); +} + +export function parseColor(value, fallback = 0xffffff) { + if (typeof value === 'number' && Number.isFinite(value)) { + return Color(value); + } + if (typeof value === 'string') { + const hex = value.trim().replace(/^#/, ''); + const parsed = Number.parseInt(hex, 16); + if (Number.isFinite(parsed)) { + return Color(parsed); + } + } + return Color(fallback); +} + +export function applyViewerCanvasProps(plugin, options = {}) { + const canvas3d = plugin?.canvas3d; + if (!canvas3d) return; + + canvas3d.setProps({ + transparentBackground: true, + sceneRadiusFactor: 3.0, + cameraFog: { name: 'off', params: {} }, + renderer: { + ...canvas3d.props.renderer, + ...(options.renderer || {}), + }, + marking: options.marking + ? { + ...canvas3d.props.marking, + ...options.marking, + } + : canvas3d.props.marking, + camera: { + helper: { axes: { name: 'off', params: {} } }, + fov: 60, + }, + cameraClipping: { + radius: 100, + far: false, + minNear: 0.1, + }, + postprocessing: { + enabled: true, + occlusion: { name: 'off', params: {} }, + shadow: { name: 'off', params: {} }, + outline: { + name: 'on', + params: { + scale: 0.5, + threshold: 0.1, + color: Color(0x111111), + includeTransparent: true, + }, + }, + dof: { name: 'off', params: {} }, + bloom: { name: 'off', params: {} }, + antialiasing: { name: 'off', params: {} }, + }, + }); +} + +export function setCanvasSpin(plugin, enabled) { + if (!plugin?.canvas3d) return; + const trackball = plugin.canvas3d.props.trackball; + plugin.canvas3d.setProps({ + trackball: { + ...trackball, + animate: enabled + ? { name: 'spin', params: { speed: DEFAULT_SPIN_SPEED, axis: [0, 1, 0] } } + : { name: 'off', params: {} }, + }, + }); +} diff --git a/frontend/webpack.frontend.config.js b/frontend/webpack.frontend.config.js index 1b65aa72..bfd63d43 100644 --- a/frontend/webpack.frontend.config.js +++ b/frontend/webpack.frontend.config.js @@ -49,7 +49,7 @@ module.exports = (env, argv) => { filename: isElectron ? 'renderer.js' : (isLocal ? '[name].js' : '[contenthash:20].js'), crossOriginLoading: 'anonymous', }, - cache: { + cache: isLocal ? false : { type: 'filesystem' }, module: { @@ -58,6 +58,7 @@ module.exports = (env, argv) => { test: /\.vue$/, loader: 'vue-loader', options: { + hotReload: false, transformAssetUrls: { video: ['src', 'poster'], source: 'src', diff --git a/package-lock.json b/package-lock.json index 35b60702..0653cd1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "d3-sankey": "^0.12.3", "interactjs": "^1.10.27", "mitt": "^3.0.1", + "molstar": "^5.9.0", "msa-webgpu": "^0.0.10" }, "devDependencies": { @@ -47,7 +48,6 @@ "imagemin": "^8.0.1", "imagemin-pngquant": "^9.0.2", "mini-css-extract-plugin": "^2.7.6", - "ngl": "2.0.1", "pako": "^2.1.0", "pulchra-wasm": "^0.0.3", "sass": "<1.33.0", @@ -2055,6 +2055,13 @@ "node": ">= 8" } }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, "node_modules/@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -2091,11 +2098,22 @@ "node": ">= 10" } }, + "node_modules/@types/argparse": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-2.0.17.tgz", + "integrity": "sha512-fueJssTf+4dW4HODshEGkIZbkLKHzgu1FvCI4cTc/MKum/534Euo3SrN+ilq8xgyHnOjtmg33/hee8iXLRg1XA==", + "license": "MIT" + }, + "node_modules/@types/benchmark": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/benchmark/-/benchmark-2.1.5.tgz", + "integrity": "sha512-cKio2eFB3v7qmKcvIHLUMw/dIx/8bhWPuzpzRT4unCPRTD8VdA9Zb0afxpcxOqR4PixRS7yT42FqGS8BYL8g1w==", + "license": "MIT" + }, "node_modules/@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "dev": true, "dependencies": { "@types/connect": "*", "@types/node": "*" @@ -2110,11 +2128,20 @@ "@types/node": "*" } }, + "node_modules/@types/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-kCFuWS0ebDbmxs0AXYn6e2r2nrGAb5KwQhknjSPSPgJcGd8+HVSILlUyFhGqML2gk39HcG7D1ydW9/qpYkN00Q==", + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/node": "*" + } + }, "node_modules/@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dev": true, "dependencies": { "@types/node": "*" } @@ -2133,7 +2160,6 @@ "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", - "dev": true, "dependencies": { "@types/ms": "*" } @@ -2161,14 +2187,21 @@ "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } }, "node_modules/@types/express": { "version": "4.17.13", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", - "dev": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.18", @@ -2180,7 +2213,6 @@ "version": "4.17.28", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", - "dev": true, "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -2207,12 +2239,27 @@ "@types/node": "*" } }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", "dev": true }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "license": "MIT" + }, "node_modules/@types/http-proxy": { "version": "1.17.8", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz", @@ -2228,11 +2275,19 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", - "dev": true + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, "node_modules/@types/minimatch": { "version": "5.1.2", @@ -2244,14 +2299,12 @@ "node_modules/@types/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", - "dev": true + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, "node_modules/@types/node": { "version": "16.11.26", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.26.tgz", - "integrity": "sha512-GZ7bu5A6+4DtG7q9GsoHXy3ALcgeIHP4NnL0Vv2wu0uUB/yQex26v0tf6/na1mm0+bS9Uw+0DFex7aaKr2qawQ==", - "dev": true + "integrity": "sha512-GZ7bu5A6+4DtG7q9GsoHXy3ALcgeIHP4NnL0Vv2wu0uUB/yQex26v0tf6/na1mm0+bS9Uw+0DFex7aaKr2qawQ==" }, "node_modules/@types/plist": { "version": "3.0.2", @@ -2267,14 +2320,22 @@ "node_modules/@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" }, "node_modules/@types/range-parser": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "dev": true + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, + "node_modules/@types/react": { + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz", + "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } }, "node_modules/@types/retry": { "version": "0.12.1", @@ -2282,6 +2343,15 @@ "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==", "dev": true }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/serve-index": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", @@ -2295,7 +2365,6 @@ "version": "1.13.10", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", - "dev": true, "dependencies": { "@types/mime": "^1", "@types/node": "*" @@ -2310,6 +2379,18 @@ "@types/node": "*" } }, + "node_modules/@types/swagger-ui-dist": { + "version": "3.30.6", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-dist/-/swagger-ui-dist-3.30.6.tgz", + "integrity": "sha512-FVxN7wjLYRtJsZBscOcOcf8oR++m38vbUFjT33Mr9HBuasX9bRDrJsp7iwixcOtKSHEEa2B7o2+4wEiXqC+Ebw==", + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/@types/verror": { "version": "1.10.6", "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.6.tgz", @@ -2351,6 +2432,12 @@ "@types/node": "*" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "license": "ISC" + }, "node_modules/@vue/compiler-core": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz", @@ -2982,8 +3069,7 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/array-flatten": { "version": "2.1.2", @@ -3175,6 +3261,16 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -4289,7 +4385,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, "engines": { "node": ">= 0.8" } @@ -4340,7 +4435,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -4353,7 +4447,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" @@ -4425,6 +4518,16 @@ "node": ">=4" } }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -4441,6 +4544,46 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -4477,12 +4620,6 @@ "node": ">=10" } }, - "node_modules/chroma-js": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-1.4.1.tgz", - "integrity": "sha512-jTwQiT859RTFN/vIf7s+Vl/Z2LcMrvMv3WUFmd/4u76AdlFC0NTNgqEEFPcRiHmAswPsMiQEDZLM8vX8qXpZNQ==", - "dev": true - }, "node_modules/chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -4686,6 +4823,16 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", @@ -4714,7 +4861,6 @@ "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, "dependencies": { "mime-db": ">= 1.43.0 < 2" }, @@ -4726,7 +4872,6 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", - "dev": true, "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", @@ -4764,7 +4909,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "dependencies": { "ms": "2.0.0" } @@ -4772,14 +4916,12 @@ "node_modules/compression/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "node_modules/compression/node_modules/negotiator": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -4788,7 +4930,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -4893,7 +5034,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -4911,7 +5051,6 @@ "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -4941,6 +5080,23 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/crc": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", @@ -5128,8 +5284,7 @@ "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" }, "node_modules/d3": { "version": "7.9.0", @@ -5603,7 +5758,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "dependencies": { "ms": "^2.1.3" }, @@ -5634,6 +5788,19 @@ "node": ">=0.10.0" } }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/decode-uri-component": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", @@ -5970,6 +6137,15 @@ "node": ">= 0.6" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -5995,6 +6171,19 @@ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "dev": true }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dir-compare": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-2.4.0.tgz", @@ -6287,7 +6476,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -6316,8 +6504,7 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/ejs": { "version": "3.1.10", @@ -6708,7 +6895,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -6717,7 +6903,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -6732,7 +6917,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "dependencies": { "es-errors": "^1.3.0" }, @@ -6780,8 +6964,7 @@ "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, "node_modules/escape-string-regexp": { "version": "1.0.5", @@ -6835,6 +7018,16 @@ "node": ">=4.0" } }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", @@ -6854,7 +7047,6 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "dev": true, "engines": { "node": ">= 0.6" } @@ -7102,8 +7294,7 @@ "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "node_modules/extract-zip": { "version": "1.7.0", @@ -7611,11 +7802,17 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, "engines": { "node": ">= 0.6" } }, + "node_modules/fp-ts": { + "version": "2.16.11", + "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.11.tgz", + "integrity": "sha512-LaI+KaX2NFkfn1ZGHoKCmcfv7yrZsC3b8NtWsTVQeHkq4F27vI5igUuO53sxqDEa2gNQMHFPmpojDw/1zmUK7w==", + "license": "MIT", + "peer": true + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -7697,7 +7894,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7724,7 +7920,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -7748,7 +7943,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -7931,7 +8125,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -7973,6 +8166,12 @@ "integrity": "sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==", "dev": true }, + "node_modules/h264-mp4-encoder": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/h264-mp4-encoder/-/h264-mp4-encoder-1.0.12.tgz", + "integrity": "sha512-xih3J+Go0o1RqGjhOt6TwXLWWGqLONRPyS8yoMu/RoS/S8WyEv4HuHp1KBsDDl8srZQ3gw9f95JYkCSjCuZbHQ==", + "license": "MIT" + }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -8049,7 +8248,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -8107,7 +8305,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -8115,6 +8312,46 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -8184,6 +8421,16 @@ "node": ">= 12" } }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/html-webpack-plugin": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.3.tgz", @@ -8223,7 +8470,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "dev": true, "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", @@ -8243,7 +8489,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, "engines": { "node": ">= 0.8" } @@ -8252,7 +8497,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "dev": true, "engines": { "node": ">= 0.8" } @@ -8718,6 +8962,12 @@ "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", "dev": true }, + "node_modules/immutable": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.6.tgz", + "integrity": "sha512-q1swsS8K7L8usSHuOqF2TAoCCkonYz0SG38wLAggaa4Wml70zixIvt2ql4coQ2C2B3hTjltJry4r6bULwgAXLQ==", + "license": "MIT" + }, "node_modules/import-local": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.3.tgz", @@ -8756,8 +9006,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "1.3.8", @@ -8765,6 +9014,12 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, "node_modules/interactjs": { "version": "1.10.27", "resolved": "https://registry.npmjs.org/interactjs/-/interactjs-1.10.27.tgz", @@ -8813,6 +9068,15 @@ "node": ">=0.10.0" } }, + "node_modules/io-ts": { + "version": "2.2.22", + "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-2.2.22.tgz", + "integrity": "sha512-FHCCztTkHoV9mdBsHpocLpdTAfh956ZQcIkWQxxS0U5HT53vtrcuYdQneEJKH6xILaLNzXVl2Cvwtoy8XNN0AA==", + "license": "MIT", + "peerDependencies": { + "fp-ts": "^2.5.0" + } + }, "node_modules/ipaddr.js": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", @@ -8822,6 +9086,30 @@ "node": ">= 10" } }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-arrayish": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", @@ -8864,6 +9152,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", @@ -8909,6 +9207,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-natural-number": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", @@ -8981,6 +9289,12 @@ "node": ">=8" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/is-retry-allowed": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", @@ -9412,6 +9726,16 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -9442,6 +9766,16 @@ "node": ">=10" } }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/matcher": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", @@ -9472,11 +9806,292 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "engines": { "node": ">= 0.4" } }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -9540,166 +10155,1174 @@ "node": ">= 0.6" } }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" }, - "engines": { - "node": ">=8.6" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, - "bin": { - "mime": "cli.js" + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.7.6", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz", + "integrity": "sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw==", + "dev": true, + "dependencies": { + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "node_modules/minipass": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", + "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" + }, + "node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "node_modules/molstar": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/molstar/-/molstar-5.9.0.tgz", + "integrity": "sha512-yNf9FpWVxoo0blc4y0joCE83/OEFNWlhlGYPEWIO0OYgkshT25R5qYJJdsKJwlEG44q2tjKbQ+e0LXV10hpBQg==", + "license": "MIT", + "dependencies": { + "@types/argparse": "^2.0.17", + "@types/benchmark": "^2.1.5", + "@types/compression": "1.8.1", + "@types/express": "^5.0.6", + "@types/node": "^22.19.17", + "@types/swagger-ui-dist": "3.30.6", + "argparse": "^2.0.1", + "compression": "^1.8.1", + "cors": "^2.8.6", + "express": "^5.2.1", + "h264-mp4-encoder": "^1.0.12", + "immutable": "^5.1.5", + "io-ts": "^2.2.22", + "mutative": "^1.3.0", + "react-markdown": "^10.1.0", + "remark-gfm": "^4.0.1", + "rxjs": "^7.8.2", + "swagger-ui-dist": "^5.32.5", + "tslib": "^2.8.1" + }, + "bin": { + "cif2bcif": "lib/commonjs/cli/cif2bcif/index.js", + "cifschema": "lib/commonjs/cli/cifschema/index.js", + "model-server": "lib/commonjs/servers/model/server.js", + "model-server-preprocess": "lib/commonjs/servers/model/preprocess.js", + "model-server-query": "lib/commonjs/servers/model/query.js", + "mvs-print-schema": "lib/commonjs/cli/mvs/mvs-print-schema.js", + "mvs-render": "lib/commonjs/cli/mvs/mvs-render.js", + "mvs-validate": "lib/commonjs/cli/mvs/mvs-validate.js", + "volume-server": "lib/commonjs/servers/volume/server.js", + "volume-server-pack": "lib/commonjs/servers/volume/pack.js", + "volume-server-query": "lib/commonjs/servers/volume/query.js" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "@google-cloud/storage": "^7.14.0", + "canvas": "^2.11.2", + "gl": "^6.0.2", + "jpeg-js": "^0.4.4", + "pngjs": "^6.0.0", + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + }, + "peerDependenciesMeta": { + "@google-cloud/storage": { + "optional": true + }, + "canvas": { + "optional": true + }, + "gl": { + "optional": true + }, + "jpeg-js": { + "optional": true + }, + "pngjs": { + "optional": true + } + } + }, + "node_modules/molstar/node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/molstar/node_modules/@types/express-serve-static-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/molstar/node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/molstar/node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/molstar/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/molstar/node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/molstar/node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/molstar/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/molstar/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/molstar/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/molstar/node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/molstar/node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/molstar/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/molstar/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">=4.0.0" + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, + "node_modules/molstar/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "dependencies": { - "mime-db": "1.52.0" + "node_modules/molstar/node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/molstar/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, + "node_modules/molstar/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, "engines": { - "node": ">=6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, + "node_modules/molstar/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", "engines": { - "node": ">=4" + "node": ">= 0.6" } }, - "node_modules/mini-css-extract-plugin": { - "version": "2.7.6", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz", - "integrity": "sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw==", - "dev": true, + "node_modules/molstar/node_modules/qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "license": "BSD-3-Clause", "dependencies": { - "schema-utils": "^4.0.0" + "side-channel": "^1.1.0" }, "engines": { - "node": ">= 12.13.0" + "node": ">=0.6" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "node_modules/molstar/node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { - "node": "*" + "node": ">= 0.10" } }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, - "node_modules/minipass": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", - "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", - "dev": true, + "node_modules/molstar/node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" }, "engines": { - "node": ">=8" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, + "node_modules/molstar/node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" }, "engines": { - "node": ">= 8" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" + "node_modules/molstar/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, - "node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, + "node_modules/molstar/node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", "dependencies": { - "minimist": "^1.2.5" + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true + "node_modules/molstar/node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/msa-webgpu": { "version": "0.0.10", @@ -9720,6 +11343,15 @@ "multicast-dns": "cli.js" } }, + "node_modules/mutative": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mutative/-/mutative-1.3.0.tgz", + "integrity": "sha512-8MJj6URmOZAV70dpFe1YnSppRTKC4DsMkXQiBDFayLcDI4ljGokHxmpqaBQuDWa4iAxWaJJ1PS8vAmbntjjKmQ==", + "license": "MIT", + "engines": { + "node": ">=14.0" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -9759,19 +11391,6 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, - "node_modules/ngl": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ngl/-/ngl-2.0.1.tgz", - "integrity": "sha512-/Jci0wWAdiFW/3xZmUVYcRpSi2pdSGZCT64fM+qiT6kg0aF+7fRUW54nJYaSEWVUsW2TL9U2YVE3ezmhNZbYrg==", - "dev": true, - "dependencies": { - "chroma-js": "^1.3.7", - "promise-polyfill": "^8.0.0", - "signals": "^1.0.0", - "sprintf-js": "^1.1.2", - "three": "^0.118.0" - } - }, "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -9911,7 +11530,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -9920,7 +11538,6 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -9948,7 +11565,6 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, "dependencies": { "ee-first": "1.1.1" }, @@ -9960,7 +11576,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", - "dev": true, "engines": { "node": ">= 0.8" } @@ -9969,7 +11584,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "dependencies": { "wrappy": "1" } @@ -10233,6 +11847,31 @@ "node": ">=0.10.0" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "node_modules/parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", @@ -10261,7 +11900,6 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, "engines": { "node": ">= 0.8" } @@ -10495,7 +12133,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=12.13.0" } @@ -10759,11 +12397,15 @@ "node": ">=0.4.0" } }, - "node_modules/promise-polyfill": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz", - "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==", - "dev": true + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } }, "node_modules/proto-list": { "version": "1.2.4", @@ -10775,7 +12417,6 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -10788,7 +12429,6 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, "engines": { "node": ">= 0.10" } @@ -10892,7 +12532,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -10939,6 +12578,56 @@ "rc": "cli.js" } }, + "node_modules/react": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.6" + } + }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, "node_modules/read-config-file": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.2.0.tgz", @@ -11182,22 +12871,88 @@ "regjsparser": "bin/parser" } }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", - "dev": true, - "engines": { - "node": ">= 0.10" + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, "node_modules/renderkid": { @@ -11502,6 +13257,41 @@ "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", "license": "Unlicense" }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -11531,6 +13321,15 @@ "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", "license": "BSD-3-Clause" }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -11609,6 +13408,13 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT", + "peer": true + }, "node_modules/schema-utils": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", @@ -11947,8 +13753,7 @@ "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "node_modules/shallow-clone": { "version": "3.0.1", @@ -12074,7 +13879,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -12093,7 +13897,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" @@ -12109,7 +13912,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -12127,7 +13929,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -12148,12 +13949,6 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, - "node_modules/signals": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/signals/-/signals-1.0.0.tgz", - "integrity": "sha1-ZfDBWZNSs1Ny7KrlolDmEHN27Wk=", - "dev": true - }, "node_modules/simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", @@ -12360,6 +14155,16 @@ "source-map": "^0.6.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", @@ -12440,7 +14245,8 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", - "dev": true + "dev": true, + "optional": true }, "node_modules/sshpk": { "version": "1.16.1", @@ -12555,6 +14361,20 @@ "node": ">=8" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -12660,6 +14480,24 @@ "webpack": "^5.0.0" } }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, "node_modules/sumchecker": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", @@ -12812,6 +14650,15 @@ "camelcase": "^3.0.0" } }, + "node_modules/swagger-ui-dist": { + "version": "5.32.6", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.32.6.tgz", + "integrity": "sha512-75ttZNaYCLoFPnozPZcTUU6mS3wKT8l7WLjU5zJSHFeJa23i5vtnze6IiCl4jDMPeQTXVXIgovq4M11NNfQvSA==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, "node_modules/tapable": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", @@ -13055,12 +14902,6 @@ "b4a": "^1.6.4" } }, - "node_modules/three": { - "version": "0.118.3", - "resolved": "https://registry.npmjs.org/three/-/three-0.118.3.tgz", - "integrity": "sha512-ijECXrNzDkHieoeh2H69kgawTGH8DiamhR4uBN8jEM7VHSKvfTdEvOoHsA8Aq7dh7PHAxhlqBsN5arBI3KixSw==", - "dev": true - }, "node_modules/throttleit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", @@ -13143,7 +14984,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, "engines": { "node": ">=0.6" } @@ -13178,6 +15018,16 @@ "node": ">=0.8" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/trim-repeated": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", @@ -13190,6 +15040,16 @@ "node": ">=0.10.0" } }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/truncate-utf8-bytes": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", @@ -13202,8 +15062,7 @@ "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, "node_modules/tunnel": { "version": "0.0.6", @@ -13281,6 +15140,12 @@ "through": "^2.3.8" } }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -13321,6 +15186,105 @@ "node": ">=4" } }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -13334,7 +15298,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, "engines": { "node": ">= 0.8" } @@ -13519,7 +15482,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true, "engines": { "node": ">= 0.8" } @@ -13538,6 +15500,34 @@ "extsprintf": "^1.2.0" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vue": { "version": "2.7.16", "resolved": "https://registry.npmjs.org/vue/-/vue-2.7.16.tgz", @@ -14035,8 +16025,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "node_modules/ws": { "version": "8.18.3", @@ -14168,6 +16157,16 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } }, "dependencies": { @@ -15572,6 +17571,11 @@ "fastq": "^1.6.0" } }, + "@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==" + }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -15599,11 +17603,20 @@ "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "dev": true }, + "@types/argparse": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-2.0.17.tgz", + "integrity": "sha512-fueJssTf+4dW4HODshEGkIZbkLKHzgu1FvCI4cTc/MKum/534Euo3SrN+ilq8xgyHnOjtmg33/hee8iXLRg1XA==" + }, + "@types/benchmark": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/benchmark/-/benchmark-2.1.5.tgz", + "integrity": "sha512-cKio2eFB3v7qmKcvIHLUMw/dIx/8bhWPuzpzRT4unCPRTD8VdA9Zb0afxpcxOqR4PixRS7yT42FqGS8BYL8g1w==" + }, "@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "dev": true, "requires": { "@types/connect": "*", "@types/node": "*" @@ -15618,11 +17631,19 @@ "@types/node": "*" } }, + "@types/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-kCFuWS0ebDbmxs0AXYn6e2r2nrGAb5KwQhknjSPSPgJcGd8+HVSILlUyFhGqML2gk39HcG7D1ydW9/qpYkN00Q==", + "requires": { + "@types/express": "*", + "@types/node": "*" + } + }, "@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dev": true, "requires": { "@types/node": "*" } @@ -15641,7 +17662,6 @@ "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", - "dev": true, "requires": { "@types/ms": "*" } @@ -15669,14 +17689,20 @@ "@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" + }, + "@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "requires": { + "@types/estree": "*" + } }, "@types/express": { "version": "4.17.13", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", - "dev": true, "requires": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.18", @@ -15688,7 +17714,6 @@ "version": "4.17.28", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", - "dev": true, "requires": { "@types/node": "*", "@types/qs": "*", @@ -15715,12 +17740,25 @@ "@types/node": "*" } }, + "@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "requires": { + "@types/unist": "*" + } + }, "@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", "dev": true }, + "@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==" + }, "@types/http-proxy": { "version": "1.17.8", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz", @@ -15736,11 +17774,18 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "requires": { + "@types/unist": "*" + } + }, "@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", - "dev": true + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, "@types/minimatch": { "version": "5.1.2", @@ -15752,14 +17797,12 @@ "@types/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", - "dev": true + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, "@types/node": { "version": "16.11.26", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.26.tgz", - "integrity": "sha512-GZ7bu5A6+4DtG7q9GsoHXy3ALcgeIHP4NnL0Vv2wu0uUB/yQex26v0tf6/na1mm0+bS9Uw+0DFex7aaKr2qawQ==", - "dev": true + "integrity": "sha512-GZ7bu5A6+4DtG7q9GsoHXy3ALcgeIHP4NnL0Vv2wu0uUB/yQex26v0tf6/na1mm0+bS9Uw+0DFex7aaKr2qawQ==" }, "@types/plist": { "version": "3.0.2", @@ -15775,14 +17818,21 @@ "@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" }, "@types/range-parser": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "dev": true + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, + "@types/react": { + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz", + "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==", + "peer": true, + "requires": { + "csstype": "^3.2.2" + } }, "@types/retry": { "version": "0.12.1", @@ -15790,6 +17840,14 @@ "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==", "dev": true }, + "@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "requires": { + "@types/node": "*" + } + }, "@types/serve-index": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", @@ -15803,7 +17861,6 @@ "version": "1.13.10", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", - "dev": true, "requires": { "@types/mime": "^1", "@types/node": "*" @@ -15818,6 +17875,16 @@ "@types/node": "*" } }, + "@types/swagger-ui-dist": { + "version": "3.30.6", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-dist/-/swagger-ui-dist-3.30.6.tgz", + "integrity": "sha512-FVxN7wjLYRtJsZBscOcOcf8oR++m38vbUFjT33Mr9HBuasX9bRDrJsp7iwixcOtKSHEEa2B7o2+4wEiXqC+Ebw==" + }, + "@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, "@types/verror": { "version": "1.10.6", "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.6.tgz", @@ -15859,6 +17926,11 @@ "@types/node": "*" } }, + "@ungap/structured-clone": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==" + }, "@vue/compiler-core": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz", @@ -16378,8 +18450,7 @@ "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "array-flatten": { "version": "2.1.2", @@ -16525,6 +18596,11 @@ "@babel/helper-define-polyfill-provider": "^0.4.2" } }, + "bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==" + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -17404,8 +19480,7 @@ "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, "cacheable-request": { "version": "6.1.0", @@ -17443,7 +19518,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "requires": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -17453,7 +19527,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, "requires": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" @@ -17499,6 +19572,11 @@ "url-to-options": "^1.0.1" } }, + "ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==" + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -17509,6 +19587,26 @@ "supports-color": "^7.1.0" } }, + "character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==" + }, + "character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==" + }, + "character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==" + }, + "character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==" + }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -17527,14 +19625,8 @@ }, "chownr": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true - }, - "chroma-js": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-1.4.1.tgz", - "integrity": "sha512-jTwQiT859RTFN/vIf7s+Vl/Z2LcMrvMv3WUFmd/4u76AdlFC0NTNgqEEFPcRiHmAswPsMiQEDZLM8vX8qXpZNQ==", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "dev": true }, "chrome-trace-event": { @@ -17699,6 +19791,11 @@ "delayed-stream": "~1.0.0" } }, + "comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==" + }, "commander": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", @@ -17721,7 +19818,6 @@ "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, "requires": { "mime-db": ">= 1.43.0 < 2" } @@ -17730,7 +19826,6 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", - "dev": true, "requires": { "bytes": "3.1.2", "compressible": "~2.0.18", @@ -17745,7 +19840,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -17753,20 +19847,17 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "negotiator": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "dev": true + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==" }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" } } }, @@ -17843,8 +19934,7 @@ "content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" }, "convert-source-map": { "version": "1.8.0", @@ -17858,8 +19948,7 @@ "cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "dev": true + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==" }, "cookie-signature": { "version": "1.0.6", @@ -17882,6 +19971,15 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, + "cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "crc": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", @@ -18009,8 +20107,7 @@ "csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" }, "d3": { "version": "7.9.0", @@ -18338,7 +20435,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "requires": { "ms": "^2.1.3" } @@ -18358,6 +20454,14 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, + "decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "requires": { + "character-entities": "^2.0.0" + } + }, "decode-uri-component": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", @@ -18621,6 +20725,11 @@ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", "dev": true }, + "dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" + }, "destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -18639,6 +20748,14 @@ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "dev": true }, + "devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "requires": { + "dequal": "^2.0.0" + } + }, "dir-compare": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-2.4.0.tgz", @@ -18876,7 +20993,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "requires": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -18902,8 +21018,7 @@ "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "ejs": { "version": "3.1.10", @@ -19211,14 +21326,12 @@ "es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" }, "es-errors": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" }, "es-module-lexer": { "version": "2.0.0", @@ -19230,7 +21343,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "requires": { "es-errors": "^1.3.0" } @@ -19269,8 +21381,7 @@ "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, "escape-string-regexp": { "version": "1.0.5", @@ -19311,6 +21422,11 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true }, + "estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==" + }, "estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", @@ -19326,8 +21442,7 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "dev": true + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, "eventemitter3": { "version": "4.0.7", @@ -19512,8 +21627,7 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extract-zip": { "version": "1.7.0", @@ -19897,8 +22011,13 @@ "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fp-ts": { + "version": "2.16.11", + "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.11.tgz", + "integrity": "sha512-LaI+KaX2NFkfn1ZGHoKCmcfv7yrZsC3b8NtWsTVQeHkq4F27vI5igUuO53sxqDEa2gNQMHFPmpojDw/1zmUK7w==", + "peer": true }, "fresh": { "version": "0.5.2", @@ -19964,8 +22083,7 @@ "function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "gensync": { "version": "1.0.0-beta.2", @@ -19983,7 +22101,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "requires": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -20001,7 +22118,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "requires": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -20140,8 +22256,7 @@ "gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" }, "got": { "version": "9.6.0", @@ -20174,6 +22289,11 @@ "integrity": "sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==", "dev": true }, + "h264-mp4-encoder": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/h264-mp4-encoder/-/h264-mp4-encoder-1.0.12.tgz", + "integrity": "sha512-xih3J+Go0o1RqGjhOt6TwXLWWGqLONRPyS8yoMu/RoS/S8WyEv4HuHp1KBsDDl8srZQ3gw9f95JYkCSjCuZbHQ==" + }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -20230,8 +22350,7 @@ "has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" }, "has-to-string-tag-x": { "version": "1.4.1", @@ -20271,11 +22390,40 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "requires": { "function-bind": "^1.1.2" } }, + "hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "requires": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + } + }, + "hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "requires": { + "@types/hast": "^3.0.0" + } + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -20332,6 +22480,11 @@ } } }, + "html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==" + }, "html-webpack-plugin": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.3.tgz", @@ -20361,7 +22514,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "dev": true, "requires": { "depd": "~2.0.0", "inherits": "~2.0.4", @@ -20373,14 +22525,12 @@ "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" }, "statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "dev": true + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" } } }, @@ -20685,6 +22835,11 @@ "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", "dev": true }, + "immutable": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.6.tgz", + "integrity": "sha512-q1swsS8K7L8usSHuOqF2TAoCCkonYz0SG38wLAggaa4Wml70zixIvt2ql4coQ2C2B3hTjltJry4r6bULwgAXLQ==" + }, "import-local": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.3.tgz", @@ -20714,8 +22869,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.8", @@ -20723,6 +22877,11 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, + "inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==" + }, "interactjs": { "version": "1.10.27", "resolved": "https://registry.npmjs.org/interactjs/-/interactjs-1.10.27.tgz", @@ -20758,12 +22917,32 @@ "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", "dev": true }, + "io-ts": { + "version": "2.2.22", + "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-2.2.22.tgz", + "integrity": "sha512-FHCCztTkHoV9mdBsHpocLpdTAfh956ZQcIkWQxxS0U5HT53vtrcuYdQneEJKH6xILaLNzXVl2Cvwtoy8XNN0AA==", + "requires": {} + }, "ipaddr.js": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", "dev": true }, + "is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==" + }, + "is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "requires": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + } + }, "is-arrayish": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", @@ -20797,6 +22976,11 @@ "has": "^1.0.3" } }, + "is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==" + }, "is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", @@ -20824,6 +23008,11 @@ "is-extglob": "^2.1.1" } }, + "is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==" + }, "is-natural-number": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", @@ -20875,6 +23064,11 @@ "integrity": "sha512-4KPGizaVGj2LK7xwJIz8o5B2ubu1D/vcQsgOGFEDlpcvgZHto4gBnyd0ig7Ws+67ixmwKoNmu0hYnpo6AaKb5g==", "dev": true }, + "is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + }, "is-retry-allowed": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", @@ -21232,6 +23426,11 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==" + }, "lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -21256,6 +23455,11 @@ "yallist": "^4.0.0" } }, + "markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==" + }, "matcher": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", @@ -21278,8 +23482,209 @@ "math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" + }, + "mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "requires": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==" + } + } + }, + "mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "requires": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + } + }, + "mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "requires": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + } + }, + "mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "requires": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + } + }, + "mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "requires": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + } + }, + "mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "requires": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + } + }, + "mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "requires": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + } + }, + "mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "requires": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + } + }, + "mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "requires": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + } + }, + "mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "requires": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + } + }, + "mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "requires": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + } + }, + "mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "requires": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + } + }, + "mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "requires": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + } + }, + "mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "requires": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + } + }, + "mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "requires": { + "@types/mdast": "^4.0.0" + } }, "media-typer": { "version": "0.3.0", @@ -21308,26 +23713,323 @@ "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", "dev": true, "requires": { - "source-map": "^0.6.1" + "source-map": "^0.6.1" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "requires": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "requires": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "requires": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "requires": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "requires": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "requires": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "requires": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "requires": { + "micromark-util-types": "^2.0.0" + } + }, + "micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "requires": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "requires": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "requires": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "requires": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "requires": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "requires": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "requires": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "requires": { + "micromark-util-symbol": "^2.0.0" + } + }, + "micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "requires": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "requires": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "requires": { + "micromark-util-symbol": "^2.0.0" + } + }, + "micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "requires": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==" + }, + "micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==" + }, + "micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "requires": { + "micromark-util-symbol": "^2.0.0" + } + }, + "micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "requires": { + "micromark-util-types": "^2.0.0" } }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "requires": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true + "micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "requires": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true + "micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==" + }, + "micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==" }, "micromatch": { "version": "4.0.8", @@ -21348,8 +24050,7 @@ "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { "version": "2.1.35", @@ -21441,11 +24142,280 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "dev": true }, + "molstar": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/molstar/-/molstar-5.9.0.tgz", + "integrity": "sha512-yNf9FpWVxoo0blc4y0joCE83/OEFNWlhlGYPEWIO0OYgkshT25R5qYJJdsKJwlEG44q2tjKbQ+e0LXV10hpBQg==", + "requires": { + "@types/argparse": "^2.0.17", + "@types/benchmark": "^2.1.5", + "@types/compression": "1.8.1", + "@types/express": "^5.0.6", + "@types/node": "^22.19.17", + "@types/swagger-ui-dist": "3.30.6", + "argparse": "^2.0.1", + "compression": "^1.8.1", + "cors": "^2.8.6", + "express": "^5.2.1", + "h264-mp4-encoder": "^1.0.12", + "immutable": "^5.1.5", + "io-ts": "^2.2.22", + "mutative": "^1.3.0", + "react-markdown": "^10.1.0", + "remark-gfm": "^4.0.1", + "rxjs": "^7.8.2", + "swagger-ui-dist": "^5.32.5", + "tslib": "^2.8.1" + }, + "dependencies": { + "@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "@types/express-serve-static-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "requires": { + "undici-types": "~6.21.0" + } + }, + "@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "requires": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "requires": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + } + }, + "body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "requires": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + } + }, + "content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==" + }, + "cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==" + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + }, + "express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "requires": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + } + }, + "finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "requires": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + } + }, + "fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==" + }, + "iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==" + }, + "merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==" + }, + "mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==" + }, + "mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "requires": { + "mime-db": "^1.54.0" + } + }, + "negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==" + }, + "qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "requires": { + "side-channel": "^1.1.0" + } + }, + "raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "requires": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + } + }, + "send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "requires": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + } + }, + "serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "requires": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + } + }, + "statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" + }, + "type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "requires": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "dependencies": { + "content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==" + } + } + } + } + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "msa-webgpu": { "version": "0.0.10", @@ -21462,6 +24432,11 @@ "thunky": "^1.0.2" } }, + "mutative": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mutative/-/mutative-1.3.0.tgz", + "integrity": "sha512-8MJj6URmOZAV70dpFe1YnSppRTKC4DsMkXQiBDFayLcDI4ljGokHxmpqaBQuDWa4iAxWaJJ1PS8vAmbntjjKmQ==" + }, "nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -21486,19 +24461,6 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, - "ngl": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ngl/-/ngl-2.0.1.tgz", - "integrity": "sha512-/Jci0wWAdiFW/3xZmUVYcRpSi2pdSGZCT64fM+qiT6kg0aF+7fRUW54nJYaSEWVUsW2TL9U2YVE3ezmhNZbYrg==", - "dev": true, - "requires": { - "chroma-js": "^1.3.7", - "promise-polyfill": "^8.0.0", - "signals": "^1.0.0", - "sprintf-js": "^1.1.2", - "three": "^0.118.0" - } - }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -21606,14 +24568,12 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" }, "object-keys": { "version": "1.1.1", @@ -21632,7 +24592,6 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, "requires": { "ee-first": "1.1.1" } @@ -21640,14 +24599,12 @@ "on-headers": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", - "dev": true + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==" }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -21832,6 +24789,27 @@ "author-regex": "^1.0.0" } }, + "parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "requires": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "dependencies": { + "@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + } + } + }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", @@ -21853,8 +24831,7 @@ "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, "pascal-case": { "version": "3.1.2", @@ -22036,7 +25013,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", - "dev": true + "devOptional": true }, "pngquant-bin": { "version": "6.0.1", @@ -22207,11 +25184,10 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, - "promise-polyfill": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz", - "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==", - "dev": true + "property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==" }, "proto-list": { "version": "1.2.4", @@ -22223,7 +25199,6 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, "requires": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -22232,8 +25207,7 @@ "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" } } }, @@ -22309,8 +25283,7 @@ "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { "version": "2.5.3", @@ -22347,6 +25320,39 @@ "strip-json-comments": "~2.0.1" } }, + "react": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "peer": true + }, + "react-dom": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", + "peer": true, + "requires": { + "scheduler": "^0.27.0" + } + }, + "react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "requires": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + } + }, "read-config-file": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.2.0.tgz", @@ -22561,6 +25567,52 @@ "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", "dev": true }, + "remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "requires": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + } + }, + "remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "requires": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + } + }, + "remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "requires": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + } + }, + "remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "requires": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + } + }, "renderkid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", @@ -22790,6 +25842,30 @@ "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" }, + "router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "requires": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==" + } + } + }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -22804,6 +25880,14 @@ "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" }, + "rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "requires": { + "tslib": "^2.1.0" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -22848,6 +25932,12 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true }, + "scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "peer": true + }, "schema-utils": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", @@ -23131,8 +26221,7 @@ "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "shallow-clone": { "version": "3.0.1", @@ -23232,7 +26321,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, "requires": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -23245,7 +26333,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, "requires": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" @@ -23255,7 +26342,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, "requires": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -23267,7 +26353,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, "requires": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -23282,12 +26367,6 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, - "signals": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/signals/-/signals-1.0.0.tgz", - "integrity": "sha1-ZfDBWZNSs1Ny7KrlolDmEHN27Wk=", - "dev": true - }, "simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", @@ -23430,6 +26509,11 @@ "source-map": "^0.6.0" } }, + "space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==" + }, "spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", @@ -23506,7 +26590,8 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", - "dev": true + "dev": true, + "optional": true }, "sshpk": { "version": "1.16.1", @@ -23597,6 +26682,15 @@ } } }, + "stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "requires": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + } + }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -23668,6 +26762,22 @@ "dev": true, "requires": {} }, + "style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "requires": { + "style-to-object": "1.0.14" + } + }, + "style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "requires": { + "inline-style-parser": "0.2.7" + } + }, "sumchecker": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", @@ -23801,6 +26911,14 @@ } } }, + "swagger-ui-dist": { + "version": "5.32.6", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.32.6.tgz", + "integrity": "sha512-75ttZNaYCLoFPnozPZcTUU6mS3wKT8l7WLjU5zJSHFeJa23i5vtnze6IiCl4jDMPeQTXVXIgovq4M11NNfQvSA==", + "requires": { + "@scarf/scarf": "=1.4.0" + } + }, "tapable": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", @@ -23986,12 +27104,6 @@ "b4a": "^1.6.4" } }, - "three": { - "version": "0.118.3", - "resolved": "https://registry.npmjs.org/three/-/three-0.118.3.tgz", - "integrity": "sha512-ijECXrNzDkHieoeh2H69kgawTGH8DiamhR4uBN8jEM7VHSKvfTdEvOoHsA8Aq7dh7PHAxhlqBsN5arBI3KixSw==", - "dev": true - }, "throttleit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", @@ -24061,8 +27173,7 @@ "toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, "token-types": { "version": "4.1.1", @@ -24084,6 +27195,11 @@ "punycode": "^2.1.1" } }, + "trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==" + }, "trim-repeated": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", @@ -24093,6 +27209,11 @@ "escape-string-regexp": "^1.0.2" } }, + "trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==" + }, "truncate-utf8-bytes": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", @@ -24105,8 +27226,7 @@ "tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, "tunnel": { "version": "0.0.6", @@ -24169,6 +27289,11 @@ "through": "^2.3.8" } }, + "undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" + }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -24197,6 +27322,70 @@ "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", "dev": true }, + "unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "requires": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "dependencies": { + "is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==" + } + } + }, + "unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "requires": { + "@types/unist": "^3.0.0" + } + }, + "unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "requires": { + "@types/unist": "^3.0.0" + } + }, + "unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "requires": { + "@types/unist": "^3.0.0" + } + }, + "unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "requires": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + } + }, + "unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "requires": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + } + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -24206,8 +27395,7 @@ "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, "unzip-crx-3": { "version": "0.2.0", @@ -24332,8 +27520,7 @@ "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, "verror": { "version": "1.10.0", @@ -24346,6 +27533,24 @@ "extsprintf": "^1.2.0" } }, + "vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "requires": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + } + }, + "vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "requires": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + } + }, "vue": { "version": "2.7.16", "resolved": "https://registry.npmjs.org/vue/-/vue-2.7.16.tgz", @@ -24694,8 +27899,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { "version": "8.18.3", @@ -24788,6 +27992,11 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", "dev": true + }, + "zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==" } } } diff --git a/package.json b/package.json index 6fed1a32..1bdebcec 100644 --- a/package.json +++ b/package.json @@ -124,7 +124,6 @@ "imagemin": "^8.0.1", "imagemin-pngquant": "^9.0.2", "mini-css-extract-plugin": "^2.7.6", - "ngl": "2.0.1", "pako": "^2.1.0", "pulchra-wasm": "^0.0.3", "sass": "<1.33.0", @@ -152,6 +151,7 @@ "d3-sankey": "^0.12.3", "interactjs": "^1.10.27", "mitt": "^3.0.1", + "molstar": "^5.9.0", "msa-webgpu": "^0.0.10" } } From e2c4132466c2d2372203a0dd985ab576283be3d8 Mon Sep 17 00:00:00 2001 From: Cameron Gilchrist Date: Wed, 3 Jun 2026 16:24:44 +0900 Subject: [PATCH 2/7] refactoring and restore some extra logic --- frontend/Alignment.vue | 9 +- frontend/AlignmentPanel.vue | 444 ++++++++++++++---- frontend/MDI.js | 2 +- frontend/MSA.vue | 96 +++- frontend/ResidueSpan.vue | 6 +- frontend/ResultView.vue | 7 +- frontend/StructureHoverTooltip.vue | 100 ++++ frontend/StructureViewerMSA.vue | 107 +---- frontend/StructureViewerMotif.vue | 23 +- frontend/TopHits.vue | 85 ++-- frontend/molstar/StructureViewer.vue | 75 +-- frontend/molstar/StructureViewerThumbnail.vue | 172 ++----- frontend/molstar/folddiscoResult.js | 243 ++-------- frontend/molstar/foldmasonResult.js | 139 ++---- frontend/molstar/foldseekData.js | 62 ++- frontend/molstar/foldseekResult.js | 441 ++++++++++++----- frontend/molstar/foldseekSelections.js | 89 +++- frontend/molstar/foldseekSuperposition.js | 77 ++- frontend/molstar/foldseekUtilities.js | 1 + frontend/molstar/interactions.js | 93 ++++ frontend/molstar/io.js | 96 ++++ .../molstar/{viewerHelpers.js => plugin.js} | 50 +- frontend/molstar/representations.js | 77 +++ frontend/molstar/screenshot.js | 31 ++ frontend/molstar/thumbnailInput.js | 67 +++ frontend/molstar/thumbnailRenderer.js | 35 ++ frontend/molstar/transforms.js | 21 + 27 files changed, 1722 insertions(+), 926 deletions(-) create mode 100644 frontend/StructureHoverTooltip.vue create mode 100644 frontend/molstar/interactions.js create mode 100644 frontend/molstar/io.js rename frontend/molstar/{viewerHelpers.js => plugin.js} (58%) create mode 100644 frontend/molstar/representations.js create mode 100644 frontend/molstar/screenshot.js create mode 100644 frontend/molstar/thumbnailInput.js create mode 100644 frontend/molstar/thumbnailRenderer.js create mode 100644 frontend/molstar/transforms.js diff --git a/frontend/Alignment.vue b/frontend/Alignment.vue index fdb6d25d..1dbd7395 100644 --- a/frontend/Alignment.vue +++ b/frontend/Alignment.vue @@ -5,6 +5,7 @@ Q {{padNumber(getQueryRowStartPos(i), (Math.max(alignment.qStartPos, alignment.dbStartPos) + alignment.alnLength+"").length, ' ')}} {{alignment.qAln.substring((i-1)*lineLen, (i-1)*lineLen+lineLen)}}
{{' '.repeat(3+(Math.max(alignment.qStartPos, alignment.dbStartPos) + alignment.alnLength+"").length)}}{{formatAlnDiff(alignment.qAln.substring((i-1)*lineLen, (i-1)*lineLen+lineLen), alignment.dbAln.substring((i-1)*lineLen, (i-1)*lineLen+lineLen))}}
T {{padNumber(getTargetRowStartPos(i), (Math.max(alignment.qStartPos, alignment.dbStartPos) + alignment.alnLength+"").length, ' ')}} {{alignment.dbAln.substring((i-1)*lineLen, (i-1)*lineLen+lineLen)}} + />
diff --git a/frontend/AlignmentPanel.vue b/frontend/AlignmentPanel.vue index 27cd6473..663a5b4e 100644 --- a/frontend/AlignmentPanel.vue +++ b/frontend/AlignmentPanel.vue @@ -1,62 +1,71 @@ @@ -95,7 +105,9 @@ \ No newline at end of file + diff --git a/frontend/StructureHoverTooltip.vue b/frontend/StructureHoverTooltip.vue new file mode 100644 index 00000000..eb06d2c4 --- /dev/null +++ b/frontend/StructureHoverTooltip.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/frontend/StructureViewerMSA.vue b/frontend/StructureViewerMSA.vue index 7d5c4768..b00db11e 100644 --- a/frontend/StructureViewerMSA.vue +++ b/frontend/StructureViewerMSA.vue @@ -12,15 +12,7 @@ @sceneEvent="handleSceneEvent" > @@ -28,31 +20,15 @@ diff --git a/frontend/molstar/StructureViewer.vue b/frontend/molstar/StructureViewer.vue index 0c5f091b..07434b7d 100644 --- a/frontend/molstar/StructureViewer.vue +++ b/frontend/molstar/StructureViewer.vue @@ -28,11 +28,10 @@ - - diff --git a/frontend/ResultView.vue b/frontend/ResultView.vue index 05aa884e..37fd3c69 100644 --- a/frontend/ResultView.vue +++ b/frontend/ResultView.vue @@ -152,9 +152,12 @@ - + - import Panel from './Panel.vue'; import AlignmentPanel from './AlignmentPanel.vue'; -import InterfaceAlignmentPanel from './InterfaceAlignmentPanel.vue'; import Ruler from './Ruler.vue'; import ResultSankeyMixin from './ResultSankeyMixin.vue'; import AllAtomPredictMixin from './AllAtomPredictMixin.vue'; import NavigationButton from './NavigationButton.vue'; -import { mockPDB, mergePdbs, concatenatePdbs, - getChainName, getAccession, getAbsOffsetTop, +import { mockPDB, mergePdbs, concatenatePdbs, + getChainName, getAccession, encodeMultimer} from './Utilities'; -import { debounce } from './lib/debounce'; import ResultFoldseekDB from './ResultFoldseekDB.vue'; import SelectToSendPanel from './SelectToSendPanel.vue'; import NameField from './NameField.vue'; @@ -199,14 +199,12 @@ import Top100Foldseek from './Top100Foldseek.vue'; export default { name: 'ResultView', mixins: [ ResultSankeyMixin, AllAtomPredictMixin ], - components: { Panel, AlignmentPanel, InterfaceAlignmentPanel, Ruler, + components: { Panel, AlignmentPanel, Ruler, NavigationButton, ResultFoldseekDB, SelectToSendPanel, NameField, TopHits, Top100Foldseek }, data() { return { alignment: null, - activeTarget: null, - alnBoxOffset: 0, selectedDatabases: 0, selectedStates: null, selectedCountsPerDb: null, @@ -239,7 +237,6 @@ export default { this.fetchStructureFileURL = this.fetchStructureFileURL.bind(this) }, mounted() { - window.addEventListener("resize", this.handleAlignmentBoxResize, { passive: true }); if (this.hits && !this.selectedStates && !this.selectedCountPerDb && !this.dbToIdx) { const obj = Object.fromEntries( n.results.map(( e, i ) => [i, Object.fromEntries( @@ -262,9 +259,6 @@ export default { }) } }, - beforeDestroy() { - window.removeEventListener("resize", this.handleAlignmentBoxResize); - }, watch: { hits: { handler(n, o) { @@ -302,6 +296,9 @@ export default { isComplex() { return this.hits?.type == "complexsearch"; }, + alignmentModalClass() { + return ['alignment', this.isComplex || this.searchType === 'interfacesearch' ? 'alignment--large' : 'alignment--compact']; + }, fluidLineLen() { if (this.$vuetify.breakpoint.xsOnly) { return 30; @@ -351,8 +348,6 @@ export default { this.$nextTick(() => { item.map(item => item.db = db); this.alignment = item; - this.activeTarget = event.target.closest('.alignment-action'); - this.alnBoxOffset = getAbsOffsetTop(this.activeTarget) + this.activeTarget.offsetHeight; }); } }, @@ -362,14 +357,8 @@ export default { closeAlignment() { this.$nextTick(() => { this.alignment = null; - this.activeTarget = null; }) }, - handleAlignmentBoxResize: debounce(function() { - if (this.activeTarget != null) { - this.alnBoxOffset = getAbsOffsetTop(this.activeTarget) + this.activeTarget.offsetHeight; - } - }, 32, false), forwardDropdown(event, items) { if (this.menuActivator) { this.menuItems = items; @@ -727,9 +716,71 @@ export default { } .alignment { - position:absolute; + position: fixed; + top: 64px; + right: 16px; + left: 16px; z-index: 999; + overflow: hidden; + overscroll-behavior: contain; box-shadow: 0 3px 5px -1px rgba(0,0,0,.2),0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12) !important; } +.alignment--large { + bottom: 16px; +} + +.alignment--compact { + height: clamp(520px, 72vh, 760px); + max-height: calc(100vh - 80px); +} + +.alignment .panel { + position: absolute; + inset: 0; + overflow: hidden; +} + +.alignment .subheading { + position: absolute; + top: 0; + right: 0; + left: 0; + z-index: 2; + padding: 4px 8px; +} + +.alignment .panel-content { + display: grid !important; + grid-template-columns: minmax(0, 1fr); + grid-template-rows: minmax(0, 1fr); + position: absolute; + top: 44px; + right: 0; + bottom: 0; + left: 0; + min-width: 0; + min-height: 0; + box-sizing: border-box; + padding: 16px; + overflow: hidden; +} + +@media print, screen and (max-width: 599px) { + .alignment { + top: 56px; + right: 8px; + left: 8px; + } + + .alignment--large { + bottom: 8px; + } + + .alignment--compact { + height: calc(100vh - 64px); + max-height: calc(100vh - 64px); + } +} + diff --git a/frontend/molstar/StructureViewer.vue b/frontend/molstar/StructureViewer.vue index 07434b7d..497ab6a1 100644 --- a/frontend/molstar/StructureViewer.vue +++ b/frontend/molstar/StructureViewer.vue @@ -30,8 +30,7 @@ import { Color } from 'molstar/lib/mol-util/color'; import StructureViewerToolbar from '../StructureViewerToolbar.vue'; import StructureViewerTooltip from '../StructureViewerTooltip.vue'; -import { createStructurePlugin, parseColor, setCanvasSpin } from './plugin.js'; -import { captureViewportPng } from './screenshot.js'; +import { applyViewerCanvasProps, captureViewportPng, createStructurePlugin, parseColor, setCanvasSpin } from './molstarViewer.js'; export default { name: 'MolstarStructureViewer', @@ -54,6 +53,7 @@ export default { hoverSubscription: null, initPromise: null, updateQueue: Promise.resolve(), + resizeObserver: null, isDisposed: false, isDisposing: false, isFullscreen: false, @@ -84,9 +84,11 @@ export default { this.initPromise = this.initialisePlugin(); await this.initPromise; if (this.isDisposed) return; - await this.scene?.mount?.(this.plugin, this.sceneState, this.sceneInput); - if (this.isDisposed) return; this.bindSceneEvents(); + if (typeof ResizeObserver !== 'undefined') { + this.resizeObserver = new ResizeObserver(() => this.handleResize()); + this.resizeObserver.observe(this.$refs.structurepanel); + } await this.runSceneUpdate(this.sceneInput, null); }, beforeDestroy() { @@ -104,6 +106,8 @@ export default { window.removeEventListener('resize', this.handleResize); document.removeEventListener('fullscreenchange', this.handleFullscreenChange); + this.resizeObserver?.disconnect(); + this.resizeObserver = null; this.clickSubscription?.unsubscribe(); this.clickSubscription = null; this.hoverSubscription?.unsubscribe(); @@ -131,7 +135,11 @@ export default { this.plugin = null; if (plugin) { try { - await this.scene?.dispose?.(plugin, this.sceneState); + if (this.scene?.dispose) { + await this.scene.dispose(plugin, this.sceneState); + } else { + await plugin.clear(); + } } catch (e) { // The plugin state may already be partially disposed during teardown. } @@ -153,6 +161,8 @@ export default { const change = this.scene.diffInput ? this.scene.diffInput(previous, next) : null; + applyViewerCanvasProps(this.plugin, this.scene?.canvasProps?.(next, this.sceneState) || {}); + this.setBackground(this.bgColor); await this.scene.update(this.plugin, this.sceneState, next, previous, change); this.$emit('sceneState', this.sceneState); }) @@ -294,8 +304,7 @@ export default { } }, async handleMakeCIF() { - const makeCIF = this.scene?.makeCIF || this.scene?.makePDB; - const cif = await makeCIF?.(this.plugin, this.sceneState, this.sceneInput); + const cif = await this.scene?.makeCIF?.(this.plugin, this.sceneState, this.sceneInput); this.$emit('makeCIF', cif); }, }, diff --git a/frontend/molstar/StructureViewerThumbnail.vue b/frontend/molstar/StructureViewerThumbnail.vue index 4c77d328..38fb3a13 100644 --- a/frontend/molstar/StructureViewerThumbnail.vue +++ b/frontend/molstar/StructureViewerThumbnail.vue @@ -13,8 +13,7 @@ import { Color } from 'molstar/lib/mol-util/color'; import { foldseekResult } from './foldseekResult.js'; import { folddiscoResult } from './folddiscoResult.js'; import { prepareFoldseekStructureInput } from './foldseekData.js'; -import { createStructurePlugin, setCanvasSpin } from './plugin.js'; -import { captureViewportPng, drawStableFrame } from './screenshot.js'; +import { captureViewportPng, createStructurePlugin, drawStableFrame, setCanvasSpin } from './molstarViewer.js'; export default { name: "StructureViewerThumbnail", @@ -140,7 +139,7 @@ export default { }; }, - async renderItem(item) { + async renderSceneForItem(item) { if (!item?.alignments?.length || !this.plugin) return false; await this.plugin.clear(); this.sceneState = {}; @@ -149,12 +148,21 @@ export default { if (!input || (!input.query && !input.target && (!input.queryPdb || !input.targetPdb))) return false; const scene = this.mode === 2 ? folddiscoResult : foldseekResult; - await scene.update(this.plugin, this.sceneState, this.thumbnailSceneInput(input)); - this.resetCamera(); + const sceneInput = this.thumbnailSceneInput(input); + await scene.update(this.plugin, this.sceneState, sceneInput); + this.fitScene(scene, sceneInput); await drawStableFrame(this.plugin); return true; }, + fitScene(scene, sceneInput) { + if (scene.resetView) { + scene.resetView(this.plugin, this.sceneState, sceneInput, { durationMs: 0 }); + } else { + this.resetCamera(); + } + }, + async captureThumbnail() { return captureViewportPng(this.plugin, (screenshot, previousValues) => { screenshot.behaviors.values.next({ @@ -189,6 +197,22 @@ export default { this.sceneState = {}; }, + moveViewportTo(targetEl, width, height) { + if (!targetEl || !this.$refs.viewport) return; + targetEl.appendChild(this.$refs.viewport); + this.resizeTo(width, height); + }, + + restoreThumbnailViewport() { + this.moveViewportTo(this.$refs.offscreenContainer, this.thumbWidth, this.thumbHeight); + }, + + stopActiveViewerInteraction() { + this.setSpin(false); + this.isSpinning = false; + this.$refs.canvas?.removeEventListener('pointerdown', this.handlePointerInteraction); + }, + canProcessQueue() { return !this.destroyed && !this.queuePaused @@ -222,9 +246,7 @@ export default { async switchViewer(id, alignments, newTargetEl) { if (!this.plugin) return; this.enqueueOperation(async () => { - this.setSpin(false); - this.isSpinning = false; - this.$refs.canvas.removeEventListener('pointerdown', this.handlePointerInteraction); + this.stopActiveViewerInteraction(); await this.mountActiveViewer(id, alignments, newTargetEl, 'Switch Mol* viewer failed for'); }); await this.operationQueue; @@ -234,11 +256,10 @@ export default { if (!this.plugin || this.destroyed) return; this.queuePaused = true; this.activeId = id; - targetEl.appendChild(this.$refs.viewport); - this.resizeTo('100%', '100%'); + this.moveViewportTo(targetEl, '100%', '100%'); try { - const rendered = await this.renderItem({ id, db: id, alignments }); + const rendered = await this.renderSceneForItem({ id, db: id, alignments }); if (!rendered || this.destroyed || !this.plugin) { await this.restoreOffscreenViewer(id); return; @@ -254,12 +275,9 @@ export default { async restoreOffscreenViewer(id) { if (this.activeId !== id) return; - this.setSpin(false); - this.isSpinning = false; - this.$refs.canvas.removeEventListener('pointerdown', this.handlePointerInteraction); + this.stopActiveViewerInteraction(); await this.clearPlugin(); - this.$refs.offscreenContainer.appendChild(this.$refs.viewport); - this.resizeTo(this.thumbWidth, this.thumbHeight); + this.restoreThumbnailViewport(); this.activeId = null; this.queuePaused = false; this.$emit('spin-change', false); @@ -272,13 +290,10 @@ export default { }, async deactivateViewerAsync() { - this.setSpin(false); - this.isSpinning = false; - this.$refs.canvas.removeEventListener('pointerdown', this.handlePointerInteraction); + this.stopActiveViewerInteraction(); await this.clearPlugin(); - this.$refs.offscreenContainer.appendChild(this.$refs.viewport); - this.resizeTo(this.thumbWidth, this.thumbHeight); + this.restoreThumbnailViewport(); this.activeId = null; this.queuePaused = false; @@ -333,29 +348,30 @@ export default { this.isRendering = true; const item = this.thumbnailQueue[this.currentQueueIndex]; + let advanceQueue = true; try { - const rendered = await this.renderItem(item); + const rendered = await this.renderSceneForItem(item); if (!this.canProcessQueue()) { - await this.clearPlugin(); - this.isRendering = false; + advanceQueue = false; return; } if (rendered) { const blob = await this.captureThumbnail(); this.$emit('thumbnail-ready', { id: item.id, blob }); } - await this.clearPlugin(); } catch (e) { console.warn('Mol* thumbnail generation failed for', item.id, e); + } finally { await this.clearPlugin(); - } + this.isRendering = false; - this.currentQueueIndex++; - this.isRendering = false; - - if (!this.destroyed) { - this.$nextTick(() => this.scheduleProcessQueue()); + if (advanceQueue) { + this.currentQueueIndex++; + if (!this.destroyed) { + this.$nextTick(() => this.scheduleProcessQueue()); + } + } } }, }, diff --git a/frontend/molstar/folddiscoResult.js b/frontend/molstar/folddiscoResult.js index 82e772d0..35c5b4c6 100644 --- a/frontend/molstar/folddiscoResult.js +++ b/frontend/molstar/folddiscoResult.js @@ -1,10 +1,21 @@ import { to_mmCIF } from 'molstar/lib/mol-model/structure/export/mmcif'; import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder'; -import { loadStructureFromData, normalizedPdbSourceFromText } from './io.js'; -import { isValidLoci, lociFromExpression, residueInfoFromLoci, structuresMatch } from './interactions.js'; -import { addUniformRepresentation, ballAndStickParams, cartoonParams } from './representations.js'; -import { mat4FromCommaStrings, transformStructureConformation } from './transforms.js'; -import { chainExpression, mergeExpressions, residueExpression } from './selectionExpressions.js'; +import { Color } from 'molstar/lib/mol-util/color'; +import { + ballAndStickParams, + cartoonParams, + chainExpression, + isValidLoci, + loadStructureFromData, + lociFromExpression, + mat4FromCommaStrings, + mergeExpressions, + normalizedPdbSourceFromText, + residueExpression, + residueInfoFromLoci, + structuresMatch, + transformStructureConformation, +} from './molstarStructure.js'; const QueryColor = 0x1e88e5; const TargetColor = 0xffc107; @@ -13,16 +24,23 @@ const TargetLightColor = 0xffe699; const MotifRadius = 0.28; async function addRepresentation(plugin, structure, label, expression, color, alpha = 1, type = 'cartoon', qualityPreset = 'viewer') { - const item = await addUniformRepresentation(plugin, structure, { + if (!expression) return null; + const component = await plugin.builders.structure.tryCreateComponentFromExpression( + structure, + expression, label, + { label }, + ); + if (!component) return null; + const representation = await plugin.builders.structure.representation.addRepresentation(component, { type, - color, - expression, + color: 'uniform', + colorParams: { value: Color(color) }, typeParams: type === 'ball-and-stick' ? ballAndStickParams({ alpha, sizeFactor: MotifRadius }) : cartoonParams({ alpha, sizeFactor: 0.2, qualityPreset }), }); - return item?.representation || null; + return representation; } function parseMotif(value = '') { @@ -181,10 +199,6 @@ function makeCIF(state) { } export const folddiscoResult = { - async mount() { - return {}; - }, - async update(plugin, state, input) { const key = `${input.alignment?.dbkey || input.alignment?.target}:${input.alignment?.queryresidues}:${input.alignment?.targetresidues}`; if (state.loadKey !== key) { @@ -207,17 +221,13 @@ export const folddiscoResult = { return structureEvent(state, event?.current, 'structure-hover'); }, - resetView(plugin, state, input) { + resetView(plugin, state, input, options = {}) { const loci = lociFromExpression(state.query, motifExpression(input.alignment?.queryresidues)); - if (loci) plugin.managers.camera.focusLoci(loci, { durationMs: 250, extraRadius: 10, minRadius: 5 }); + if (loci) plugin.managers.camera.focusLoci(loci, { durationMs: options.durationMs ?? 250, extraRadius: 10, minRadius: 5 }); else plugin.managers.camera.reset(); }, async makeCIF(plugin, state) { return makeCIF(state); }, - - async dispose(plugin) { - await plugin?.clear?.(); - }, }; diff --git a/frontend/molstar/foldmasonResult.js b/frontend/molstar/foldmasonResult.js index cb3a4d4a..b8b2c52d 100644 --- a/frontend/molstar/foldmasonResult.js +++ b/frontend/molstar/foldmasonResult.js @@ -1,27 +1,45 @@ import { OrderedSet } from 'molstar/lib/mol-data/int'; import { to_mmCIF } from 'molstar/lib/mol-model/structure/export/mmcif'; import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder'; +import { Color } from 'molstar/lib/mol-util/color'; import { tmalign, parseMatrix as parseTMMatrix } from 'tmalign-wasm'; import { pulchra } from 'pulchra-wasm'; import { decodeMultimer, mergeMultimer, revertChainInfo, splitMultimer, storeChains } from '../Utilities.js'; -import { mockPDB } from './foldseekUtilities.js'; -import { loadStructureFromData } from './io.js'; -import { focusCurrentLoci, isValidLoci, lociFromExpression, OneToThree, residueInfoFromLoci, structuresMatch } from './interactions.js'; -import { addUniformRepresentation, cartoonParams } from './representations.js'; -import { mat4FromRotationTranslation, transformStructureConformation } from './transforms.js'; -import { mergeExpressions, residueExpression } from './selectionExpressions.js'; - -const ReferenceColor = 0x1e88e5; -const RegularColor = 0xffc107; +import { + cartoonParams, + focusCurrentLoci, + isValidLoci, + loadStructureFromData, + lociFromExpression, + mat4FromRotationTranslation, + mergeExpressions, + mockPDB, + OneToThree, + residueExpression, + residueInfoFromLoci, + structuresMatch, + transformStructureConformation, +} from './molstarStructure.js'; + +const ReferenceColor = 0x49a9fc; +const RegularColor = 0xffd761; const MaskColor = 0x666666; async function addRepresentation(plugin, structure, label, color, expression, alpha = 1, type = 'cartoon', qualityPreset = 'viewer') { - return addUniformRepresentation(plugin, structure, { + if (!expression) return null; + const component = await plugin.builders.structure.tryCreateComponentFromExpression( + structure, + expression, label, + { label }, + ); + if (!component) return null; + const representation = await plugin.builders.structure.representation.addRepresentation(component, { type, - color, - expression, + color: 'uniform', + colorParams: { value: Color(color) }, typeParams: cartoonParams({ alpha, qualityPreset }), }); + return { component, representation }; } async function addStructure(plugin, state, input, index) { @@ -39,14 +57,15 @@ async function addStructure(plugin, state, input, index) { } const color = index === input.reference ? ReferenceColor : RegularColor; + const alpha = index === input.reference ? 1.0 : 0.7; const item = { index, entry, structure, - base: await addRepresentation(plugin, structure, `foldmason-${index}`, color, MS.struct.generator.all(), 1, 'cartoon', input.representationQuality), + base: await addRepresentation(plugin, structure, `foldmason-${index}`, color, MS.struct.generator.all(), alpha, 'cartoon', input.representationQuality), mask: null, }; - item.mask = await addRepresentation(plugin, structure, `foldmason-${index}-mask`, MaskColor, maskExpression(entry, input.mask), 1, 'cartoon', input.representationQuality); + item.mask = await addRepresentation(plugin, structure, `foldmason-${index}-mask`, MaskColor, maskExpression(entry, input.mask), alpha, 'cartoon', input.representationQuality); state.structures.set(index, item); if (index === input.reference) state.referenceStructure = structure; } @@ -121,7 +140,7 @@ async function updateMasks(plugin, state, input) { `foldmason-${item.index}-mask`, MaskColor, maskExpression(item.entry, input.mask), - 1, + item.index === input.reference ? 1.0 : 0.5, 'cartoon', input.representationQuality, ); @@ -166,10 +185,9 @@ async function setFocus(plugin, state, input) { if (state.focusKey === key) return; state.focusKey = key; if (!Number.isInteger(focus) || focus < 0) return; - const reference = state.structures.get(input.reference); if (!reference) return; - const loci = lociForItem(reference, [focus]); + const loci = lociForItem(reference, input.selectedColumns || [focus]); if (!loci) return; plugin.managers.camera.focusLoci(loci, { durationMs: 250, @@ -235,7 +253,6 @@ function structureEvent(state, current, type) { name: item.entry.name, }; } - const column = columnForResidue(item.entry, residue.chain, residue.residue); const residueInfo = residueInfoForColumn(item.entry, column) || { chain: residue.chain, @@ -404,10 +421,6 @@ function makeCIF(state) { } export const foldmasonResult = { - async mount() { - return {}; - }, - async update(plugin, state, input) { await updateStructures(plugin, state, input); await setSelection(plugin, state, input); @@ -437,8 +450,4 @@ export const foldmasonResult = { async makeCIF(plugin, state) { return makeCIF(state); }, - - async dispose(plugin) { - await plugin?.clear?.(); - }, }; diff --git a/frontend/molstar/foldseekArrows.js b/frontend/molstar/foldseekArrows.js index 04bf47ae..4b3c04d9 100644 --- a/frontend/molstar/foldseekArrows.js +++ b/frontend/molstar/foldseekArrows.js @@ -1,21 +1,17 @@ -import { Color } from 'molstar/lib/mol-util/color'; -import { OrderedSet } from 'molstar/lib/mol-data/int'; import { Vec3 } from 'molstar/lib/mol-math/linear-algebra'; -import { StructureElement, StructureProperties, Unit } from 'molstar/lib/mol-model/structure'; -import { StateTransforms } from 'molstar/lib/mol-plugin-state/transforms'; -import { PluginStateObject } from 'molstar/lib/mol-plugin-state/objects'; -import { StateTransformer } from 'molstar/lib/mol-state'; -import { Task } from 'molstar/lib/mol-task'; import { Shape } from 'molstar/lib/mol-model/shape'; import { Mesh } from 'molstar/lib/mol-geo/geometry/mesh/mesh'; import { MeshBuilder } from 'molstar/lib/mol-geo/geometry/mesh/mesh-builder'; import { addCylinder } from 'molstar/lib/mol-geo/geometry/mesh/builder/cylinder'; import { Sphere3D } from 'molstar/lib/mol-math/geometry'; +import { PluginStateObject } from 'molstar/lib/mol-plugin-state/objects'; +import { StateTransforms } from 'molstar/lib/mol-plugin-state/transforms'; +import { StateTransformer } from 'molstar/lib/mol-state'; +import { Task } from 'molstar/lib/mol-task'; +import { Color } from 'molstar/lib/mol-util/color'; import { ParamDefinition as PD } from 'molstar/lib/mol-util/param-definition'; -import { getChainName } from './foldseekUtilities.js'; -const FoldseekTransform = StateTransformer.builderFactory('foldseek'); -const FoldseekArrowsShape = FoldseekTransform({ +const FoldseekArrowsShape = StateTransformer.builderFactory('foldseek')({ name: 'alignment-arrows-shape', display: { name: 'Alignment Arrows' }, from: PluginStateObject.Root, @@ -126,60 +122,23 @@ function getMatchingResiduePairs(alignment) { return pairs; } -function caOrdinalCoordinateMap(structure) { - const map = new Map(); - if (!structure) return map; - - const loc = StructureElement.Location.create(structure); - const chainCounts = new Map(); - for (const unit of structure.units) { - if (!Unit.isAtomic(unit)) continue; - loc.unit = unit; - - const { elements } = unit; - const size = OrderedSet.size(elements); - for (let i = 0; i < size; i++) { - loc.element = OrderedSet.getAt(elements, i); - if (StructureProperties.atom.label_atom_id(loc) !== 'CA') continue; - - const authChain = StructureProperties.chain.auth_asym_id(loc); - const labelChain = StructureProperties.chain.label_asym_id(loc); - const coordinate = [ - StructureProperties.atom.x(loc), - StructureProperties.atom.y(loc), - StructureProperties.atom.z(loc), - ]; - - for (const chain of new Set([authChain, labelChain].filter(Boolean))) { - const ordinal = (chainCounts.get(chain) || 0) + 1; - chainCounts.set(chain, ordinal); - map.set(`${chain}:${ordinal}`, coordinate); - } - } - } - - return map; -} - function alignmentArrowPairs(state, input) { - const queryStructure = state.query?.structure?.cell?.obj?.data; - const targetStructure = state.target?.structure?.cell?.obj?.data; - if (!queryStructure || !targetStructure) return []; - - const queryCoords = caOrdinalCoordinateMap(queryStructure); - const targetCoords = caOrdinalCoordinateMap(targetStructure); const pairs = []; - for (const alignment of input?.alignments || []) { - const queryChain = state.chainOverrides?.query?.[getChainName(alignment.query)] || getChainName(alignment.query); - const targetChain = state.chainOverrides?.target?.[getChainName(alignment.target)] || getChainName(alignment.target); - + for (const [index, alignment] of (input?.alignments || []).entries()) { + const queryMap = state.alignmentMaps?.query?.[index]; + const targetMap = state.alignmentMaps?.target?.[index]; + if (!queryMap || !targetMap) continue; for (const match of getMatchingResiduePairs(alignment)) { - const query = queryCoords.get(`${queryChain}:${match.query}`); - const target = targetCoords.get(`${targetChain}:${match.target - alignment.dbStartPos + 1}`); + const query = residueCoordinate(queryMap.toStructure.get(match.query)); + const target = residueCoordinate(targetMap.toStructure.get(match.target)); if (query && target) pairs.push([query, target]); } } return pairs; } + +function residueCoordinate(residue) { + return residue ? [residue.x, residue.y, residue.z] : null; +} diff --git a/frontend/molstar/foldseekChainLabels.js b/frontend/molstar/foldseekChainLabels.js new file mode 100644 index 00000000..fe67bb4f --- /dev/null +++ b/frontend/molstar/foldseekChainLabels.js @@ -0,0 +1,293 @@ +import { Vec3 } from 'molstar/lib/mol-math/linear-algebra'; +import { OrderedSet } from 'molstar/lib/mol-data/int'; +import { StructureElement, StructureProperties } from 'molstar/lib/mol-model/structure'; +import { Shape } from 'molstar/lib/mol-model/shape'; +import { Text } from 'molstar/lib/mol-geo/geometry/text/text'; +import { TextBuilder } from 'molstar/lib/mol-geo/geometry/text/text-builder'; +import { PluginStateObject } from 'molstar/lib/mol-plugin-state/objects'; +import { StateTransforms } from 'molstar/lib/mol-plugin-state/transforms'; +import { StateTransformer } from 'molstar/lib/mol-state'; +import { Task } from 'molstar/lib/mol-task'; +import { ValueCell } from 'molstar/lib/mol-util'; +import { Color } from 'molstar/lib/mol-util/color'; +import { ParamDefinition as PD } from 'molstar/lib/mol-util/param-definition'; + +const QueryChainLabelColor = Color(0x1e88e5); +const TargetChainLabelColor = Color(0xffc107); +const SeparatorChainLabelColor = Color(0xffffff); +const ChainLabelsRef = 'foldseek-chain-labels'; +const ChainLabelsRepresentationRef = 'foldseek-chain-labels-representation'; +const DefaultChainLabelScale = 5; + +const ChainLabelTextParams = { + ...Text.Params, + chainScale: PD.Numeric(DefaultChainLabelScale, { min: 0.1, max: 20, step: 0.1 }), +}; + +const FoldseekChainLabelsShape = StateTransformer.builderFactory('foldseek')({ + name: 'chain-labels-shape', + display: { name: 'Chain Labels' }, + from: PluginStateObject.Root, + to: PluginStateObject.Shape.Provider, + params: { + labels: PD.Value([]), + }, +})({ + apply({ params }) { + return Task.create('Chain Labels', async () => new PluginStateObject.Shape.Provider({ + label: 'Chain Labels', + data: params, + params: ChainLabelTextParams, + geometryUtils: Text.Utils, + getShape: (_, data, props, text) => { + const groups = []; + const geometry = buildChainLabelText(data.labels, props, text, groups); + + return Shape.create( + 'Chain Labels', + data, + geometry, + group => groups[group]?.color || SeparatorChainLabelColor, + () => 1, + group => groups[group]?.tooltip || '', + ); + }, + }, { label: 'Chain Labels' })); + }, +}); + +export async function setChainLabels(plugin, state, input) { + const labels = chainLabelData(state, input); + const enabled = input?.structureMode === 'multimer' + && input?.showChainLabels !== false + && labels.length > 0; + + if (!enabled) { + await deleteChainLabels(plugin, state); + return; + } + + const chainScale = input?.chainLabelScale ?? input?.chainLabelSize ?? DefaultChainLabelScale; + const key = chainLabelKey(labels, chainScale); + if (state.chainLabels?.key === key) return; + + await deleteChainLabels(plugin, state); + await plugin.state.data.build() + .toRoot() + .apply(FoldseekChainLabelsShape, { labels }, { + ref: ChainLabelsRef, + tags: ['foldseek-chain-labels'], + state: { isCollapsed: true }, + }) + .apply(StateTransforms.Representation.ShapeRepresentation3D, { + chainScale, + background: false, + attachment: 'middle-center', + }, { + ref: ChainLabelsRepresentationRef, + tags: ['foldseek-chain-labels-representation'], + }) + .commit(); + + state.chainLabels = { ref: ChainLabelsRef, key }; +} + +async function deleteChainLabels(plugin, state) { + if (!state.chainLabels?.ref) return; + await plugin.state.data.build().delete(state.chainLabels.ref).commit(); + state.chainLabels = null; +} + +function chainLabelData(state, input) { + if (!state.query?.structure || !state.target?.structure) return []; + + const queryAnchors = structureChainAnchors(state.query.structure); + const targetAnchors = structureChainAnchors(state.target.structure); + const pairs = []; + const seen = new Set(); + for (const [index, queryMap] of (state.alignmentMaps?.query || []).entries()) { + const targetMap = state.alignmentMaps?.target?.[index]; + if (!queryMap || !targetMap) continue; + + const queryChain = queryMap.chain; + const targetChain = targetMap.chain; + const key = `${queryChain}:${targetChain}`; + if (seen.has(key)) continue; + seen.add(key); + + const query = queryAnchors.byChain.get(queryChain) || queryAnchors.list[index]; + const target = targetAnchors.byChain.get(targetChain) || targetAnchors.list[index]; + if (!query || !target) continue; + + pairs.push({ + queryChain: query.chain || queryChain, + targetChain: target.chain || targetChain, + query, + target, + }); + } + + const sceneCenter = labelSceneCenter(pairs); + return pairs.map(pair => chainLabelEntry(pair, sceneCenter, input)); +} + +function structureChainAnchors(structureRef) { + const structure = structureRef?.cell?.obj?.data || structureRef; + if (!structure?.units?.length) return { byChain: new Map(), list: [] }; + + const byChain = new Map(); + const list = []; + const loc = StructureElement.Location.create(structure); + + for (const unit of structure.units) { + if (OrderedSet.size(unit.elements) === 0) continue; + + loc.unit = unit; + loc.element = OrderedSet.getAt(unit.elements, 0); + + const auth = StructureProperties.chain.auth_asym_id(loc) || ''; + const label = StructureProperties.chain.label_asym_id(loc) || ''; + const chain = auth || label; + if (!chain) continue; + + const { center, radius } = unit.lookup3d.boundary.sphere; + const anchor = { + chain, + center: Vec3.transformMat4(Vec3(), center, unit.conformation.operator.matrix), + radius, + }; + + list.push(anchor); + if (auth && !byChain.has(auth)) byChain.set(auth, anchor); + if (label && !byChain.has(label)) byChain.set(label, anchor); + } + + return { byChain, list }; +} + +function chainLabelEntry({ queryChain, targetChain, query, target }, sceneCenter, input) { + const pairCenter = Vec3.scale(Vec3(), Vec3.add(Vec3(), query.center, target.center), 0.5); + const pairRadius = Math.max(query.radius, target.radius, Vec3.distance(pairCenter, query.center), Vec3.distance(pairCenter, target.center)); + const outward = outerLabelDirection(pairCenter, sceneCenter, query.center, target.center); + const offset = pairRadius * (input?.chainLabelOffset ?? 1.05); + + return { + queryChain, + targetChain, + position: Vec3.scaleAndAdd(Vec3(), pairCenter, outward, offset), + depth: pairRadius, + tooltip: `Query chain ${queryChain} / Target chain ${targetChain}`, + }; +} + +function labelSceneCenter(pairs) { + const center = Vec3(); + let count = 0; + for (const { query, target } of pairs) { + Vec3.add(center, center, query.center); + Vec3.add(center, center, target.center); + count += 2; + } + return count > 0 ? Vec3.scale(center, center, 1 / count) : center; +} + +function outerLabelDirection(pairCenter, sceneCenter, queryCenter, targetCenter) { + const outward = Vec3.sub(Vec3(), pairCenter, sceneCenter); + if (Vec3.magnitude(outward) > 0.001) return Vec3.normalize(outward, outward); + + Vec3.sub(outward, queryCenter, targetCenter); + if (Vec3.magnitude(outward) > 0.001) return Vec3.normalize(outward, outward); + + return Vec3.set(outward, 0, 1, 0); +} + +function chainLabelKey(labels, chainScale) { + return [ + chainScale, + labels.map(label => [ + label.queryChain, + label.targetChain, + ...label.position.map(roundCoordinate), + ].join(',')).join(';'), + ].join(':'); +} + +function roundCoordinate(value) { + return Number.isFinite(value) ? value.toFixed(2) : ''; +} + +function buildChainLabelText(labels, props, oldText, groupMeta) { + const params = { ...PD.getDefaultValues(Text.Params), ...props }; + const characterCount = labels.reduce((sum, label) => sum + labelTextLength(label), 0); + const textBuilder = TextBuilder.create(params, Math.max(1, characterCount), Math.max(1, labels.length), oldText); + const quadGroups = []; + const extraQuadCount = (params.background ? 1 : 0) + (params.tether ? 1 : 0); + let quadOffset = 0; + + for (const label of labels) { + const { text, groups } = chainLabelText(label, groupMeta); + if (!text) continue; + + const [x, y, z] = label.position; + const scale = label.scale ?? params.chainScale ?? DefaultChainLabelScale; + const baseGroup = groups[0] ?? 0; + + textBuilder.add(text, x, y, z, label.depth, scale, baseGroup); + + for (let i = 0; i < extraQuadCount; i++) { + quadGroups[quadOffset + i] = baseGroup; + } + for (let i = 0; i < groups.length; i++) { + quadGroups[quadOffset + extraQuadCount + i] = groups[i]; + } + quadOffset += extraQuadCount + text.length; + } + + const text = textBuilder.getText(); + applyTextGroups(text, quadGroups); + return text; +} + +function labelTextLength(label) { + return String(label.queryChain).length + String(label.targetChain).length + 1; +} + +function chainLabelRuns(label) { + return [ + { text: `${label.queryChain}`, color: QueryChainLabelColor }, + { text: '-', color: SeparatorChainLabelColor }, + { text: `${label.targetChain}`, color: TargetChainLabelColor }, + ]; +} + +function chainLabelText(label, groupMeta) { + const text = []; + const groups = []; + for (const run of chainLabelRuns(label)) { + const group = groupMeta.length; + groupMeta.push({ color: run.color, tooltip: label.tooltip }); + text.push(run.text); + for (let i = 0; i < run.text.length; i++) { + groups.push(group); + } + } + return { text: text.join(''), groups }; +} + +function applyTextGroups(text, quadGroups) { + const groups = text.groupBuffer.ref.value; + const quadCount = Math.min(quadGroups.length, text.charCount); + + for (let quad = 0; quad < quadCount; quad++) { + const group = quadGroups[quad]; + if (group === undefined) continue; + + const offset = quad * 4; + groups[offset] = group; + groups[offset + 1] = group; + groups[offset + 2] = group; + groups[offset + 3] = group; + } + + ValueCell.update(text.groupBuffer, groups); +} diff --git a/frontend/molstar/foldseekData.js b/frontend/molstar/foldseekData.js index 4aba3fce..8eaabe0a 100644 --- a/frontend/molstar/foldseekData.js +++ b/frontend/molstar/foldseekData.js @@ -1,6 +1,19 @@ import { Mat4 } from 'molstar/lib/mol-math/linear-algebra'; import { pulchra } from 'pulchra-wasm'; -import { getAccession, getChainName, mockPDB } from './foldseekUtilities.js'; +import { detectStructureFormat, mockPDB } from './molstarStructure.js'; + +export function getChainName(name) { + if (!name || /_v[0-9]+$/.test(name)) return 'A'; + if (/^[A-Za-z0-9]$/.test(name)) return name; + const pos = name.lastIndexOf('_'); + return pos !== -1 ? name.substring(pos + 1, pos + 2) : 'A'; +} + +function getAccession(name) { + if (!name || /_v[0-9]+$/.test(name)) return name; + const pos = name.lastIndexOf('_'); + return pos !== -1 ? name.substring(0, pos) : name; +} export async function prepareFoldseekStructureInput(ctx) { if (!ctx.alignments?.length || (ctx.structureMode !== 'interface' && typeof ctx.alignments[0].tCa === 'undefined')) { @@ -27,7 +40,9 @@ export async function prepareFoldseekStructureInput(ctx) { structureMode: ctx.structureMode || 'alignment', interfaceCutoff: ctx.interfaceCutoff || 10, queryIndex: queryIndex(ctx), - queryChain: activeQueryChain(ctx, activeQuery), + queryChain: ctx.structureMode === 'multimer' || ctx.structureMode === 'interface' + ? null + : activeQueryChain(ctx, activeQuery), }; } @@ -46,7 +61,7 @@ async function buildQuery(ctx) { const data = await loadQueryData(ctx); if (!data) return null; - const format = detectFormat(data); + const format = detectStructureFormat(data); return { data, format, label: 'query' }; } @@ -212,8 +227,3 @@ function mergePdbChunks(chunks) { .flatMap(chunk => (chunk || '').split(/\r?\n/).filter(line => line.startsWith('ATOM') || line.startsWith('HETATM'))) .join('\n'); } - -function detectFormat(data) { - const trimmed = (data || '').trimStart(); - return trimmed.startsWith('#') || trimmed.startsWith('data_') ? 'mmcif' : 'pdb'; -} diff --git a/frontend/molstar/foldseekInterface.js b/frontend/molstar/foldseekInterface.js new file mode 100644 index 00000000..c8708ef6 --- /dev/null +++ b/frontend/molstar/foldseekInterface.js @@ -0,0 +1,299 @@ +import { StructureElement } from 'molstar/lib/mol-model/structure'; +import { setSubtreeVisibility } from 'molstar/lib/mol-plugin/behavior/static/state'; +import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder'; +import { StateTransforms } from 'molstar/lib/mol-plugin-state/transforms'; +import { + addUniformRepresentation, + chainExpression, + componentStructure, + lociFromExpression as lociFromStructureExpression, + mergeExpressions, + representationObject, + residueRanges, + residueInfosFromLoci, + structureResidueKeys, +} from './molstarStructure.js'; + +const InterfaceChainColors = { + query: [0x1e88e5, 0x90caf9], + target: [0xffc107, 0xffe082], +}; + +export function interfaceVisibilityKey(input, queryMode = input?.showQuery || 0, targetMode = input?.showTarget || 0) { + return [ + queryMode, + targetMode, + ].join(':'); +} + +export function interfaceSceneKey(input, baseSceneKey) { + return [ + baseSceneKey(input), + input?.interfaceCutoff ?? '', + ].join('|'); +} + +export function prepareInterfaceState(state, input, { query, target }) { + const interfaceState = computeInterfaceState(query, target, input, state.alignmentMaps); + state.interfaceRegions = interfaceState.regions; + state.interfaceSelections = interfaceState.selections; +} + +function computeInterfaceState(query, target, input, alignmentMaps) { + if (input?.structureMode !== 'interface') { + return { + regions: { query: [], target: [] }, + selections: { query: new Map(), target: new Map() }, + }; + } + const queryState = computeInterfaceSide(query, alignmentMaps?.query || [], input.interfaceCutoff); + const targetState = computeInterfaceSide(target, alignmentMaps?.target || [], input.interfaceCutoff); + return { + regions: { + query: queryState.regions, + target: targetState.regions, + }, + selections: { + query: queryState.selections, + target: targetState.selections, + }, + }; +} + +function computeInterfaceSide(structureRef, alignmentMaps, cutoff) { + const selections = interfaceSelectionsByChain(structureRef, chainsFromMaps(alignmentMaps), cutoff); + const regions = mapInterfaceRegions(alignmentMaps, selections); + for (const region of regions) { + const selection = selections.get(region.chain); + if (selection) selection.regions.push(region); + } + return { selections, regions }; +} + +export async function applyInterfaceVisibility(plugin, state, input) { + const queryMode = input?.showQuery || 0; + const targetMode = input?.showTarget || 0; + const key = interfaceVisibilityKey(input, queryMode, targetMode); + if (state.visibilityKey === key) return; + state.visibilityKey = key; + + if (state.query) await setInterfaceMode(plugin, state.query, queryMode); + if (state.target) await setInterfaceMode(plugin, state.target, targetMode); +} + +export async function buildInterfaceRepresentations(plugin, state, structure, side, input) { + const mode = side === 'query' ? input.showQuery || 0 : input.showTarget || 0; + const type = side === 'query' ? input.queryRepresentation || 'cartoon' : input.targetRepresentation || 'cartoon'; + const entries = interfaceEntries(state, side); + const stateEntry = { + structure, + type, + mode, + entries, + background: null, + }; + stateEntry.background = await createInterfaceBackground(plugin, state, stateEntry, side); + await applyInterfaceBackgroundMode(plugin, stateEntry, mode); + for (const entry of entries) { + const label = `${side}-${entry.chain}-interface`; + const representation = await addInterfaceRepresentation( + plugin, + structure, + label, + entry.interfaceExpression, + type, + entry.color, + ); + if (!representation) continue; + await emphasizeInterface(plugin, representation, entry.interfaceLoci, input); + } + + return stateEntry; +} + +async function setInterfaceMode(plugin, stateEntry, mode) { + if (!stateEntry || stateEntry.mode === mode) return; + stateEntry.mode = mode; + await applyInterfaceBackgroundMode(plugin, stateEntry, mode); + plugin.managers.camera.reset(); +} + +function interfaceEntries(state, side) { + const palette = side === 'query' ? InterfaceChainColors.query : InterfaceChainColors.target; + const selections = state.interfaceSelections?.[side] || new Map(); + return chainsFromMaps(state.alignmentMaps?.[side]).map((chain, index) => ({ + chain, + color: palette[index % palette.length], + chainExpression: chainExpression(chain), + interfaceExpression: selections.get(chain)?.expression || null, + interfaceLoci: selections.get(chain)?.loci || null, + regions: selections.get(chain)?.regions || [], + })); +} + +async function createInterfaceBackground(plugin, state, stateEntry, side) { + const background = []; + for (const entry of stateEntry.entries) { + const expression = entry.interfaceExpression + ? MS.struct.modifier.exceptBy({ 0: entry.chainExpression, by: entry.interfaceExpression }) + : entry.chainExpression; + const label = `${side}-${entry.chain}-context`; + const representation = await addInterfaceRepresentation( + plugin, + stateEntry.structure, + label, + expression, + stateEntry.type, + entry.color, + ); + if (!representation) continue; + markNonSequenceMappable(state, representation); + const loci = lociFromStructureExpression(stateEntry.structure, expression); + background.push({ + ...representation, + chain: entry.chain, + color: entry.color, + contextBundle: loci ? StructureElement.Bundle.fromLoci(loci) : null, + }); + } + return background; +} + +async function applyInterfaceBackgroundMode(plugin, stateEntry, mode) { + const background = stateEntry.background || []; + if (background.length === 0) return; + + const update = plugin.state.data.build(); + for (const item of background) { + if (item?.component?.ref) { + setSubtreeVisibility(plugin.state.data, item.component.ref, mode === 0); + } + + const ref = representationRef(item); + if (!ref) continue; + update.to(ref).applyOrUpdate( + `${ref}-interface-context-transparency`, + StateTransforms.Representation.TransparencyStructureRepresentation3DFromBundle, + { + kind: 'element-loci', + layers: mode === 1 && item.contextBundle + ? [{ bundle: item.contextBundle, value: 0.3 }] + : [], + }, + { tags: ['foldseek-interface-context-transparency'] }, + ); + } + await update.commit({ doNotUpdateCurrent: true }); +} + +async function addInterfaceRepresentation(plugin, structure, label, expression, type, color, typeParams = {}) { + return addUniformRepresentation(plugin, structure, { + label, + expression, + type, + color, + typeParams, + }); +} + +async function emphasizeInterface(plugin, representation, loci, input) { + const ref = representationRef(representation); + if (!loci || !ref) return; + + const update = plugin.state.data.build(); + update.to(ref).apply(StateTransforms.Representation.EmissiveStructureRepresentation3DFromBundle, { + layers: [{ + bundle: StructureElement.Bundle.fromLoci(loci), + value: input.interfaceHighlightValue ?? 1, + }], + }, { tags: 'foldseek-interface-emissive' }); + update.to(ref).apply(StateTransforms.Representation.ThemeStrengthRepresentation3D, { + overpaintStrength: 1, + transparencyStrength: 1, + emissiveStrength: input.interfaceHighlightStrength ?? 0.1, + substanceStrength: 1, + wiggleStrength: 1, + }, { tags: 'foldseek-interface-emissive-strength' }); + await update.commit({ doNotUpdateCurrent: true }); +} + +function representationRef(item) { + return item?.representation?.ref || item?.representation?.cell?.transform?.ref; +} + +function mapInterfaceRegions(alignmentMaps, selectionsByChain) { + const regions = []; + for (const map of alignmentMaps) { + const interfaceKeys = selectionsByChain.get(map.chain)?.keys; + if (!interfaceKeys?.size) continue; + + const positions = []; + for (const [position, residue] of map.toStructure.entries()) { + if (structureResidueKeys(residue, map.chain).some(key => interfaceKeys.has(key))) { + positions.push(position); + } + } + + for (const [start, end] of residueRanges(positions.sort((a, b) => a - b))) { + regions.push({ chain: map.chain, index: map.index, start, end }); + } + } + return regions; +} + +function interfaceSelectionsByChain(structureRef, chains, cutoff = 10) { + const structure = structureRef?.cell?.obj?.data; + const chainList = Array.from(new Set((chains || []).filter(Boolean))); + const selections = new Map(chainList.map(chain => [ + chain, + { expression: null, loci: null, keys: new Set(), regions: [] }, + ])); + if (!structure || chainList.length < 2) return selections; + + for (const chain of chainList) { + const expression = interfaceSurroundingsExpression(chain, chainList, cutoff); + const loci = lociFromStructureExpression(structure, expression); + const selection = selections.get(chain); + if (!selection) continue; + selection.expression = expression; + selection.loci = loci; + for (const residue of residueInfosFromLoci(loci)) { + for (const key of structureResidueKeys(residue, chain)) { + selection.keys.add(key); + } + } + } + + return selections; +} + +function interfaceSurroundingsExpression(chain, chains, cutoff) { + const currentChain = chainExpression(chain); + const otherChains = mergeExpressions( + chains + .filter(other => other !== chain) + .map(chainExpression), + ); + if (!currentChain || !otherChains) return null; + + return MS.struct.modifier.intersectBy({ + 0: currentChain, + by: MS.struct.modifier.includeSurroundings({ + 0: otherChains, + radius: cutoff, + 'as-whole-residues': true, + }), + }); +} + +function chainsFromMaps(alignmentMaps = []) { + return Array.from(new Set(alignmentMaps.map(map => map.chain).filter(Boolean))); +} + +function markNonSequenceMappable(state, item) { + if (!item) return; + const repr = representationObject(item); + if (repr) state.nonSequenceMappable?.add(repr); + const component = componentStructure(item); + if (component) state.nonSequenceMappable?.add(component); +} diff --git a/frontend/molstar/foldseekResult.js b/frontend/molstar/foldseekResult.js index f2d1c3ab..d539ebdc 100644 --- a/frontend/molstar/foldseekResult.js +++ b/frontend/molstar/foldseekResult.js @@ -1,30 +1,38 @@ import { to_mmCIF } from 'molstar/lib/mol-model/structure/export/mmcif'; +import { OrderedSet } from 'molstar/lib/mol-data/int'; +import { StructureElement, StructureProperties, Unit } from 'molstar/lib/mol-model/structure'; import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder'; -import { setSubtreeVisibility } from 'molstar/lib/mol-plugin/behavior/static/state'; +import { StateTransforms } from 'molstar/lib/mol-plugin-state/transforms'; +import { tmalign, parse as parseTMOutput, parseMatrix as parseTMMatrix } from 'tmalign-wasm'; import { - alignmentRegions, - alignedSelectionExpression, - atomGroupExpression, - backgroundExpressionForMode, - baseSelectionExpressionForMode, - chainColorEntries, - computeInterfaceRegions, - computeResidueMaps, - interfaceExpressionForChain, - mergeSelectionExpressions, - selectionExpressionForChains, - structureChains, - structureSelectionExpression, -} from './foldseekSelections.js'; -import { superposeTargetWithFoldseekAlignment } from './foldseekSuperposition.js'; -import { transformStructureConformation } from './transforms.js'; + addUniformRepresentation, + atomToPdbRow, + chainExpression, + componentStructure, + expressionForResidues, + isValidLoci, + loadStructureFromData, + lociFromExpression as lociFromStructureExpression, + mat4FromRotationTranslation, + mergeExpressions, + residueInfosFromLoci, + representationObject, + residuesForMappedRange, + structureResidueKeys, + transformStructureConformation, +} from './molstarStructure.js'; +import { getChainName } from './foldseekData.js'; import { setArrows } from './foldseekArrows.js'; -import { loadStructureFromData } from './io.js'; -import { isValidLoci, lociFromExpression as lociFromStructureExpression, residueInfoFromLoci, structuresMatch } from './interactions.js'; -import { addUniformRepresentation } from './representations.js'; -import { getChainName } from './foldseekUtilities.js'; - -const QueryChainSurfaceColors = [ +import { setChainLabels } from './foldseekChainLabels.js'; +import { + applyInterfaceVisibility, + buildInterfaceRepresentations, + interfaceSceneKey, + interfaceVisibilityKey, + prepareInterfaceState, +} from './foldseekInterface.js'; + +const ChainSurfaceColors = [ 0x991999, 0x00bfbf, 0xe9967a, @@ -34,375 +42,443 @@ const QueryChainSurfaceColors = [ 0xd55e00, 0xcc79a7, ]; -const InterfacePalettes = { - query: [0x1e88e5, 0xe53935, 0x991999, 0x00bfbf], - target: [0xffc107, 0x43a047, 0xe9967a, 0x009e73], -}; -async function buildStructureRepresentations(plugin, state, structure, side, input, color, alignedColor) { - if (input?.structureMode === 'interface') { - return buildInterfaceStructureRepresentations(plugin, state, structure, side, input); - } - const mode = input[`show${capitalize(side)}`] || 0; - const type = input[`${side}Representation`] || 'cartoon'; - const aligned = withInteractionChain( - await addRepresentation( - plugin, - structure, - `${side}-aligned`, - type, - alignedColor, - baseSelectionExpressionForMode(state, input, side, 0), - representationOverrides(input, side), - ), - input, - side, - ); - registerInteraction(state, aligned, { side, chain: aligned?.chain, sequenceMappable: true }); - const stateEntry = { - structure, - type, - mode, - aligned, - background: null, - surfaces: await addChainSurfaces(plugin, state, structure, side, input), - baseColor: color, - alignedColor, - }; - await setBackgroundRepresentation(plugin, state, stateEntry, input, side, mode); - setSurfaceVisibility(plugin, stateEntry, input, side, mode); - return stateEntry; +function selectionsKey(selections) { + return selections.map(selectionKey).join(';'); } -async function applyVisibility(plugin, state, input) { - const queryMode = input?.showQuery || 0; - const targetMode = input?.showTarget || 0; - const key = visibilityKey(input, queryMode, targetMode); - if (state.visibilityKey === key) return; - state.visibilityKey = key; +function selectionKey(selection, { state = null, focus = false } = {}) { + if (!selection) return 'none'; + if (selection.source === 'molstar') { + return `molstar:${selection.hoverKey || state?.hoverKey || ''}`; + } + return [ + selection.side || 'target', + selection.index, + selection.start, + selection.length, + ...(focus ? [selection.token || 0] : []), + ].join(':'); +} - if (state.query) { - await setSelectionMode(plugin, state, state.query, input, 'query', queryMode); +function handleStructureHover(plugin, state, event) { + const sceneEvent = structureEventFromCurrent(state, event?.current, 'structure-hover'); + if (sceneEvent?.residue && isValidLoci(event?.current?.loci) && !isNonHoverable(state, event.current)) { + state.hoverLoci = event.current.loci; + state.hoverKey = `${sceneEvent.side}:${sceneEvent.chain}:${sceneEvent.labelResidue}:${sceneEvent.authResidue}`; + state.hoverSelectionKey = `molstar:${state.hoverKey}`; + plugin.managers.interactivity.lociHighlights.highlightOnly({ loci: state.hoverLoci }, false); + sceneEvent.hoverSource = 'molstar'; + sceneEvent.hoverKey = state.hoverKey; + } else { + state.hoverLoci = null; + state.hoverKey = null; + state.hoverSelectionKey = 'none'; + plugin.managers.interactivity.lociHighlights.clearHighlights(); } - if (state.target) { - await setSelectionMode(plugin, state, state.target, input, 'target', targetMode); + return sceneEvent; +} + +function diffInput(previous, next) { + if (!previous) { + return { + rebuild: true, + visibility: true, + selection: true, + hover: true, + focus: true, + }; } - await setArrows(plugin, state, input); + + const rebuild = sceneKey(previous) !== sceneKey(next); + return { + rebuild, + visibility: rebuild || visibilityKey(previous) !== visibilityKey(next), + selection: rebuild || selectionsKey(previous?.highlightSelections || []) !== selectionsKey(next?.highlightSelections || []), + hover: rebuild || selectionKey(previous?.hoverSelection) !== selectionKey(next?.hoverSelection), + focus: rebuild || selectionKey(previous?.focusSelection, { focus: true }) !== selectionKey(next?.focusSelection, { focus: true }), + }; } -function visibilityKey(input, queryMode = input?.showQuery || 0, targetMode = input?.showTarget || 0) { +function standardVisibilityKey(input, queryMode = input?.showQuery || 0, targetMode = input?.showTarget || 0) { return [ queryMode, targetMode, input?.showArrows ? 1 : 0, - input?.queryRepresentation || 'cartoon', - input?.targetRepresentation || 'cartoon', - input?.queryAlpha ?? '', - input?.targetAlpha ?? '', - input?.representationQuality || '', - input?.queryIndex ?? '', - input?.queryChain ?? '', + input?.showChainLabels === false ? 0 : 1, + input?.chainLabelScale ?? input?.chainLabelSize ?? '', + input?.chainLabelOffset ?? '', ].join(':'); } -async function addRepresentation(plugin, structure, label, type, color, expression = MS.struct.generator.all(), typeParams = {}) { - return addUniformRepresentation(plugin, structure, { - label, - type, - color, - expression, - typeParams, - state: { tag: `${label}-representation` }, - }); +function sceneKey(input) { + return input?.structureMode === 'interface' + ? interfaceSceneKey(input, baseSceneKey) + : baseSceneKey(input); } -async function addChainSurfaces(plugin, state, structure, side, input) { - if (input?.structureMode !== 'multimer' || side !== 'query') return []; - - const entries = querySurfaceEntries(state, input); - - const surfaces = []; - for (const entry of entries) { - const surface = await addRepresentation( - plugin, - structure, - `${side}-${entry.chain}-${entry.level || 'surface'}`, - 'gaussian-surface', - entry.color, - entry.expression, - { - alpha: input.chainSurfaceAlpha || 0.18, - visuals: ['gaussian-surface-mesh'], - smoothness: 1.5, - ignoreHydrogens: true, - material: { - metalness: 0, - roughness: 0.35, - bumpiness: 0, - }, - }, - ); - if (surface) { - registerInteraction(state, surface, { - side, - chain: entry.chain, - sequenceMappable: entry.level === 'aligned', - }); - surfaces.push({ ...surface, chain: entry.chain, level: entry.level || 'base' }); - } - } - return surfaces; +function visibilityKey(input) { + return input?.structureMode === 'interface' + ? interfaceVisibilityKey(input) + : standardVisibilityKey(input); } -function querySurfaceEntries(state, input) { - const byChain = new Map(); - const resolver = (chain) => state?.chainOverrides?.query?.[chain] || chain; - for (const region of alignmentRegions(input.alignments, 'query', resolver)) { - if (!byChain.has(region.chain)) byChain.set(region.chain, []); - byChain.get(region.chain).push(atomGroupExpression(region.chain, region.start, region.end)); - } - const entries = []; - let i = 0; - for (const [chain, expressions] of byChain.entries()) { - const alignedExpression = mergeSelectionExpressions(expressions); - const color = QueryChainSurfaceColors[i % QueryChainSurfaceColors.length]; - entries.push({ - chain, - level: 'aligned', - color, - expression: alignedExpression, - }); - entries.push({ - chain, - level: 'unaligned', - color, - expression: MS.struct.modifier.exceptBy({ - 0: atomGroupExpression(chain), - by: alignedExpression, - }), - }); - i += 1; +function rangesByChain(alignments, side, input = null) { + const ranges = new Map(); + for (const { chain, start, end } of alignmentRegions(alignments, side, input)) { + if (!ranges.has(chain)) ranges.set(chain, []); + ranges.get(chain).push([start, end]); } + return ranges; +} - if (input?.queryChain) return entries; - - const queryChains = selectionExpressionForChains(input.alignments, 'query', resolver); - if (queryChains) { - entries.push({ - chain: 'other', - level: 'other', - color: 0xcccccc, - expression: MS.struct.modifier.exceptBy({ - 0: MS.struct.generator.all(), - by: queryChains, - }), +function alignmentRegions(alignments, side, input = null) { + const regions = []; + for (const alignment of alignments || []) { + const name = side === 'query' ? alignment.query : alignment.target; + const start = side === 'query' ? alignment.qStartPos : alignment.dbStartPos; + const end = side === 'query' ? alignment.qEndPos : alignment.dbEndPos; + if (!Number.isFinite(start) || !Number.isFinite(end)) continue; + regions.push({ + chain: structureChainForAlignment(input, side, getChainName(name)), + start: Math.min(start, end), + end: Math.max(start, end), }); } - return entries; + return regions; } -async function buildInterfaceStructureRepresentations(plugin, state, structure, side, input) { - const mode = input[`show${capitalize(side)}`] || 0; - const type = input[`${side}Representation`] || 'cartoon'; - const aligned = []; +function structureSelectionExpression(state, input, selection) { + if (!selection) return null; + const side = selection?.side || 'target'; + const alignment = input?.alignments?.[selection?.index]; + if (!alignment || !Number.isFinite(selection.start) || !Number.isFinite(selection.length)) return null; + const start = selection.start; + const end = selection.start + Math.max(0, selection.length - 1); + if (!Number.isFinite(end) || end < start) return null; + return atomGroupExpressionForFoldseekRange(state, side, selection.index, start, end); +} - for (const { chain, color } of chainColorEntries(input, side, InterfacePalettes)) { - const expression = interfaceExpressionForChain(state, side, chain) || atomGroupExpression(chain); - const representation = await addRepresentation( - plugin, - structure, - `${side}-${chain}-interface`, - type, - color, - expression, - ); - if (representation) { - registerInteraction(state, representation, { side, chain, sequenceMappable: true }); - aligned.push({ ...representation, chain, color }); - } - } +function lociForSelection(state, input, selection) { + const side = selection?.side || 'target'; + return lociFromStructureExpression( + state[side]?.structure, + structureSelectionExpression(state, input, selection), + ); +} - const stateEntry = { - structure, - type, - mode, - aligned, - background: null, - surfaces: [], - baseColor: null, - alignedColor: null, +function buildAlignmentMaps(query, target, input) { + const alignmentMaps = { + query: buildSideAlignmentMaps(query, input, 'query'), + target: buildSideAlignmentMaps(target, input, 'target'), + }; + return { + alignmentMaps, + structureResidues: { + query: structureResidueAlignmentMap(alignmentMaps.query), + target: structureResidueAlignmentMap(alignmentMaps.target), + }, }; - await setInterfaceBackgroundRepresentation(plugin, state, stateEntry, input, side, mode); - return stateEntry; } -function capitalize(value) { - return value.charAt(0).toUpperCase() + value.slice(1); +function buildSideAlignmentMaps(structureRef, input, side) { + const alignments = input?.alignments || []; + const structureResidues = structureResiduesByChain(structureRef); + + return alignments.map((alignment, index) => { + const sourceName = side === 'query' ? alignment.query : alignment.target; + const sourceChain = getChainName(sourceName); + const chain = structureChainForAlignment(input, side, sourceChain); + const residues = structureResidues.get(chain) || []; + const lookup = residueNumberLookup(residues); + const positions = alignmentPositions(alignment, side); + const toStructure = new Map(); + + for (const position of positions) { + const residue = lookup.label.get(position) || lookup.auth.get(position); + if (!residue) continue; + toStructure.set(position, residue); + } + + const start = side === 'query' ? alignment.qStartPos : alignment.dbStartPos; + const end = side === 'query' ? alignment.qEndPos : alignment.dbEndPos; + return { + index, + side, + sourceChain, + chain, + start: Math.min(start, end), + end: Math.max(start, end), + toStructure, + }; + }); } -async function setSelectionMode(plugin, state, stateEntry, input, side, mode) { - if (!stateEntry || stateEntry.mode === mode) return; - stateEntry.mode = mode; - if (input?.structureMode === 'interface') { - await setInterfaceBackgroundRepresentation(plugin, state, stateEntry, input, side, mode); - } else { - await setBackgroundRepresentation(plugin, state, stateEntry, input, side, mode); +function structureResidueAlignmentMap(alignmentMaps) { + const mapped = new Map(); + for (const map of alignmentMaps) { + for (const [position, residue] of map.toStructure.entries()) { + for (const key of structureResidueKeys(residue)) { + if (!mapped.has(key)) { + mapped.set(key, { + chain: map.chain, + index: map.index, + residue: position, + structureResidue: residue, + }); + } + } + } } - setSurfaceVisibility(plugin, stateEntry, input, side, mode); - plugin.managers.camera.reset(); + return mapped; } -async function setBackgroundRepresentation(plugin, state, stateEntry, input, side, mode) { - const expression = backgroundExpressionForMode(state, input, side, mode); - await deleteRepresentations(plugin, stateEntry.background); - stateEntry.background = null; - - if (!expression) { - return; +function structureChainForAlignment(input, side, sourceChain) { + if ( + side === 'query' + && input?.structureMode !== 'multimer' + && input?.structureMode !== 'interface' + && input?.queryChain + ) { + return input.queryChain; } + return sourceChain; +} - if (side === 'query' && input?.queryChain && mode === 2) { - stateEntry.background = await addQueryFullBackgroundRepresentations(plugin, state, stateEntry, input); - return; - } +function alignmentPositions(alignment, side) { + const sequence = side === 'query' ? alignment?.qAln : alignment?.dbAln; + let position = side === 'query' ? alignment?.qStartPos : alignment?.dbStartPos; + if (!sequence || !Number.isFinite(position)) return []; - stateEntry.background = await addRepresentation( - plugin, - stateEntry.structure, - `${side}-background`, - stateEntry.type, - stateEntry.baseColor, - expression, - representationOverrides(input, side), - ); - registerInteraction(state, stateEntry.background, { side, sequenceMappable: false }); + const positions = []; + for (const residue of sequence) { + if (residue !== '-') { + positions.push(position); + position += 1; + } + } + return positions; } -async function addQueryFullBackgroundRepresentations(plugin, state, stateEntry, input) { - const background = []; - const base = alignedSelectionExpression(state, input, 'query'); - const chains = structureChains(stateEntry.structure); - for (const chain of chains) { - const chainExpression = atomGroupExpression(chain); - const representation = await addRepresentation( - plugin, - stateEntry.structure, - `query-${chain}-background`, - stateEntry.type, - stateEntry.baseColor, - base - ? MS.struct.modifier.exceptBy({ 0: chainExpression, by: base }) - : chainExpression, - representationOverrides(input, 'query'), - ); - if (representation) { - registerInteraction(state, representation, { side: 'query', chain, sequenceMappable: false }); - background.push({ ...representation, chain }); +function residueNumberLookup(residues) { + const label = new Map(); + const auth = new Map(); + for (const residue of residues) { + if (Number.isFinite(residue.labelResidue) && !label.has(residue.labelResidue)) { + label.set(residue.labelResidue, residue); } - } - return background.length > 0 ? background : null; -} - -async function setInterfaceBackgroundRepresentation(plugin, state, stateEntry, input, side, mode) { - await deleteRepresentations(plugin, stateEntry.background); - stateEntry.background = null; - if (mode === 0) return; - - const alpha = mode === 1 ? 0.28 : 1; - const background = []; - for (const { chain, color } of chainColorEntries(input, side, InterfacePalettes)) { - const chainExpression = atomGroupExpression(chain); - const interfaceExpression = interfaceExpressionForChain(state, side, chain); - const expression = interfaceExpression - ? MS.struct.modifier.exceptBy({ 0: chainExpression, by: interfaceExpression }) - : chainExpression; - const representation = await addRepresentation( - plugin, - stateEntry.structure, - `${side}-${chain}-context`, - stateEntry.type, - color, - expression, - { alpha }, - ); - if (representation) { - registerInteraction(state, representation, { side, chain, sequenceMappable: false }); - background.push({ ...representation, chain, color }); + if (Number.isFinite(residue.authResidue) && !auth.has(residue.authResidue)) { + auth.set(residue.authResidue, residue); } } - stateEntry.background = background; -} + return { label, auth }; +} + +function atomGroupExpressionForFoldseekRange(state, side, index, start, end) { + const map = state.alignmentMaps?.[side]?.[index]; + if (!map) return null; + return expressionForResidues(residuesForMappedRange(map, start, end)); +} + +function alignmentChains(state, side) { + return Array.from(new Set((state.alignmentMaps?.[side] || []).map(map => map.chain).filter(Boolean))); +} + +function structureResiduesByChain(structureRef) { + const structure = structureRef?.cell?.obj?.data; + const residuesByChain = new Map(); + if (!structure) return residuesByChain; + + const seenResidues = new Set(); + const loc = StructureElement.Location.create(structure); + + for (const unit of structure.units) { + if (!Unit.isAtomic(unit)) continue; + loc.unit = unit; + const size = OrderedSet.size(unit.elements); + for (let i = 0; i < size; i++) { + loc.element = OrderedSet.getAt(unit.elements, i); + if (StructureProperties.atom.label_atom_id(loc) !== 'CA') continue; + + const authChain = StructureProperties.chain.auth_asym_id(loc); + const labelChain = StructureProperties.chain.label_asym_id(loc); + const authResidue = StructureProperties.residue.auth_seq_id(loc); + const labelResidue = StructureProperties.residue.label_seq_id(loc); + const key = `${labelChain}:${authChain}:${labelResidue}:${authResidue}`; + if (seenResidues.has(key)) continue; + seenResidues.add(key); + const residue = { + chain: labelChain || authChain, + authChain, + labelChain, + labelResidue, + authResidue, + x: StructureProperties.atom.x(loc), + y: StructureProperties.atom.y(loc), + z: StructureProperties.atom.z(loc), + }; -async function deleteRepresentations(plugin, representations) { - const items = Array.isArray(representations) - ? representations - : (representations ? [representations] : []); - for (const item of items) { - if (item?.component?.ref) { - await plugin.state.data.build().delete(item.component.ref).commit(); + if (authChain) { + if (!residuesByChain.has(authChain)) residuesByChain.set(authChain, []); + residuesByChain.get(authChain).push(residue); + } + if (labelChain && labelChain !== authChain) { + if (!residuesByChain.has(labelChain)) residuesByChain.set(labelChain, []); + residuesByChain.get(labelChain).push(residue); + } } } + + return residuesByChain; } -function representationOverrides(input, side) { +async function buildFoldseekRepresentations(plugin, state, structure, side, input) { + const mode = side === 'query' ? input.showQuery || 0 : input.showTarget || 0; + const type = side === 'query' ? input.queryRepresentation || 'cartoon' : input.targetRepresentation || 'cartoon'; + const unalignedColor = input[`${side}UnalignedColor`] || (side === 'query' ? 0xa5cff5 : 0xffe699); + const highlightColor = input[`${side}Color`] || (side === 'query' ? 0x1e88e5 : 0xffc107); const alpha = input?.[`${side}Alpha`]; - return { - ...(Number.isFinite(alpha) ? { alpha } : {}), - ...(input?.representationQuality ? { qualityPreset: input.representationQuality } : {}), + const base = await addUniformRepresentation(plugin, structure, { + label: `${side}-base`, + type, + color: unalignedColor, + typeParams: { + ...(Number.isFinite(alpha) ? { alpha } : {}), + ...(input?.representationQuality ? { qualityPreset: input.representationQuality } : {}), + }, + }); + const stateEntry = { + structure, + mode, + base, + highlightColor, + surfaces: [], }; -} - -function withInteractionChain(representation, input, side) { - if (!representation || side !== 'query' || !input?.queryChain) return representation; - return { ...representation, chain: input.queryChain }; -} - -function setSurfaceVisibility(plugin, stateEntry, input, side, mode) { - if (input?.structureMode !== 'multimer' || side !== 'query') return; - - for (const surface of stateEntry.surfaces || []) { - const ref = surface.component?.ref; - if (!ref) continue; - - const visible = surface.level === 'aligned' - || (surface.level === 'unaligned' && mode >= 1) - || (surface.level === 'other' && mode >= 2); - setSubtreeVisibility(plugin.state.data, ref, !visible); + if (input?.structureMode === 'multimer' && side === 'query') { + stateEntry.surfaces = await addQuerySurfaces(plugin, state, structure, input); } + await applyFoldseekVisualState(plugin, state, stateEntry, input, side, mode); + return stateEntry; } -function needsBaseRebuild(state, input) { - return state.baseKey !== baseSceneKey(input); +async function addQuerySurfaces(plugin, state, structure, input) { + const defaultAlpha = input.chainSurfaceAlpha ?? 0.14; + const promises = alignmentChains(state, 'query').map(async (chain, i) => { + const initialState = { pickable: false }; + const typeParams = { + alpha: defaultAlpha, + visuals: ['structure-gaussian-surface-mesh'], + quality: 'higher', + smoothness: 1.5, + density: 0.5, + bumpFrequency: 0, + bumpAmplitude: 0, + transparentBackfaces: 'off', + ignoreHydrogens: true, + ignoreLight: true, + doubleSided: false, + flipSided: false, + flatShaded: false, + material: { + metalness: 0, + roughness: 1, + bumpiness: 0, + }, + }; + const label = `query-${chain}-chain-surface`; + const surface = await addUniformRepresentation( + plugin, + structure, + { + label, + expression: chainExpression(chain), + type: 'gaussian-surface', + color: ChainSurfaceColors[i % ChainSurfaceColors.length], + typeParams, + initialState, + }, + ); + if (!surface) return null; + markNonSequenceMappable(state, surface); + markNonHoverable(state, surface); + return { ...surface, chain, level: 'chain' }; + }); + return (await Promise.all(promises)).filter(Boolean); } -function diffInput(previous, next) { - if (!previous) { - return { - rebuild: true, - visibility: true, - selection: true, - hover: true, - focus: true, - }; +async function setSelectionMode(plugin, state, stateEntry, input, side, mode) { + if (!stateEntry || stateEntry.mode === mode) return; + stateEntry.mode = mode; + await applyFoldseekVisualState(plugin, state, stateEntry, input, side, mode); + focusVisibleStructure(plugin, stateEntry); +} + +async function applyFoldseekVisualState(plugin, state, stateEntry, input, side, mode) { + const representationRef = representationStateRef(stateEntry.base); + if (!representationRef) return; + + const all = MS.struct.generator.all(); + const alignedExpression = mergeExpressions( + (state.alignmentMaps?.[side] || []).map(map => atomGroupExpressionForFoldseekRange(state, side, map.index, map.start, map.end)), + ) || all; + const alignedChainsExpression = mergeExpressions(alignmentChains(state, side).map(chain => chainExpression(chain))) || all; + const visibleExpression = mode === 1 || (mode !== 0 && side === 'query' && (input?.queryChain || input?.structureMode === 'multimer')) + ? alignedChainsExpression + : (mode === 0 ? alignedExpression : all); + const hiddenExpression = mode === 2 && !(side === 'query' && (input?.queryChain || input?.structureMode === 'multimer')) + ? null + : MS.struct.modifier.exceptBy({ 0: all, by: visibleExpression }); + const overpaintLoci = lociFromStructureExpression(stateEntry.structure, alignedExpression); + const hiddenLoci = hiddenExpression ? lociFromStructureExpression(stateEntry.structure, hiddenExpression) : null; + stateEntry.visibleExpression = visibleExpression; + + await plugin.state.data.build() + .to(representationRef) + .applyOrUpdate( + `${representationRef}-foldseek-overpaint`, + StateTransforms.Representation.OverpaintStructureRepresentation3DFromBundle, + { + kind: 'element-loci', + layers: overpaintLoci + ? [{ bundle: StructureElement.Bundle.fromLoci(overpaintLoci), color: stateEntry.highlightColor, clear: false }] + : [], + }, + { tags: ['foldseek-visual-state'] }, + ) + .to(representationRef) + .applyOrUpdate( + `${representationRef}-foldseek-transparency`, + StateTransforms.Representation.TransparencyStructureRepresentation3DFromBundle, + { + kind: 'element-loci', + layers: hiddenLoci + ? [{ bundle: StructureElement.Bundle.fromLoci(hiddenLoci), value: 1 }] + : [], + }, + { tags: ['foldseek-visual-state'] }, + ) + .commit({ doNotUpdateCurrent: true }); +} + +function focusVisibleStructure(plugin, stateEntry, options = {}) { + const loci = lociFromStructureExpression(stateEntry?.structure, stateEntry?.visibleExpression); + if (loci) { + plugin.managers.camera.focusLoci(loci, { + durationMs: options.durationMs ?? 250, + extraRadius: 6, + minRadius: 3, + }); + } else { + plugin.managers.camera.reset(); } - - const rebuild = baseSceneKey(previous) !== baseSceneKey(next); - return { - rebuild, - visibility: rebuild || visibilityKey(previous) !== visibilityKey(next), - selection: rebuild || selectionsKey(previous?.highlightSelections || []) !== selectionsKey(next?.highlightSelections || []), - hover: rebuild || hoverInputKey(previous?.hoverSelection) !== hoverInputKey(next?.hoverSelection), - focus: rebuild || focusInputKey(previous?.focusSelection) !== focusInputKey(next?.focusSelection), - }; } function resetSceneState(state, input) { - state.baseKey = baseSceneKey(input); - state.structureMode = input?.structureMode || 'alignment'; - state.activeQueryChain = input?.queryChain || null; + state.baseKey = sceneKey(input); state.query = null; state.target = null; state.arrows = null; + state.chainLabels = null; state.tmAlignResults = null; state.focusKey = null; state.selectionKey = null; @@ -410,37 +486,47 @@ function resetSceneState(state, input) { state.visibilityKey = null; state.hoverLoci = null; state.hoverKey = null; - state.chainOverrides = { query: {}, target: {} }; + state.alignmentMaps = { query: [], target: [] }; + state.structureResidues = { query: new Map(), target: new Map() }; state.interfaceRegions = { query: [], target: [] }; - state.interfaceResidueMap = { query: {}, target: {} }; - state.residueMap = { query: {}, target: {} }; - state.interactionByRepr = new WeakMap(); - state.interactionByComponent = new WeakMap(); + state.interfaceSelections = { query: new Map(), target: new Map() }; + state.nonSequenceMappable = new WeakSet(); + state.nonHoverable = new WeakSet(); + state.structureSide = new WeakMap(); } function baseSceneKey(input) { if (!input) return 'empty'; - return [ - input.structureMode || 'alignment', - sourceKey(input.query), - sourceKey(input.target), - transformKey(input.targetTransform), - alignmentsKey(input.alignments), - input.queryIndex ?? '', - input.queryChain ?? '', - ].join('|'); + return JSON.stringify({ + structureMode: input.structureMode || 'alignment', + query: sourceKey(input.query), + target: sourceKey(input.target), + targetTransform: transformKey(input.targetTransform), + alignments: (input.alignments || []).map(alignmentKey), + superpositionAlignments: (input.superpositionAlignments || []).map(alignmentKey), + queryIndex: input.queryIndex ?? null, + queryChain: input.queryChain ?? null, + queryRepresentation: input.queryRepresentation || 'cartoon', + targetRepresentation: input.targetRepresentation || 'cartoon', + queryAlpha: input.queryAlpha ?? null, + targetAlpha: input.targetAlpha ?? null, + representationQuality: input.representationQuality || null, + queryColor: input.queryColor ?? null, + targetColor: input.targetColor ?? null, + queryUnalignedColor: input.queryUnalignedColor ?? null, + targetUnalignedColor: input.targetUnalignedColor ?? null, + chainSurfaceAlpha: input.chainSurfaceAlpha ?? null, + arrowColor: input.arrowColor ?? null, + }); } function sourceKey(source) { - if (!source) return 'none'; - const data = source.data || ''; - return [ - source.format || '', - source.label || '', - data.length, - data.slice(0, 96), - data.slice(-96), - ].join(':'); + if (!source) return null; + return { + format: source.format || '', + label: source.label || '', + data: source.data || '', + }; } function transformKey(transform) { @@ -451,136 +537,129 @@ function transformKey(transform) { return JSON.stringify(transform); } -function alignmentsKey(alignments = []) { - return alignments.map((alignment, index) => [ - index, - alignment.id, - alignment.query, - alignment.target, - alignment.db, - alignment.qStartPos, - alignment.qEndPos, - alignment.dbStartPos, - alignment.dbEndPos, - alignment.complexu, - alignment.complext, - ].join(':')).join(';'); -} - -function lociFromExpression(state, expression, side = 'target') { - if (!state[side] || !expression) return null; - return lociFromStructureExpression(state[side].structure, expression); -} - -function structureEventFromInteraction(state, input, current, type) { - const picked = interactionInfoFromCurrent(state, current); - const event = structureEventFromLoci(state, current?.loci, type); - if (!event.side && picked?.side) event.side = picked.side; - if (picked?.chain && picked.side === event.side) { - event.chain = picked.chain; - if (event.residue && typeof event.residue === 'object') { - event.residue = { - ...event.residue, - chain: picked.chain, - }; - } - } - if (picked?.sequenceMappable === false) { - event.sequenceMappable = false; - } - addFoldseekResidueMapping(state, input, event); - return event; +function alignmentKey(alignment) { + return { + id: alignment.id, + query: alignment.query, + target: alignment.target, + db: alignment.db, + qStartPos: alignment.qStartPos, + qEndPos: alignment.qEndPos, + dbStartPos: alignment.dbStartPos, + dbEndPos: alignment.dbEndPos, + qAln: alignment.qAln, + dbAln: alignment.dbAln, + complexu: alignment.complexu, + complext: alignment.complext, + }; } -function addFoldseekResidueMapping(state, input, event) { - if (!event?.side || !event.chain || !event.residue || event.sequenceMappable === false) return; +function structureEventFromCurrent(state, current, type) { + if (!isValidLoci(current?.loci)) return { type, residue: null }; + if (isNonHoverable(state, current)) return { type, residue: null }; - const chainResidues = state.residueMap?.[event.side]?.[event.chain] || []; - const residueIndex = chainResidues.findIndex(entry => ( - (Number.isFinite(event.labelResidue) && entry.labelResidue === event.labelResidue) - || (Number.isFinite(event.authResidue) && entry.authResidue === event.authResidue) - )); - if (residueIndex < 0) return; + const side = structureSideForLoci(state, current.loci); + const residueCandidates = side ? residueInfosFromLoci(current.loci) : []; + if (!side || residueCandidates.length === 0) return { type, residue: null }; - const alignment = (input?.alignments || []).find(entry => { - const sideName = event.side === 'query' ? entry.query : entry.target; - const chain = state.chainOverrides?.[event.side]?.[getChainName(sideName)] || getChainName(sideName); - return chain === event.chain; - }); - if (!alignment) return; + const mapped = isNonSequenceMappable(state, current) + ? null + : mappedFoldseekResidue(state, side, residueCandidates); + const residue = mapped?.structureResidue || residueCandidates[0]; + const chain = mapped?.chain || residue.chain; - const alignmentStart = event.side === 'query' ? 1 : alignment.dbStartPos; - if (!Number.isFinite(alignmentStart)) return; + const event = { + type, + side, + chain, + authChain: residue.authChain, + labelChain: residue.labelChain, + residue: { + chain, + residue: residue.residue, + oneLetter: residue.oneLetter, + threeLetter: residue.threeLetter, + }, + authResidue: residue.authResidue, + labelResidue: residue.labelResidue, + residues: residue.residues, + }; + + if (!mapped) { + event.sequenceMappable = false; + return event; + } - event.foldseekResidue = alignmentStart + residueIndex; - event.residues = [event.foldseekResidue]; + return { + ...event, + chain: mapped.chain, + residue: { + ...event.residue, + chain: mapped.chain, + }, + alignmentIndex: mapped.index, + foldseekResidue: mapped.residue, + residues: [mapped.residue], + }; } -function registerInteraction(state, item, meta) { - if (!item || !meta?.side) return; - const repr = representationObject(item); - if (repr) state.interactionByRepr?.set(repr, meta); - const component = componentStructure(item); - if (component) state.interactionByComponent?.set(component, meta); +function structureSideForLoci(state, loci) { + const structure = loci?.structure; + return state.structureSide?.get(structure) + || state.structureSide?.get(structure?.root) + || null; } -function interactionInfoFromCurrent(state, current) { - const byRepresentation = current?.repr - ? state.interactionByRepr?.get(current.repr) - : null; - if (byRepresentation) return byRepresentation; - return isValidLoci(current?.loci) - ? state.interactionByComponent?.get(current.loci.structure) - : null; +function registerStructureSide(state, side, structureRef) { + const structure = structureRef?.cell?.obj?.data || structureRef; + if (!structure) return; + state.structureSide?.set(structure, side); + if (structure.root) state.structureSide?.set(structure.root, side); } -function representationObject(item) { - return item?.representation?.cell?.obj?.data?.repr - || item?.representation?.obj?.data?.repr - || item?.representation?.data?.repr - || null; +function mappedFoldseekResidue(state, side, residue) { + const residues = Array.isArray(residue) ? residue : [residue]; + const mappedResidues = state.structureResidues?.[side]; + if (!mappedResidues) return null; + + for (const candidate of residues) { + for (const key of structureResidueKeys(candidate)) { + const mapped = mappedResidues.get(key); + if (mapped) return mapped; + } + } + return null; } -function componentStructure(item) { - return item?.component?.cell?.obj?.data - || item?.component?.obj?.data - || item?.component?.data - || null; +function markNonSequenceMappable(state, item) { + if (!item) return; + const repr = representationObject(item); + if (repr) state.nonSequenceMappable?.add(repr); + const component = componentStructure(item); + if (component) state.nonSequenceMappable?.add(component); } -function structureEventFromLoci(state, loci, type) { - if (!isValidLoci(loci)) { - return { type, residue: null }; - } +function markNonHoverable(state, item) { + const repr = representationObject(item); + if (repr) state.nonHoverable?.add(repr); +} - const lociStructure = loci.structure; - const side = ['query', 'target'].find((name) => { - const structure = state[name]?.structure?.cell?.obj?.data; - return structuresMatch(lociStructure, structure); - }); - if (!side) return { type, residue: null }; +function isNonHoverable(state, current) { + return Boolean(current?.repr && state.nonHoverable?.has(current.repr)); +} - const residue = residueInfoFromLoci(loci); - if (!residue) { - return { type, residue: null }; - } +function isNonSequenceMappable(state, current) { + return Boolean( + (current?.repr && state.nonSequenceMappable?.has(current.repr)) + || (isValidLoci(current?.loci) && state.nonSequenceMappable?.has(current.loci.structure)), + ); +} - return { - type, - side, - chain: residue.chain, - authChain: residue.authChain, - labelChain: residue.labelChain, - residue: { - chain: residue.chain, - residue: residue.residue, - oneLetter: residue.oneLetter, - threeLetter: residue.threeLetter, - }, - authResidue: residue.authResidue, - labelResidue: residue.labelResidue, - residues: residue.residues, - }; +function representationStateRef(item) { + return item?.representation?.ref + || item?.representation?.cell?.transform?.ref + || item?.representation?.cell?.ref + || null; } async function setStructureSelection(plugin, state, input) { @@ -594,17 +673,9 @@ async function setStructureSelection(plugin, state, input) { return; } - const lociList = []; - for (const side of ['query', 'target']) { - if (!state[side]) continue; - const expression = mergeSelectionExpressions( - selections - .filter(selection => (selection.side || 'target') === side) - .map(selection => structureSelectionExpression(state, input, selection)), - ); - const loci = lociFromExpression(state, expression, side); - if (loci) lociList.push(loci); - } + const lociList = selections + .map(selection => lociForSelection(state, input, selection)) + .filter(Boolean); if (lociList.length === 0) { plugin.managers.interactivity.lociSelects.deselectAll(); @@ -618,13 +689,13 @@ async function setStructureSelection(plugin, state, input) { } async function setStructureHover(plugin, state, input) { - const key = hoverSelectionKey(input?.hoverSelection, state); + const key = selectionKey(input?.hoverSelection, { state }); if (state.hoverSelectionKey === key) return; state.hoverSelectionKey = key; const loci = input?.hoverSelection?.source === 'molstar' ? state.hoverLoci - : lociFromInputSelection(state, input, input?.hoverSelection); + : lociForSelection(state, input, input?.hoverSelection); if (!loci) { plugin.managers.interactivity.lociHighlights.clearHighlights(); return; @@ -633,61 +704,14 @@ async function setStructureHover(plugin, state, input) { plugin.managers.interactivity.lociHighlights.highlightOnly({ loci }, false); } -function selectionsKey(selections) { - return selections.map(selection => [ - selection.side || 'target', - selection.index, - selection.start, - selection.length, - ].join(':')).join(';'); -} - -function hoverSelectionKey(selection, state) { - if (!selection) return 'none'; - if (selection.source === 'molstar') { - return `molstar:${selection.hoverKey || state.hoverKey || ''}`; - } - return [ - selection.side || 'target', - selection.index, - selection.start, - selection.length, - ].join(':'); -} - -function hoverInputKey(selection) { - if (!selection) return 'none'; - return [ - selection.source || 'sequence', - selection.hoverKey || '', - selection.side || 'target', - selection.index, - selection.start, - selection.length, - ].join(':'); -} - -function focusInputKey(selection) { - if (!selection) return 'none'; - return [ - selection.side || 'target', - selection.index, - selection.start, - selection.length, - selection.token || 0, - ].join(':'); -} - async function setTargetFocus(plugin, state, input) { const selection = input?.focusSelection; - const key = selection - ? `${selection.side || 'target'}:${selection.index}:${selection.start}:${selection.length}:${selection.token || 0}` - : null; + const key = selectionKey(selection, { focus: true }); if (state.focusKey === key) return; state.focusKey = key; if (!selection) return; - const loci = lociFromInputSelection(state, input, selection); + const loci = lociForSelection(state, input, selection); if (!loci) return; plugin.managers.camera.focusLoci(loci, { @@ -697,97 +721,138 @@ async function setTargetFocus(plugin, state, input) { }); } -function lociFromInputSelection(state, input, selection) { - const side = selection?.side || 'target'; - if (!state[side]) return null; - return lociFromExpression(state, structureSelectionExpression(state, input, selection), side); -} - -function resolveChainOverrides(structure, input, side) { - const chains = structureChains(structure); - if (chains.length === 0) return {}; - - const requested = Array.from(new Set(alignmentRegions(input?.alignments || [], side).map(region => region.chain))); - const explicitQueryChain = side === 'query' && input?.queryChain ? input.queryChain : null; - const useQueryIndexFallback = side === 'query' - && !explicitQueryChain - && requested.length === 1 - && Number.isInteger(input?.queryIndex) - && chains.length > 1 - && chains[input.queryIndex]; - const overrides = {}; - for (const chain of requested) { - if (explicitQueryChain) { - overrides[chain] = explicitQueryChain; - } else if (useQueryIndexFallback) { - overrides[chain] = chains[input.queryIndex]; - } else if (chains.includes(chain)) { - overrides[chain] = chain; - } else if (side === 'query' && Number.isInteger(input?.queryIndex) && chains[input.queryIndex]) { - overrides[chain] = chains[input.queryIndex]; - } else if (chains.length === 1) { - overrides[chain] = chains[0]; +async function rebuildScene(plugin, state, input) { + await plugin.clear(); + await plugin.dataTransaction(async () => { + resetSceneState(state, input); + if (!input) return; + + let target = input.target ? await loadStructureFromData(plugin, input.target) : null; + const query = input.query ? await loadStructureFromData(plugin, input.query) : null; + + target = input?.structureMode === 'multimer' + ? await prepareMultimerTarget(plugin, target, query, input, state) + : await prepareSuperposedTarget(plugin, target, query, input, state); + registerStructureSide(state, 'target', target); + registerStructureSide(state, 'query', query); + const alignmentState = buildAlignmentMaps(query, target, input); + state.alignmentMaps = alignmentState.alignmentMaps; + state.structureResidues = alignmentState.structureResidues; + if (input?.structureMode === 'interface') { + prepareInterfaceState(state, input, { query, target }); } - } - return overrides; + + if (target) { + state.target = input?.structureMode === 'interface' + ? await buildInterfaceRepresentations(plugin, state, target, 'target', input) + : await buildFoldseekRepresentations(plugin, state, target, 'target', input); + } + + if (query) { + state.query = input?.structureMode === 'interface' + ? await buildInterfaceRepresentations(plugin, state, query, 'query', input) + : await buildFoldseekRepresentations(plugin, state, query, 'query', input); + } + }); + + focusVisibleStructure(plugin, state.target || state.query); } -async function rebuildBaseScene(plugin, state, input) { - await plugin.clear(); - resetSceneState(state, input); - if (!input) return; - - let target = input.target ? await loadStructureFromData(plugin, input.target) : null; - const query = input.query ? await loadStructureFromData(plugin, input.query) : null; - state.chainOverrides = { - query: resolveChainOverrides(query, input, 'query'), - target: resolveChainOverrides(target, input, 'target'), +async function prepareSuperposedTarget(plugin, target, query, input, state) { + if (!target || !query) return target; + const superposition = await superposeTargetWithFoldseekAlignment( + plugin, + target, + query, + input.superpositionAlignments || input.alignments, + input, + ); + state.tmAlignResults = superposition.results; + return superposition.structure; +} + +async function superposeTargetWithFoldseekAlignment(plugin, target, query, alignments, input) { + if (!alignments?.length || !alignments[0]?.qAln || !alignments[0]?.dbAln) { + return { structure: target, results: null }; + } + const queryPdb = makeAlignedCaPdb(query, rangesByChain(alignments, 'query', input), true); + const targetPdb = makeAlignedCaPdb(target, rangesByChain(alignments, 'target', input), false); + if (!queryPdb || !targetPdb) { + return { structure: target, results: null }; + } + const alnFasta = `>target\n${alignments[0].dbAln}\n\n>query\n${alignments[0].qAln}`; + const tm = await tmalign(targetPdb, queryPdb, alnFasta); + const { t, u } = parseTMMatrix(tm.matrix); + return { + structure: await transformStructureConformation(plugin, target, mat4FromRotationTranslation(t, u)), + results: parseTMOutput(tm.output), }; +} - if (target && input.targetTransform) { - target = await transformStructureConformation(plugin, target, input.targetTransform); - } else if (target && query) { - const superposition = await superposeTargetWithFoldseekAlignment( - plugin, - target, - query, - input.superpositionAlignments || input.alignments, - state.chainOverrides, - ); - target = superposition.structure; - state.tmAlignResults = superposition.results; +function makeAlignedCaPdb(structureRef, ranges, useRangeStart) { + const structure = structureRef?.cell?.obj?.data; + if (!structure || ranges.size === 0) return ''; + + const rows = []; + const loc = StructureElement.Location.create(structure); + let serial = 1; + const chainOrdinals = new Map(); + + for (const unit of structure.units) { + if (!Unit.isAtomic(unit)) continue; + loc.unit = unit; + + const { elements } = unit; + const size = OrderedSet.size(elements); + for (let i = 0; i < size; i++) { + loc.element = OrderedSet.getAt(elements, i); + + const atomName = StructureProperties.atom.label_atom_id(loc); + if (atomName !== 'CA') continue; + + const authChain = StructureProperties.chain.auth_asym_id(loc) || ''; + const labelChain = StructureProperties.chain.label_asym_id(loc) || ''; + const chain = ranges.has(authChain) ? authChain : (ranges.has(labelChain) ? labelChain : null); + if (!chain) continue; + const chainRanges = ranges.get(chain); + + const ordinalKey = chain || authChain || labelChain || 'A'; + const ordinal = (chainOrdinals.get(ordinalKey) || 0) + 1; + chainOrdinals.set(ordinalKey, ordinal); + + if (!alignedOrdinalInRanges(chainRanges, ordinal, useRangeStart)) continue; + + rows.push(atomToPdbRow({ + serial, + atomName, + resName: StructureProperties.atom.auth_comp_id(loc) || StructureProperties.atom.label_comp_id(loc) || 'ALA', + chain, + resno: ordinal, + x: StructureProperties.atom.x(loc), + y: StructureProperties.atom.y(loc), + z: StructureProperties.atom.z(loc), + })); + serial += 1; + } } - const interfaceData = computeInterfaceRegions(query, target, input); - state.interfaceRegions = interfaceData.regions; - state.interfaceResidueMap = interfaceData.residueMap; - state.residueMap = computeResidueMaps(query, target, input, state.chainOverrides); + return rows.join('\n'); +} - if (target) { - state.target = await buildStructureRepresentations( - plugin, - state, - target, - 'target', - input, - input.targetUnalignedColor || 0xffe699, - input.targetColor || 0xffc107, - ); - } +function alignedOrdinalInRanges(ranges, ordinal, useRangeStart) { + return ranges.some(([start, end]) => ( + useRangeStart + ? ordinal >= start && ordinal <= end + : ordinal >= 1 && ordinal <= (end - start + 1) + )); +} - if (query) { - state.query = await buildStructureRepresentations( - plugin, - state, - query, - 'query', - input, - input.queryUnalignedColor || 0xa5cff5, - input.queryColor || 0x1e88e5, - ); +async function prepareMultimerTarget(plugin, target, query, input, state) { + if (!target) return target; + if (input.targetTransform) { + return transformStructureConformation(plugin, target, input.targetTransform); } - - plugin.managers.camera.reset(); + return prepareSuperposedTarget(plugin, target, query, input, state); } function makeCIF(state) { @@ -806,10 +871,6 @@ function makeCIF(state) { } export const foldseekResult = { - async mount() { - return {}; - }, - diffInput, async update(plugin, state, input, previous, change = null) { @@ -821,46 +882,44 @@ export const foldseekResult = { focus: true, }; - if (plan.rebuild && needsBaseRebuild(state, input)) { - await rebuildBaseScene(plugin, state, input); + if (plan.rebuild && state.baseKey !== sceneKey(input)) { + await rebuildScene(plugin, state, input); + } + if (plan.visibility) { + await plugin.dataTransaction(async () => { + if (input?.structureMode === 'interface') { + await applyInterfaceVisibility(plugin, state, input); + return; + } + const queryMode = input?.showQuery || 0; + const targetMode = input?.showTarget || 0; + const key = standardVisibilityKey(input, queryMode, targetMode); + if (state.visibilityKey === key) return; + state.visibilityKey = key; + if (state.query) await setSelectionMode(plugin, state, state.query, input, 'query', queryMode); + if (state.target) await setSelectionMode(plugin, state, state.target, input, 'target', targetMode); + await setArrows(plugin, state, input); + await setChainLabels(plugin, state, input); + }); } - if (plan.visibility) await applyVisibility(plugin, state, input); if (plan.selection) await setStructureSelection(plugin, state, input); if (plan.hover) await setStructureHover(plugin, state, input); if (plan.focus) await setTargetFocus(plugin, state, input); }, onHover(plugin, state, input, event) { - const sceneEvent = structureEventFromInteraction(state, input, event?.current, 'structure-hover'); - if (sceneEvent?.residue && isValidLoci(event?.current?.loci)) { - state.hoverLoci = event.current.loci; - state.hoverKey = `${sceneEvent.side}:${sceneEvent.chain}:${sceneEvent.labelResidue}:${sceneEvent.authResidue}`; - state.hoverSelectionKey = `molstar:${state.hoverKey}`; - plugin.managers.interactivity.lociHighlights.highlightOnly({ loci: state.hoverLoci }, false); - sceneEvent.hoverSource = 'molstar'; - sceneEvent.hoverKey = state.hoverKey; - } else { - state.hoverLoci = null; - state.hoverKey = null; - state.hoverSelectionKey = 'none'; - plugin.managers.interactivity.lociHighlights.clearHighlights(); - } - return sceneEvent; + return handleStructureHover(plugin, state, event); }, onClick(plugin, state, input, event) { - return structureEventFromInteraction(state, input, event?.current, 'structure-click'); + return structureEventFromCurrent(state, event?.current, 'structure-click'); }, - resetView(plugin) { - plugin.managers.camera.reset(); + resetView(plugin, state, input, options = {}) { + focusVisibleStructure(plugin, state?.target || state?.query, options); }, async makeCIF(plugin, state, input) { return makeCIF(state, input); }, - - async dispose(plugin) { - await plugin?.clear?.(); - }, }; diff --git a/frontend/molstar/foldseekSelections.js b/frontend/molstar/foldseekSelections.js deleted file mode 100644 index 3aec0df7..00000000 --- a/frontend/molstar/foldseekSelections.js +++ /dev/null @@ -1,390 +0,0 @@ -import { OrderedSet } from 'molstar/lib/mol-data/int'; -import { StructureElement, StructureProperties, Unit } from 'molstar/lib/mol-model/structure'; -import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder'; -import { getChainName } from './foldseekUtilities.js'; -import { - mergeExpressions, - residueRangeExpression, - residueRangeTest, -} from './selectionExpressions.js'; - -export function rangesByChain(alignments, side, chainResolver = null) { - const ranges = new Map(); - for (const { chain, start, end } of alignmentRegions(alignments, side, chainResolver)) { - if (!ranges.has(chain)) ranges.set(chain, []); - ranges.get(chain).push([start, end]); - } - return ranges; -} - -function chainsBySide(alignments, side, chainResolver = null) { - return Array.from(new Set(alignmentRegions(alignments, side, chainResolver).map(({ chain }) => chain))); -} - -export function alignmentRegions(alignments, side, chainResolver = null) { - const regions = []; - for (const alignment of alignments || []) { - const name = side === 'query' ? alignment.query : alignment.target; - const start = side === 'query' ? alignment.qStartPos : alignment.dbStartPos; - const end = side === 'query' ? alignment.qEndPos : alignment.dbEndPos; - if (!Number.isFinite(start) || !Number.isFinite(end)) continue; - regions.push({ - chain: resolveChain(chainResolver, getChainName(name)), - start: Math.min(start, end), - end: Math.max(start, end), - }); - } - return regions; -} - -export function selectionExpressionForChains(alignments, side, chainResolver = null) { - return mergeSelectionExpressions(chainsBySide(alignments, side, chainResolver).map(chain => atomGroupExpression(chain))); -} - -export function chainColorEntries(input, side, palettes) { - const palette = side === 'query' ? palettes.query : palettes.target; - const seen = new Set(); - const entries = []; - for (const { chain } of alignmentRegions(input.alignments, side)) { - if (seen.has(chain)) continue; - seen.add(chain); - entries.push({ - chain, - color: palette[entries.length % palette.length], - }); - } - return entries; -} - -export function interfaceExpressionForChain(state, side, chain) { - const expressions = (state.interfaceRegions?.[side] || []) - .filter(region => region.chain === chain) - .map(({ start, end }) => atomGroupExpression(chain, start, end)); - return mergeSelectionExpressions(expressions); -} - -export function baseSelectionExpressionForMode(state, input, side, mode) { - const chainResolver = stateChainResolver(state, side); - if (input?.structureMode === 'interface' && mode === 0) { - return selectionExpressionForInterface(state, side) - || selectionExpressionForChains(input.alignments, side, chainResolver) - || MS.struct.generator.all(); - } - return alignedSelectionExpression(state, input, side) || MS.struct.generator.all(); -} - -function selectionExpressionForMode(state, input, side, mode) { - const queryChain = activeQueryChainExpression(input, side); - if (mode === 0) { - return baseSelectionExpressionForMode(state, input, side, mode); - } - if (mode === 1) { - if (queryChain) return queryChain; - return selectionExpressionForChains(input.alignments, side, stateChainResolver(state, side)) || MS.struct.generator.all(); - } - return MS.struct.generator.all(); -} - -function activeQueryChainExpression(input, side) { - if (side !== 'query' || !input?.queryChain) return null; - return atomGroupExpression(input.queryChain); -} - -export function backgroundExpressionForMode(state, input, side, mode) { - if (mode === 0) return null; - - const visible = selectionExpressionForMode(state, input, side, mode); - const base = alignedSelectionExpression(state, input, side); - if (!base) return visible; - - return MS.struct.modifier.exceptBy({ - 0: visible, - by: base, - }); -} - -export function alignedSelectionExpression(state, input, side) { - return mergeSelectionExpressions( - alignmentRegions(input?.alignments, side, stateChainResolver(state, side)).map(({ chain, start, end }) => ( - atomGroupExpressionForAlignmentRange(state, side, chain, start, end, start) - )), - ); -} - -export function structureSelectionExpression(state, input, selection) { - const side = selection?.side || 'target'; - const alignment = input?.alignments?.[selection?.index]; - if (!alignment || !Number.isFinite(selection.start) || !Number.isFinite(selection.length)) return null; - const start = selection.start; - const end = selection.start + Math.max(0, selection.length - 1); - if (!Number.isFinite(end) || end < start) return null; - const chain = resolvedChainForState(state, side, getChainName(alignment[side])); - const alignmentStart = side === 'query' ? alignment.qStartPos : alignment.dbStartPos; - - if (input?.structureMode === 'interface') { - const residues = (state.interfaceResidueMap?.[side]?.[chain] || []).filter(entry => ( - (entry.labelResidue >= start && entry.labelResidue <= end) - || (entry.authResidue >= start && entry.authResidue <= end) - )); - return atomGroupExpressionForResidueMap(chain, residues); - } - - return atomGroupExpressionForAlignmentRange(state, side, chain, start, end, alignmentStart); -} - -export function resolvedChainForState(state, side, chain) { - return resolveChain(stateChainResolver(state, side), chain); -} - -export function stateChainResolver(state, side) { - return (chain) => state?.chainOverrides?.[side]?.[chain] || chain; -} - -function resolveChain(chainResolver, chain) { - return typeof chainResolver === 'function' ? chainResolver(chain) : chain; -} - -export function atomGroupExpression(chain, start, end) { - return residueRangeExpression(chain, start, end); -} - -export function structureChains(structureRef) { - const structure = structureRef?.cell?.obj?.data; - if (!structure) return []; - - const entries = []; - const seenEntries = new Set(); - const loc = StructureElement.Location.create(structure); - for (const unit of structure.units) { - if (!Unit.isAtomic(unit)) continue; - loc.unit = unit; - const size = OrderedSet.size(unit.elements); - for (let i = 0; i < size; i++) { - loc.element = OrderedSet.getAt(unit.elements, i); - const auth = StructureProperties.chain.auth_asym_id(loc) || ''; - const label = StructureProperties.chain.label_asym_id(loc) || ''; - const key = `${auth}:${label}`; - if (seenEntries.has(key)) continue; - seenEntries.add(key); - entries.push({ auth, label }); - } - } - - const authChains = uniqueChains(entries.map(entry => entry.auth).filter(Boolean)); - const labelChains = uniqueChains(entries.map(entry => entry.label).filter(Boolean)); - if (labelChains.length > authChains.length) return labelChains; - if (authChains.length > 0) return authChains; - if (labelChains.length > 0) return labelChains; - return []; -} - -function uniqueChains(values) { - const seen = new Set(); - const chains = []; - for (const value of values) { - if (seen.has(value)) continue; - seen.add(value); - chains.push(value); - } - return chains; -} - -export function mergeSelectionExpressions(expressions) { - return mergeExpressions(expressions); -} - -export function computeResidueMaps(query, target, input, chainOverrides = {}) { - return { - query: structureResidueMap(query, chainsBySide(input.alignments, 'query', stateChainResolver({ chainOverrides }, 'query'))), - target: structureResidueMap(target, chainsBySide(input.alignments, 'target', stateChainResolver({ chainOverrides }, 'target'))), - }; -} - -function residueRanges(residues) { - if (residues.length === 0) return []; - const ranges = []; - let start = residues[0]; - let prev = start; - for (let i = 1; i < residues.length; i++) { - if (residues[i] === prev + 1) { - prev = residues[i]; - continue; - } - ranges.push([start, prev]); - start = residues[i]; - prev = residues[i]; - } - ranges.push([start, prev]); - return ranges; -} - -export function computeInterfaceRegions(query, target, input) { - if (input?.structureMode !== 'interface') { - return { - regions: { query: [], target: [] }, - residueMap: { query: {}, target: {} }, - }; - } - const queryChains = chainsBySide(input.alignments, 'query'); - const targetChains = chainsBySide(input.alignments, 'target'); - const queryInterface = interfaceRegions(query, queryChains, input.interfaceCutoff); - const targetInterface = interfaceRegions(target, targetChains, input.interfaceCutoff); - return { - regions: { - query: queryInterface.regions, - target: targetInterface.regions, - }, - residueMap: { - query: structureResidueMap(query, queryChains), - target: structureResidueMap(target, targetChains), - }, - }; -} - -function selectionExpressionForInterface(state, side) { - return mergeSelectionExpressions( - (state.interfaceRegions?.[side] || []).map(({ chain, start, end }) => atomGroupExpression(chain, start, end)), - ); -} - -function atomGroupExpressionForResidueMap(chain, residues) { - if (!residues.length) return null; - - const labelResidues = residues - .map(entry => entry.labelResidue) - .filter(Number.isFinite) - .sort((a, b) => a - b); - const authResidues = residues - .filter(entry => !Number.isFinite(entry.labelResidue)) - .map(entry => entry.authResidue) - .filter(Number.isFinite) - .sort((a, b) => a - b); - const expressions = [ - ...residueRanges(labelResidues).map(([start, end]) => atomGroupExpressionForProperty(chain, MS.struct.atomProperty.macromolecular.label_seq_id(), start, end)), - ...residueRanges(authResidues).map(([start, end]) => atomGroupExpressionForProperty(chain, MS.struct.atomProperty.macromolecular.auth_seq_id(), start, end)), - ]; - return mergeSelectionExpressions(expressions); -} - -function atomGroupExpressionForAlignmentRange(state, side, chain, start, end, alignmentStart) { - const residueMap = state.residueMap?.[side]?.[chain] || []; - const residues = residuesForFoldseekRange(residueMap, start, end, alignmentStart); - return atomGroupExpressionForResidueMap(chain, residues); -} - -function residuesForFoldseekRange(residueMap, start, end, alignmentStart) { - if (!Number.isFinite(alignmentStart)) return []; - const from = Math.max(0, start - alignmentStart); - const to = end - alignmentStart; - return to >= 0 ? residueMap.slice(from, to + 1) : []; -} - -function atomGroupExpressionForProperty(chain, residueProperty, start, end) { - const { or } = MS.core.logic; - const { eq } = MS.core.rel; - const { macromolecular } = MS.struct.atomProperty; - return MS.struct.generator.atomGroups({ - 'chain-test': or([ - eq([macromolecular.auth_asym_id(), chain]), - eq([macromolecular.label_asym_id(), chain]), - ]), - 'residue-test': residueRangeTest(residueProperty, start, end), - }); -} - -function interfaceRegions(structureRef, chains, cutoff = 10) { - const structure = structureRef?.cell?.obj?.data; - if (!structure) return { regions: [] }; - - const chainSet = new Set(chains); - const residuesByChain = new Map(); - const loc = StructureElement.Location.create(structure); - - for (const unit of structure.units) { - if (!Unit.isAtomic(unit)) continue; - loc.unit = unit; - const size = OrderedSet.size(unit.elements); - for (let i = 0; i < size; i++) { - loc.element = OrderedSet.getAt(unit.elements, i); - if (StructureProperties.atom.label_atom_id(loc) !== 'CA') continue; - - const authChain = StructureProperties.chain.auth_asym_id(loc); - const labelChain = StructureProperties.chain.label_asym_id(loc); - const chain = chainSet.has(authChain) ? authChain : (chainSet.has(labelChain) ? labelChain : null); - if (!chain) continue; - - if (!residuesByChain.has(chain)) residuesByChain.set(chain, []); - residuesByChain.get(chain).push({ - residue: StructureProperties.residue.label_seq_id(loc) || StructureProperties.residue.auth_seq_id(loc), - x: StructureProperties.atom.x(loc), - y: StructureProperties.atom.y(loc), - z: StructureProperties.atom.z(loc), - }); - } - } - - const cutoffSq = cutoff * cutoff; - const hits = new Map(Array.from(chainSet).map(chain => [chain, new Set()])); - for (const [chain, residues] of residuesByChain.entries()) { - const otherResidues = []; - for (const [otherChain, entries] of residuesByChain.entries()) { - if (otherChain !== chain) otherResidues.push(...entries); - } - - for (const residue of residues) { - if (otherResidues.some(other => squaredDistance(residue, other) <= cutoffSq)) { - hits.get(chain)?.add(residue.residue); - } - } - } - - const regions = []; - for (const [chain, residues] of hits.entries()) { - const ordered = Array.from(residues).sort((a, b) => a - b); - for (const [start, end] of residueRanges(ordered)) { - regions.push({ chain, start, end }); - } - } - return { regions }; -} - -function structureResidueMap(structureRef, chains) { - const structure = structureRef?.cell?.obj?.data; - if (!structure) return {}; - - const chainSet = new Set(chains); - const residuesByChain = new Map(Array.from(chainSet).map(chain => [chain, []])); - const seenResidues = new Set(); - const loc = StructureElement.Location.create(structure); - - for (const unit of structure.units) { - if (!Unit.isAtomic(unit)) continue; - loc.unit = unit; - const size = OrderedSet.size(unit.elements); - for (let i = 0; i < size; i++) { - loc.element = OrderedSet.getAt(unit.elements, i); - if (StructureProperties.atom.label_atom_id(loc) !== 'CA') continue; - - const authChain = StructureProperties.chain.auth_asym_id(loc); - const labelChain = StructureProperties.chain.label_asym_id(loc); - const chain = chainSet.has(authChain) ? authChain : (chainSet.has(labelChain) ? labelChain : null); - if (!chain) continue; - - const authResidue = StructureProperties.residue.auth_seq_id(loc); - const labelResidue = StructureProperties.residue.label_seq_id(loc); - const key = `${chain}:${labelResidue}:${authResidue}`; - if (seenResidues.has(key)) continue; - seenResidues.add(key); - residuesByChain.get(chain)?.push({ labelResidue, authResidue }); - } - } - - return Object.fromEntries(residuesByChain); -} - -function squaredDistance(a, b) { - const dx = a.x - b.x; - const dy = a.y - b.y; - const dz = a.z - b.z; - return dx * dx + dy * dy + dz * dz; -} diff --git a/frontend/molstar/foldseekSuperposition.js b/frontend/molstar/foldseekSuperposition.js deleted file mode 100644 index 8f4a102f..00000000 --- a/frontend/molstar/foldseekSuperposition.js +++ /dev/null @@ -1,154 +0,0 @@ -import { OrderedSet } from 'molstar/lib/mol-data/int'; -import { StructureElement, StructureProperties, Unit } from 'molstar/lib/mol-model/structure'; -import { tmalign, parse as parseTMOutput, parseMatrix as parseTMMatrix } from 'tmalign-wasm'; -import { rangesByChain } from './foldseekSelections.js'; -import { mat4FromRotationTranslation, transformStructureConformation } from './transforms.js'; - -export async function superposeTargetWithFoldseekAlignment(plugin, target, query, alignments, chainOverrides = {}) { - if (!alignments?.length || !alignments[0]?.qAln || !alignments[0]?.dbAln) { - return { structure: target, results: null }; - } - - const queryPdb = makeAlignedCaPdb(query, rangesByChain(alignments, 'query', chainResolver(chainOverrides.query)), true); - const targetPdb = makeAlignedCaPdb(target, rangesByChain(alignments, 'target', chainResolver(chainOverrides.target)), false); - if (!queryPdb || !targetPdb) { - return { structure: target, results: null }; - } - - const alnFasta = `>target\n${alignments[0].dbAln}\n\n>query\n${alignments[0].qAln}`; - const tm = await tmalign(targetPdb, queryPdb, alnFasta); - const { t, u } = parseTMMatrix(tm.matrix); - - return { - structure: await transformStructureConformation(plugin, target, mat4FromRotationTranslation(t, u)), - results: parseTMOutput(tm.output), - }; -} - -function chainResolver(overrides = {}) { - return (chain) => overrides?.[chain] || chain; -} - -function makeAlignedCaPdb(structureRef, ranges, useRangeStart) { - const structure = structureRef?.cell?.obj?.data; - if (!structure || ranges.size === 0) return ''; - - const rows = []; - const loc = StructureElement.Location.create(structure); - let serial = 1; - const chainOrdinals = new Map(); - const fallbackRanges = hasSingleStructureChain(structure) && ranges.size === 1 - ? ranges.values().next().value - : null; - - for (const unit of structure.units) { - if (!Unit.isAtomic(unit)) continue; - loc.unit = unit; - - const { elements } = unit; - const size = OrderedSet.size(elements); - for (let i = 0; i < size; i++) { - loc.element = OrderedSet.getAt(elements, i); - - const atomName = StructureProperties.atom.label_atom_id(loc); - if (atomName !== 'CA') continue; - - const authChain = StructureProperties.chain.auth_asym_id(loc) || ''; - const labelChain = StructureProperties.chain.label_asym_id(loc) || ''; - const match = matchingChainRanges(ranges, authChain, labelChain, fallbackRanges); - if (!match) continue; - - const ordinalKey = match.chain || authChain || labelChain || 'A'; - const ordinal = (chainOrdinals.get(ordinalKey) || 0) + 1; - chainOrdinals.set(ordinalKey, ordinal); - - const resno = matchingResidueNumber(match.ranges, ordinal, useRangeStart); - if (!Number.isFinite(resno)) continue; - - rows.push(atomToPdbRow({ - serial, - atomName, - resName: StructureProperties.atom.auth_comp_id(loc) || StructureProperties.atom.label_comp_id(loc) || 'ALA', - chain: match.chain || authChain || labelChain || 'A', - resno, - x: StructureProperties.atom.x(loc), - y: StructureProperties.atom.y(loc), - z: StructureProperties.atom.z(loc), - })); - serial += 1; - } - } - - return rows.join('\n'); -} - -function matchingChainRanges(ranges, authChain, labelChain, fallbackRanges) { - if (authChain && ranges.has(authChain)) { - return { chain: authChain, ranges: ranges.get(authChain) }; - } - if (labelChain && ranges.has(labelChain)) { - return { chain: labelChain, ranges: ranges.get(labelChain) }; - } - if (fallbackRanges) { - const chain = ranges.keys().next().value || authChain || labelChain || 'A'; - return { chain, ranges: fallbackRanges }; - } - return null; -} - -function matchingResidueNumber(ranges, ordinal, useRangeStart) { - const range = useRangeStart - ? ranges.find(([start, end]) => ordinal >= start && ordinal <= end) - : ranges.find(([start, end]) => ordinal >= 1 && ordinal <= (end - start + 1)); - return range ? ordinal : undefined; -} - -function hasSingleStructureChain(structure) { - const chains = new Set(); - const loc = StructureElement.Location.create(structure); - for (const unit of structure.units) { - if (!Unit.isAtomic(unit)) continue; - loc.unit = unit; - const size = OrderedSet.size(unit.elements); - for (let i = 0; i < size; i++) { - loc.element = OrderedSet.getAt(unit.elements, i); - const auth = StructureProperties.chain.auth_asym_id(loc) || ''; - const label = StructureProperties.chain.label_asym_id(loc) || ''; - chains.add(`${auth}:${label}`); - if (chains.size > 1) return false; - } - } - return chains.size <= 1; -} - -function atomToPdbRow(atom) { - const atomName = formatAtomName(atom.atomName, atom.element || atom.atomName?.[0] || 'C'); - return [ - (atom.group || 'ATOM').padEnd(6).slice(0, 6), - String(atom.serial).padStart(5), - ' ', - atomName, - atom.altId || ' ', - atom.resName.slice(0, 3).padStart(3), - ' ', - String(atom.chain).slice(0, 1).padStart(1), - String(atom.resno).padStart(4), - atom.insCode || ' ', - ' ', - atom.x.toFixed(3).padStart(8), - atom.y.toFixed(3).padStart(8), - atom.z.toFixed(3).padStart(8), - (Number.isFinite(atom.occupancy) ? atom.occupancy : 1).toFixed(2).padStart(6), - (Number.isFinite(atom.bFactor) ? atom.bFactor : 0).toFixed(2).padStart(6), - ' ', - String(atom.element || '').trim().slice(0, 2).padStart(2), - ' ', - ].join(''); -} - -function formatAtomName(atomName, element) { - const name = String(atomName || '').slice(0, 4); - if (name.length >= 4) return name; - const symbol = String(element || '').trim(); - return symbol.length <= 1 ? ` ${name.padEnd(3)}` : name.padEnd(4); -} diff --git a/frontend/molstar/foldseekUtilities.js b/frontend/molstar/foldseekUtilities.js deleted file mode 100644 index a78ab1f6..00000000 --- a/frontend/molstar/foldseekUtilities.js +++ /dev/null @@ -1,39 +0,0 @@ -import { OneToThree } from './interactions.js'; - -export function getChainName(name) { - if (!name || /_v[0-9]+$/.test(name)) return 'A'; - if (/^[A-Za-z0-9]$/.test(name)) return name; - const pos = name.lastIndexOf('_'); - return pos !== -1 ? name.substring(pos + 1, pos + 2) : 'A'; -} - -export function getAccession(name) { - if (!name || /_v[0-9]+$/.test(name)) return name; - const pos = name.lastIndexOf('_'); - return pos !== -1 ? name.substring(0, pos) : name; -} - -export function mockPDB(ca, seq, chain, start = 1) { - const atoms = ca.split(','); - const pdb = []; - let j = 1; - for (let i = 0; i < atoms.length; i += 3, j++) { - const resno = start + j - 1; - const [x, y, z] = atoms.slice(i, i + 3).map(element => Number.parseFloat(element)); - const residue = seq !== '' && atoms.length / 3 === seq.length ? seq[i / 3] : 'A'; - pdb.push( - 'ATOM ' + - j.toString().padStart(5) + - ' CA ' + - (OneToThree[residue] || 'ALA') + - chain.toString().padStart(2) + - resno.toString().padStart(4) + - ' ' + - x.toFixed(3).padStart(8) + - y.toFixed(3).padStart(8) + - z.toFixed(3).padStart(8) + - ' 1.00 0.00 C ', - ); - } - return pdb.join('\n'); -} diff --git a/frontend/molstar/interactions.js b/frontend/molstar/interactions.js deleted file mode 100644 index 25389187..00000000 --- a/frontend/molstar/interactions.js +++ /dev/null @@ -1,100 +0,0 @@ -import { OrderedSet } from 'molstar/lib/mol-data/int'; -import { StructureElement, StructureProperties } from 'molstar/lib/mol-model/structure'; - -export const OneToThree = { - A: 'ALA', - R: 'ARG', - N: 'ASN', - D: 'ASP', - C: 'CYS', - E: 'GLU', - Q: 'GLN', - G: 'GLY', - H: 'HIS', - I: 'ILE', - L: 'LEU', - K: 'LYS', - M: 'MET', - F: 'PHE', - P: 'PRO', - S: 'SER', - T: 'THR', - W: 'TRP', - Y: 'TYR', - V: 'VAL', - U: 'SEC', - O: 'PYL', - X: 'ALA', -}; - -export const ThreeToOne = {}; -for (const [one, three] of Object.entries(OneToThree)) { - if (!ThreeToOne[three]) ThreeToOne[three] = one; -} - -export function isValidLoci(loci) { - return StructureElement.Loci.is(loci) && !StructureElement.Loci.isEmpty(loci); -} - -export function lociFromExpression(structureRef, expression) { - const structure = structureRef?.cell?.obj?.data || structureRef; - if (!structure || !expression) return null; - const loci = StructureElement.Loci.fromExpression(structure, expression); - return StructureElement.Loci.isEmpty(loci) ? null : loci; -} - -export function structureElementLocationFromLoci(loci) { - if (!isValidLoci(loci)) return null; - const entry = loci.elements?.[0]; - if (!entry || OrderedSet.size(entry.indices) === 0) return null; - - const loc = StructureElement.Location.create(loci.structure); - loc.unit = entry.unit; - loc.element = OrderedSet.getAt(entry.indices, 0); - return loc; -} - -export function residueInfoFromLoci(loci) { - const loc = structureElementLocationFromLoci(loci); - if (!loc) return null; - - const authResidue = StructureProperties.residue.auth_seq_id(loc); - const labelResidue = StructureProperties.residue.label_seq_id(loc); - const threeLetter = StructureProperties.residue.label_comp_id(loc) - || StructureProperties.residue.auth_comp_id(loc) - || ''; - const authChain = StructureProperties.chain.auth_asym_id(loc) || ''; - const labelChain = StructureProperties.chain.label_asym_id(loc) || ''; - const chain = authChain || labelChain; - - return { - chain, - authChain, - labelChain, - residue: labelResidue || authResidue, - authResidue, - labelResidue, - oneLetter: ThreeToOne[threeLetter] || '', - threeLetter, - residues: Array.from(new Set([labelResidue, authResidue].filter(Number.isFinite))), - }; -} - -export function structuresMatch(a, b) { - if (!a || !b) return false; - if (a === b) return true; - if (a.root && a.root === b) return true; - if (a.root && b.root && a.root === b.root) return true; - return false; -} - -export function focusCurrentLoci(plugin, current, options = {}) { - const loci = current?.loci; - if (!isValidLoci(loci)) return; - plugin.managers.camera.focusLoci(loci, { - durationMs: 250, - extraRadius: 6, - minRadius: 3, - ...options, - }); -} diff --git a/frontend/molstar/io.js b/frontend/molstar/io.js deleted file mode 100644 index e297005a..00000000 --- a/frontend/molstar/io.js +++ /dev/null @@ -1,106 +0,0 @@ -export async function loadStructureFromData(plugin, source, label = source?.label) { - const raw = await plugin.builders.data.rawData( - { data: source.data, label }, - { state: { isGhost: true } }, - ); - const trajectory = await plugin.builders.structure.parseTrajectory(raw, source.format || 'pdb'); - const model = await plugin.builders.structure.createModel(trajectory); - return plugin.builders.structure.createStructure(model); -} - -export function detectStructureFormat(raw) { - const text = raw?.trimStart?.() || ''; - return text[0] === '#' || text.startsWith('data_') ? 'mmcif' : 'pdb'; -} - -export function structureSourceFromText(raw, label) { - const data = raw?.trimStart?.() || ''; - return { - data, - label, - format: detectStructureFormat(data), - }; -} - -export function normalizedPdbSourceFromText(raw, label, options = {}) { - const data = raw?.trimStart?.() || ''; - const format = detectStructureFormat(data); - return { - data: format === 'pdb' ? normalizeWhitespacePdb(data, options) : data, - label, - format, - }; -} - -function normalizeWhitespacePdb(text, options = {}) { - return text - .split(/\r?\n/) - .map(line => { - const trimmed = line.trim(); - if (!trimmed) return ''; - if (options.dropConect && trimmed.startsWith('CONECT')) return ''; - if (!trimmed.startsWith('ATOM') && !trimmed.startsWith('HETATM')) { - return trimmed; - } - if (hasFixedAtomColumns(line)) return line.trimEnd(); - return formatWhitespaceAtom(trimmed.split(/\s+/)); - }) - .filter(Boolean) - .join('\n'); -} - -function hasFixedAtomColumns(line) { - return line.length >= 54 - && Number.isFinite(Number.parseInt(line.slice(22, 26), 10)) - && Number.isFinite(Number.parseFloat(line.slice(30, 38))) - && Number.isFinite(Number.parseFloat(line.slice(38, 46))) - && Number.isFinite(Number.parseFloat(line.slice(46, 54))); -} - -function formatWhitespaceAtom(parts) { - if (parts.length < 9) return null; - const hasChain = !/^-?\d+$/.test(parts[4]); - const resSeqIndex = hasChain ? 5 : 4; - const coordIndex = hasChain ? 6 : 5; - const serial = Number.parseInt(parts[1], 10) || 1; - const atomName = parts[2] || 'CA'; - const resName = parts[3] || 'UNK'; - const chain = hasChain ? parts[4] : 'A'; - const resSeq = Number.parseInt(parts[resSeqIndex], 10); - const x = Number.parseFloat(parts[coordIndex]); - const y = Number.parseFloat(parts[coordIndex + 1]); - const z = Number.parseFloat(parts[coordIndex + 2]); - const occupancy = Number.parseFloat(parts[coordIndex + 3]); - const bFactor = Number.parseFloat(parts[coordIndex + 4]); - if (!Number.isFinite(resSeq) || !Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) return null; - - const element = (parts[coordIndex + 5] || atomName.replace(/[^A-Za-z]/g, '').slice(0, 2)).toUpperCase(); - return [ - (parts[0] === 'HETATM' ? 'HETATM' : 'ATOM').padEnd(6), - String(serial).padStart(5).slice(-5), - ' ', - formatAtomName(atomName, element), - ' ', - resName.padStart(3).slice(-3), - ' ', - chain.slice(0, 1), - String(resSeq).padStart(4).slice(-4), - ' ', - ' ', - x.toFixed(3).padStart(8), - y.toFixed(3).padStart(8), - z.toFixed(3).padStart(8), - (Number.isFinite(occupancy) ? occupancy : 1).toFixed(2).padStart(6), - (Number.isFinite(bFactor) ? bFactor : 0).toFixed(2).padStart(6), - ' ', - element.padStart(2).slice(-2), - (parts[coordIndex + 6] || '').padStart(2).slice(-2), - ].join(''); -} - -function formatAtomName(name, element) { - const value = String(name || '').slice(0, 4); - return element.length === 1 && value.length < 4 - ? ` ${value.padEnd(3)}` - : value.padStart(4); -} diff --git a/frontend/molstar/molstarStructure.js b/frontend/molstar/molstarStructure.js new file mode 100644 index 00000000..83db30f8 --- /dev/null +++ b/frontend/molstar/molstarStructure.js @@ -0,0 +1,554 @@ +import { OrderedSet } from 'molstar/lib/mol-data/int'; +import { Mat4 } from 'molstar/lib/mol-math/linear-algebra'; +import { StructureElement, StructureProperties } from 'molstar/lib/mol-model/structure'; +import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder'; +import { StateTransforms } from 'molstar/lib/mol-plugin-state/transforms'; +import { Color } from 'molstar/lib/mol-util/color'; + +export const OneToThree = { + A: 'ALA', + R: 'ARG', + N: 'ASN', + D: 'ASP', + C: 'CYS', + E: 'GLU', + Q: 'GLN', + G: 'GLY', + H: 'HIS', + I: 'ILE', + L: 'LEU', + K: 'LYS', + M: 'MET', + F: 'PHE', + P: 'PRO', + S: 'SER', + T: 'THR', + W: 'TRP', + Y: 'TYR', + V: 'VAL', + U: 'SEC', + O: 'PYL', + X: 'ALA', +}; + +const ThreeToOne = {}; +for (const [one, three] of Object.entries(OneToThree)) { + if (!ThreeToOne[three]) ThreeToOne[three] = one; +} + +const CartoonQualityPresets = { + viewer: { + quality: 'highest', + linearSegments: 18, + radialSegments: 36, + }, + thumbnail: { + quality: 'higher', + linearSegments: 10, + radialSegments: 16, + }, +}; + +export async function loadStructureFromData(plugin, source, label = source?.label) { + const raw = await plugin.builders.data.rawData( + { data: source.data, label }, + { state: { isGhost: true } }, + ); + const trajectory = await plugin.builders.structure.parseTrajectory(raw, source.format || 'pdb'); + const model = await plugin.builders.structure.createModel(trajectory); + return plugin.builders.structure.createStructure(model); +} + +export function detectStructureFormat(raw) { + const text = raw?.trimStart?.() || ''; + return text[0] === '#' || text.startsWith('data_') ? 'mmcif' : 'pdb'; +} + +export function normalizedPdbSourceFromText(raw, label, options = {}) { + const data = raw?.trimStart?.() || ''; + const format = detectStructureFormat(data); + return { + data: format === 'pdb' ? normalizeWhitespacePdb(data, options) : data, + label, + format, + }; +} + +function normalizeWhitespacePdb(text, options = {}) { + return text + .split(/\r?\n/) + .map(line => { + const trimmed = line.trim(); + if (!trimmed) return ''; + if (options.dropConect && trimmed.startsWith('CONECT')) return ''; + if (!trimmed.startsWith('ATOM') && !trimmed.startsWith('HETATM')) { + return trimmed; + } + if (hasFixedAtomColumns(line)) return line.trimEnd(); + return formatWhitespaceAtom(trimmed.split(/\s+/)); + }) + .filter(Boolean) + .join('\n'); +} + +function hasFixedAtomColumns(line) { + return line.length >= 54 + && Number.isFinite(Number.parseInt(line.slice(22, 26), 10)) + && Number.isFinite(Number.parseFloat(line.slice(30, 38))) + && Number.isFinite(Number.parseFloat(line.slice(38, 46))) + && Number.isFinite(Number.parseFloat(line.slice(46, 54))); +} + +function formatWhitespaceAtom(parts) { + if (parts.length < 9) return null; + const hasChain = !/^-?\d+$/.test(parts[4]); + const resSeqIndex = hasChain ? 5 : 4; + const coordIndex = hasChain ? 6 : 5; + const serial = Number.parseInt(parts[1], 10) || 1; + const atomName = parts[2] || 'CA'; + const resName = parts[3] || 'UNK'; + const chain = hasChain ? parts[4] : 'A'; + const resSeq = Number.parseInt(parts[resSeqIndex], 10); + const x = Number.parseFloat(parts[coordIndex]); + const y = Number.parseFloat(parts[coordIndex + 1]); + const z = Number.parseFloat(parts[coordIndex + 2]); + const occupancy = Number.parseFloat(parts[coordIndex + 3]); + const bFactor = Number.parseFloat(parts[coordIndex + 4]); + if (!Number.isFinite(resSeq) || !Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) return null; + + const element = (parts[coordIndex + 5] || atomName.replace(/[^A-Za-z]/g, '').slice(0, 2)).toUpperCase(); + return atomToPdbRow({ + group: parts[0] === 'HETATM' ? 'HETATM' : 'ATOM', + serial, + atomName, + resName, + chain, + resno: resSeq, + x, + y, + z, + occupancy, + bFactor, + element, + charge: parts[coordIndex + 6] || '', + }); +} + +function formatAtomName(name, element) { + const value = String(name || '').slice(0, 4); + return element.length === 1 && value.length < 4 + ? ` ${value.padEnd(3)}` + : value.padStart(4); +} + +export function atomToPdbRow(atom) { + const atomName = formatAtomName(atom.atomName || 'CA', atom.element || atom.atomName?.[0] || 'C'); + return [ + (atom.group || 'ATOM').padEnd(6).slice(0, 6), + String(atom.serial).padStart(5).slice(-5), + ' ', + atomName, + atom.altId || ' ', + String(atom.resName || 'ALA').padStart(3).slice(-3), + ' ', + String(atom.chain || 'A').slice(0, 1), + String(atom.resno).padStart(4).slice(-4), + atom.insCode || ' ', + ' ', + atom.x.toFixed(3).padStart(8), + atom.y.toFixed(3).padStart(8), + atom.z.toFixed(3).padStart(8), + (Number.isFinite(atom.occupancy) ? atom.occupancy : 1).toFixed(2).padStart(6), + (Number.isFinite(atom.bFactor) ? atom.bFactor : 0).toFixed(2).padStart(6), + ' ', + String(atom.element || '').trim().slice(0, 2).padStart(2), + String(atom.charge || '').padStart(2).slice(-2), + ].join(''); +} + +export function isValidLoci(loci) { + return StructureElement.Loci.is(loci) && !StructureElement.Loci.isEmpty(loci); +} + +export function lociFromExpression(structureRef, expression) { + const structure = structureRef?.cell?.obj?.data || structureRef; + if (!structure || !expression) return null; + const loci = StructureElement.Loci.fromExpression(structure, expression); + return StructureElement.Loci.isEmpty(loci) ? null : loci; +} + +function residueInfoFromLocation(loc) { + const authResidue = StructureProperties.residue.auth_seq_id(loc); + const labelResidue = StructureProperties.residue.label_seq_id(loc); + const threeLetter = StructureProperties.residue.label_comp_id(loc) + || StructureProperties.residue.auth_comp_id(loc) + || ''; + const authChain = StructureProperties.chain.auth_asym_id(loc) || ''; + const labelChain = StructureProperties.chain.label_asym_id(loc) || ''; + const chain = authChain || labelChain; + + return { + chain, + authChain, + labelChain, + residue: labelResidue || authResidue, + authResidue, + labelResidue, + threeLetter, + oneLetter: ThreeToOne[threeLetter] || '', + residues: [labelResidue || authResidue], + }; +} + +function structureElementLocationFromLoci(loci) { + if (!isValidLoci(loci)) return null; + const entry = loci.elements?.[0]; + if (!entry || OrderedSet.size(entry.indices) === 0) return null; + + const loc = StructureElement.Location.create(loci.structure); + loc.unit = entry.unit; + loc.element = OrderedSet.getAt(entry.indices, 0); + return loc; +} + +export function residueInfoFromLoci(loci) { + const loc = structureElementLocationFromLoci(loci); + return loc ? residueInfoFromLocation(loc) : null; +} + +export function residueInfosFromLoci(loci) { + if (!isValidLoci(loci)) return []; + + const residues = []; + const seen = new Set(); + StructureElement.Loci.forEachLocation(loci, (loc) => { + const residue = residueInfoFromLocation(loc); + const key = [ + residue.authChain, + residue.labelChain, + residue.authResidue, + residue.labelResidue, + ].join(':'); + if (seen.has(key)) return; + seen.add(key); + residues.push(residue); + }); + return residues; +} + +export function structuresMatch(a, b) { + if (!a || !b) return false; + if (a === b) return true; + return a.root === b.root && a.elementCount === b.elementCount; +} + +export function focusCurrentLoci(plugin, current, options = {}) { + if (isValidLoci(current?.loci)) { + plugin.managers.camera.focusLoci(current.loci, { + durationMs: 250, + extraRadius: 6, + minRadius: 3, + ...options, + }); + } +} + +export function cartoonParams(overrides = {}) { + const { qualityPreset = 'viewer', ...typeOverrides } = overrides; + const quality = CartoonQualityPresets[qualityPreset] || CartoonQualityPresets.viewer; + return { + ...quality, + ignoreLight: false, + sizeFactor: 0.25, + visuals: ['polymer-trace', 'polymer-gap'], + helixProfile: 'rounded', + nucleicProfile: 'rounded', + material: { + metalness: 0, + roughness: 1, + bumpiness: 0, + }, + ...typeOverrides, + }; +} + +export function ballAndStickParams(overrides = {}) { + return { + quality: 'higher', + sizeFactor: 0.28, + sizeAspectRatio: 0.7, + aromaticBonds: true, + material: { + metalness: 0, + roughness: 0.5, + bumpiness: 0, + }, + ...overrides, + }; +} + +export function representationTypeParams(type, overrides = {}) { + if (type === 'cartoon') return cartoonParams(overrides); + if (type === 'ball-and-stick') return ballAndStickParams(overrides); + return { + quality: 'higher', + ignoreLight: false, + ...overrides, + }; +} + +export async function addUniformRepresentation(plugin, structure, options) { + const { + label, + expression = MS.struct.generator.all(), + type = 'cartoon', + color, + typeParams = {}, + tag = `${label}-representation`, + initialState, + } = options; + if (!expression) return null; + + const component = await plugin.builders.structure.tryCreateComponentFromExpression( + structure, + expression, + label, + { label }, + ); + if (!component) return null; + + return { + component, + representation: await plugin.builders.structure.representation.addRepresentation(component, { + type, + color: 'uniform', + colorParams: { value: Color(color) }, + typeParams: representationTypeParams(type, typeParams), + }, { + tag, + ...(initialState ? { initialState } : {}), + }), + }; +} + +export function chainExpression(chain) { + const test = chainTest(chain); + return test ? MS.struct.generator.atomGroups({ + 'chain-test': test, + 'residue-test': residueAtomTest(), + }) : null; +} + +export function residueExpression(chain, residue) { + const tests = { + 'residue-test': MS.core.logic.and([residueAtomTest(), residueEqTest(residue)]), + }; + const chainExpr = chainTest(chain); + if (chainExpr) tests['chain-test'] = chainExpr; + return MS.struct.generator.atomGroups(tests); +} + +export function mergeExpressions(expressions) { + const valid = expressions.filter(Boolean); + if (valid.length === 0) return null; + if (valid.length === 1) return valid[0]; + return MS.struct.combinator.merge(valid.map(expression => MS.struct.modifier.union([expression]))); +} + +export function expressionForResidues(residues) { + if (!residues?.length) return null; + + const groups = new Map(); + for (const residue of residues) { + const useLabel = residue.labelChain && Number.isFinite(residue.labelResidue); + const chainProperty = useLabel + ? MS.struct.atomProperty.macromolecular.label_asym_id() + : MS.struct.atomProperty.macromolecular.auth_asym_id(); + const residueProperty = useLabel + ? MS.struct.atomProperty.macromolecular.label_seq_id() + : MS.struct.atomProperty.macromolecular.auth_seq_id(); + const chainId = useLabel ? residue.labelChain : residue.authChain; + const residueId = useLabel ? residue.labelResidue : residue.authResidue; + if (!chainId || !Number.isFinite(residueId)) continue; + + const key = `${useLabel ? 'label' : 'auth'}:${chainId}`; + if (!groups.has(key)) groups.set(key, { chainId, chainProperty, residueProperty, residues: [] }); + groups.get(key).residues.push(residueId); + } + + return mergeExpressions(Array.from(groups.values()).flatMap(group => ( + residueRanges(group.residues.sort((a, b) => a - b)) + .map(([start, end]) => atomGroupExpressionForProperty(group.chainId, group.chainProperty, group.residueProperty, start, end)) + ))); +} + +export function residuesForMappedRange(map, start, end) { + const residues = []; + for (let position = start; position <= end; position++) { + const residue = map.toStructure.get(position); + if (residue) residues.push(residue); + } + return residues; +} + +export function structureResidueKeys(residue, chain = null) { + const keys = []; + if (chain) { + if (Number.isFinite(residue.labelResidue)) keys.push(`${chain}:label:${residue.labelResidue}`); + if (Number.isFinite(residue.authResidue)) keys.push(`${chain}:auth:${residue.authResidue}`); + return keys; + } + if (residue.labelChain && Number.isFinite(residue.labelResidue)) { + keys.push(`${residue.labelChain}:label:${residue.labelResidue}`); + } + if (residue.authChain && Number.isFinite(residue.authResidue)) { + keys.push(`${residue.authChain}:auth:${residue.authResidue}`); + } + return keys; +} + +export function residueRanges(residues) { + if (residues.length === 0) return []; + const ranges = []; + let start = residues[0]; + let prev = start; + for (let i = 1; i < residues.length; i++) { + if (residues[i] === prev + 1) { + prev = residues[i]; + continue; + } + ranges.push([start, prev]); + start = residues[i]; + prev = residues[i]; + } + ranges.push([start, prev]); + return ranges; +} + +function residueRangeTest(property, start, end) { + const { and } = MS.core.logic; + const { gre, lte } = MS.core.rel; + const tests = []; + if (Number.isFinite(start)) tests.push(gre([property, start])); + if (Number.isFinite(end)) tests.push(lte([property, end])); + return tests.length === 1 ? tests[0] : and(tests); +} + +function chainTest(chain) { + if (!chain) return null; + const { or } = MS.core.logic; + const { eq } = MS.core.rel; + const { macromolecular } = MS.struct.atomProperty; + return or([ + eq([macromolecular.auth_asym_id(), chain]), + eq([macromolecular.label_asym_id(), chain]), + ]); +} + +function residueEqTest(residue) { + const { or } = MS.core.logic; + const { eq } = MS.core.rel; + const { macromolecular } = MS.struct.atomProperty; + return or([ + eq([macromolecular.auth_seq_id(), residue]), + eq([macromolecular.label_seq_id(), residue]), + ]); +} + +function residueAtomTest() { + return MS.core.logic.not([MS.struct.atomProperty.macromolecular.isHet()]); +} + +function atomGroupExpressionForProperty(chain, chainProperty, residueProperty, start, end) { + const { eq } = MS.core.rel; + return MS.struct.generator.atomGroups({ + 'chain-test': eq([chainProperty, chain]), + 'residue-test': residueRangeTest(residueProperty, start, end), + }); +} + +function structureRef(structure) { + return structure?.ref || structure; +} + +export function representationObject(item) { + return item?.representation?.cell?.obj?.data?.repr + || item?.representation?.obj?.data?.repr + || item?.representation?.data?.repr + || item?.repr + || null; +} + +export function componentStructure(item) { + return item?.component?.cell?.obj?.data + || item?.component?.obj?.data + || item?.component?.data + || null; +} + +export async function transformStructureConformation(plugin, structure, matrix, options = {}) { + if (!matrix) return structure; + const params = { + transform: { + name: 'matrix', + params: { data: matrix, transpose: false }, + }, + }; + const stateOptions = options.tags ? { tags: options.tags } : undefined; + const builder = plugin.state.data.build().to(structureRef(structure)); + const update = options.method === 'apply' + ? builder.apply(StateTransforms.Model.TransformStructureConformation, params, stateOptions) + : builder.insert(StateTransforms.Model.TransformStructureConformation, params, stateOptions); + return update.commit(); +} + +export function mat4FromRotationTranslation(t, u) { + return Mat4.ofRows([ + [u[0][0], u[0][1], u[0][2], t[0]], + [u[1][0], u[1][1], u[1][2], t[1]], + [u[2][0], u[2][1], u[2][2], t[2]], + [0, 0, 0, 1], + ]); +} + +function matrixParamsFromCommaStrings(tmat, umat) { + const t = String(tmat || '').split(',').map(Number); + const u = String(umat || '').split(',').map(Number); + if (t.length < 3 || u.length < 9 || t.some(Number.isNaN) || u.some(Number.isNaN)) return null; + return { + t, + u: [ + [u[0], u[1], u[2]], + [u[3], u[4], u[5]], + [u[6], u[7], u[8]], + ], + }; +} + +export function mat4FromCommaStrings(tmat, umat) { + const params = matrixParamsFromCommaStrings(tmat, umat); + return params ? mat4FromRotationTranslation(params.t, params.u) : null; +} + +export function mockPDB(ca, seq, chain, start = 1) { + const atoms = ca.split(','); + const pdb = []; + let j = 1; + for (let i = 0; i < atoms.length; i += 3, j++) { + const resno = start + j - 1; + const [x, y, z] = atoms.slice(i, i + 3).map(element => Number.parseFloat(element)); + const residue = seq !== '' && atoms.length / 3 === seq.length ? seq[i / 3] : 'A'; + pdb.push(atomToPdbRow({ + serial: j, + atomName: 'CA', + resName: OneToThree[residue] || 'ALA', + chain, + resno, + x, + y, + z, + element: 'C', + })); + } + return pdb.join('\n'); +} diff --git a/frontend/molstar/plugin.js b/frontend/molstar/molstarViewer.js similarity index 77% rename from frontend/molstar/plugin.js rename to frontend/molstar/molstarViewer.js index 96aba733..58d4aa65 100644 --- a/frontend/molstar/plugin.js +++ b/frontend/molstar/molstarViewer.js @@ -1,12 +1,14 @@ -import { PluginContext } from 'molstar/lib/mol-plugin/context'; +import { canvasToBlob } from 'molstar/lib/mol-canvas3d/util'; +import { SsaoParams } from 'molstar/lib/mol-canvas3d/passes/ssao'; +import { PluginBehaviors } from 'molstar/lib/mol-plugin/behavior'; import { PluginConfig } from 'molstar/lib/mol-plugin/config'; +import { PluginContext } from 'molstar/lib/mol-plugin/context'; import { PluginSpec } from 'molstar/lib/mol-plugin/spec'; -import { PluginBehaviors } from 'molstar/lib/mol-plugin/behavior'; -import { SsaoParams } from 'molstar/lib/mol-canvas3d/passes/ssao'; +import { Task } from 'molstar/lib/mol-task'; import { Color } from 'molstar/lib/mol-util/color'; import { ParamDefinition as PD } from 'molstar/lib/mol-util/param-definition'; -export const DEFAULT_SPIN_SPEED = 0.05; +const DEFAULT_SPIN_SPEED = 0.05; const DefaultSsaoParams = PD.getDefaultValues(SsaoParams); @@ -124,3 +126,32 @@ export function setCanvasSpin(plugin, enabled) { }, }); } + +export async function drawStableFrame(plugin) { + plugin?.canvas3d?.commit(true); + await new Promise(resolve => requestAnimationFrame(resolve)); + plugin?.canvas3d?.commit(true); + await new Promise(resolve => requestAnimationFrame(resolve)); +} + +export async function captureViewportPng(plugin, configure, taskName = 'Generate Image') { + const screenshot = plugin?.helpers?.viewportScreenshot; + if (!screenshot) return null; + + await drawStableFrame(plugin); + + const previousValues = screenshot.values; + const previousCropParams = screenshot.cropParams; + configure(screenshot, previousValues, previousCropParams); + + try { + return await plugin.runTask(Task.create(taskName, async (ctx) => { + await screenshot.draw(ctx); + return canvasToBlob(screenshot.canvas, 'image/png'); + })); + } finally { + screenshot.behaviors.values.next(previousValues); + screenshot.behaviors.cropParams.next(previousCropParams); + screenshot.resetCrop(); + } +} diff --git a/frontend/molstar/representations.js b/frontend/molstar/representations.js deleted file mode 100644 index c83cb8ec..00000000 --- a/frontend/molstar/representations.js +++ /dev/null @@ -1,77 +0,0 @@ -import { Color } from 'molstar/lib/mol-util/color'; - -const CartoonQualityPresets = { - viewer: { - quality: 'highest', - linearSegments: 18, - radialSegments: 36, - }, - thumbnail: { - quality: 'higher', - linearSegments: 10, - radialSegments: 16, - }, -}; - -export function cartoonParams(overrides = {}) { - const { qualityPreset = 'viewer', ...typeOverrides } = overrides; - const quality = CartoonQualityPresets[qualityPreset] || CartoonQualityPresets.viewer; - return { - ...quality, - ignoreLight: false, - sizeFactor: 0.25, - visuals: ['polymer-trace', 'polymer-gap'], - helixProfile: 'rounded', - nucleicProfile: 'rounded', - material: { - metalness: 0, - roughness: 1, - bumpiness: 0, - }, - ...typeOverrides, - }; -} - -export function ballAndStickParams(overrides = {}) { - return { - quality: 'higher', - sizeFactor: 0.28, - sizeAspectRatio: 0.7, - aromaticBonds: true, - material: { - metalness: 0, - roughness: 0.5, - bumpiness: 0, - }, - ...overrides, - }; -} - -export function representationParams(type, overrides = {}) { - if (type === 'cartoon') return cartoonParams(overrides); - if (type === 'ball-and-stick') return ballAndStickParams(overrides); - return { - quality: 'higher', - ignoreLight: false, - ...overrides, - }; -} - -export async function addUniformRepresentation(plugin, structure, options) { - if (!options?.expression) return null; - const component = await plugin.builders.structure.tryCreateComponentFromExpression( - structure, - options.expression, - options.label, - { label: options.label }, - ); - if (!component) return null; - - const representation = await plugin.builders.structure.representation.addRepresentation(component, { - type: options.type, - color: 'uniform', - colorParams: { value: Color(options.color) }, - typeParams: representationParams(options.type, options.typeParams || {}), - }, options.state || {}); - return { component, representation }; -} diff --git a/frontend/molstar/screenshot.js b/frontend/molstar/screenshot.js deleted file mode 100644 index 7e74be1f..00000000 --- a/frontend/molstar/screenshot.js +++ /dev/null @@ -1,31 +0,0 @@ -import { Task } from 'molstar/lib/mol-task'; -import { canvasToBlob } from 'molstar/lib/mol-canvas3d/util'; - -export async function drawStableFrame(plugin) { - plugin?.canvas3d?.commit(true); - await new Promise(resolve => requestAnimationFrame(resolve)); - plugin?.canvas3d?.commit(true); - await new Promise(resolve => requestAnimationFrame(resolve)); -} - -export async function captureViewportPng(plugin, configure, taskName = 'Generate Image') { - const screenshot = plugin?.helpers?.viewportScreenshot; - if (!screenshot) return null; - - await drawStableFrame(plugin); - - const previousValues = screenshot.values; - const previousCropParams = screenshot.cropParams; - configure(screenshot, previousValues, previousCropParams); - - try { - return await plugin.runTask(Task.create(taskName, async (ctx) => { - await screenshot.draw(ctx); - return canvasToBlob(screenshot.canvas, 'image/png'); - })); - } finally { - screenshot.behaviors.values.next(previousValues); - screenshot.behaviors.cropParams.next(previousCropParams); - screenshot.resetCrop(); - } -} diff --git a/frontend/molstar/selectionExpressions.js b/frontend/molstar/selectionExpressions.js deleted file mode 100644 index 9b67fdd9..00000000 --- a/frontend/molstar/selectionExpressions.js +++ /dev/null @@ -1,67 +0,0 @@ -import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder'; - -export function chainExpression(chain) { - const test = chainTest(chain); - return test ? MS.struct.generator.atomGroups({ 'chain-test': test }) : null; -} - -export function residueExpression(chain, residue) { - const tests = { - 'residue-test': residueEqTest(residue), - }; - const chainExpr = chainTest(chain); - if (chainExpr) tests['chain-test'] = chainExpr; - return MS.struct.generator.atomGroups(tests); -} - -export function residueRangeExpression(chain, start, end) { - const tests = {}; - const chainExpr = chainTest(chain); - if (chainExpr) tests['chain-test'] = chainExpr; - if (Number.isFinite(start) || Number.isFinite(end)) { - const { or } = MS.core.logic; - const { macromolecular } = MS.struct.atomProperty; - tests['residue-test'] = or([ - residueRangeTest(macromolecular.auth_seq_id(), start, end), - residueRangeTest(macromolecular.label_seq_id(), start, end), - ]); - } - return MS.struct.generator.atomGroups(tests); -} - -export function mergeExpressions(expressions) { - const valid = expressions.filter(Boolean); - if (valid.length === 0) return null; - if (valid.length === 1) return valid[0]; - return MS.struct.combinator.merge(valid.map(expression => MS.struct.modifier.union([expression]))); -} - -export function residueRangeTest(property, start, end) { - const { and } = MS.core.logic; - const { gre, lte } = MS.core.rel; - const tests = []; - if (Number.isFinite(start)) tests.push(gre([property, start])); - if (Number.isFinite(end)) tests.push(lte([property, end])); - return tests.length === 1 ? tests[0] : and(tests); -} - -function chainTest(chain) { - if (!chain) return null; - const { or } = MS.core.logic; - const { eq } = MS.core.rel; - const { macromolecular } = MS.struct.atomProperty; - return or([ - eq([macromolecular.auth_asym_id(), chain]), - eq([macromolecular.label_asym_id(), chain]), - ]); -} - -function residueEqTest(residue) { - const { or } = MS.core.logic; - const { eq } = MS.core.rel; - const { macromolecular } = MS.struct.atomProperty; - return or([ - eq([macromolecular.auth_seq_id(), residue]), - eq([macromolecular.label_seq_id(), residue]), - ]); -} diff --git a/frontend/molstar/transforms.js b/frontend/molstar/transforms.js deleted file mode 100644 index 65e6ac4f..00000000 --- a/frontend/molstar/transforms.js +++ /dev/null @@ -1,50 +0,0 @@ -import { StateTransforms } from 'molstar/lib/mol-plugin-state/transforms'; -import { Mat4 } from 'molstar/lib/mol-math/linear-algebra'; - -function structureRef(structure) { - return structure?.ref || structure; -} - -export async function transformStructureConformation(plugin, structure, matrix, options = {}) { - if (!matrix) return structure; - const params = { - transform: { - name: 'matrix', - params: { data: matrix, transpose: false }, - }, - }; - const stateOptions = options.tags ? { tags: options.tags } : undefined; - const builder = plugin.state.data.build().to(structureRef(structure)); - const update = options.method === 'apply' - ? builder.apply(StateTransforms.Model.TransformStructureConformation, params, stateOptions) - : builder.insert(StateTransforms.Model.TransformStructureConformation, params, stateOptions); - return update.commit(); -} - -export function mat4FromRotationTranslation(t, u) { - return Mat4.ofRows([ - [u[0][0], u[0][1], u[0][2], t[0]], - [u[1][0], u[1][1], u[1][2], t[1]], - [u[2][0], u[2][1], u[2][2], t[2]], - [0, 0, 0, 1], - ]); -} - -function matrixParamsFromCommaStrings(tmat, umat) { - const t = String(tmat || '').split(',').map(Number); - const u = String(umat || '').split(',').map(Number); - if (t.length < 3 || u.length < 9 || t.some(Number.isNaN) || u.some(Number.isNaN)) return null; - return { - t, - u: [ - [u[0], u[1], u[2]], - [u[3], u[4], u[5]], - [u[6], u[7], u[8]], - ], - }; -} - -export function mat4FromCommaStrings(tmat, umat) { - const params = matrixParamsFromCommaStrings(tmat, umat); - return params ? mat4FromRotationTranslation(params.t, params.u) : null; -} From 07a50b893bb5ba62725b622592c51345b85c2615 Mon Sep 17 00:00:00 2001 From: Cameron Gilchrist Date: Mon, 22 Jun 2026 20:00:16 +0900 Subject: [PATCH 5/7] make surfaces non pickable when highlighted --- frontend/molstar/foldseekResult.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/frontend/molstar/foldseekResult.js b/frontend/molstar/foldseekResult.js index d539ebdc..f8b0f376 100644 --- a/frontend/molstar/foldseekResult.js +++ b/frontend/molstar/foldseekResult.js @@ -3,6 +3,7 @@ import { OrderedSet } from 'molstar/lib/mol-data/int'; import { StructureElement, StructureProperties, Unit } from 'molstar/lib/mol-model/structure'; import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder'; import { StateTransforms } from 'molstar/lib/mol-plugin-state/transforms'; +import { MarkerAction } from 'molstar/lib/mol-util/marker-action'; import { tmalign, parse as parseTMOutput, parseMatrix as parseTMMatrix } from 'tmalign-wasm'; import { addUniformRepresentation, @@ -364,7 +365,6 @@ async function buildFoldseekRepresentations(plugin, state, structure, side, inpu async function addQuerySurfaces(plugin, state, structure, input) { const defaultAlpha = input.chainSurfaceAlpha ?? 0.14; const promises = alignmentChains(state, 'query').map(async (chain, i) => { - const initialState = { pickable: false }; const typeParams = { alpha: defaultAlpha, visuals: ['structure-gaussian-surface-mesh'], @@ -395,10 +395,10 @@ async function addQuerySurfaces(plugin, state, structure, input) { type: 'gaussian-surface', color: ChainSurfaceColors[i % ChainSurfaceColors.length], typeParams, - initialState, }, ); if (!surface) return null; + setRepresentationNonPickable(surface); markNonSequenceMappable(state, surface); markNonHoverable(state, surface); return { ...surface, chain, level: 'chain' }; @@ -644,6 +644,13 @@ function markNonHoverable(state, item) { if (repr) state.nonHoverable?.add(repr); } +function setRepresentationNonPickable(item) { + representationObject(item)?.setState?.({ + pickable: false, + markerActions: MarkerAction.None, + }); +} + function isNonHoverable(state, current) { return Boolean(current?.repr && state.nonHoverable?.has(current.repr)); } From f67d6bc2528dcd13888a4ad291d111d15c33a96b Mon Sep 17 00:00:00 2001 From: Cameron Gilchrist Date: Tue, 23 Jun 2026 15:25:08 +0900 Subject: [PATCH 6/7] simplification --- frontend/MSA.vue | 18 +++---- frontend/StructureViewerMSA.vue | 23 +------- frontend/StructureViewerToolbar.vue | 50 ++++------------- frontend/TopHits.vue | 9 +--- frontend/molstar/StructureViewerThumbnail.vue | 53 +++++++------------ frontend/molstar/folddiscoResult.js | 31 +++++------ frontend/molstar/foldmasonResult.js | 37 ++++--------- frontend/molstar/foldseekInterface.js | 48 ++++++----------- frontend/molstar/foldseekResult.js | 41 ++++---------- frontend/molstar/molstarStructure.js | 38 +++++++++++++ 10 files changed, 131 insertions(+), 217 deletions(-) diff --git a/frontend/MSA.vue b/frontend/MSA.vue index 3ff195bb..5c78ed50 100644 --- a/frontend/MSA.vue +++ b/frontend/MSA.vue @@ -814,9 +814,10 @@ export default { this.selectedColumns.splice(0, this.selectedColumns.length, ...columns); this.$emit('changedSelection', this.selectedColumns); this.$nextTick(() => { - this.$refs.structViewer?.updateAllHighlights?.(); if (columns.length > 0) { this.$refs.structViewer?.moveView?.(columns[columns.length - 1]); + } else { + this.$refs.structViewer?.refreshScene?.(); } this.updatingFromMSAViewer = false; }); @@ -1038,7 +1039,6 @@ export default { } this.$emit('changedSelection', this.selectedColumns) this.syncMSAViewerSelectionFromSelectedColumns() - this.$refs.structViewer?.updateAllHighlights?.() this.$refs.structViewer?.moveView?.(idx) }, spliceActiveIndex(idx) { @@ -1051,7 +1051,7 @@ export default { this.selectedColumns.splice(i, 1) this.$emit('changedSelection', this.selectedColumns) this.syncMSAViewerSelectionFromSelectedColumns() - this.$refs.structViewer?.updateAllHighlights?.() + this.$refs.structViewer?.refreshScene?.() }, clearStructurePreviewFromAlignment() { this.previewColumn = -1; @@ -1059,7 +1059,7 @@ export default { this.structurePreviewMarker.visible = false; this.$nextTick(() => { setTimeout(() => { - this.$refs.structViewer?.updateAllPreview?.(); + this.$refs.structViewer?.refreshScene?.(); }); }); }, @@ -1073,7 +1073,7 @@ export default { this.structurePreviewMarker.visible = false; this.$nextTick(() => { setTimeout(() => { - this.$refs.structViewer?.updateAllPreview?.(); + this.$refs.structViewer?.refreshScene?.(); }); }); }, @@ -1085,7 +1085,7 @@ export default { this.structurePreviewMarker.visible = false this.$nextTick(() => { setTimeout(()=> { - this.$refs.structViewer?.updateAllPreview?.() + this.$refs.structViewer?.refreshScene?.() }) }) @@ -1096,14 +1096,14 @@ export default { if (fromStruct) { this.$nextTick(() => { setTimeout(()=>{ - this.$refs.structViewer?.updateAllPreview?.() + this.$refs.structViewer?.refreshScene?.() this.updateStructurePreviewMarker() }) }) } else { this.$nextTick(() => { setTimeout(()=>{ - this.$refs.structViewer?.updateAllPreview?.() + this.$refs.structViewer?.refreshScene?.() this.updateStructurePreviewMarker() this.$refs.structViewer?.moveView?.(Number(idx)) }) @@ -1162,7 +1162,7 @@ export default { if (!this.updatingFromMSAViewer) { this.msaViewer?.clearSelection?.() } - this.$refs.structViewer?.updateAllHighlights?.() + this.$refs.structViewer?.refreshScene?.() }, async exportMSAViewerSelectionAsFasta() { if (!this.msaViewer || this.selectedColumns.length === 0) return; diff --git a/frontend/StructureViewerMSA.vue b/frontend/StructureViewerMSA.vue index b00db11e..62d46dbd 100644 --- a/frontend/StructureViewerMSA.vue +++ b/frontend/StructureViewerMSA.vue @@ -85,15 +85,8 @@ export default { moveView(alnPos) { this.focusColumn = Number(alnPos); this.focusToken++; - this.updateToken++; - }, - updateMask() { - this.updateToken++; }, - updateAllHighlights() { - this.updateToken++; - }, - updateAllPreview() { + refreshScene() { this.updateToken++; }, async handleMakeCIF(cif) { @@ -135,20 +128,6 @@ export default { this.$emit('changePreview', -1, true); }, }, - watch: { - selection() { - this.updateToken++; - }, - mask() { - this.updateToken++; - }, - selectedColumns() { - this.updateToken++; - }, - previewColumn() { - this.updateToken++; - }, - }, }; diff --git a/frontend/StructureViewerToolbar.vue b/frontend/StructureViewerToolbar.vue index f9662493..8f364f38 100644 --- a/frontend/StructureViewerToolbar.vue +++ b/frontend/StructureViewerToolbar.vue @@ -4,7 +4,7 @@ {{ structureFileIcon }} @@ -13,7 +13,7 @@ {{ $MDI.SavePNG }} @@ -22,7 +22,7 @@ {{ ($LOCAL) ? $MDI.CircleHalf : $MDI.CircleOneThird }} @@ -33,7 +33,7 @@ {{ ($LOCAL) ? $MDI.CircleHalf : $MDI.CircleOneThird }} @@ -44,7 +44,7 @@ {{ $MDI.ArrowRightCircle }} @@ -54,7 +54,7 @@ {{ $MDI.Restore }} @@ -63,7 +63,7 @@ {{ $MDI.AxisZRotateCounterclockwise }} @@ -72,7 +72,7 @@ {{ $MDI.Fullscreen }} @@ -113,49 +113,17 @@ export default { return this.cifButtonLabel ? this.$MDI.SaveCIF : this.$MDI.SavePDB; }, toolbarIconProps: function() { - return (this.isFullscreen) ? { - 'right': true - } : { - - } + return this.isFullscreen ? { right: true } : {}; }, toolbarButtonProps: function() { return (this.isFullscreen) ? { small: false, - style: 'margin-bottom: 15px;', } : { small: true, style: "width: 24px;", } }, }, - methods: { - handleClickSpin() { - this.$emit("toggleSpin"); - }, - handleClickMakeCIF() { - this.$emit("makeCIF"); - this.$emit("makePDB"); - }, - handleClickMakeImage() { - this.$emit("makeImage"); - }, - handleClickResetView() { - this.$emit("resetView"); - }, - handleClickFullscreen() { - this.$emit("toggleFullscreen"); - }, - handleClickCycleQuery() { - this.$emit("toggleQuery"); - }, - handleClickToggleTarget() { - this.$emit("toggleTarget"); - }, - handleClickToggleArrows() { - this.$emit("toggleArrows"); - } - } } diff --git a/frontend/TopHits.vue b/frontend/TopHits.vue index 52e79e09..8953ce52 100644 --- a/frontend/TopHits.vue +++ b/frontend/TopHits.vue @@ -168,13 +168,12 @@ export default { this.viewerSpinning = false; this.$nextTick(() => { if (this.$refs.thumbnailViewer) { - this.$refs.thumbnailViewer.deactivateViewer(); + this.$refs.thumbnailViewer.clearActiveViewer(); } }); return; } - const wasActive = this.activeCardId !== null; this.activeCardId = cardId; this.$nextTick(() => { @@ -183,11 +182,7 @@ export default { const targetEl = targetEntry.$refs.viewerSlot; if (this.$refs.thumbnailViewer) { - if (wasActive) { - this.$refs.thumbnailViewer.switchViewer(cardId, hit.topHit, targetEl); - } else { - this.$refs.thumbnailViewer.activateViewer(cardId, hit.topHit, targetEl); - } + this.$refs.thumbnailViewer.setActiveViewer(cardId, hit.topHit, targetEl); } }); }, diff --git a/frontend/molstar/StructureViewerThumbnail.vue b/frontend/molstar/StructureViewerThumbnail.vue index 38fb3a13..71ea3e13 100644 --- a/frontend/molstar/StructureViewerThumbnail.vue +++ b/frontend/molstar/StructureViewerThumbnail.vue @@ -237,22 +237,17 @@ export default { } }, - async activateViewer(id, alignments, targetEl) { - if (this.activeId === id || !this.plugin) return; - this.enqueueOperation(() => this.mountActiveViewer(id, alignments, targetEl, 'Interactive Mol* viewer failed for')); - await this.operationQueue; - }, - - async switchViewer(id, alignments, newTargetEl) { + async setActiveViewer(id, alignments, targetEl) { if (!this.plugin) return; this.enqueueOperation(async () => { + if (this.activeId === id) return; this.stopActiveViewerInteraction(); - await this.mountActiveViewer(id, alignments, newTargetEl, 'Switch Mol* viewer failed for'); + await this.mountActiveViewer(id, alignments, targetEl); }); await this.operationQueue; }, - async mountActiveViewer(id, alignments, targetEl, errorPrefix) { + async mountActiveViewer(id, alignments, targetEl) { if (!this.plugin || this.destroyed) return; this.queuePaused = true; this.activeId = id; @@ -268,7 +263,7 @@ export default { this.$refs.canvas.addEventListener('pointerdown', this.handlePointerInteraction, { passive: true }); this.$emit('viewer-ready'); } catch (e) { - console.warn(errorPrefix, id, e); + console.warn('Interactive Mol* viewer failed for', id, e); await this.restoreOffscreenViewer(id); } }, @@ -284,20 +279,10 @@ export default { this.scheduleProcessQueue(); }, - deactivateViewer() { + clearActiveViewer() { if (this.activeId === null || !this.plugin) return; - this.enqueueOperation(() => this.deactivateViewerAsync()); - }, - - async deactivateViewerAsync() { - this.stopActiveViewerInteraction(); - await this.clearPlugin(); - - this.restoreThumbnailViewport(); - - this.activeId = null; - this.queuePaused = false; - this.scheduleProcessQueue(); + const id = this.activeId; + this.enqueueOperation(() => this.restoreOffscreenViewer(id)); }, handlePointerInteraction() { @@ -325,13 +310,9 @@ export default { this.plugin?.managers?.camera?.reset(); }, - async processQueue() { - if (!this.canProcessQueue() || this.isRendering || this.thumbnailQueue.length === 0 || this.currentQueueIndex >= this.thumbnailQueue.length) { - return; - } - - this.enqueueOperation(() => this.processQueueItem()); - await this.operationQueue; + nextQueueItem() { + if (!this.canProcessQueue() || this.isRendering) return null; + return this.thumbnailQueue[this.currentQueueIndex] || null; }, enqueueOperation(operation) { @@ -341,13 +322,17 @@ export default { return this.operationQueue; }, + async processQueue() { + if (!this.nextQueueItem()) return; + this.enqueueOperation(() => this.processQueueItem()); + await this.operationQueue; + }, + async processQueueItem() { - if (!this.canProcessQueue() || this.isRendering || this.thumbnailQueue.length === 0 || this.currentQueueIndex >= this.thumbnailQueue.length) { - return; - } + const item = this.nextQueueItem(); + if (!item) return; this.isRendering = true; - const item = this.thumbnailQueue[this.currentQueueIndex]; let advanceQueue = true; try { diff --git a/frontend/molstar/folddiscoResult.js b/frontend/molstar/folddiscoResult.js index 35c5b4c6..9f1b8f96 100644 --- a/frontend/molstar/folddiscoResult.js +++ b/frontend/molstar/folddiscoResult.js @@ -1,7 +1,7 @@ import { to_mmCIF } from 'molstar/lib/mol-model/structure/export/mmcif'; import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder'; -import { Color } from 'molstar/lib/mol-util/color'; import { + addUniformRepresentation, ballAndStickParams, cartoonParams, chainExpression, @@ -13,6 +13,7 @@ import { normalizedPdbSourceFromText, residueExpression, residueInfoFromLoci, + representationRef, structuresMatch, transformStructureConformation, } from './molstarStructure.js'; @@ -24,23 +25,19 @@ const TargetLightColor = 0xffe699; const MotifRadius = 0.28; async function addRepresentation(plugin, structure, label, expression, color, alpha = 1, type = 'cartoon', qualityPreset = 'viewer') { - if (!expression) return null; - const component = await plugin.builders.structure.tryCreateComponentFromExpression( + return addUniformRepresentation( + plugin, structure, - expression, - label, - { label }, + { + label, + expression, + color, + type, + typeParams: type === 'ball-and-stick' + ? ballAndStickParams({ alpha, sizeFactor: MotifRadius }) + : cartoonParams({ alpha, sizeFactor: 0.2, qualityPreset }), + }, ); - if (!component) return null; - const representation = await plugin.builders.structure.representation.addRepresentation(component, { - type, - color: 'uniform', - colorParams: { value: Color(color) }, - typeParams: type === 'ball-and-stick' - ? ballAndStickParams({ alpha, sizeFactor: MotifRadius }) - : cartoonParams({ alpha, sizeFactor: 0.2, qualityPreset }), - }); - return representation; } function parseMotif(value = '') { @@ -115,7 +112,7 @@ async function addRepr(plugin, state, input, structure, label, expression, color type, input?.representationQuality || 'viewer', ); - const ref = repr?.ref || repr?.cell?.transform?.ref; + const ref = representationRef(repr); if (ref) state.reprRefs.push(ref); } diff --git a/frontend/molstar/foldmasonResult.js b/frontend/molstar/foldmasonResult.js index b8b2c52d..66c91ac6 100644 --- a/frontend/molstar/foldmasonResult.js +++ b/frontend/molstar/foldmasonResult.js @@ -1,12 +1,12 @@ import { OrderedSet } from 'molstar/lib/mol-data/int'; import { to_mmCIF } from 'molstar/lib/mol-model/structure/export/mmcif'; import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder'; -import { Color } from 'molstar/lib/mol-util/color'; import { tmalign, parseMatrix as parseTMMatrix } from 'tmalign-wasm'; import { pulchra } from 'pulchra-wasm'; import { decodeMultimer, mergeMultimer, revertChainInfo, splitMultimer, storeChains } from '../Utilities.js'; import { - cartoonParams, + addUniformRepresentation, + deleteComponent, focusCurrentLoci, isValidLoci, loadStructureFromData, @@ -24,22 +24,14 @@ import { const ReferenceColor = 0x49a9fc; const RegularColor = 0xffd761; const MaskColor = 0x666666; -async function addRepresentation(plugin, structure, label, color, expression, alpha = 1, type = 'cartoon', qualityPreset = 'viewer') { - if (!expression) return null; - const component = await plugin.builders.structure.tryCreateComponentFromExpression( - structure, - expression, + +function addRepresentation(plugin, structure, label, color, expression, alpha = 1, qualityPreset = 'viewer') { + return addUniformRepresentation(plugin, structure, { label, - { label }, - ); - if (!component) return null; - const representation = await plugin.builders.structure.representation.addRepresentation(component, { - type, - color: 'uniform', - colorParams: { value: Color(color) }, - typeParams: cartoonParams({ alpha, qualityPreset }), + expression, + color, + typeParams: { alpha, qualityPreset }, }); - return { component, representation }; } async function addStructure(plugin, state, input, index) { @@ -62,20 +54,14 @@ async function addStructure(plugin, state, input, index) { index, entry, structure, - base: await addRepresentation(plugin, structure, `foldmason-${index}`, color, MS.struct.generator.all(), alpha, 'cartoon', input.representationQuality), + base: await addRepresentation(plugin, structure, `foldmason-${index}`, color, MS.struct.generator.all(), alpha, input.representationQuality), mask: null, }; - item.mask = await addRepresentation(plugin, structure, `foldmason-${index}-mask`, MaskColor, maskExpression(entry, input.mask), alpha, 'cartoon', input.representationQuality); + item.mask = await addRepresentation(plugin, structure, `foldmason-${index}-mask`, MaskColor, maskExpression(entry, input.mask), alpha, input.representationQuality); state.structures.set(index, item); if (index === input.reference) state.referenceStructure = structure; } -async function deleteRepresentation(plugin, item) { - if (item?.component?.ref) { - await plugin.state.data.build().delete(item.component.ref).commit(); - } -} - async function removeStructure(plugin, state, index) { const item = state.structures.get(index); if (!item) return; @@ -133,7 +119,7 @@ async function updateStructures(plugin, state, input) { async function updateMasks(plugin, state, input) { for (const item of state.structures.values()) { - await deleteRepresentation(plugin, item.mask); + await deleteComponent(plugin, item.mask); item.mask = await addRepresentation( plugin, item.structure, @@ -141,7 +127,6 @@ async function updateMasks(plugin, state, input) { MaskColor, maskExpression(item.entry, input.mask), item.index === input.reference ? 1.0 : 0.5, - 'cartoon', input.representationQuality, ); } diff --git a/frontend/molstar/foldseekInterface.js b/frontend/molstar/foldseekInterface.js index c8708ef6..cbcbacf3 100644 --- a/frontend/molstar/foldseekInterface.js +++ b/frontend/molstar/foldseekInterface.js @@ -4,11 +4,11 @@ import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder' import { StateTransforms } from 'molstar/lib/mol-plugin-state/transforms'; import { addUniformRepresentation, + addRepresentationToSet, chainExpression, - componentStructure, lociFromExpression as lociFromStructureExpression, mergeExpressions, - representationObject, + representationRef, residueRanges, residueInfosFromLoci, structureResidueKeys, @@ -96,13 +96,15 @@ export async function buildInterfaceRepresentations(plugin, state, structure, si await applyInterfaceBackgroundMode(plugin, stateEntry, mode); for (const entry of entries) { const label = `${side}-${entry.chain}-interface`; - const representation = await addInterfaceRepresentation( + const representation = await addUniformRepresentation( plugin, structure, - label, - entry.interfaceExpression, - type, - entry.color, + { + label, + expression: entry.interfaceExpression, + type, + color: entry.color, + }, ); if (!representation) continue; await emphasizeInterface(plugin, representation, entry.interfaceLoci, input); @@ -138,13 +140,15 @@ async function createInterfaceBackground(plugin, state, stateEntry, side) { ? MS.struct.modifier.exceptBy({ 0: entry.chainExpression, by: entry.interfaceExpression }) : entry.chainExpression; const label = `${side}-${entry.chain}-context`; - const representation = await addInterfaceRepresentation( + const representation = await addUniformRepresentation( plugin, stateEntry.structure, - label, - expression, - stateEntry.type, - entry.color, + { + label, + expression, + type: stateEntry.type, + color: entry.color, + }, ); if (!representation) continue; markNonSequenceMappable(state, representation); @@ -186,16 +190,6 @@ async function applyInterfaceBackgroundMode(plugin, stateEntry, mode) { await update.commit({ doNotUpdateCurrent: true }); } -async function addInterfaceRepresentation(plugin, structure, label, expression, type, color, typeParams = {}) { - return addUniformRepresentation(plugin, structure, { - label, - expression, - type, - color, - typeParams, - }); -} - async function emphasizeInterface(plugin, representation, loci, input) { const ref = representationRef(representation); if (!loci || !ref) return; @@ -217,10 +211,6 @@ async function emphasizeInterface(plugin, representation, loci, input) { await update.commit({ doNotUpdateCurrent: true }); } -function representationRef(item) { - return item?.representation?.ref || item?.representation?.cell?.transform?.ref; -} - function mapInterfaceRegions(alignmentMaps, selectionsByChain) { const regions = []; for (const map of alignmentMaps) { @@ -291,9 +281,5 @@ function chainsFromMaps(alignmentMaps = []) { } function markNonSequenceMappable(state, item) { - if (!item) return; - const repr = representationObject(item); - if (repr) state.nonSequenceMappable?.add(repr); - const component = componentStructure(item); - if (component) state.nonSequenceMappable?.add(component); + addRepresentationToSet(state.nonSequenceMappable, item, { includeComponent: true }); } diff --git a/frontend/molstar/foldseekResult.js b/frontend/molstar/foldseekResult.js index f8b0f376..77aef389 100644 --- a/frontend/molstar/foldseekResult.js +++ b/frontend/molstar/foldseekResult.js @@ -3,13 +3,12 @@ import { OrderedSet } from 'molstar/lib/mol-data/int'; import { StructureElement, StructureProperties, Unit } from 'molstar/lib/mol-model/structure'; import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder'; import { StateTransforms } from 'molstar/lib/mol-plugin-state/transforms'; -import { MarkerAction } from 'molstar/lib/mol-util/marker-action'; import { tmalign, parse as parseTMOutput, parseMatrix as parseTMMatrix } from 'tmalign-wasm'; import { addUniformRepresentation, + addRepresentationToSet, atomToPdbRow, chainExpression, - componentStructure, expressionForResidues, isValidLoci, loadStructureFromData, @@ -17,8 +16,9 @@ import { mat4FromRotationTranslation, mergeExpressions, residueInfosFromLoci, - representationObject, + representationRef, residuesForMappedRange, + setRepresentationNonPickable, structureResidueKeys, transformStructureConformation, } from './molstarStructure.js'; @@ -414,8 +414,8 @@ async function setSelectionMode(plugin, state, stateEntry, input, side, mode) { } async function applyFoldseekVisualState(plugin, state, stateEntry, input, side, mode) { - const representationRef = representationStateRef(stateEntry.base); - if (!representationRef) return; + const ref = representationRef(stateEntry.base); + if (!ref) return; const all = MS.struct.generator.all(); const alignedExpression = mergeExpressions( @@ -433,9 +433,9 @@ async function applyFoldseekVisualState(plugin, state, stateEntry, input, side, stateEntry.visibleExpression = visibleExpression; await plugin.state.data.build() - .to(representationRef) + .to(ref) .applyOrUpdate( - `${representationRef}-foldseek-overpaint`, + `${ref}-foldseek-overpaint`, StateTransforms.Representation.OverpaintStructureRepresentation3DFromBundle, { kind: 'element-loci', @@ -445,9 +445,9 @@ async function applyFoldseekVisualState(plugin, state, stateEntry, input, side, }, { tags: ['foldseek-visual-state'] }, ) - .to(representationRef) + .to(ref) .applyOrUpdate( - `${representationRef}-foldseek-transparency`, + `${ref}-foldseek-transparency`, StateTransforms.Representation.TransparencyStructureRepresentation3DFromBundle, { kind: 'element-loci', @@ -632,23 +632,11 @@ function mappedFoldseekResidue(state, side, residue) { } function markNonSequenceMappable(state, item) { - if (!item) return; - const repr = representationObject(item); - if (repr) state.nonSequenceMappable?.add(repr); - const component = componentStructure(item); - if (component) state.nonSequenceMappable?.add(component); + addRepresentationToSet(state.nonSequenceMappable, item, { includeComponent: true }); } function markNonHoverable(state, item) { - const repr = representationObject(item); - if (repr) state.nonHoverable?.add(repr); -} - -function setRepresentationNonPickable(item) { - representationObject(item)?.setState?.({ - pickable: false, - markerActions: MarkerAction.None, - }); + addRepresentationToSet(state.nonHoverable, item); } function isNonHoverable(state, current) { @@ -662,13 +650,6 @@ function isNonSequenceMappable(state, current) { ); } -function representationStateRef(item) { - return item?.representation?.ref - || item?.representation?.cell?.transform?.ref - || item?.representation?.cell?.ref - || null; -} - async function setStructureSelection(plugin, state, input) { const selections = input?.highlightSelections || []; const key = selectionsKey(selections); diff --git a/frontend/molstar/molstarStructure.js b/frontend/molstar/molstarStructure.js index 83db30f8..ca3b6ced 100644 --- a/frontend/molstar/molstarStructure.js +++ b/frontend/molstar/molstarStructure.js @@ -4,6 +4,7 @@ import { StructureElement, StructureProperties } from 'molstar/lib/mol-model/str import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder'; import { StateTransforms } from 'molstar/lib/mol-plugin-state/transforms'; import { Color } from 'molstar/lib/mol-util/color'; +import { MarkerAction } from 'molstar/lib/mol-util/marker-action'; export const OneToThree = { A: 'ALA', @@ -471,6 +472,15 @@ function structureRef(structure) { return structure?.ref || structure; } +export function representationRef(item) { + return item?.representation?.ref + || item?.representation?.cell?.transform?.ref + || item?.representation?.cell?.ref + || item?.ref + || item?.cell?.transform?.ref + || null; +} + export function representationObject(item) { return item?.representation?.cell?.obj?.data?.repr || item?.representation?.obj?.data?.repr @@ -486,6 +496,34 @@ export function componentStructure(item) { || null; } +export function componentRef(item) { + return item?.component?.ref + || item?.component?.cell?.transform?.ref + || item?.component?.cell?.ref + || null; +} + +export async function deleteComponent(plugin, item) { + const ref = componentRef(item); + if (ref) await plugin.state.data.build().delete(ref).commit(); +} + +export function addRepresentationToSet(set, item, options = {}) { + const repr = representationObject(item); + if (repr) set?.add(repr); + if (options.includeComponent) { + const component = componentStructure(item); + if (component) set?.add(component); + } +} + +export function setRepresentationNonPickable(item) { + representationObject(item)?.setState?.({ + pickable: false, + markerActions: MarkerAction.None, + }); +} + export async function transformStructureConformation(plugin, structure, matrix, options = {}) { if (!matrix) return structure; const params = { From 3161b33266f741cb1eef986cd7e30325568d76a4 Mon Sep 17 00:00:00 2001 From: Cameron Gilchrist Date: Mon, 29 Jun 2026 14:57:53 +0900 Subject: [PATCH 7/7] add chain parsing fallback --- frontend/molstar/foldseekData.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/molstar/foldseekData.js b/frontend/molstar/foldseekData.js index 8eaabe0a..62cf1b4a 100644 --- a/frontend/molstar/foldseekData.js +++ b/frontend/molstar/foldseekData.js @@ -6,7 +6,8 @@ export function getChainName(name) { if (!name || /_v[0-9]+$/.test(name)) return 'A'; if (/^[A-Za-z0-9]$/.test(name)) return name; const pos = name.lastIndexOf('_'); - return pos !== -1 ? name.substring(pos + 1, pos + 2) : 'A'; + const chain = pos !== -1 ? name.substring(pos + 1, pos + 2) : ''; + return chain || 'A'; } function getAccession(name) {