jbrowse-plugin-msaview 2.4.4 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AddHighlightModel/GenomeMouseoverHighlight.js +2 -2
- package/dist/AddHighlightModel/HighlightComponents.js +0 -1
- package/dist/AddHighlightModel/MsaToGenomeHighlight.js +5 -3
- package/dist/AddHighlightModel/index.js +2 -2
- package/dist/AddHighlightModel/util.d.ts +1 -6
- package/dist/AddHighlightModel/util.js +1 -7
- package/dist/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.js +0 -1
- package/dist/BgzipFastaMsaAdapter/configSchema.js +0 -1
- package/dist/BgzipFastaMsaAdapter/index.js +0 -1
- package/dist/LaunchMsaView/components/LaunchMsaViewDialog.js +0 -1
- package/dist/LaunchMsaView/components/LaunchPanelContent.js +0 -1
- package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +0 -1
- package/dist/LaunchMsaView/components/ManualMSALoader/fetchGeneList.js +0 -1
- package/dist/LaunchMsaView/components/ManualMSALoader/launchView.js +0 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.js +0 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/MsaAlgorithmSelect.js +0 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastAutomaticPanel.js +0 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastManualPanel.js +0 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastMethodSelector.js +0 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastPanel.js +0 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastRIDPanel.js +0 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBISettingsDialog.js +0 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/blastLaunchView.js +0 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/consts.js +0 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.js +0 -1
- package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +0 -1
- package/dist/LaunchMsaView/components/PreLoadedMSA/fetchMSAData.js +0 -1
- package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.js +0 -1
- package/dist/LaunchMsaView/components/PreLoadedMSA/types.js +0 -1
- package/dist/LaunchMsaView/components/SubmitCancelActions.js +0 -1
- package/dist/LaunchMsaView/components/TabPanel.js +1 -2
- package/dist/LaunchMsaView/components/TranscriptSelector.js +0 -1
- package/dist/LaunchMsaView/components/calculateProteinSequence.js +0 -1
- package/dist/LaunchMsaView/components/fetchSeq.js +0 -1
- package/dist/LaunchMsaView/components/types.js +0 -1
- package/dist/LaunchMsaView/components/useFeatureSequence.js +0 -1
- package/dist/LaunchMsaView/components/useSWRFeatureSequence.js +0 -1
- package/dist/LaunchMsaView/components/useTranscriptSelection.js +0 -1
- package/dist/LaunchMsaView/components/util.js +0 -1
- package/dist/LaunchMsaView/index.js +0 -1
- package/dist/LaunchMsaView/util.js +1 -2
- package/dist/LaunchMsaViewExtensionPoint/index.js +0 -1
- package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +8 -1
- package/dist/MsaViewPanel/afterCreateAutoruns.js +25 -21
- package/dist/MsaViewPanel/blosum62.js +0 -1
- package/dist/MsaViewPanel/components/ConnectStructureDialog.js +0 -1
- package/dist/MsaViewPanel/components/ErrorBoundary.d.ts +1 -1
- package/dist/MsaViewPanel/components/ErrorBoundary.js +0 -1
- package/dist/MsaViewPanel/components/LoadingBLAST.js +0 -1
- package/dist/MsaViewPanel/components/MsaViewPanel.js +0 -1
- package/dist/MsaViewPanel/components/RIDLink.js +0 -1
- package/dist/MsaViewPanel/doLaunchBlast.js +0 -1
- package/dist/MsaViewPanel/genomeToMSA.d.ts +0 -6
- package/dist/MsaViewPanel/genomeToMSA.js +8 -30
- package/dist/MsaViewPanel/genomeToMSA.test.js +0 -1
- package/dist/MsaViewPanel/index.js +0 -1
- package/dist/MsaViewPanel/model.d.ts +143 -514
- package/dist/MsaViewPanel/model.js +18 -61
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.js +5 -22
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.test.js +0 -1
- package/dist/MsaViewPanel/msaDataStore.js +0 -1
- package/dist/MsaViewPanel/pairwiseAlignment.js +2 -10
- package/dist/MsaViewPanel/pairwiseAlignment.test.js +0 -1
- package/dist/MsaViewPanel/structureConnection.d.ts +0 -6
- package/dist/MsaViewPanel/structureConnection.js +0 -17
- package/dist/MsaViewPanel/structureConnection.test.js +1 -52
- package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.d.ts +1 -0
- package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.js +92 -0
- package/dist/MsaViewPanel/types.js +0 -1
- package/dist/MsaViewPanel/util.d.ts +1 -3
- package/dist/MsaViewPanel/util.js +1 -3
- package/dist/components/ExternalLink.js +0 -1
- package/dist/components/ReadOnlyTextField2.js +0 -1
- package/dist/components/TextField2.js +0 -1
- package/dist/index.js +0 -1
- package/dist/jbrowse-plugin-msaview.umd.production.min.js +40 -40
- package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
- package/dist/utils/blastCache.js +2 -5
- package/dist/utils/fetch.js +0 -1
- package/dist/utils/msa.js +9 -9
- package/dist/utils/ncbiBlast.js +5 -6
- package/dist/utils/swrConfig.js +0 -1
- package/dist/utils/taxonomyNames.js +0 -1
- package/dist/utils/types.js +0 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -2
- package/package.json +11 -11
- package/src/AddHighlightModel/GenomeMouseoverHighlight.tsx +4 -1
- package/src/AddHighlightModel/MsaToGenomeHighlight.tsx +5 -8
- package/src/AddHighlightModel/index.tsx +4 -1
- package/src/AddHighlightModel/util.ts +1 -10
- package/src/LaunchMsaView/components/TabPanel.tsx +1 -1
- package/src/LaunchMsaView/util.ts +1 -3
- package/src/MsaViewPanel/afterCreateAutoruns.ts +24 -21
- package/src/MsaViewPanel/genomeToMSA.ts +10 -29
- package/src/MsaViewPanel/model.ts +21 -66
- package/src/MsaViewPanel/msaCoordToGenomeCoord.ts +5 -21
- package/src/MsaViewPanel/pairwiseAlignment.ts +2 -7
- package/src/MsaViewPanel/structureConnection.test.ts +1 -61
- package/src/MsaViewPanel/structureConnection.ts +0 -22
- package/src/MsaViewPanel/syncGenomeHoverToMsaColumn.test.ts +112 -0
- package/src/MsaViewPanel/util.ts +3 -7
- package/src/utils/blastCache.ts +2 -4
- package/src/utils/msa.ts +10 -8
- package/src/utils/ncbiBlast.ts +5 -6
- package/src/version.ts +1 -1
- package/dist/AddHighlightModel/GenomeMouseoverHighlight.js.map +0 -1
- package/dist/AddHighlightModel/HighlightComponents.js.map +0 -1
- package/dist/AddHighlightModel/MsaToGenomeHighlight.js.map +0 -1
- package/dist/AddHighlightModel/index.js.map +0 -1
- package/dist/AddHighlightModel/util.js.map +0 -1
- package/dist/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.js.map +0 -1
- package/dist/BgzipFastaMsaAdapter/configSchema.js.map +0 -1
- package/dist/BgzipFastaMsaAdapter/index.js.map +0 -1
- package/dist/LaunchMsaView/components/LaunchMsaViewDialog.js.map +0 -1
- package/dist/LaunchMsaView/components/LaunchPanelContent.js.map +0 -1
- package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js.map +0 -1
- package/dist/LaunchMsaView/components/ManualMSALoader/fetchGeneList.js.map +0 -1
- package/dist/LaunchMsaView/components/ManualMSALoader/launchView.js.map +0 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.js.map +0 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/MsaAlgorithmSelect.js.map +0 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastAutomaticPanel.js.map +0 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastManualPanel.js.map +0 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastMethodSelector.js.map +0 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastPanel.js.map +0 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastRIDPanel.js.map +0 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBISettingsDialog.js.map +0 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/blastLaunchView.js.map +0 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/consts.js.map +0 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.js.map +0 -1
- package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js.map +0 -1
- package/dist/LaunchMsaView/components/PreLoadedMSA/fetchMSAData.js.map +0 -1
- package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.js.map +0 -1
- package/dist/LaunchMsaView/components/PreLoadedMSA/types.js.map +0 -1
- package/dist/LaunchMsaView/components/SubmitCancelActions.js.map +0 -1
- package/dist/LaunchMsaView/components/TabPanel.js.map +0 -1
- package/dist/LaunchMsaView/components/TranscriptSelector.js.map +0 -1
- package/dist/LaunchMsaView/components/calculateProteinSequence.js.map +0 -1
- package/dist/LaunchMsaView/components/fetchSeq.js.map +0 -1
- package/dist/LaunchMsaView/components/types.js.map +0 -1
- package/dist/LaunchMsaView/components/useFeatureSequence.js.map +0 -1
- package/dist/LaunchMsaView/components/useSWRFeatureSequence.js.map +0 -1
- package/dist/LaunchMsaView/components/useTranscriptSelection.js.map +0 -1
- package/dist/LaunchMsaView/components/util.js.map +0 -1
- package/dist/LaunchMsaView/index.js.map +0 -1
- package/dist/LaunchMsaView/util.js.map +0 -1
- package/dist/LaunchMsaViewExtensionPoint/index.js.map +0 -1
- package/dist/MsaViewPanel/afterCreateAutoruns.js.map +0 -1
- package/dist/MsaViewPanel/blosum62.js.map +0 -1
- package/dist/MsaViewPanel/components/ConnectStructureDialog.js.map +0 -1
- package/dist/MsaViewPanel/components/ErrorBoundary.js.map +0 -1
- package/dist/MsaViewPanel/components/LoadingBLAST.js.map +0 -1
- package/dist/MsaViewPanel/components/MsaViewPanel.js.map +0 -1
- package/dist/MsaViewPanel/components/RIDLink.js.map +0 -1
- package/dist/MsaViewPanel/doLaunchBlast.js.map +0 -1
- package/dist/MsaViewPanel/genomeToMSA.js.map +0 -1
- package/dist/MsaViewPanel/genomeToMSA.test.js.map +0 -1
- package/dist/MsaViewPanel/index.js.map +0 -1
- package/dist/MsaViewPanel/model.js.map +0 -1
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.js.map +0 -1
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.test.js.map +0 -1
- package/dist/MsaViewPanel/msaDataStore.js.map +0 -1
- package/dist/MsaViewPanel/pairwiseAlignment.js.map +0 -1
- package/dist/MsaViewPanel/pairwiseAlignment.test.js.map +0 -1
- package/dist/MsaViewPanel/structureConnection.js.map +0 -1
- package/dist/MsaViewPanel/structureConnection.test.js.map +0 -1
- package/dist/MsaViewPanel/types.js.map +0 -1
- package/dist/MsaViewPanel/util.js.map +0 -1
- package/dist/components/ExternalLink.js.map +0 -1
- package/dist/components/ReadOnlyTextField2.js.map +0 -1
- package/dist/components/TextField2.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/utils/blastCache.js.map +0 -1
- package/dist/utils/fetch.js.map +0 -1
- package/dist/utils/msa.js.map +0 -1
- package/dist/utils/ncbiBlast.js.map +0 -1
- package/dist/utils/swrConfig.js.map +0 -1
- package/dist/utils/taxonomyNames.js.map +0 -1
- package/dist/utils/types.js.map +0 -1
- package/dist/version.js.map +0 -1
package/dist/utils/blastCache.js
CHANGED
|
@@ -15,10 +15,8 @@ async function getDB() {
|
|
|
15
15
|
});
|
|
16
16
|
}
|
|
17
17
|
function createCacheKey(proteinSequence, blastDatabase, blastProgram, transcriptId) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
return `${blastDatabase}:${blastProgram}:${proteinSequence}`;
|
|
18
|
+
const idPart = transcriptId ? `:${transcriptId}` : '';
|
|
19
|
+
return `${blastDatabase}:${blastProgram}${idPart}:${proteinSequence}`;
|
|
22
20
|
}
|
|
23
21
|
export async function saveBlastResult({ proteinSequence, blastDatabase, blastProgram, msaAlgorithm, msa, tree, treeMetadata, rid, geneId, transcriptId, transcriptName, geneName, }) {
|
|
24
22
|
const db = await getDB();
|
|
@@ -55,4 +53,3 @@ export async function clearAllCachedResults() {
|
|
|
55
53
|
const db = await getDB();
|
|
56
54
|
await db.clear(STORE_NAME);
|
|
57
55
|
}
|
|
58
|
-
//# sourceMappingURL=blastCache.js.map
|
package/dist/utils/fetch.js
CHANGED
package/dist/utils/msa.js
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import { textfetch, timeout } from './fetch';
|
|
2
2
|
const base = `https://www.ebi.ac.uk/Tools/services/rest`;
|
|
3
|
+
const email = 'colin.diesh@gmail.com';
|
|
3
4
|
const algorithms = {
|
|
4
5
|
clustalo: {
|
|
5
|
-
params: { email
|
|
6
|
+
params: { email },
|
|
6
7
|
msaResult: 'aln-clustal_num',
|
|
7
8
|
treeResult: 'phylotree',
|
|
8
9
|
},
|
|
9
10
|
muscle: {
|
|
10
|
-
params: { email
|
|
11
|
+
params: { email, format: 'clw', tree: 'tree1' },
|
|
11
12
|
msaResult: 'fa',
|
|
12
13
|
treeResult: 'phylotree',
|
|
13
14
|
},
|
|
14
15
|
kalign: {
|
|
15
|
-
params: { email
|
|
16
|
+
params: { email, stype: 'protein' },
|
|
16
17
|
msaResult: 'fa',
|
|
17
18
|
treeResult: 'phylotree',
|
|
18
19
|
},
|
|
19
20
|
mafft: {
|
|
20
|
-
params: { email
|
|
21
|
+
params: { email, stype: 'protein' },
|
|
21
22
|
msaResult: 'fa',
|
|
22
23
|
treeResult: 'phylotree',
|
|
23
24
|
},
|
|
@@ -25,10 +26,6 @@ const algorithms = {
|
|
|
25
26
|
async function wait({ onProgress, jobId, algorithm, }) {
|
|
26
27
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
27
28
|
while (true) {
|
|
28
|
-
for (let i = 0; i < 10; i++) {
|
|
29
|
-
await timeout(1000);
|
|
30
|
-
onProgress(`Re-checking MSA status in... ${10 - i}`);
|
|
31
|
-
}
|
|
32
29
|
const result = await textfetch(`${base}/${algorithm}/status/${jobId}`);
|
|
33
30
|
if (result === 'FINISHED') {
|
|
34
31
|
break;
|
|
@@ -36,6 +33,10 @@ async function wait({ onProgress, jobId, algorithm, }) {
|
|
|
36
33
|
else if (result.includes('FAILURE')) {
|
|
37
34
|
throw new Error(`Failed to run: jobId ${jobId}`);
|
|
38
35
|
}
|
|
36
|
+
for (let i = 0; i < 10; i++) {
|
|
37
|
+
onProgress(`Re-checking MSA status in... ${10 - i}`);
|
|
38
|
+
await timeout(1000);
|
|
39
|
+
}
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
42
|
export async function launchMSA({ algorithm, sequence, onProgress, }) {
|
|
@@ -51,4 +52,3 @@ export async function launchMSA({ algorithm, sequence, onProgress, }) {
|
|
|
51
52
|
tree: await textfetch(`${base}/${algorithm}/result/${jobId}/${config.treeResult}`),
|
|
52
53
|
};
|
|
53
54
|
}
|
|
54
|
-
//# sourceMappingURL=msa.js.map
|
package/dist/utils/ncbiBlast.js
CHANGED
|
@@ -58,16 +58,16 @@ async function initialQuery({ query, blastProgram, blastDatabase, baseUrl, }) {
|
|
|
58
58
|
async function waitForRid({ rid, onProgress, baseUrl, }) {
|
|
59
59
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
60
60
|
while (true) {
|
|
61
|
-
const iter = 20;
|
|
62
|
-
for (let i = 0; i < iter; i++) {
|
|
63
|
-
await timeout(1000);
|
|
64
|
-
onProgress(`Re-checking BLAST status in... ${iter - i}`);
|
|
65
|
-
}
|
|
66
61
|
const res = await textfetch(`${baseUrl}?CMD=Get&FORMAT_OBJECT=SearchInfo&RID=${rid}`);
|
|
67
62
|
const statusMatch = /\s+Status=(\S+)/m.exec(res);
|
|
68
63
|
const status = statusMatch?.[1];
|
|
69
64
|
const hasHits = /\s+ThereAreHits=yes/m.test(res);
|
|
70
65
|
if (status === 'WAITING') {
|
|
66
|
+
const iter = 20;
|
|
67
|
+
for (let i = 0; i < iter; i++) {
|
|
68
|
+
onProgress(`Re-checking BLAST status in... ${iter - i}`);
|
|
69
|
+
await timeout(1000);
|
|
70
|
+
}
|
|
71
71
|
continue;
|
|
72
72
|
}
|
|
73
73
|
if (status === 'FAILED') {
|
|
@@ -84,4 +84,3 @@ async function waitForRid({ rid, onProgress, baseUrl, }) {
|
|
|
84
84
|
throw new Error(`BLAST ${rid} returned unexpected status: ${status ?? 'unknown'}`);
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
-
//# sourceMappingURL=ncbiBlast.js.map
|
package/dist/utils/swrConfig.js
CHANGED
package/dist/utils/types.js
CHANGED
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "2.
|
|
1
|
+
export declare const version = "2.5.0";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export const version = '2.
|
|
2
|
-
//# sourceMappingURL=version.js.map
|
|
1
|
+
export const version = '2.5.0';
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.
|
|
2
|
+
"version": "2.5.0",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"name": "jbrowse-plugin-msaview",
|
|
5
5
|
"repository": {
|
|
@@ -20,35 +20,35 @@
|
|
|
20
20
|
"g2p_mapper": "^2.1.5",
|
|
21
21
|
"idb": "^8.0.3",
|
|
22
22
|
"pako-esm2": "^2.0.2",
|
|
23
|
-
"react-msaview": "^5.
|
|
23
|
+
"react-msaview": "^5.1.1",
|
|
24
24
|
"swr": "^2.4.1"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@emotion/react": "^11.14.0",
|
|
28
28
|
"@eslint/js": "^10.0.1",
|
|
29
29
|
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
|
|
30
|
-
"@jbrowse/core": "^4.
|
|
31
|
-
"@jbrowse/mobx-state-tree": "^5.
|
|
32
|
-
"@jbrowse/plugin-linear-genome-view": "^4.
|
|
30
|
+
"@jbrowse/core": "^4.3.0",
|
|
31
|
+
"@jbrowse/mobx-state-tree": "^5.10.2",
|
|
32
|
+
"@jbrowse/plugin-linear-genome-view": "^4.3.0",
|
|
33
33
|
"@mui/icons-material": "^7.3.11",
|
|
34
34
|
"@mui/material": "^7.3.11",
|
|
35
35
|
"@mui/system": "^7.3.11",
|
|
36
36
|
"@mui/x-data-grid": "^8.28.7",
|
|
37
37
|
"@types/node": "^25.9.1",
|
|
38
38
|
"@types/react": "^19.2.15",
|
|
39
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
40
|
-
"@typescript-eslint/parser": "^8.
|
|
39
|
+
"@typescript-eslint/eslint-plugin": "^8.60.0",
|
|
40
|
+
"@typescript-eslint/parser": "^8.60.0",
|
|
41
41
|
"esbuild": "^0.28.0",
|
|
42
42
|
"eslint": "^10.4.0",
|
|
43
43
|
"eslint-plugin-import-x": "^4.16.2",
|
|
44
44
|
"eslint-plugin-react": "^7.37.5",
|
|
45
45
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
46
46
|
"eslint-plugin-unicorn": "^64.0.0",
|
|
47
|
-
"mobx": "^6.15.
|
|
48
|
-
"mobx-react": "^9.2.
|
|
47
|
+
"mobx": "^6.15.4",
|
|
48
|
+
"mobx-react": "^9.2.2",
|
|
49
49
|
"prettier": "^3.8.3",
|
|
50
50
|
"pretty-bytes": "^7.1.0",
|
|
51
|
-
"puppeteer": "^
|
|
51
|
+
"puppeteer": "^25.1.0",
|
|
52
52
|
"react": "^19.2.6",
|
|
53
53
|
"react-dom": "^19.2.6",
|
|
54
54
|
"rimraf": "^6.1.3",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"serve": "^14.2.6",
|
|
57
57
|
"tss-react": "^4.9.21",
|
|
58
58
|
"typescript": "^6.0.3",
|
|
59
|
-
"typescript-eslint": "^8.
|
|
59
|
+
"typescript-eslint": "^8.60.0",
|
|
60
60
|
"vitest": "^4.1.7"
|
|
61
61
|
},
|
|
62
62
|
"scripts": {
|
|
@@ -4,6 +4,7 @@ import { getSession } from '@jbrowse/core/util'
|
|
|
4
4
|
import { observer } from 'mobx-react'
|
|
5
5
|
|
|
6
6
|
import { hasHoverPosition, useStyles } from './util'
|
|
7
|
+
import { isMsaView } from '../MsaViewPanel/model'
|
|
7
8
|
|
|
8
9
|
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
9
10
|
|
|
@@ -13,7 +14,9 @@ const GenomeMouseoverHighlight = observer(function ({
|
|
|
13
14
|
model: LinearGenomeViewModel
|
|
14
15
|
}) {
|
|
15
16
|
const { hovered, views } = getSession(model)
|
|
16
|
-
const hasMsaView = views.some(
|
|
17
|
+
const hasMsaView = views.some(
|
|
18
|
+
s => isMsaView(s) && s.connectedViewId === model.id,
|
|
19
|
+
)
|
|
17
20
|
return hasMsaView && hasHoverPosition(hovered) ? (
|
|
18
21
|
<GenomeMouseoverHighlightRenderer model={model} hovered={hovered} />
|
|
19
22
|
) : null
|
|
@@ -4,9 +4,9 @@ import { getSession } from '@jbrowse/core/util'
|
|
|
4
4
|
import { observer } from 'mobx-react'
|
|
5
5
|
|
|
6
6
|
import { hasHoverPosition, useStyles } from './util'
|
|
7
|
+
import { isMsaView } from '../MsaViewPanel/model'
|
|
7
8
|
import { getCanonicalRefName } from '../MsaViewPanel/util'
|
|
8
9
|
|
|
9
|
-
import type { JBrowsePluginMsaViewModel } from '../MsaViewPanel/model'
|
|
10
10
|
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
11
11
|
|
|
12
12
|
type LGV = LinearGenomeViewModel
|
|
@@ -17,18 +17,15 @@ const MsaToGenomeHighlight = observer(function MsaToGenomeHighlight2({
|
|
|
17
17
|
model: LGV
|
|
18
18
|
}) {
|
|
19
19
|
const { views, hovered } = getSession(model)
|
|
20
|
-
const msaView = views
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
const msaView = views
|
|
21
|
+
.filter(isMsaView)
|
|
22
|
+
.find(v => v.connectedViewId === model.id)
|
|
23
23
|
const highlights = msaView?.connectedHighlights
|
|
24
24
|
|
|
25
25
|
// Suppress codon highlight while hovering the LGV — GenomeMouseoverHighlight
|
|
26
26
|
// handles the single-bp display in that case
|
|
27
27
|
return !hasHoverPosition(hovered) && highlights?.length ? (
|
|
28
|
-
<MsaToGenomeHighlightRenderer
|
|
29
|
-
model={model}
|
|
30
|
-
highlights={Array.from(highlights)}
|
|
31
|
-
/>
|
|
28
|
+
<MsaToGenomeHighlightRenderer model={model} highlights={highlights} />
|
|
32
29
|
) : null
|
|
33
30
|
})
|
|
34
31
|
|
|
@@ -3,6 +3,7 @@ import React from 'react'
|
|
|
3
3
|
import { getSession } from '@jbrowse/core/util'
|
|
4
4
|
|
|
5
5
|
import HighlightComponents from './HighlightComponents'
|
|
6
|
+
import { isMsaView } from '../MsaViewPanel/model'
|
|
6
7
|
|
|
7
8
|
import type PluginManager from '@jbrowse/core/PluginManager'
|
|
8
9
|
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
@@ -16,7 +17,9 @@ export default function AddHighlightComponentsModelF(
|
|
|
16
17
|
(rest: React.ReactNode[], { model }: { model: LinearGenomeViewModel }) => {
|
|
17
18
|
// Quick check: don't add any components if no MSA view exists
|
|
18
19
|
const { views } = getSession(model)
|
|
19
|
-
const hasMsaView = views.some(
|
|
20
|
+
const hasMsaView = views.some(
|
|
21
|
+
v => isMsaView(v) && v.connectedViewId === model.id,
|
|
22
|
+
)
|
|
20
23
|
if (!hasMsaView) {
|
|
21
24
|
return rest
|
|
22
25
|
}
|
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
import { makeStyles } from 'tss-react/mui'
|
|
2
2
|
|
|
3
|
-
export
|
|
4
|
-
hovered: unknown,
|
|
5
|
-
): hovered is { hoverPosition: { coord: number; refName: string } } {
|
|
6
|
-
return (
|
|
7
|
-
!!hovered &&
|
|
8
|
-
typeof hovered === 'object' &&
|
|
9
|
-
'hoverPosition' in hovered &&
|
|
10
|
-
!!hovered.hoverPosition
|
|
11
|
-
)
|
|
12
|
-
}
|
|
3
|
+
export { hasHoverPosition } from '../MsaViewPanel/util'
|
|
13
4
|
|
|
14
5
|
export const useStyles = makeStyles()({
|
|
15
6
|
highlight: {
|
|
@@ -40,9 +40,7 @@ export function getTranscriptLength(feature: Feature) {
|
|
|
40
40
|
const cdsLen = sum(
|
|
41
41
|
feature
|
|
42
42
|
.get('subfeatures')
|
|
43
|
-
?.filter(
|
|
44
|
-
f => (f.get('type') as string | undefined)?.toLowerCase() === 'cds',
|
|
45
|
-
)
|
|
43
|
+
?.filter(f => f.get('type') === 'CDS')
|
|
46
44
|
.map(s => s.get('end') - s.get('start')) ?? [],
|
|
47
45
|
)
|
|
48
46
|
return {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getSession } from '@jbrowse/core/util'
|
|
2
2
|
|
|
3
3
|
import { doLaunchBlast } from './doLaunchBlast'
|
|
4
|
-
import {
|
|
4
|
+
import { genomeToMSA } from './genomeToMSA'
|
|
5
5
|
import {
|
|
6
6
|
cleanupOldData,
|
|
7
7
|
generateDataStoreId,
|
|
@@ -19,8 +19,7 @@ import type { JBrowsePluginMsaViewModel } from './model'
|
|
|
19
19
|
export function loadStoredData(self: JBrowsePluginMsaViewModel) {
|
|
20
20
|
const { dataStoreId, rows } = self
|
|
21
21
|
if (dataStoreId && rows.length === 0) {
|
|
22
|
-
|
|
23
|
-
;(async () => {
|
|
22
|
+
void (async () => {
|
|
24
23
|
try {
|
|
25
24
|
self.setLoadingStoredData(true)
|
|
26
25
|
const storedData = await retrieveMsaData(dataStoreId)
|
|
@@ -56,8 +55,7 @@ export function storeDataToIndexedDB(self: JBrowsePluginMsaViewModel) {
|
|
|
56
55
|
// data observables change while the write is pending) don't kick off a
|
|
57
56
|
// duplicate write and leave an orphan IndexedDB entry
|
|
58
57
|
self.setIsStoringData(true)
|
|
59
|
-
|
|
60
|
-
;(async () => {
|
|
58
|
+
void (async () => {
|
|
61
59
|
try {
|
|
62
60
|
const newId = generateDataStoreId()
|
|
63
61
|
const success = await storeMsaData(newId, {
|
|
@@ -80,8 +78,7 @@ export function storeDataToIndexedDB(self: JBrowsePluginMsaViewModel) {
|
|
|
80
78
|
|
|
81
79
|
export function launchBlastIfNeeded(self: JBrowsePluginMsaViewModel) {
|
|
82
80
|
if (self.blastParams) {
|
|
83
|
-
|
|
84
|
-
;(async () => {
|
|
81
|
+
void (async () => {
|
|
85
82
|
try {
|
|
86
83
|
self.setProgress('Submitting query')
|
|
87
84
|
self.setError(undefined)
|
|
@@ -101,8 +98,7 @@ export function launchBlastIfNeeded(self: JBrowsePluginMsaViewModel) {
|
|
|
101
98
|
export function processInit(self: JBrowsePluginMsaViewModel) {
|
|
102
99
|
const { init } = self
|
|
103
100
|
if (init) {
|
|
104
|
-
|
|
105
|
-
;(async () => {
|
|
101
|
+
void (async () => {
|
|
106
102
|
try {
|
|
107
103
|
self.setError(undefined)
|
|
108
104
|
const { msaData, msaUrl, treeData, treeUrl, querySeqName } = init
|
|
@@ -150,18 +146,25 @@ export function processInit(self: JBrowsePluginMsaViewModel) {
|
|
|
150
146
|
}
|
|
151
147
|
}
|
|
152
148
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Mirror the connected genome view's hover position onto the MSA's hovered
|
|
151
|
+
* column. Returns the autorun body so it can keep a flag tracking whether the
|
|
152
|
+
* MSA's mouseCol was set by this sync: that way an unrelated session hover
|
|
153
|
+
* change clears the column only when the genome put it there, never wiping a
|
|
154
|
+
* column the user is hovering directly in the MSA.
|
|
155
|
+
*/
|
|
156
|
+
export function syncGenomeHoverToMsaColumn(self: JBrowsePluginMsaViewModel) {
|
|
157
|
+
let genomeDrivenCol = false
|
|
158
|
+
return () => {
|
|
159
|
+
const col = genomeToMSA({ model: self })
|
|
160
|
+
if (col !== undefined) {
|
|
161
|
+
self.setMousePos(col)
|
|
162
|
+
genomeDrivenCol = true
|
|
163
|
+
} else if (genomeDrivenCol) {
|
|
164
|
+
self.setMousePos(undefined)
|
|
165
|
+
genomeDrivenCol = false
|
|
166
|
+
}
|
|
167
|
+
}
|
|
165
168
|
}
|
|
166
169
|
|
|
167
170
|
export function highlightConnectedStructures(self: JBrowsePluginMsaViewModel) {
|
|
@@ -1,53 +1,34 @@
|
|
|
1
1
|
import { getSession } from '@jbrowse/core/util'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { hasHoverPosition } from './util'
|
|
4
4
|
|
|
5
5
|
import type { JBrowsePluginMsaViewModel } from './model'
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
* Convert a genome coordinate from session.hovered to a visible MSA column.
|
|
9
|
-
*
|
|
10
|
-
* @param model - The MSA view model
|
|
11
|
-
* @returns The visible column index, or undefined if no mapping exists
|
|
12
|
-
*/
|
|
13
7
|
export function genomeToMSA({ model }: { model: JBrowsePluginMsaViewModel }) {
|
|
14
8
|
const { hovered } = getSession(model)
|
|
15
9
|
const { querySeqName, transcriptToMsaMap, connectedView, mafRegion } = model
|
|
16
10
|
|
|
17
|
-
if (!connectedView?.initialized || !
|
|
11
|
+
if (!connectedView?.initialized || !hasHoverPosition(hovered)) {
|
|
18
12
|
return undefined
|
|
19
13
|
}
|
|
20
14
|
|
|
21
15
|
const { coord: hoverCoord, refName } = hovered.hoverPosition
|
|
22
16
|
|
|
23
|
-
// Handle MAF region mapping
|
|
24
17
|
if (mafRegion) {
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
if (
|
|
19
|
+
refName !== mafRegion.refName ||
|
|
20
|
+
!connectedView.assemblyNames.includes(mafRegion.assemblyName) ||
|
|
21
|
+
hoverCoord < mafRegion.start ||
|
|
22
|
+
hoverCoord >= mafRegion.end
|
|
23
|
+
) {
|
|
27
24
|
return undefined
|
|
28
25
|
}
|
|
29
|
-
|
|
30
|
-
const viewAssemblies = connectedView.assemblyNames
|
|
31
|
-
if (!viewAssemblies.includes(mafRegion.assemblyName)) {
|
|
32
|
-
return undefined
|
|
33
|
-
}
|
|
34
|
-
// Check if the hover coordinate is within the MAF region
|
|
35
|
-
if (hoverCoord < mafRegion.start || hoverCoord >= mafRegion.end) {
|
|
36
|
-
return undefined
|
|
37
|
-
}
|
|
38
|
-
// Calculate the ungapped position relative to the region start
|
|
39
|
-
const ungappedPos = hoverCoord - mafRegion.start
|
|
40
|
-
// Convert to visible column using the query sequence
|
|
41
|
-
return model.seqPosToVisibleCol(querySeqName, ungappedPos)
|
|
26
|
+
return model.seqPosToVisibleCol(querySeqName, hoverCoord - mafRegion.start)
|
|
42
27
|
}
|
|
43
28
|
|
|
44
|
-
// Handle transcript mapping (original behavior)
|
|
45
29
|
if (transcriptToMsaMap) {
|
|
46
|
-
const
|
|
47
|
-
// g2p maps genome coordinate to sequence position (0-based)
|
|
48
|
-
const seqPos = g2p[hoverCoord]
|
|
30
|
+
const seqPos = transcriptToMsaMap.g2p[hoverCoord]
|
|
49
31
|
if (seqPos !== undefined) {
|
|
50
|
-
// Convert sequence position to visible column
|
|
51
32
|
return model.seqPosToVisibleCol(querySeqName, seqPos)
|
|
52
33
|
}
|
|
53
34
|
}
|
|
@@ -2,7 +2,7 @@ import { lazy } from 'react'
|
|
|
2
2
|
|
|
3
3
|
import { BaseViewModel } from '@jbrowse/core/pluggableElementTypes'
|
|
4
4
|
import { getSession } from '@jbrowse/core/util'
|
|
5
|
-
import { addDisposer,
|
|
5
|
+
import { addDisposer, types } from '@jbrowse/mobx-state-tree'
|
|
6
6
|
import { genomeToTranscriptSeqMapping } from 'g2p_mapper'
|
|
7
7
|
import { autorun } from 'mobx'
|
|
8
8
|
import { MSAModelF } from 'react-msaview'
|
|
@@ -16,15 +16,11 @@ import {
|
|
|
16
16
|
processInit,
|
|
17
17
|
runCleanup,
|
|
18
18
|
storeDataToIndexedDB,
|
|
19
|
-
|
|
19
|
+
syncGenomeHoverToMsaColumn,
|
|
20
20
|
} from './afterCreateAutoruns'
|
|
21
|
-
import { genomeToMSA } from './genomeToMSA'
|
|
22
21
|
import { msaCoordToGenomeCoord } from './msaCoordToGenomeCoord'
|
|
23
22
|
import { buildAlignmentMaps, runPairwiseAlignment } from './pairwiseAlignment'
|
|
24
|
-
import {
|
|
25
|
-
getProteinViews,
|
|
26
|
-
ungappedToGappedPosition,
|
|
27
|
-
} from './structureConnection'
|
|
23
|
+
import { getProteinViews } from './structureConnection'
|
|
28
24
|
import { getCanonicalRefName } from './util'
|
|
29
25
|
|
|
30
26
|
import type { ProteinView, StructureConnection } from './structureConnection'
|
|
@@ -84,13 +80,6 @@ export default function stateModelFactory() {
|
|
|
84
80
|
/**
|
|
85
81
|
* #property
|
|
86
82
|
*/
|
|
87
|
-
connectedHighlights: types.array(
|
|
88
|
-
types.model({
|
|
89
|
-
refName: types.string,
|
|
90
|
-
start: types.number,
|
|
91
|
-
end: types.number,
|
|
92
|
-
}),
|
|
93
|
-
),
|
|
94
83
|
/**
|
|
95
84
|
* #property
|
|
96
85
|
*/
|
|
@@ -220,37 +209,16 @@ export default function stateModelFactory() {
|
|
|
220
209
|
/**
|
|
221
210
|
* #getter
|
|
222
211
|
*/
|
|
223
|
-
get
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if (globalCol !== undefined) {
|
|
234
|
-
return self.globalColToVisibleCol(globalCol)
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
return undefined
|
|
241
|
-
},
|
|
242
|
-
}))
|
|
243
|
-
|
|
244
|
-
.views(self => ({
|
|
245
|
-
/**
|
|
246
|
-
* #getter
|
|
247
|
-
*/
|
|
248
|
-
get mouseCol2(): number | undefined {
|
|
249
|
-
const structureCol = self.structureHoverCol
|
|
250
|
-
if (structureCol !== undefined) {
|
|
251
|
-
return structureCol
|
|
252
|
-
}
|
|
253
|
-
return genomeToMSA({ model: self as JBrowsePluginMsaViewModel })
|
|
212
|
+
get connectedHighlights(): IRegion[] {
|
|
213
|
+
const { mouseCol, mouseClickCol } = self
|
|
214
|
+
return [
|
|
215
|
+
mouseCol === undefined
|
|
216
|
+
? undefined
|
|
217
|
+
: msaCoordToGenomeCoord({ model: self, coord: mouseCol }),
|
|
218
|
+
mouseClickCol === undefined
|
|
219
|
+
? undefined
|
|
220
|
+
: msaCoordToGenomeCoord({ model: self, coord: mouseClickCol }),
|
|
221
|
+
].filter((r): r is IRegion => r !== undefined)
|
|
254
222
|
},
|
|
255
223
|
}))
|
|
256
224
|
|
|
@@ -279,24 +247,6 @@ export default function stateModelFactory() {
|
|
|
279
247
|
setRid(arg: string) {
|
|
280
248
|
self.rid = arg
|
|
281
249
|
},
|
|
282
|
-
/**
|
|
283
|
-
* #action
|
|
284
|
-
*/
|
|
285
|
-
setConnectedHighlights(r: IRegion[]) {
|
|
286
|
-
self.connectedHighlights = cast(r)
|
|
287
|
-
},
|
|
288
|
-
/**
|
|
289
|
-
* #action
|
|
290
|
-
*/
|
|
291
|
-
addToConnectedHighlights(r: IRegion) {
|
|
292
|
-
self.connectedHighlights.push(r)
|
|
293
|
-
},
|
|
294
|
-
/**
|
|
295
|
-
* #action
|
|
296
|
-
*/
|
|
297
|
-
clearConnectedHighlights() {
|
|
298
|
-
self.connectedHighlights = cast([])
|
|
299
|
-
},
|
|
300
250
|
/**
|
|
301
251
|
* #action
|
|
302
252
|
*/
|
|
@@ -406,14 +356,13 @@ export default function stateModelFactory() {
|
|
|
406
356
|
ungappedMsaSequence,
|
|
407
357
|
structureSequence,
|
|
408
358
|
)
|
|
409
|
-
const { seq1ToSeq2
|
|
359
|
+
const { seq1ToSeq2 } = buildAlignmentMaps(alignment)
|
|
410
360
|
|
|
411
361
|
const connection: StructureConnection = {
|
|
412
362
|
proteinViewId,
|
|
413
363
|
structureIdx,
|
|
414
364
|
msaRowName: rowName,
|
|
415
365
|
msaToStructure: Object.fromEntries(seq1ToSeq2),
|
|
416
|
-
structureToMsa: Object.fromEntries(seq2ToSeq1),
|
|
417
366
|
}
|
|
418
367
|
|
|
419
368
|
self.connectedStructures.push(connection)
|
|
@@ -504,7 +453,6 @@ export default function stateModelFactory() {
|
|
|
504
453
|
storeDataToIndexedDB,
|
|
505
454
|
launchBlastIfNeeded,
|
|
506
455
|
processInit,
|
|
507
|
-
updateGenomeHighlights,
|
|
508
456
|
highlightConnectedStructures,
|
|
509
457
|
autoConnectStructures,
|
|
510
458
|
observeProteinHighlights,
|
|
@@ -516,6 +464,7 @@ export default function stateModelFactory() {
|
|
|
516
464
|
}),
|
|
517
465
|
)
|
|
518
466
|
}
|
|
467
|
+
addDisposer(self, autorun(syncGenomeHoverToMsaColumn(self)))
|
|
519
468
|
},
|
|
520
469
|
}))
|
|
521
470
|
}
|
|
@@ -526,3 +475,9 @@ export type JBrowsePluginMsaViewStateModel = ReturnType<
|
|
|
526
475
|
export type JBrowsePluginMsaViewModel = Instance<JBrowsePluginMsaViewStateModel>
|
|
527
476
|
|
|
528
477
|
export { type MafRegion, type MsaViewInitState } from './types'
|
|
478
|
+
|
|
479
|
+
export function isMsaView(view: {
|
|
480
|
+
type: string
|
|
481
|
+
}): view is JBrowsePluginMsaViewModel {
|
|
482
|
+
return view.type === 'MsaView'
|
|
483
|
+
}
|
|
@@ -21,45 +21,29 @@ export function msaCoordToGenomeCoord({
|
|
|
21
21
|
}) {
|
|
22
22
|
const { querySeqName, transcriptToMsaMap, mafRegion } = model
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
const queryRow = model.rows.find(f => f[0] === querySeqName)
|
|
26
|
-
const querySeq = queryRow?.[1]
|
|
24
|
+
const querySeq = model.rows.find(f => f[0] === querySeqName)?.[1]
|
|
27
25
|
if (!querySeq) {
|
|
28
26
|
return undefined
|
|
29
27
|
}
|
|
30
28
|
|
|
31
|
-
// Convert gapped MSA column to ungapped sequence coordinate
|
|
32
|
-
// Returns undefined if the position is a gap
|
|
33
29
|
const ungappedPos = gappedToUngappedPosition(querySeq, mouseCol)
|
|
34
30
|
if (ungappedPos === undefined) {
|
|
35
31
|
return undefined
|
|
36
32
|
}
|
|
37
33
|
|
|
38
|
-
// Handle MAF region mapping
|
|
39
34
|
if (mafRegion) {
|
|
40
35
|
const genomePos = mafRegion.start + ungappedPos
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
return {
|
|
46
|
-
refName: mafRegion.refName,
|
|
47
|
-
start: genomePos,
|
|
48
|
-
end: genomePos + 1,
|
|
49
|
-
}
|
|
36
|
+
return genomePos < mafRegion.end
|
|
37
|
+
? { refName: mafRegion.refName, start: genomePos, end: genomePos + 1 }
|
|
38
|
+
: undefined
|
|
50
39
|
}
|
|
51
40
|
|
|
52
|
-
// Handle transcript mapping (original behavior)
|
|
53
41
|
if (transcriptToMsaMap) {
|
|
54
42
|
const { refName, p2g } = transcriptToMsaMap
|
|
55
43
|
const s = p2g[ungappedPos]
|
|
56
44
|
const e = p2g[ungappedPos + 1]
|
|
57
45
|
return s !== undefined && e !== undefined
|
|
58
|
-
? {
|
|
59
|
-
refName,
|
|
60
|
-
start: Math.min(s, e),
|
|
61
|
-
end: Math.max(s, e),
|
|
62
|
-
}
|
|
46
|
+
? { refName, start: Math.min(s, e), end: Math.max(s, e) }
|
|
63
47
|
: undefined
|
|
64
48
|
}
|
|
65
49
|
|