@apollo-annotation/jbrowse-plugin-apollo 1.0.0 → 1.1.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/ApolloInternetAccount/components/AuthTypeSelector.d.ts +8 -2
- package/dist/ApolloInternetAccount/components/AuthTypeSelector.d.ts.map +1 -1
- package/dist/ApolloInternetAccount/components/LoginButtons.d.ts +8 -3
- package/dist/ApolloInternetAccount/components/LoginButtons.d.ts.map +1 -1
- package/dist/ApolloInternetAccount/configSchema.d.ts +44 -0
- package/dist/ApolloInternetAccount/configSchema.d.ts.map +1 -0
- package/dist/ApolloInternetAccount/model.d.ts +174 -0
- package/dist/ApolloInternetAccount/model.d.ts.map +1 -0
- package/dist/ApolloJobModel.d.ts.map +1 -1
- package/dist/ApolloRefNameAliasAdapter/configSchema.d.ts +8 -0
- package/dist/ApolloRefNameAliasAdapter/configSchema.d.ts.map +1 -0
- package/dist/ApolloSequenceAdapter/configSchema.d.ts +8 -0
- package/dist/ApolloSequenceAdapter/configSchema.d.ts.map +1 -0
- package/dist/ApolloTextSearchAdapter/configSchema.d.ts +20 -0
- package/dist/ApolloTextSearchAdapter/configSchema.d.ts.map +1 -0
- package/dist/BackendDrivers/CollaborationServerDriver.d.ts +1 -1
- package/dist/BackendDrivers/CollaborationServerDriver.d.ts.map +1 -1
- package/dist/BackendDrivers/LocalDriver/LocalDriver.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/ApolloFeatureDetailsWidget.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/Attributes.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/DefaultAttributeEditor.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/DefaultAttributeViewer.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/NumberTextField.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/StringTextField.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/TranscriptSequence.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/TranscriptWidgetEditLocation.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/Translation.d.ts +14 -0
- package/dist/FeatureDetailsWidget/Translation.d.ts.map +1 -0
- package/dist/LinearApolloDisplay/components/CheckResultWarnings.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/configSchema.d.ts +2 -0
- package/dist/LinearApolloDisplay/configSchema.d.ts.map +1 -0
- package/dist/LinearApolloDisplay/glyphs/ExonGlyph.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/glyphs/GeneGlyph.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/glyphs/TranscriptGlyph.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/stateModel/base.d.ts +1 -18
- package/dist/LinearApolloDisplay/stateModel/base.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/stateModel/index.d.ts +1 -17
- package/dist/LinearApolloDisplay/stateModel/index.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/stateModel/layouts.d.ts +2 -19
- package/dist/LinearApolloDisplay/stateModel/layouts.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/stateModel/mouseEvents.d.ts +2 -34
- package/dist/LinearApolloDisplay/stateModel/mouseEvents.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/stateModel/rendering.d.ts +2 -19
- package/dist/LinearApolloDisplay/stateModel/rendering.d.ts.map +1 -1
- package/dist/LinearApolloReferenceSequenceDisplay/configSchema.d.ts +2 -0
- package/dist/LinearApolloReferenceSequenceDisplay/configSchema.d.ts.map +1 -0
- package/dist/LinearApolloReferenceSequenceDisplay/stateModel/base.d.ts +1 -1
- package/dist/LinearApolloReferenceSequenceDisplay/stateModel/base.d.ts.map +1 -1
- package/dist/LinearApolloReferenceSequenceDisplay/stateModel/index.d.ts +1 -1
- package/dist/LinearApolloReferenceSequenceDisplay/stateModel/rendering.d.ts +1 -1
- package/dist/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.d.ts.map +1 -1
- package/dist/LinearApolloSixFrameDisplay/configSchema.d.ts +2 -0
- package/dist/LinearApolloSixFrameDisplay/configSchema.d.ts.map +1 -0
- package/dist/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.d.ts.map +1 -1
- package/dist/LinearApolloSixFrameDisplay/stateModel/base.d.ts +1 -1
- package/dist/LinearApolloSixFrameDisplay/stateModel/base.d.ts.map +1 -1
- package/dist/LinearApolloSixFrameDisplay/stateModel/index.d.ts +1 -1
- package/dist/LinearApolloSixFrameDisplay/stateModel/index.d.ts.map +1 -1
- package/dist/LinearApolloSixFrameDisplay/stateModel/layouts.d.ts +2 -3
- package/dist/LinearApolloSixFrameDisplay/stateModel/layouts.d.ts.map +1 -1
- package/dist/LinearApolloSixFrameDisplay/stateModel/mouseEvents.d.ts +2 -2
- package/dist/LinearApolloSixFrameDisplay/stateModel/mouseEvents.d.ts.map +1 -1
- package/dist/LinearApolloSixFrameDisplay/stateModel/rendering.d.ts +2 -3
- package/dist/LinearApolloSixFrameDisplay/stateModel/rendering.d.ts.map +1 -1
- package/dist/OntologyManager/OntologyStore/fulltext.d.ts.map +1 -1
- package/dist/OntologyManager/index.d.ts +734 -0
- package/dist/OntologyManager/index.d.ts.map +1 -0
- package/dist/TabularEditor/HybridGrid/NumberCell.d.ts.map +1 -1
- package/dist/components/AddCodingSequence.d.ts +14 -0
- package/dist/components/AddCodingSequence.d.ts.map +1 -0
- package/dist/components/AddRefSeqAliases.d.ts.map +1 -1
- package/dist/components/CopyFeature.d.ts.map +1 -1
- package/dist/components/CreateApolloAnnotation.d.ts.map +1 -1
- package/dist/components/DeleteFeature.d.ts.map +1 -1
- package/dist/components/DownloadGFF3.d.ts.map +1 -1
- package/dist/components/DuplicateFeatureDialog.d.ts +12 -0
- package/dist/components/DuplicateFeatureDialog.d.ts.map +1 -0
- package/dist/components/ImportAnnotations.d.ts +9 -0
- package/dist/components/ImportAnnotations.d.ts.map +1 -0
- package/dist/components/ManageUsers.d.ts.map +1 -1
- package/dist/components/OntologyTermMultiSelect.d.ts.map +1 -1
- package/dist/components/ViewChangeLog.d.ts.map +1 -1
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/extensions/annotationFromPileup.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +830 -434
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +825 -429
- package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.development.js +863 -468
- package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
- package/dist/session/ClientDataStore.d.ts +2194 -0
- package/dist/session/ClientDataStore.d.ts.map +1 -0
- package/dist/session/handleApolloFeaturesUrlParam.d.ts +8 -0
- package/dist/session/handleApolloFeaturesUrlParam.d.ts.map +1 -0
- package/dist/session/session.d.ts +6788 -0
- package/dist/session/session.d.ts.map +1 -0
- package/dist/util/displayUtils.d.ts +36 -0
- package/dist/util/displayUtils.d.ts.map +1 -0
- package/dist/util/sequenceUtils.d.ts +2 -0
- package/dist/util/sequenceUtils.d.ts.map +1 -0
- package/package.json +14 -14
- package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +73 -38
- package/src/ApolloInternetAccount/components/LoginButtons.tsx +31 -10
- package/src/ApolloInternetAccount/model.ts +8 -5
- package/src/ApolloJobModel.ts +3 -8
- package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +1 -1
- package/src/BackendDrivers/CollaborationServerDriver.ts +7 -11
- package/src/BackendDrivers/LocalDriver/LocalDriver.ts +13 -4
- package/src/FeatureDetailsWidget/ApolloFeatureDetailsWidget.tsx +1 -0
- package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +4 -2
- package/src/FeatureDetailsWidget/Attributes.tsx +1 -0
- package/src/FeatureDetailsWidget/DefaultAttributeEditor.tsx +1 -0
- package/src/FeatureDetailsWidget/DefaultAttributeViewer.tsx +1 -0
- package/src/FeatureDetailsWidget/NumberTextField.tsx +1 -0
- package/src/FeatureDetailsWidget/StringTextField.tsx +1 -0
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +2 -1
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +17 -354
- package/src/FeatureDetailsWidget/Translation.tsx +358 -0
- package/src/LinearApolloDisplay/components/CheckResultWarnings.tsx +4 -1
- package/src/LinearApolloDisplay/glyphs/ExonGlyph.ts +2 -5
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +4 -6
- package/src/LinearApolloDisplay/glyphs/TranscriptGlyph.ts +39 -6
- package/src/LinearApolloDisplay/stateModel/base.ts +47 -23
- package/src/LinearApolloDisplay/stateModel/layouts.ts +7 -5
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +4 -1
- package/src/LinearApolloDisplay/stateModel/rendering.ts +1 -5
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +10 -10
- package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +5 -3
- package/src/LinearApolloSixFrameDisplay/components/TrackLines.tsx +1 -1
- package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +2 -3
- package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +12 -20
- package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +10 -6
- package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +38 -45
- package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +1 -4
- package/src/OntologyManager/OntologyStore/fulltext.ts +0 -2
- package/src/OntologyManager/OntologyStore/index.ts +1 -1
- package/src/TabularEditor/HybridGrid/ChangeHandling.ts +2 -2
- package/src/TabularEditor/HybridGrid/NumberCell.tsx +2 -1
- package/src/components/AddAssemblyAliases.tsx +1 -1
- package/src/components/AddCodingSequence.tsx +255 -0
- package/src/components/AddFeature.tsx +1 -1
- package/src/components/AddRefSeqAliases.tsx +3 -2
- package/src/components/CopyFeature.tsx +9 -7
- package/src/components/CreateApolloAnnotation.tsx +28 -23
- package/src/components/DeleteAssembly.tsx +2 -2
- package/src/components/DeleteFeature.tsx +1 -4
- package/src/components/DownloadGFF3.tsx +4 -3
- package/src/components/DuplicateFeatureDialog.tsx +154 -0
- package/src/components/DuplicateTranscript.tsx +1 -1
- package/src/components/FilterTranscripts.tsx +1 -1
- package/src/components/ImportAnnotations.tsx +108 -0
- package/src/components/ManageUsers.tsx +1 -2
- package/src/components/OntologyTermAutocomplete.tsx +1 -3
- package/src/components/OntologyTermMultiSelect.tsx +3 -1
- package/src/components/ViewChangeLog.tsx +2 -1
- package/src/components/index.ts +2 -0
- package/src/extensions/annotationFromPileup.ts +34 -26
- package/src/index.ts +9 -8
- package/src/session/ClientDataStore.ts +16 -21
- package/src/session/handleApolloFeaturesUrlParam.ts +122 -0
- package/src/session/session.ts +49 -9
- package/src/util/annotationFeatureUtils.ts +1 -1
- package/src/util/displayUtils.ts +1 -1
- package/src/util/glyphUtils.ts +3 -3
- package/src/util/sequenceUtils.ts +29 -0
|
@@ -41,6 +41,7 @@ var xDataGrid = require('@mui/x-data-grid');
|
|
|
41
41
|
var gff = require('@gmod/gff');
|
|
42
42
|
var fileSaver = require('file-saver');
|
|
43
43
|
var AccountCircleIcon = require('@mui/icons-material/AccountCircle');
|
|
44
|
+
var BusinessIcon = require('@mui/icons-material/Business');
|
|
44
45
|
var AdapterType = require('@jbrowse/core/pluggableElementTypes/AdapterType');
|
|
45
46
|
var BaseAdapter = require('@jbrowse/core/data_adapters/BaseAdapter');
|
|
46
47
|
var nanoid = require('nanoid');
|
|
@@ -53,9 +54,9 @@ var MoreHorizIcon = require('@mui/icons-material/MoreHoriz');
|
|
|
53
54
|
var AddBoxIcon = require('@mui/icons-material/AddBox');
|
|
54
55
|
var mst$1 = require('@apollo-annotation/mst');
|
|
55
56
|
var styled = require('@emotion/styled');
|
|
57
|
+
var RemoveIcon = require('@mui/icons-material/Remove');
|
|
56
58
|
var ContentCopyIcon = require('@mui/icons-material/ContentCopy');
|
|
57
59
|
var ContentCutIcon = require('@mui/icons-material/ContentCut');
|
|
58
|
-
var RemoveIcon = require('@mui/icons-material/Remove');
|
|
59
60
|
var ClearIcon = require('@mui/icons-material/Clear');
|
|
60
61
|
var UnfoldLessIcon = require('@mui/icons-material/UnfoldLess');
|
|
61
62
|
var tracks = require('@jbrowse/core/util/tracks');
|
|
@@ -69,7 +70,7 @@ var RedoIcon = require('@mui/icons-material/Redo');
|
|
|
69
70
|
var UndoIcon = require('@mui/icons-material/Undo');
|
|
70
71
|
var SaveIcon = require('@mui/icons-material/Save');
|
|
71
72
|
|
|
72
|
-
var version = "1.
|
|
73
|
+
var version = "1.1.0";
|
|
73
74
|
|
|
74
75
|
const ApolloConfigSchema = configuration.ConfigurationSchema('ApolloInternetAccount', {
|
|
75
76
|
baseURL: {
|
|
@@ -1372,9 +1373,7 @@ async function getValidTerms(ontologyStore, fetchValidTerms, filterTerms, signal
|
|
|
1372
1373
|
result = customTermList;
|
|
1373
1374
|
}
|
|
1374
1375
|
}
|
|
1375
|
-
|
|
1376
|
-
result = await ontologyStore.getAllTerms();
|
|
1377
|
-
}
|
|
1376
|
+
result ??= await ontologyStore.getAllTerms();
|
|
1378
1377
|
return filterTerms ? result.filter((element) => filterTerms(element)) : result;
|
|
1379
1378
|
}
|
|
1380
1379
|
|
|
@@ -1555,7 +1554,7 @@ function CopyFeature({ changeManager, handleClose, session, sourceAssemblyId, so
|
|
|
1555
1554
|
setErrorMessage('No assemblies to copy to');
|
|
1556
1555
|
return;
|
|
1557
1556
|
}
|
|
1558
|
-
const assembly = await assemblyManager.waitForAssembly(selectedAssemblyId);
|
|
1557
|
+
const assembly = (await assemblyManager.waitForAssembly(selectedAssemblyId));
|
|
1559
1558
|
if (!assembly) {
|
|
1560
1559
|
return;
|
|
1561
1560
|
}
|
|
@@ -1584,7 +1583,7 @@ function CopyFeature({ changeManager, handleClose, session, sourceAssemblyId, so
|
|
|
1584
1583
|
event.preventDefault();
|
|
1585
1584
|
setErrorMessage('');
|
|
1586
1585
|
const featureLength = sourceFeature.length;
|
|
1587
|
-
const assembly = await assemblyManager.waitForAssembly(selectedAssemblyId);
|
|
1586
|
+
const assembly = (await assemblyManager.waitForAssembly(selectedAssemblyId));
|
|
1588
1587
|
if (!assembly) {
|
|
1589
1588
|
setErrorMessage(`Assembly not found: ${selectedAssemblyId}.`);
|
|
1590
1589
|
return;
|
|
@@ -1922,8 +1921,7 @@ function DeleteFeature({ changeManager, handleClose, selectedFeature, session, s
|
|
|
1922
1921
|
locationChanges.push(geneChange);
|
|
1923
1922
|
}
|
|
1924
1923
|
}
|
|
1925
|
-
if (featureTypeOntology
|
|
1926
|
-
featureTypeOntology.isTypeOf(sourceFeature.type, 'exon')) {
|
|
1924
|
+
if (featureTypeOntology?.isTypeOf(sourceFeature.type, 'exon')) {
|
|
1927
1925
|
const cdsChange = trimCDS(sourceFeature);
|
|
1928
1926
|
if (cdsChange) {
|
|
1929
1927
|
if (cdsChange.typeName === 'DeleteFeatureChange') {
|
|
@@ -2176,7 +2174,7 @@ function getOverlappingEdge(feature, x, minMax) {
|
|
|
2176
2174
|
return;
|
|
2177
2175
|
}
|
|
2178
2176
|
function isSelectedFeature(feature, selectedFeature) {
|
|
2179
|
-
return
|
|
2177
|
+
return feature._id === selectedFeature?._id;
|
|
2180
2178
|
}
|
|
2181
2179
|
function makeFeatureLabel(feature) {
|
|
2182
2180
|
let name;
|
|
@@ -2782,6 +2780,153 @@ function AddAssemblyAliases({ changeManager, handleClose, session, }) {
|
|
|
2782
2780
|
}, pageSizeOptions: [5], processRowUpdate: processRowUpdate, disableRowSelectionOnClick: true }) }) }), errorMessage ? (jsxRuntime.jsx(material.DialogContent, { children: jsxRuntime.jsx(material.DialogContentText, { color: "error", children: errorMessage }) })) : null] }));
|
|
2783
2781
|
}
|
|
2784
2782
|
|
|
2783
|
+
const START_CODON = 'ATG';
|
|
2784
|
+
const STOP_CODONS = new Set(['TAA', 'TAG', 'TGA']);
|
|
2785
|
+
function findLongestOrf(sequence) {
|
|
2786
|
+
const starts = [
|
|
2787
|
+
null,
|
|
2788
|
+
null,
|
|
2789
|
+
null,
|
|
2790
|
+
];
|
|
2791
|
+
let longest;
|
|
2792
|
+
for (let start = 0; start < sequence.length - 2; start++) {
|
|
2793
|
+
const codon = sequence.slice(start, start + 3).toUpperCase();
|
|
2794
|
+
const frame = (start % 3);
|
|
2795
|
+
if (codon === START_CODON && starts[frame] === null) {
|
|
2796
|
+
starts[frame] = start;
|
|
2797
|
+
}
|
|
2798
|
+
else if (STOP_CODONS.has(codon)) {
|
|
2799
|
+
const inFrameStart = starts[frame];
|
|
2800
|
+
if (inFrameStart === null) {
|
|
2801
|
+
continue;
|
|
2802
|
+
}
|
|
2803
|
+
const length = start - inFrameStart;
|
|
2804
|
+
if (!longest || longest[1] - longest[0] < length) {
|
|
2805
|
+
longest = [inFrameStart, start];
|
|
2806
|
+
}
|
|
2807
|
+
starts[frame] = null;
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
return longest;
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
// Returns the genomic half-open boundary for stitched position `pos`.
|
|
2814
|
+
// For strand +1: the inclusive start (min) of the nucleotide.
|
|
2815
|
+
// For strand -1: the exclusive end (max) of the nucleotide.
|
|
2816
|
+
// Symmetric usage:
|
|
2817
|
+
// plus: cdsMin = helper(orf[0]), cdsMax = helper(orf[1]+3)
|
|
2818
|
+
// minus: cdsMax = helper(orf[0]), cdsMin = helper(orf[1]+3)
|
|
2819
|
+
function stitchedToGenomicBound(pos, exons, strand) {
|
|
2820
|
+
let cum = 0;
|
|
2821
|
+
for (const exon of exons) {
|
|
2822
|
+
const len = exon.max - exon.min;
|
|
2823
|
+
if (pos <= cum + len) {
|
|
2824
|
+
const off = pos - cum;
|
|
2825
|
+
return strand === 1 ? exon.min + off : exon.max - off;
|
|
2826
|
+
}
|
|
2827
|
+
cum += len;
|
|
2828
|
+
}
|
|
2829
|
+
const last = exons.at(-1);
|
|
2830
|
+
if (!last) {
|
|
2831
|
+
throw new Error('No exons found');
|
|
2832
|
+
}
|
|
2833
|
+
return strand === 1 ? last.max : last.min;
|
|
2834
|
+
}
|
|
2835
|
+
function AddCodingSequence({ changeManager, handleClose, refName, session, sourceAssemblyId, sourceFeature, }) {
|
|
2836
|
+
const [method, setMethod] = React.useState('longest-orf');
|
|
2837
|
+
const [minInput, setMinInput] = React.useState('');
|
|
2838
|
+
const [maxInput, setMaxInput] = React.useState('');
|
|
2839
|
+
const [errorMessage, setErrorMessage] = React.useState('');
|
|
2840
|
+
async function onSubmit(event) {
|
|
2841
|
+
event.preventDefault();
|
|
2842
|
+
setErrorMessage('');
|
|
2843
|
+
let cdsMin;
|
|
2844
|
+
let cdsMax;
|
|
2845
|
+
try {
|
|
2846
|
+
if (method === 'longest-orf') {
|
|
2847
|
+
const exonParts = sourceFeature.transcriptExonParts.filter((p) => p.type === 'exon');
|
|
2848
|
+
if (exonParts.length === 0) {
|
|
2849
|
+
setErrorMessage('No exons found in this transcript');
|
|
2850
|
+
return;
|
|
2851
|
+
}
|
|
2852
|
+
const backendDriver = session.apolloDataStore.getBackendDriver(sourceAssemblyId);
|
|
2853
|
+
if (!backendDriver) {
|
|
2854
|
+
setErrorMessage('No backend driver found for this assembly');
|
|
2855
|
+
return;
|
|
2856
|
+
}
|
|
2857
|
+
let stitchedSequence = '';
|
|
2858
|
+
for (const exon of exonParts) {
|
|
2859
|
+
const { seq } = await backendDriver.getSequence({
|
|
2860
|
+
assemblyName: sourceAssemblyId,
|
|
2861
|
+
refName,
|
|
2862
|
+
start: exon.min,
|
|
2863
|
+
end: exon.max,
|
|
2864
|
+
});
|
|
2865
|
+
stitchedSequence += sourceFeature.strand === -1 ? util.revcom(seq) : seq;
|
|
2866
|
+
}
|
|
2867
|
+
const orf = findLongestOrf(stitchedSequence);
|
|
2868
|
+
if (!orf) {
|
|
2869
|
+
;
|
|
2870
|
+
session.notify('No open reading frame found in this transcript', 'error');
|
|
2871
|
+
handleClose();
|
|
2872
|
+
return;
|
|
2873
|
+
}
|
|
2874
|
+
const strand = sourceFeature.strand ?? 1;
|
|
2875
|
+
if (strand === 1) {
|
|
2876
|
+
cdsMin = stitchedToGenomicBound(orf[0], exonParts, 1);
|
|
2877
|
+
cdsMax = stitchedToGenomicBound(orf[1] + 3, exonParts, 1);
|
|
2878
|
+
}
|
|
2879
|
+
else {
|
|
2880
|
+
cdsMax = stitchedToGenomicBound(orf[0], exonParts, -1);
|
|
2881
|
+
cdsMin = stitchedToGenomicBound(orf[1] + 3, exonParts, -1);
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
else {
|
|
2885
|
+
cdsMin = Number(minInput) - 1;
|
|
2886
|
+
cdsMax = Number(maxInput);
|
|
2887
|
+
}
|
|
2888
|
+
const _id = new ObjectID().toHexString();
|
|
2889
|
+
const addedFeature = {
|
|
2890
|
+
_id,
|
|
2891
|
+
refSeq: sourceFeature.refSeq,
|
|
2892
|
+
min: cdsMin,
|
|
2893
|
+
max: cdsMax,
|
|
2894
|
+
type: 'CDS',
|
|
2895
|
+
};
|
|
2896
|
+
if (sourceFeature.strand) {
|
|
2897
|
+
addedFeature.strand = sourceFeature.strand;
|
|
2898
|
+
}
|
|
2899
|
+
const change = new shared.AddFeatureChange({
|
|
2900
|
+
changedIds: [sourceFeature._id],
|
|
2901
|
+
typeName: 'AddFeatureChange',
|
|
2902
|
+
assembly: sourceAssemblyId,
|
|
2903
|
+
addedFeature,
|
|
2904
|
+
parentFeatureId: sourceFeature._id,
|
|
2905
|
+
});
|
|
2906
|
+
await changeManager.submit(change);
|
|
2907
|
+
session.apolloSetSelectedFeature(_id);
|
|
2908
|
+
handleClose();
|
|
2909
|
+
}
|
|
2910
|
+
catch (error) {
|
|
2911
|
+
setErrorMessage(error instanceof Error ? error.message : 'An unknown error occurred');
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
const manualError = method === 'manual' &&
|
|
2915
|
+
Boolean(minInput) &&
|
|
2916
|
+
Boolean(maxInput) &&
|
|
2917
|
+
Number(maxInput) <= Number(minInput);
|
|
2918
|
+
const submitDisabled = method === 'manual' ? !(minInput && maxInput) || manualError : false;
|
|
2919
|
+
return (jsxRuntime.jsxs(Dialog, { open: true, title: "Add coding sequence", handleClose: handleClose, maxWidth: false, "data-testid": "add-coding-sequence", children: [jsxRuntime.jsxs("form", { onSubmit: (event) => {
|
|
2920
|
+
void onSubmit(event);
|
|
2921
|
+
}, children: [jsxRuntime.jsxs(material.DialogContent, { style: { display: 'flex', flexDirection: 'column' }, children: [jsxRuntime.jsx(material.FormControl, { children: jsxRuntime.jsxs(material.RadioGroup, { value: method, onChange: (e) => {
|
|
2922
|
+
setMethod(e.target.value);
|
|
2923
|
+
}, children: [jsxRuntime.jsx(material.FormControlLabel, { value: "longest-orf", control: jsxRuntime.jsx(material.Radio, {}), label: "Calculate longest open reading frame" }), jsxRuntime.jsx(material.FormControlLabel, { value: "manual", control: jsxRuntime.jsx(material.Radio, {}), label: "Manual" })] }) }), method === 'manual' ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(material.TextField, { margin: "dense", id: "cds-min", label: "Min", type: "number", fullWidth: true, variant: "outlined", value: minInput, onChange: (e) => {
|
|
2924
|
+
setMinInput(e.target.value);
|
|
2925
|
+
} }), jsxRuntime.jsx(material.TextField, { margin: "dense", id: "cds-max", label: "Max", type: "number", fullWidth: true, variant: "outlined", value: maxInput, onChange: (e) => {
|
|
2926
|
+
setMaxInput(e.target.value);
|
|
2927
|
+
}, error: manualError, helperText: manualError ? '"Max" must be greater than "Min"' : null })] })) : null] }), jsxRuntime.jsxs(material.DialogActions, { children: [jsxRuntime.jsx(material.Button, { variant: "contained", type: "submit", disabled: submitDisabled, children: "Submit" }), jsxRuntime.jsx(material.Button, { variant: "outlined", type: "button", onClick: handleClose, children: "Cancel" })] })] }), errorMessage ? (jsxRuntime.jsx(material.DialogContent, { children: jsxRuntime.jsx(material.DialogContentText, { color: "error", children: errorMessage }) })) : null] }));
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2785
2930
|
class BackendDriver {
|
|
2786
2931
|
clientStore;
|
|
2787
2932
|
constructor(clientStore) {
|
|
@@ -2854,7 +2999,7 @@ class LocalDriver extends BackendDriver {
|
|
|
2854
2999
|
async getSequence(region) {
|
|
2855
3000
|
const session = util.getSession(this.clientStore);
|
|
2856
3001
|
const { assemblyManager } = session;
|
|
2857
|
-
const assembly = await assemblyManager.waitForAssembly(region.assemblyName);
|
|
3002
|
+
const assembly = (await assemblyManager.waitForAssembly(region.assemblyName));
|
|
2858
3003
|
if (!assembly) {
|
|
2859
3004
|
throw new Error(`Assembly not found: "${region.assemblyName}"`);
|
|
2860
3005
|
}
|
|
@@ -2873,7 +3018,10 @@ class LocalDriver extends BackendDriver {
|
|
|
2873
3018
|
const adapter = new CLASS(
|
|
2874
3019
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
2875
3020
|
adapterConf, undefined, pluginManager);
|
|
2876
|
-
const seq = await adapter.getSequence(
|
|
3021
|
+
const seq = await adapter.getSequence({
|
|
3022
|
+
...region,
|
|
3023
|
+
refName: assembly.getSeqAdapterRefName(region.refName),
|
|
3024
|
+
});
|
|
2877
3025
|
if (!seq) {
|
|
2878
3026
|
throw new Error(`Sequence not found: ${JSON.stringify(region)}`);
|
|
2879
3027
|
}
|
|
@@ -2882,7 +3030,7 @@ class LocalDriver extends BackendDriver {
|
|
|
2882
3030
|
async getRegions(assemblyName) {
|
|
2883
3031
|
const session = util.getSession(this.clientStore);
|
|
2884
3032
|
const { assemblyManager } = session;
|
|
2885
|
-
const assembly = await assemblyManager.waitForAssembly(assemblyName);
|
|
3033
|
+
const assembly = (await assemblyManager.waitForAssembly(assemblyName));
|
|
2886
3034
|
if (!assembly) {
|
|
2887
3035
|
throw new Error(`Assembly not found: "${assemblyName}"`);
|
|
2888
3036
|
}
|
|
@@ -2898,7 +3046,7 @@ class LocalDriver extends BackendDriver {
|
|
|
2898
3046
|
async getRefNameAliases(assemblyName) {
|
|
2899
3047
|
const session = util.getSession(this.clientStore);
|
|
2900
3048
|
const { assemblyManager } = session;
|
|
2901
|
-
const assembly = await assemblyManager.waitForAssembly(assemblyName);
|
|
3049
|
+
const assembly = (await assemblyManager.waitForAssembly(assemblyName));
|
|
2902
3050
|
if (!assembly) {
|
|
2903
3051
|
throw new Error(`Assembly not found: "${assemblyName}"`);
|
|
2904
3052
|
}
|
|
@@ -3557,7 +3705,7 @@ class CollaborationServerDriver extends BackendDriver {
|
|
|
3557
3705
|
const localSessionId = shared.makeUserSessionId(token);
|
|
3558
3706
|
const changeManager = new ChangeManager(this.clientStore);
|
|
3559
3707
|
// Save server last change sequence into session storage
|
|
3560
|
-
internetAccount.setLastChangeSequenceNumber(
|
|
3708
|
+
internetAccount.setLastChangeSequenceNumber(message.changeSequence);
|
|
3561
3709
|
if (message.userSessionId === localSessionId) {
|
|
3562
3710
|
return; // we did this change, no need to apply it again
|
|
3563
3711
|
}
|
|
@@ -3605,14 +3753,10 @@ class CollaborationServerDriver extends BackendDriver {
|
|
|
3605
3753
|
const seq = await inFlightPromise;
|
|
3606
3754
|
return { seq, refSeq };
|
|
3607
3755
|
}
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
let apolloRefSeq = apolloAssembly.refSeqs.get(refSeq);
|
|
3613
|
-
if (!apolloRefSeq) {
|
|
3614
|
-
apolloRefSeq = apolloAssembly.addRefSeq(refSeq, refName);
|
|
3615
|
-
}
|
|
3756
|
+
const apolloAssembly = this.clientStore.assemblies.get(assemblyName) ??
|
|
3757
|
+
this.clientStore.addAssembly(assemblyName);
|
|
3758
|
+
const apolloRefSeq = apolloAssembly.refSeqs.get(refSeq) ??
|
|
3759
|
+
apolloAssembly.addRefSeq(refSeq, refName);
|
|
3616
3760
|
const clientStoreSequence = apolloRefSeq.getSequence(start, end);
|
|
3617
3761
|
if (clientStoreSequence.length === end - start) {
|
|
3618
3762
|
return { seq: clientStoreSequence, refSeq };
|
|
@@ -3969,7 +4113,7 @@ function AddFeature({ changeManager, handleClose, region, session, }) {
|
|
|
3969
4113
|
setType(NewFeature[value]);
|
|
3970
4114
|
}
|
|
3971
4115
|
};
|
|
3972
|
-
let submitDisabled =
|
|
4116
|
+
let submitDisabled = error || !(start && end && type);
|
|
3973
4117
|
if ((type === NewFeature.CUSTOM && !customType) ||
|
|
3974
4118
|
(!strand && type === NewFeature.GENE_AND_SUBFEATURES) ||
|
|
3975
4119
|
(!strand && type === NewFeature.TRANSCRIPT_AND_SUBFEATURES)) {
|
|
@@ -3990,7 +4134,7 @@ function AddFeature({ changeManager, handleClose, region, session, }) {
|
|
|
3990
4134
|
function DeleteAssembly({ changeManager, handleClose, session, }) {
|
|
3991
4135
|
const { internetAccounts } = mobxStateTree.getRoot(session);
|
|
3992
4136
|
const [errorMessage, setErrorMessage] = React.useState('');
|
|
3993
|
-
const [confirmDelete,
|
|
4137
|
+
const [confirmDelete, setConfirmDelete] = React.useState(false);
|
|
3994
4138
|
const [submitted, setSubmitted] = React.useState(false);
|
|
3995
4139
|
const apolloInternetAccounts = internetAccounts.filter((ia) => ia.type === 'ApolloInternetAccount');
|
|
3996
4140
|
if (apolloInternetAccounts.length === 0) {
|
|
@@ -4031,7 +4175,7 @@ function DeleteAssembly({ changeManager, handleClose, session, }) {
|
|
|
4031
4175
|
event.preventDefault();
|
|
4032
4176
|
}
|
|
4033
4177
|
return (jsxRuntime.jsxs(Dialog, { open: true, title: "Delete Assembly", handleClose: handleClose, maxWidth: false, "data-testid": "delete-assembly", children: [jsxRuntime.jsxs("form", { onSubmit: onSubmit, children: [jsxRuntime.jsxs(material.DialogContent, { style: { display: 'flex', flexDirection: 'column' }, children: [apolloInternetAccounts.length > 1 ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(material.DialogContentText, { children: "Select account" }), jsxRuntime.jsx(material.Select, { value: selectedInternetAccount.internetAccountId, onChange: handleChangeInternetAccount, disabled: submitted && !errorMessage, children: internetAccounts.map((option) => (jsxRuntime.jsx(material.MenuItem, { value: option.internetAccountId, children: option.name }, option.id))) })] })) : null, jsxRuntime.jsx(material.DialogContentText, { children: "Select assembly" }), jsxRuntime.jsx(material.Select, { labelId: "label", value: selectedAssembly?.name ?? '', onChange: handleChangeAssembly, disabled: assemblies.length === 0, children: assemblies.map((option) => (jsxRuntime.jsx(material.MenuItem, { value: option.name, children: option.displayName }, option.name))) }), jsxRuntime.jsx(material.DialogContentText, { children: jsxRuntime.jsx("strong", { style: { color: 'red' }, children: "NOTE: All assembly data will be deleted and this operation cannot be undone!" }) }), jsxRuntime.jsx(material.FormGroup, { children: jsxRuntime.jsx(material.FormControlLabel, { control: jsxRuntime.jsx(material.Checkbox, { checked: confirmDelete, onChange: () => {
|
|
4034
|
-
|
|
4178
|
+
setConfirmDelete(!confirmDelete);
|
|
4035
4179
|
} }), label: "I understand that all assembly data will be deleted" }) })] }), jsxRuntime.jsxs(material.DialogActions, { children: [jsxRuntime.jsx(material.Button, { disabled: !selectedAssembly || !confirmDelete, variant: "contained", type: "submit", children: "Delete" }), jsxRuntime.jsx(material.Button, { variant: "outlined", type: "submit", onClick: handleClose, children: "Cancel" })] })] }), errorMessage ? (jsxRuntime.jsx(material.DialogContent, { children: jsxRuntime.jsx(material.DialogContentText, { color: "error", children: errorMessage }) })) : null] }));
|
|
4036
4180
|
}
|
|
4037
4181
|
|
|
@@ -4040,7 +4184,7 @@ function Export(props) {
|
|
|
4040
4184
|
return (jsxRuntime.jsx(material.SvgIcon, { viewBox: "0 0 24 24", ...props, children: jsxRuntime.jsx("path", { d: "M23,12L19,8V11H10V13H19V16M1,18V6C1,4.89 1.9,4 3,4H15A2,2 0 0,1 17,6V9H15V6H3V18H15V15H17V18A2,2 0 0,1 15,20H3A2,2 0 0,1 1,18Z" }) }));
|
|
4041
4185
|
}
|
|
4042
4186
|
function DownloadGFF3({ handleClose, session, assembly: assemblyName, }) {
|
|
4043
|
-
const [includeFASTA,
|
|
4187
|
+
const [includeFASTA, setIncludeFASTA] = React.useState(false);
|
|
4044
4188
|
const [errorMessage, setErrorMessage] = React.useState('');
|
|
4045
4189
|
const { getInternetAccount } = session.apolloDataStore;
|
|
4046
4190
|
const { assemblyManager } = session;
|
|
@@ -4089,7 +4233,7 @@ function DownloadGFF3({ handleClose, session, assembly: assemblyName, }) {
|
|
|
4089
4233
|
window.open(exportUri, '_blank');
|
|
4090
4234
|
}
|
|
4091
4235
|
return (jsxRuntime.jsxs(Dialog, { open: true, title: "Export annotations", handleClose: handleClose, maxWidth: false, "data-testid": "download-gff3", children: [jsxRuntime.jsxs("form", { onSubmit: onSubmit, children: [jsxRuntime.jsxs(material.DialogContent, { style: { display: 'flex', flexDirection: 'column' }, children: [jsxRuntime.jsxs(material.DialogContentText, { children: ["Exporting annotations for ", assemblyName] }), jsxRuntime.jsx(material.FormGroup, { children: jsxRuntime.jsx(material.FormControlLabel, { "data-testid": "include-fasta-checkbox", control: jsxRuntime.jsx(material.Checkbox, { checked: includeFASTA, onChange: () => {
|
|
4092
|
-
|
|
4236
|
+
setIncludeFASTA(!includeFASTA);
|
|
4093
4237
|
}, disabled: !internetAccountConfigId }), label: "Include fasta sequence in GFF output" }) })] }), jsxRuntime.jsxs(material.DialogActions, { children: [jsxRuntime.jsx(material.Button, { variant: "contained", type: "submit", children: "Download" }), jsxRuntime.jsx(material.Button, { variant: "outlined", type: "submit", onClick: handleClose, children: "Cancel" })] })] }), errorMessage ? (jsxRuntime.jsx(material.DialogContent, { children: jsxRuntime.jsx(material.DialogContentText, { color: "error", children: errorMessage }) })) : null] }));
|
|
4094
4238
|
}
|
|
4095
4239
|
function getAssemblyGFF3Stream(assemblyName) {
|
|
@@ -4128,6 +4272,49 @@ async function* getFeaturesForAssembly(assemblyName) {
|
|
|
4128
4272
|
db.close();
|
|
4129
4273
|
}
|
|
4130
4274
|
|
|
4275
|
+
function ImportAnnotations({ assemblyName, handleClose, session, }) {
|
|
4276
|
+
const { changeManager } = session.apolloDataStore;
|
|
4277
|
+
const [file, setFile] = React.useState();
|
|
4278
|
+
const [errorMessage, setErrorMessage] = React.useState('');
|
|
4279
|
+
const [loading, setLoading] = React.useState(false);
|
|
4280
|
+
function handleChangeFile(e) {
|
|
4281
|
+
if (e.target.files) {
|
|
4282
|
+
setFile(e.target.files[0]);
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
4285
|
+
async function onSubmit(event) {
|
|
4286
|
+
event.preventDefault();
|
|
4287
|
+
if (!file) {
|
|
4288
|
+
return;
|
|
4289
|
+
}
|
|
4290
|
+
setLoading(true);
|
|
4291
|
+
setErrorMessage('');
|
|
4292
|
+
try {
|
|
4293
|
+
const stream = file
|
|
4294
|
+
.stream()
|
|
4295
|
+
.pipeThrough(new TransformStream(new gff.GFFTransformer({ parseSequences: false })));
|
|
4296
|
+
for await (const value of stream) {
|
|
4297
|
+
const annotationFeature = shared.gff3ToAnnotationFeature(value);
|
|
4298
|
+
const change = new shared.AddFeatureChange({
|
|
4299
|
+
changedIds: [annotationFeature._id],
|
|
4300
|
+
typeName: 'AddFeatureChange',
|
|
4301
|
+
assembly: assemblyName,
|
|
4302
|
+
addedFeature: annotationFeature,
|
|
4303
|
+
});
|
|
4304
|
+
await changeManager.submit(change);
|
|
4305
|
+
}
|
|
4306
|
+
handleClose();
|
|
4307
|
+
}
|
|
4308
|
+
catch (error) {
|
|
4309
|
+
setErrorMessage(String(error));
|
|
4310
|
+
}
|
|
4311
|
+
finally {
|
|
4312
|
+
setLoading(false);
|
|
4313
|
+
}
|
|
4314
|
+
}
|
|
4315
|
+
return (jsxRuntime.jsxs(Dialog, { open: true, title: "Import annotations", handleClose: handleClose, children: [loading ? jsxRuntime.jsx(material.LinearProgress, {}) : null, jsxRuntime.jsxs("form", { onSubmit: onSubmit, children: [jsxRuntime.jsxs(material.DialogContent, { children: [jsxRuntime.jsx(material.DialogContentText, { children: "Import annotations from a GFF3 file into the current Apollo track. Features are parsed and stored locally in the browser, so importing very large GFF3 files is not recommended." }), jsxRuntime.jsx("input", { type: "file", accept: ".gff,.gff3", onChange: handleChangeFile, disabled: loading }), errorMessage ? (jsxRuntime.jsx(material.DialogContentText, { color: "error", children: errorMessage })) : null] }), jsxRuntime.jsxs(material.DialogActions, { children: [jsxRuntime.jsx(material.Button, { variant: "contained", type: "submit", disabled: !file || loading, children: "Import" }), jsxRuntime.jsx(material.Button, { variant: "outlined", onClick: handleClose, children: "Cancel" })] })] })] }));
|
|
4316
|
+
}
|
|
4317
|
+
|
|
4131
4318
|
function ImportFeatures({ changeManager, handleClose, session, }) {
|
|
4132
4319
|
const { apolloDataStore } = session;
|
|
4133
4320
|
const [file, setFile] = React.useState();
|
|
@@ -4531,7 +4718,7 @@ function ManageUsers({ changeManager, handleClose, session, }) {
|
|
|
4531
4718
|
});
|
|
4532
4719
|
return newRow;
|
|
4533
4720
|
}
|
|
4534
|
-
return (jsxRuntime.jsxs(Dialog, { open: true, fullScreen: true, title: "Manage users", handleClose: handleClose, "data-testid": "manage-users", children: [jsxRuntime.jsxs(material.DialogContent, { children: [apolloInternetAccounts.length > 1 ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(material.DialogContentText, { children: "Select account" }), jsxRuntime.jsx(material.Select, { value: selectedInternetAccount.internetAccountId, onChange: handleChangeInternetAccount, disabled: !errorMessage, children: internetAccounts.map((option) => (jsxRuntime.jsx(material.MenuItem, { value: option.internetAccountId, children: option.name }, option.id))) })] })) : null, jsxRuntime.jsx("div", { style: { height: '100%', width: '100%' }, children: jsxRuntime.jsx(xDataGrid.DataGrid, { pagination: true, rows: users, columns: gridColumns, getRowId: (row) => row._id,
|
|
4721
|
+
return (jsxRuntime.jsxs(Dialog, { open: true, fullScreen: true, title: "Manage users", handleClose: handleClose, "data-testid": "manage-users", children: [jsxRuntime.jsxs(material.DialogContent, { children: [apolloInternetAccounts.length > 1 ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(material.DialogContentText, { children: "Select account" }), jsxRuntime.jsx(material.Select, { value: selectedInternetAccount.internetAccountId, onChange: handleChangeInternetAccount, disabled: !errorMessage, children: internetAccounts.map((option) => (jsxRuntime.jsx(material.MenuItem, { value: option.internetAccountId, children: option.name }, option.id))) })] })) : null, jsxRuntime.jsx("div", { style: { height: '100%', width: '100%' }, children: jsxRuntime.jsx(xDataGrid.DataGrid, { pagination: true, rows: users, columns: gridColumns, getRowId: (row) => row._id, showToolbar: true, getRowHeight: () => 'auto', isCellEditable: (params) => !isCurrentUser(params.id), processRowUpdate: processRowUpdate, onProcessRowUpdateError: (error) => {
|
|
4535
4722
|
setErrorMessage(String(error));
|
|
4536
4723
|
} }) })] }), jsxRuntime.jsx(material.DialogActions, { children: jsxRuntime.jsx(material.Button, { variant: "outlined", type: "submit", onClick: handleClose, children: "Close" }) }), errorMessage ? (jsxRuntime.jsx(material.DialogContent, { children: jsxRuntime.jsx(material.DialogContentText, { color: "error", children: errorMessage }) })) : null] }));
|
|
4537
4724
|
}
|
|
@@ -4841,12 +5028,13 @@ const AddRefSeqAliases = mobxReact.observer(function AddRefSeqAliases({ changeMa
|
|
|
4841
5028
|
const [enableSubmit, setEnableSubmit] = React.useState(false);
|
|
4842
5029
|
const [selectedAssembly, setSelectedAssembly] = React.useState();
|
|
4843
5030
|
const [selectedRows, setSelectedRows] = React.useState([]);
|
|
4844
|
-
const [refNameAliasMap, setRefNameAliasMap] = React.useState(new Map());
|
|
5031
|
+
const [refNameAliasMap, setRefNameAliasMap] = React.useState(() => new Map());
|
|
4845
5032
|
const { apolloDataStore } = session;
|
|
4846
5033
|
const { collaborationServerDriver } = apolloDataStore;
|
|
4847
5034
|
const assemblies = collaborationServerDriver.getAssemblies();
|
|
4848
5035
|
React.useEffect(() => {
|
|
4849
5036
|
if (assemblies.length > 0) {
|
|
5037
|
+
// eslint-disable-next-line @eslint-react/set-state-in-effect
|
|
4850
5038
|
setSelectedAssembly(assemblies[0]);
|
|
4851
5039
|
collaborationServerDriver
|
|
4852
5040
|
.getRefNameAliases(assemblies[0].name)
|
|
@@ -4858,7 +5046,7 @@ const AddRefSeqAliases = mobxReact.observer(function AddRefSeqAliases({ changeMa
|
|
|
4858
5046
|
setErrorMessage('Error fetching refName aliases for assembly');
|
|
4859
5047
|
});
|
|
4860
5048
|
}
|
|
4861
|
-
// eslint-disable-next-line react
|
|
5049
|
+
// eslint-disable-next-line @eslint-react/exhaustive-deps
|
|
4862
5050
|
}, []);
|
|
4863
5051
|
const initializeRefNameAliasMap = (refNameAliasesList) => {
|
|
4864
5052
|
const initialMap = new Map();
|
|
@@ -5263,17 +5451,23 @@ const useStyles$d = tssReact.makeStyles()((theme) => ({
|
|
|
5263
5451
|
}));
|
|
5264
5452
|
function GoogleButton(props) {
|
|
5265
5453
|
const { classes } = useStyles$d();
|
|
5266
|
-
const {
|
|
5267
|
-
return (jsxRuntime.jsx(material.Button, { className: classes.loginButton, variant: "outlined", startIcon: jsxRuntime.jsx(Google, {
|
|
5454
|
+
const { message } = props;
|
|
5455
|
+
return (jsxRuntime.jsx(material.Button, { className: classes.loginButton, variant: "outlined", startIcon: jsxRuntime.jsx(Google, {}), ...props, children: message }));
|
|
5268
5456
|
}
|
|
5269
5457
|
function MicrosoftButton(props) {
|
|
5270
5458
|
const { classes } = useStyles$d();
|
|
5271
|
-
const {
|
|
5272
|
-
return (jsxRuntime.jsx(material.Button, { className: classes.loginButton, variant: "outlined", startIcon: jsxRuntime.jsx(Microsoft, {
|
|
5459
|
+
const { message } = props;
|
|
5460
|
+
return (jsxRuntime.jsx(material.Button, { className: classes.loginButton, variant: "outlined", startIcon: jsxRuntime.jsx(Microsoft, {}), ...props, children: message }));
|
|
5273
5461
|
}
|
|
5274
5462
|
function GuestButton(props) {
|
|
5275
5463
|
const { classes } = useStyles$d();
|
|
5276
|
-
|
|
5464
|
+
const { message } = props;
|
|
5465
|
+
return (jsxRuntime.jsx(material.Button, { className: classes.loginButton, variant: "outlined", startIcon: jsxRuntime.jsx(AccountCircleIcon, { fontSize: "small" }), ...props, children: message }));
|
|
5466
|
+
}
|
|
5467
|
+
function GenericButton(props) {
|
|
5468
|
+
const { classes } = useStyles$d();
|
|
5469
|
+
const { message } = props;
|
|
5470
|
+
return (jsxRuntime.jsx(material.Button, { className: classes.loginButton, variant: "outlined", startIcon: jsxRuntime.jsx(BusinessIcon, { fontSize: "small" }), ...props, children: message }));
|
|
5277
5471
|
}
|
|
5278
5472
|
|
|
5279
5473
|
const useStyles$c = tssReact.makeStyles()((theme) => ({
|
|
@@ -5285,7 +5479,7 @@ const useStyles$c = tssReact.makeStyles()((theme) => ({
|
|
|
5285
5479
|
const AuthTypeSelector = ({ baseURL, handleClose, name, }) => {
|
|
5286
5480
|
const { classes } = useStyles$c();
|
|
5287
5481
|
const [errorMessage, setErrorMessage] = React.useState('');
|
|
5288
|
-
const [loginTypes, setLoginTypes] = React.useState(
|
|
5482
|
+
const [loginTypes, setLoginTypes] = React.useState(null);
|
|
5289
5483
|
React.useEffect(() => {
|
|
5290
5484
|
const controller = new AbortController();
|
|
5291
5485
|
const { signal } = controller;
|
|
@@ -5299,6 +5493,9 @@ const AuthTypeSelector = ({ baseURL, handleClose, name, }) => {
|
|
|
5299
5493
|
}
|
|
5300
5494
|
const data = (await response.json());
|
|
5301
5495
|
setLoginTypes(data);
|
|
5496
|
+
if (data.length === 0) {
|
|
5497
|
+
setErrorMessage('No login types configured');
|
|
5498
|
+
}
|
|
5302
5499
|
}
|
|
5303
5500
|
getAuthTypes().catch((error) => {
|
|
5304
5501
|
if (!aborting.isAbortException(error)) {
|
|
@@ -5310,26 +5507,35 @@ const AuthTypeSelector = ({ baseURL, handleClose, name, }) => {
|
|
|
5310
5507
|
};
|
|
5311
5508
|
}, [baseURL]);
|
|
5312
5509
|
function handleClick(authType) {
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
}
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5510
|
+
handleClose(authType);
|
|
5511
|
+
}
|
|
5512
|
+
if (loginTypes === null) {
|
|
5513
|
+
return 'Loading…';
|
|
5514
|
+
}
|
|
5515
|
+
const firstLoginType = loginTypes.at(0);
|
|
5516
|
+
if (firstLoginType && loginTypes.length === 1 && !firstLoginType.needsPopup) {
|
|
5517
|
+
handleClick(firstLoginType);
|
|
5518
|
+
}
|
|
5519
|
+
return (jsxRuntime.jsxs(Dialog, { open: true, title: `Log in to ${name}`, handleClose: handleClose, maxWidth: false, "data-testid": "login-apollo", children: [jsxRuntime.jsx(material.DialogContent, { style: { display: 'flex', flexDirection: 'column', paddingTop: 8 }, children: loginTypes.map((loginType) => {
|
|
5520
|
+
if (loginType.name === 'google') {
|
|
5521
|
+
return (jsxRuntime.jsx(GoogleButton, { message: loginType.message, onClick: () => {
|
|
5522
|
+
handleClick(loginType);
|
|
5523
|
+
} }, loginType.name));
|
|
5524
|
+
}
|
|
5525
|
+
if (loginType.name === 'microsoft') {
|
|
5526
|
+
return (jsxRuntime.jsx(MicrosoftButton, { message: loginType.message, onClick: () => {
|
|
5527
|
+
handleClick(loginType);
|
|
5528
|
+
} }, loginType.name));
|
|
5529
|
+
}
|
|
5530
|
+
if (loginType.name === 'guest') {
|
|
5531
|
+
return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx(material.Divider, { className: classes.divider }), jsxRuntime.jsx(GuestButton, { message: loginType.message, onClick: () => {
|
|
5532
|
+
handleClick(loginType);
|
|
5533
|
+
} })] }, loginType.name));
|
|
5534
|
+
}
|
|
5535
|
+
return (jsxRuntime.jsx(GenericButton, { message: loginType.message, onClick: () => {
|
|
5536
|
+
handleClick(loginType);
|
|
5537
|
+
} }, loginType.name));
|
|
5538
|
+
}) }), jsxRuntime.jsx(material.DialogActions, { children: jsxRuntime.jsx(material.Button, { variant: "outlined", type: "submit", onClick: () => {
|
|
5333
5539
|
handleClose();
|
|
5334
5540
|
}, children: "Cancel" }) }), errorMessage ? (jsxRuntime.jsx(material.DialogContent, { children: jsxRuntime.jsx(material.DialogContentText, { color: "error", children: errorMessage }) })) : null] }));
|
|
5335
5541
|
};
|
|
@@ -5525,13 +5731,13 @@ const stateModelFactory$3 = (configSchema) => {
|
|
|
5525
5731
|
},
|
|
5526
5732
|
]);
|
|
5527
5733
|
});
|
|
5528
|
-
if (authType
|
|
5734
|
+
if (authType.needsPopup) {
|
|
5529
5735
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
5530
|
-
self.openAuthWindow(authType, resolve, reject);
|
|
5736
|
+
self.openAuthWindow(authType.name, resolve, reject);
|
|
5531
5737
|
return;
|
|
5532
5738
|
}
|
|
5533
5739
|
const url = new URL('auth/login', baseURL);
|
|
5534
|
-
const searchParams = new URLSearchParams({ type: authType });
|
|
5740
|
+
const searchParams = new URLSearchParams({ type: authType.name });
|
|
5535
5741
|
url.search = searchParams.toString();
|
|
5536
5742
|
const uri = url.toString();
|
|
5537
5743
|
const response = await fetch(uri, { signal: self.controller.signal });
|
|
@@ -5652,7 +5858,7 @@ const stateModelFactory$3 = (configSchema) => {
|
|
|
5652
5858
|
socket.on('COMMON', (message) => {
|
|
5653
5859
|
if ('checkResult' in message) {
|
|
5654
5860
|
if (message.deleted) {
|
|
5655
|
-
deleteCheckResult(message.checkResult._id
|
|
5861
|
+
deleteCheckResult(message.checkResult._id);
|
|
5656
5862
|
}
|
|
5657
5863
|
else {
|
|
5658
5864
|
addCheckResult(message.checkResult);
|
|
@@ -6272,7 +6478,9 @@ const DefaultAttributeEditor = mobxReact.observer(function DefaultAttributeEdito
|
|
|
6272
6478
|
return newValues;
|
|
6273
6479
|
});
|
|
6274
6480
|
}
|
|
6275
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [newValues.map((value, idx) => (
|
|
6481
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [newValues.map((value, idx) => (
|
|
6482
|
+
// eslint-disable-next-line @eslint-react/no-array-index-key
|
|
6483
|
+
jsxRuntime.jsxs("div", { style: { display: 'flex' }, children: [jsxRuntime.jsx(StringTextField, { value: value, onChangeCommitted: (editedValue) => {
|
|
6276
6484
|
updateValue(idx, editedValue);
|
|
6277
6485
|
}, variant: "outlined", fullWidth: true }), jsxRuntime.jsx(material.IconButton, { "aria-label": "delete", size: "medium", edge: "end", onClick: () => {
|
|
6278
6486
|
deleteValue(idx);
|
|
@@ -6284,7 +6492,9 @@ const DefaultAttributeEditor = mobxReact.observer(function DefaultAttributeEdito
|
|
|
6284
6492
|
});
|
|
6285
6493
|
|
|
6286
6494
|
function DefaultAttributeViewer({ values }) {
|
|
6287
|
-
return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: values?.map((value, idx) => (jsxRuntime.jsx(material.Typography
|
|
6495
|
+
return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: values?.map((value, idx) => (jsxRuntime.jsx(material.Typography
|
|
6496
|
+
// eslint-disable-next-line @eslint-react/no-array-index-key
|
|
6497
|
+
, { variant: "body2", color: "textSecondary", children: value }, `${idx}.${value}`))) }));
|
|
6288
6498
|
}
|
|
6289
6499
|
|
|
6290
6500
|
const useStyles$a = tssReact.makeStyles()((theme) => ({
|
|
@@ -6590,6 +6800,7 @@ const ApolloFeatureDetailsWidget = mobxReact.observer(function ApolloFeatureDeta
|
|
|
6590
6800
|
const { classes } = useStyles$8();
|
|
6591
6801
|
const [panelState, setPanelState] = React.useState(['attributes']);
|
|
6592
6802
|
React.useEffect(() => {
|
|
6803
|
+
// eslint-disable-next-line @eslint-react/set-state-in-effect
|
|
6593
6804
|
setPanelState(['attributes']);
|
|
6594
6805
|
}, [feature]);
|
|
6595
6806
|
if (!(feature && currentAssembly)) {
|
|
@@ -6891,7 +7102,7 @@ const TranscriptSequence = mobxReact.observer(function TranscriptSequence({ asse
|
|
|
6891
7102
|
else {
|
|
6892
7103
|
setSequenceOptions(defaultSequenceOptions);
|
|
6893
7104
|
}
|
|
6894
|
-
// eslint-disable-next-line react
|
|
7105
|
+
// eslint-disable-next-line @eslint-react/exhaustive-deps
|
|
6895
7106
|
}, [feature]);
|
|
6896
7107
|
if (!(currentAssembly && refData)) {
|
|
6897
7108
|
return null;
|
|
@@ -6954,19 +7165,6 @@ const TranscriptSequence = mobxReact.observer(function TranscriptSequence({ asse
|
|
|
6954
7165
|
.join(';'), "(strand=", feature.strand === 1 ? '+' : '-', ";length=", getSequenceLength(sequenceSegments), ")", jsxRuntime.jsx("br", {}), wrapSequence(sequenceSegments, SEQUENCE_WRAP_LENGTH)] })] }));
|
|
6955
7166
|
});
|
|
6956
7167
|
|
|
6957
|
-
const StyledTextField = styled(NumberTextField)(() => ({
|
|
6958
|
-
'&.MuiFormControl-root': {
|
|
6959
|
-
marginTop: 0,
|
|
6960
|
-
marginBottom: 0,
|
|
6961
|
-
width: '100%',
|
|
6962
|
-
},
|
|
6963
|
-
'& .MuiInputBase-input': {
|
|
6964
|
-
fontSize: 12,
|
|
6965
|
-
height: 20,
|
|
6966
|
-
padding: 1,
|
|
6967
|
-
paddingLeft: 10,
|
|
6968
|
-
},
|
|
6969
|
-
}));
|
|
6970
7168
|
const SequenceContainer = styled('div')({
|
|
6971
7169
|
display: 'flex',
|
|
6972
7170
|
justifyContent: 'center',
|
|
@@ -6988,6 +7186,187 @@ const StyledAccordionSummary$1 = styled(material.AccordionSummary)(() => ({
|
|
|
6988
7186
|
maxHeight: 30,
|
|
6989
7187
|
},
|
|
6990
7188
|
}));
|
|
7189
|
+
function Translation({ changeInProgress, cdsLocations, refData, strand,
|
|
7190
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
7191
|
+
updateCDSLocation, cdsMin, cdsMax, feature, session, }) {
|
|
7192
|
+
const seqRef = React.useRef(null);
|
|
7193
|
+
const { notify } = session;
|
|
7194
|
+
const cdsSequences = [];
|
|
7195
|
+
const [firstLocation] = cdsLocations;
|
|
7196
|
+
for (const loc of firstLocation) {
|
|
7197
|
+
const seq = refData.getSequence(loc.min, loc.max);
|
|
7198
|
+
cdsSequences.push(strand === -1 ? util.revcom(seq) : seq);
|
|
7199
|
+
}
|
|
7200
|
+
const cdsSequence = cdsSequences.join('');
|
|
7201
|
+
const proteinSequence = [];
|
|
7202
|
+
for (let codonGenomicPos = 0; codonGenomicPos < cdsSequence.length; codonGenomicPos += 3) {
|
|
7203
|
+
const codonSeq = cdsSequence
|
|
7204
|
+
.slice(codonGenomicPos, codonGenomicPos + 3)
|
|
7205
|
+
.toUpperCase();
|
|
7206
|
+
const protein = util.defaultCodonTable[codonSeq] || '&';
|
|
7207
|
+
proteinSequence.push(protein);
|
|
7208
|
+
}
|
|
7209
|
+
const onCopyClick = () => {
|
|
7210
|
+
const seqDiv = seqRef.current;
|
|
7211
|
+
if (!seqDiv) {
|
|
7212
|
+
return;
|
|
7213
|
+
}
|
|
7214
|
+
void copyToClipboard(seqDiv);
|
|
7215
|
+
};
|
|
7216
|
+
// Codon position is the index of the start codon in the CDS genomic sequence
|
|
7217
|
+
// Calculate the genomic location of the start codon based on the codon position in the CDS
|
|
7218
|
+
const getCodonGenomicLocation = (codonGenomicPosition) => {
|
|
7219
|
+
const [firstLocation] = cdsLocations;
|
|
7220
|
+
let cdsLen = 0;
|
|
7221
|
+
const sortedCDSLocations = firstLocation.toSorted(({ min: a }, { min: b }) => a - b);
|
|
7222
|
+
// Suppose CDS locations are [{min: 0, max: 10}, {min: 20, max: 30}, {min: 40, max: 50}]
|
|
7223
|
+
// and codonGenomicPosition is 25
|
|
7224
|
+
// ((10 - 0) + (30 - 20) + (50 - 40)) > 25
|
|
7225
|
+
// So, start codon is in (40, 50)
|
|
7226
|
+
// 40 + (25-20) = 45 is the genomic location of the start codon
|
|
7227
|
+
if (strand === 1) {
|
|
7228
|
+
for (const loc of sortedCDSLocations) {
|
|
7229
|
+
const locLength = loc.max - loc.min;
|
|
7230
|
+
if (cdsLen + locLength > codonGenomicPosition) {
|
|
7231
|
+
return loc.min + (codonGenomicPosition - cdsLen);
|
|
7232
|
+
}
|
|
7233
|
+
cdsLen += locLength;
|
|
7234
|
+
}
|
|
7235
|
+
}
|
|
7236
|
+
else if (strand === -1) {
|
|
7237
|
+
for (let i = sortedCDSLocations.length - 1; i >= 0; i--) {
|
|
7238
|
+
const loc = sortedCDSLocations[i];
|
|
7239
|
+
const locLength = loc.max - loc.min;
|
|
7240
|
+
if (cdsLen + locLength > codonGenomicPosition) {
|
|
7241
|
+
return loc.max - (codonGenomicPosition - cdsLen);
|
|
7242
|
+
}
|
|
7243
|
+
cdsLen += locLength;
|
|
7244
|
+
}
|
|
7245
|
+
}
|
|
7246
|
+
if (strand === 1) {
|
|
7247
|
+
return cdsMin;
|
|
7248
|
+
}
|
|
7249
|
+
return cdsMax;
|
|
7250
|
+
};
|
|
7251
|
+
const trimTranslationSequence = () => {
|
|
7252
|
+
if ((proteinSequence.at(0) === 'M' && proteinSequence.at(-1) === '*') ||
|
|
7253
|
+
proteinSequence.length === 0) {
|
|
7254
|
+
return;
|
|
7255
|
+
}
|
|
7256
|
+
// Trim any sequence before first start codon and after stop codon
|
|
7257
|
+
const startCodonIndex = proteinSequence.indexOf('M');
|
|
7258
|
+
const stopCodonIndex = proteinSequence.lastIndexOf('*');
|
|
7259
|
+
const startCodonPos = startCodonIndex * 3;
|
|
7260
|
+
const stopCodonPos = stopCodonIndex * 3;
|
|
7261
|
+
const startCodonGenomicLoc = getCodonGenomicLocation(startCodonPos);
|
|
7262
|
+
let stopCodonGenomicLoc = getCodonGenomicLocation(stopCodonPos);
|
|
7263
|
+
if (strand === 1) {
|
|
7264
|
+
if (startCodonGenomicLoc > stopCodonGenomicLoc) {
|
|
7265
|
+
notify('Start codon genomic location should be less than stop codon genomic location', 'error');
|
|
7266
|
+
return;
|
|
7267
|
+
}
|
|
7268
|
+
let promise;
|
|
7269
|
+
stopCodonGenomicLoc += 3; // move to end of stop codon
|
|
7270
|
+
if (startCodonGenomicLoc !== cdsMin) {
|
|
7271
|
+
promise = new Promise((resolve) => {
|
|
7272
|
+
updateCDSLocation(cdsMin, startCodonGenomicLoc, feature, true, () => {
|
|
7273
|
+
resolve(true);
|
|
7274
|
+
});
|
|
7275
|
+
});
|
|
7276
|
+
}
|
|
7277
|
+
if (stopCodonGenomicLoc !== cdsMax) {
|
|
7278
|
+
if (promise) {
|
|
7279
|
+
void promise.then(() => {
|
|
7280
|
+
updateCDSLocation(cdsMax, stopCodonGenomicLoc, feature, false);
|
|
7281
|
+
});
|
|
7282
|
+
}
|
|
7283
|
+
else {
|
|
7284
|
+
updateCDSLocation(cdsMax, stopCodonGenomicLoc, feature, false);
|
|
7285
|
+
}
|
|
7286
|
+
}
|
|
7287
|
+
}
|
|
7288
|
+
if (strand === -1) {
|
|
7289
|
+
// reverse strand
|
|
7290
|
+
if (startCodonGenomicLoc < stopCodonGenomicLoc) {
|
|
7291
|
+
notify('Start codon genomic location should be less than stop codon genomic location', 'error');
|
|
7292
|
+
return;
|
|
7293
|
+
}
|
|
7294
|
+
let promise;
|
|
7295
|
+
stopCodonGenomicLoc -= 3; // move to end of stop codon
|
|
7296
|
+
if (startCodonGenomicLoc !== cdsMax) {
|
|
7297
|
+
promise = new Promise((resolve) => {
|
|
7298
|
+
updateCDSLocation(cdsMax, startCodonGenomicLoc, feature, false, () => {
|
|
7299
|
+
resolve(true);
|
|
7300
|
+
});
|
|
7301
|
+
});
|
|
7302
|
+
}
|
|
7303
|
+
if (stopCodonGenomicLoc !== cdsMin) {
|
|
7304
|
+
if (promise) {
|
|
7305
|
+
void promise.then(() => {
|
|
7306
|
+
updateCDSLocation(cdsMin, stopCodonGenomicLoc, feature, true);
|
|
7307
|
+
});
|
|
7308
|
+
}
|
|
7309
|
+
else {
|
|
7310
|
+
updateCDSLocation(cdsMin, stopCodonGenomicLoc, feature, true);
|
|
7311
|
+
}
|
|
7312
|
+
}
|
|
7313
|
+
}
|
|
7314
|
+
notify('Translation sequence trimmed to start and stop codons', 'success');
|
|
7315
|
+
};
|
|
7316
|
+
return (jsxRuntime.jsx("div", { children: jsxRuntime.jsxs(material.Accordion, { children: [jsxRuntime.jsx(StyledAccordionSummary$1, { expandIcon: jsxRuntime.jsx(ExpandMoreIcon, { style: { color: 'white' } }), "aria-controls": "panel1-content", id: "panel1-header", children: jsxRuntime.jsx(material.Typography, { component: "span", fontWeight: 'bold', children: "Translation" }) }), jsxRuntime.jsxs(material.AccordionDetails, { children: [jsxRuntime.jsx(SequenceContainer, { children: jsxRuntime.jsx(material.Typography, { component: 'span', ref: seqRef, style: { maxHeight: 120, overflowY: 'scroll' }, children: proteinSequence.map((protein, idx) => {
|
|
7317
|
+
const codonGenomicPos = idx * 3;
|
|
7318
|
+
if (protein === 'M') {
|
|
7319
|
+
return (jsxRuntime.jsx(material.Typography, { component: 'span', style: {
|
|
7320
|
+
backgroundColor: changeInProgress
|
|
7321
|
+
? 'lightgray'
|
|
7322
|
+
: 'yellow',
|
|
7323
|
+
cursor: 'pointer',
|
|
7324
|
+
border: '1px solid black',
|
|
7325
|
+
}, onClick: () => {
|
|
7326
|
+
if (changeInProgress) {
|
|
7327
|
+
return;
|
|
7328
|
+
}
|
|
7329
|
+
// NOTE: codonGenomicPos is important here for calculating the genomic location
|
|
7330
|
+
// of the start codon. We are using the codonGenomicPos as the key in the typography
|
|
7331
|
+
// elements to maintain the genomic postion of the codon start
|
|
7332
|
+
const startCodonGenomicLocation = getCodonGenomicLocation(codonGenomicPos);
|
|
7333
|
+
if (startCodonGenomicLocation !== cdsMin &&
|
|
7334
|
+
strand === 1) {
|
|
7335
|
+
updateCDSLocation(cdsMin, startCodonGenomicLocation, feature, true);
|
|
7336
|
+
}
|
|
7337
|
+
if (startCodonGenomicLocation !== cdsMax &&
|
|
7338
|
+
strand === -1) {
|
|
7339
|
+
updateCDSLocation(cdsMax, startCodonGenomicLocation, feature, false);
|
|
7340
|
+
}
|
|
7341
|
+
}, children: protein }, codonGenomicPos));
|
|
7342
|
+
}
|
|
7343
|
+
if (protein === '*') {
|
|
7344
|
+
return (jsxRuntime.jsx(material.Typography, { style: { backgroundColor: 'red', color: 'white' }, component: 'span', children: protein }, codonGenomicPos));
|
|
7345
|
+
}
|
|
7346
|
+
// Pass the codonGenomicPos as the key to maintain the genomic position of the codon
|
|
7347
|
+
return (jsxRuntime.jsx(material.Typography, { component: 'span', children: protein }, codonGenomicPos));
|
|
7348
|
+
}) }) }), jsxRuntime.jsxs("div", { style: {
|
|
7349
|
+
marginTop: 10,
|
|
7350
|
+
display: 'flex',
|
|
7351
|
+
flexDirection: 'row',
|
|
7352
|
+
alignItems: 'center',
|
|
7353
|
+
gap: 10,
|
|
7354
|
+
}, children: [jsxRuntime.jsx(material.Tooltip, { title: "Copy", children: jsxRuntime.jsx("button", { onClick: onCopyClick, style: { border: 'none', background: 'none', padding: 0 }, disabled: changeInProgress, children: jsxRuntime.jsx(ContentCopyIcon, { style: { fontSize: 15 } }) }) }), jsxRuntime.jsx(material.Tooltip, { title: "Trim", children: jsxRuntime.jsx("button", { onClick: trimTranslationSequence, style: { border: 'none', background: 'none', padding: 0 }, disabled: changeInProgress, children: jsxRuntime.jsx(ContentCutIcon, { style: { fontSize: 15 } }) }) })] })] })] }) }));
|
|
7355
|
+
}
|
|
7356
|
+
|
|
7357
|
+
const StyledTextField = styled(NumberTextField)(() => ({
|
|
7358
|
+
'&.MuiFormControl-root': {
|
|
7359
|
+
marginTop: 0,
|
|
7360
|
+
marginBottom: 0,
|
|
7361
|
+
width: '100%',
|
|
7362
|
+
},
|
|
7363
|
+
'& .MuiInputBase-input': {
|
|
7364
|
+
fontSize: 12,
|
|
7365
|
+
height: 20,
|
|
7366
|
+
padding: 1,
|
|
7367
|
+
paddingLeft: 10,
|
|
7368
|
+
},
|
|
7369
|
+
}));
|
|
6991
7370
|
const Strand = (props) => {
|
|
6992
7371
|
const { strand } = props;
|
|
6993
7372
|
return (jsxRuntime.jsx("div", { children: strand === 1 ? (jsxRuntime.jsx(AddIcon, {})) : strand === -1 ? (jsxRuntime.jsx(RemoveIcon, {})) : (jsxRuntime.jsx(material.Typography, { component: 'span', children: "N/A" })) }));
|
|
@@ -7006,7 +7385,6 @@ const TranscriptWidgetEditLocation = mobxReact.observer(function TranscriptWidge
|
|
|
7006
7385
|
const currentAssembly = session.apolloDataStore.assemblies.get(assembly);
|
|
7007
7386
|
const refData = currentAssembly?.getByRefName(refName);
|
|
7008
7387
|
const { changeManager } = session.apolloDataStore;
|
|
7009
|
-
const seqRef = React.useRef(null);
|
|
7010
7388
|
const { changeInProgress } = session;
|
|
7011
7389
|
if (!refData) {
|
|
7012
7390
|
return null;
|
|
@@ -7433,184 +7811,6 @@ const TranscriptWidgetEditLocation = mobxReact.observer(function TranscriptWidge
|
|
|
7433
7811
|
},
|
|
7434
7812
|
];
|
|
7435
7813
|
};
|
|
7436
|
-
const getTranslationSequence = () => {
|
|
7437
|
-
let wholeSequence = '';
|
|
7438
|
-
const [firstLocation] = cdsLocations;
|
|
7439
|
-
const sortedCDSLocations = firstLocation.toSorted(({ min: a }, { min: b }) => a - b);
|
|
7440
|
-
for (const loc of sortedCDSLocations) {
|
|
7441
|
-
wholeSequence += refData.getSequence(loc.min, loc.max);
|
|
7442
|
-
}
|
|
7443
|
-
if (strand === -1) {
|
|
7444
|
-
// Original: ACGCAT
|
|
7445
|
-
// Complement: TGCGTA
|
|
7446
|
-
// Reverse complement: ATGCGT
|
|
7447
|
-
wholeSequence = util.revcom(wholeSequence);
|
|
7448
|
-
}
|
|
7449
|
-
const elements = [];
|
|
7450
|
-
for (let codonGenomicPos = 0; codonGenomicPos < wholeSequence.length; codonGenomicPos += 3) {
|
|
7451
|
-
const codonSeq = wholeSequence
|
|
7452
|
-
.slice(codonGenomicPos, codonGenomicPos + 3)
|
|
7453
|
-
.toUpperCase();
|
|
7454
|
-
const protein = util.defaultCodonTable[codonSeq] || '&';
|
|
7455
|
-
// highlight start codon and stop codons
|
|
7456
|
-
if (codonSeq === 'ATG') {
|
|
7457
|
-
elements.push(jsxRuntime.jsx(material.Typography, { component: 'span', style: {
|
|
7458
|
-
backgroundColor: changeInProgress ? 'lightgray' : 'yellow',
|
|
7459
|
-
cursor: 'pointer',
|
|
7460
|
-
border: '1px solid black',
|
|
7461
|
-
}, onClick: () => {
|
|
7462
|
-
if (changeInProgress) {
|
|
7463
|
-
return;
|
|
7464
|
-
}
|
|
7465
|
-
// NOTE: codonGenomicPos is important here for calculating the genomic location
|
|
7466
|
-
// of the start codon. We are using the codonGenomicPos as the key in the typography
|
|
7467
|
-
// elements to maintain the genomic postion of the codon start
|
|
7468
|
-
const startCodonGenomicLocation = getCodonGenomicLocation(codonGenomicPos);
|
|
7469
|
-
if (startCodonGenomicLocation !== cdsMin && strand === 1) {
|
|
7470
|
-
updateCDSLocation(cdsMin, startCodonGenomicLocation, feature, true);
|
|
7471
|
-
}
|
|
7472
|
-
if (startCodonGenomicLocation !== cdsMax && strand === -1) {
|
|
7473
|
-
updateCDSLocation(cdsMax, startCodonGenomicLocation, feature, false);
|
|
7474
|
-
}
|
|
7475
|
-
}, children: protein }, codonGenomicPos));
|
|
7476
|
-
}
|
|
7477
|
-
else if (['TAA', 'TAG', 'TGA'].includes(codonSeq)) {
|
|
7478
|
-
elements.push(jsxRuntime.jsx(material.Typography, { style: { backgroundColor: 'red', color: 'white' }, component: 'span', children: protein }, codonGenomicPos));
|
|
7479
|
-
}
|
|
7480
|
-
else {
|
|
7481
|
-
elements.push(
|
|
7482
|
-
// Pass the codonGenomicPos as the key to maintain the genomic position of the codon
|
|
7483
|
-
jsxRuntime.jsx(material.Typography, { component: 'span', children: protein }, codonGenomicPos));
|
|
7484
|
-
}
|
|
7485
|
-
}
|
|
7486
|
-
return elements;
|
|
7487
|
-
};
|
|
7488
|
-
// Codon position is the index of the start codon in the CDS genomic sequence
|
|
7489
|
-
// Calculate the genomic location of the start codon based on the codon position in the CDS
|
|
7490
|
-
const getCodonGenomicLocation = (codonGenomicPosition) => {
|
|
7491
|
-
const [firstLocation] = cdsLocations;
|
|
7492
|
-
let cdsLen = 0;
|
|
7493
|
-
const sortedCDSLocations = firstLocation.toSorted(({ min: a }, { min: b }) => a - b);
|
|
7494
|
-
// Suppose CDS locations are [{min: 0, max: 10}, {min: 20, max: 30}, {min: 40, max: 50}]
|
|
7495
|
-
// and codonGenomicPosition is 25
|
|
7496
|
-
// ((10 - 0) + (30 - 20) + (50 - 40)) > 25
|
|
7497
|
-
// So, start codon is in (40, 50)
|
|
7498
|
-
// 40 + (25-20) = 45 is the genomic location of the start codon
|
|
7499
|
-
if (strand === 1) {
|
|
7500
|
-
for (const loc of sortedCDSLocations) {
|
|
7501
|
-
const locLength = loc.max - loc.min;
|
|
7502
|
-
if (cdsLen + locLength > codonGenomicPosition) {
|
|
7503
|
-
return loc.min + (codonGenomicPosition - cdsLen);
|
|
7504
|
-
}
|
|
7505
|
-
cdsLen += locLength;
|
|
7506
|
-
}
|
|
7507
|
-
}
|
|
7508
|
-
else if (strand === -1) {
|
|
7509
|
-
for (let i = sortedCDSLocations.length - 1; i >= 0; i--) {
|
|
7510
|
-
const loc = sortedCDSLocations[i];
|
|
7511
|
-
const locLength = loc.max - loc.min;
|
|
7512
|
-
if (cdsLen + locLength > codonGenomicPosition) {
|
|
7513
|
-
return loc.max - (codonGenomicPosition - cdsLen);
|
|
7514
|
-
}
|
|
7515
|
-
cdsLen += locLength;
|
|
7516
|
-
}
|
|
7517
|
-
}
|
|
7518
|
-
if (strand === 1) {
|
|
7519
|
-
return cdsMin;
|
|
7520
|
-
}
|
|
7521
|
-
return cdsMax;
|
|
7522
|
-
};
|
|
7523
|
-
const trimTranslationSequence = () => {
|
|
7524
|
-
const sequenceElements = getTranslationSequence();
|
|
7525
|
-
const translationSequence = sequenceElements
|
|
7526
|
-
.map((el) => el.props.children)
|
|
7527
|
-
.join('');
|
|
7528
|
-
if (translationSequence.startsWith('M') &&
|
|
7529
|
-
translationSequence.endsWith('*')) {
|
|
7530
|
-
return;
|
|
7531
|
-
}
|
|
7532
|
-
// NOTE: We are maintaining the genomic location of the codon start as the "key"
|
|
7533
|
-
// in typography elements. See getTranslationSequence function
|
|
7534
|
-
const translSeqCodonStartGenomicPosArr = [];
|
|
7535
|
-
for (const el of sequenceElements) {
|
|
7536
|
-
translSeqCodonStartGenomicPosArr.push({
|
|
7537
|
-
codonGenomicPos: el.key,
|
|
7538
|
-
sequenceLetter: el.props.children,
|
|
7539
|
-
});
|
|
7540
|
-
}
|
|
7541
|
-
if (translSeqCodonStartGenomicPosArr.length === 0) {
|
|
7542
|
-
return;
|
|
7543
|
-
}
|
|
7544
|
-
// Trim any sequence before first start codon and after stop codon
|
|
7545
|
-
const startCodonIndex = translationSequence.indexOf('M');
|
|
7546
|
-
const stopCodonIndex = translationSequence.indexOf('*');
|
|
7547
|
-
const startCodonPos = translSeqCodonStartGenomicPosArr[startCodonIndex].codonGenomicPos;
|
|
7548
|
-
const stopCodonPos = translSeqCodonStartGenomicPosArr[stopCodonIndex].codonGenomicPos;
|
|
7549
|
-
if (!startCodonPos || !stopCodonPos) {
|
|
7550
|
-
return;
|
|
7551
|
-
}
|
|
7552
|
-
const startCodonGenomicLoc = getCodonGenomicLocation(startCodonPos);
|
|
7553
|
-
let stopCodonGenomicLoc = getCodonGenomicLocation(stopCodonPos);
|
|
7554
|
-
if (strand === 1) {
|
|
7555
|
-
if (startCodonGenomicLoc > stopCodonGenomicLoc) {
|
|
7556
|
-
notify('Start codon genomic location should be less than stop codon genomic location', 'error');
|
|
7557
|
-
return;
|
|
7558
|
-
}
|
|
7559
|
-
let promise;
|
|
7560
|
-
stopCodonGenomicLoc += 3; // move to end of stop codon
|
|
7561
|
-
if (startCodonGenomicLoc !== cdsMin) {
|
|
7562
|
-
promise = new Promise((resolve) => {
|
|
7563
|
-
updateCDSLocation(cdsMin, startCodonGenomicLoc, feature, true, () => {
|
|
7564
|
-
resolve(true);
|
|
7565
|
-
});
|
|
7566
|
-
});
|
|
7567
|
-
}
|
|
7568
|
-
if (stopCodonGenomicLoc !== cdsMax) {
|
|
7569
|
-
if (promise) {
|
|
7570
|
-
void promise.then(() => {
|
|
7571
|
-
updateCDSLocation(cdsMax, stopCodonGenomicLoc, feature, false);
|
|
7572
|
-
});
|
|
7573
|
-
}
|
|
7574
|
-
else {
|
|
7575
|
-
updateCDSLocation(cdsMax, stopCodonGenomicLoc, feature, false);
|
|
7576
|
-
}
|
|
7577
|
-
}
|
|
7578
|
-
}
|
|
7579
|
-
if (strand === -1) {
|
|
7580
|
-
// reverse strand
|
|
7581
|
-
if (startCodonGenomicLoc < stopCodonGenomicLoc) {
|
|
7582
|
-
notify('Start codon genomic location should be less than stop codon genomic location', 'error');
|
|
7583
|
-
return;
|
|
7584
|
-
}
|
|
7585
|
-
let promise;
|
|
7586
|
-
stopCodonGenomicLoc -= 3; // move to end of stop codon
|
|
7587
|
-
if (startCodonGenomicLoc !== cdsMax) {
|
|
7588
|
-
promise = new Promise((resolve) => {
|
|
7589
|
-
updateCDSLocation(cdsMax, startCodonGenomicLoc, feature, false, () => {
|
|
7590
|
-
resolve(true);
|
|
7591
|
-
});
|
|
7592
|
-
});
|
|
7593
|
-
}
|
|
7594
|
-
if (stopCodonGenomicLoc !== cdsMin) {
|
|
7595
|
-
if (promise) {
|
|
7596
|
-
void promise.then(() => {
|
|
7597
|
-
updateCDSLocation(cdsMin, stopCodonGenomicLoc, feature, true);
|
|
7598
|
-
});
|
|
7599
|
-
}
|
|
7600
|
-
else {
|
|
7601
|
-
updateCDSLocation(cdsMin, stopCodonGenomicLoc, feature, true);
|
|
7602
|
-
}
|
|
7603
|
-
}
|
|
7604
|
-
}
|
|
7605
|
-
notify('Translation sequence trimmed to start and stop codons', 'success');
|
|
7606
|
-
};
|
|
7607
|
-
const onCopyClick = () => {
|
|
7608
|
-
const seqDiv = seqRef.current;
|
|
7609
|
-
if (!seqDiv) {
|
|
7610
|
-
return;
|
|
7611
|
-
}
|
|
7612
|
-
void copyToClipboard(seqDiv);
|
|
7613
|
-
};
|
|
7614
7814
|
return (jsxRuntime.jsxs("div", { children: [cdsPresent && (jsxRuntime.jsx("div", { children: jsxRuntime.jsxs(material.Grid, { container: true, justifyContent: "center", alignItems: "center", style: { textAlign: 'center' }, children: [jsxRuntime.jsx(material.Grid, { size: 1 }), strand === 1 ? (jsxRuntime.jsx(material.Grid, { size: 4, children: jsxRuntime.jsx(StyledTextField, { margin: "dense", variant: "outlined", value: cdsMin + 1, onChangeCommitted: (newLocation) => {
|
|
7615
7815
|
return updateCDSLocation(cdsMin, newLocation - 1, feature, true);
|
|
7616
7816
|
}, style: { border: '1px solid black', borderRadius: 5 }, disabled: changeInProgress }) })) : (jsxRuntime.jsx(material.Grid, { size: 4, children: jsxRuntime.jsx(StyledTextField, { margin: "dense", variant: "outlined", value: cdsMax, onChangeCommitted: (newLocation) => {
|
|
@@ -7620,8 +7820,12 @@ const TranscriptWidgetEditLocation = mobxReact.observer(function TranscriptWidge
|
|
|
7620
7820
|
}, style: { border: '1px solid black', borderRadius: 5 }, disabled: changeInProgress }) })) : (jsxRuntime.jsx(material.Grid, { size: 4, children: jsxRuntime.jsx(StyledTextField, { margin: "dense", variant: "outlined", value: cdsMin + 1, onChangeCommitted: (newLocation) => {
|
|
7621
7821
|
return updateCDSLocation(cdsMin, newLocation - 1, feature, true);
|
|
7622
7822
|
}, style: { border: '1px solid black', borderRadius: 5 }, disabled: changeInProgress }) })), jsxRuntime.jsx(material.Grid, { size: 1 })] }) })), jsxRuntime.jsxs("div", { style: { marginTop: 5, marginBottom: 10 }, children: [jsxRuntime.jsx("div", { style: { textAlign: 'center' }, children: jsxRuntime.jsx(material.Typography, { children: "Exons" }) }), transcriptExonParts.map((loc, index) => {
|
|
7623
|
-
return (
|
|
7624
|
-
|
|
7823
|
+
return (
|
|
7824
|
+
// eslint-disable-next-line @eslint-react/no-array-index-key
|
|
7825
|
+
jsxRuntime.jsx("div", { children: loc.type === 'exon' && (jsxRuntime.jsxs(material.Grid, { container: true, justifyContent: "center", alignItems: "center", style: { textAlign: 'center' }, children: [jsxRuntime.jsx(material.Grid, { size: 1, children: index !== 0 &&
|
|
7826
|
+
getFivePrimeSpliceSite(loc, index).map((site, idx) => (jsxRuntime.jsx(material.Typography
|
|
7827
|
+
// eslint-disable-next-line @eslint-react/no-array-index-key
|
|
7828
|
+
, { component: 'span', color: site.color, children: site.spliceSite }, idx))) }), strand === 1 ? (jsxRuntime.jsx(material.Grid, { size: 4, style: { padding: 0 }, children: jsxRuntime.jsx(StyledTextField, { margin: "dense", variant: "outlined", value: loc.min + 1, onChangeCommitted: (newLocation) => {
|
|
7625
7829
|
return handleExonLocationChange(loc.min, newLocation - 1, feature, true);
|
|
7626
7830
|
}, disabled: changeInProgress }) })) : (jsxRuntime.jsx(material.Grid, { size: 4, style: { padding: 0 }, children: jsxRuntime.jsx(StyledTextField, { margin: "dense", variant: "outlined", value: loc.max, onChangeCommitted: (newLocation) => {
|
|
7627
7831
|
return handleExonLocationChange(loc.max, newLocation, feature, false);
|
|
@@ -7630,14 +7834,10 @@ const TranscriptWidgetEditLocation = mobxReact.observer(function TranscriptWidge
|
|
|
7630
7834
|
}, disabled: changeInProgress }) })) : (jsxRuntime.jsx(material.Grid, { size: 4, style: { padding: 0 }, children: jsxRuntime.jsx(StyledTextField, { margin: "dense", variant: "outlined", value: loc.min + 1, onChangeCommitted: (newLocation) => {
|
|
7631
7835
|
return handleExonLocationChange(loc.min, newLocation - 1, feature, true);
|
|
7632
7836
|
}, disabled: changeInProgress }) })), jsxRuntime.jsx(material.Grid, { size: 1, children: index !== transcriptExonParts.length - 1 &&
|
|
7633
|
-
getThreePrimeSpliceSite(loc, index).map((site, idx) => (jsxRuntime.jsx(material.Typography
|
|
7634
|
-
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
flexDirection: 'row',
|
|
7638
|
-
alignItems: 'center',
|
|
7639
|
-
gap: 10,
|
|
7640
|
-
}, children: [jsxRuntime.jsx(material.Tooltip, { title: "Copy", children: jsxRuntime.jsx("button", { onClick: onCopyClick, style: { border: 'none', background: 'none', padding: 0 }, disabled: changeInProgress, children: jsxRuntime.jsx(ContentCopyIcon, { style: { fontSize: 15 } }) }) }), jsxRuntime.jsx(material.Tooltip, { title: "Trim", children: jsxRuntime.jsx("button", { onClick: trimTranslationSequence, style: { border: 'none', background: 'none', padding: 0 }, disabled: changeInProgress, children: jsxRuntime.jsx(ContentCutIcon, { style: { fontSize: 15 } }) }) })] })] })] }) }))] }));
|
|
7837
|
+
getThreePrimeSpliceSite(loc, index).map((site, idx) => (jsxRuntime.jsx(material.Typography
|
|
7838
|
+
// eslint-disable-next-line @eslint-react/no-array-index-key
|
|
7839
|
+
, { component: 'span', color: site.color, children: site.spliceSite }, idx))) })] })) }, index));
|
|
7840
|
+
})] }), cdsPresent && (jsxRuntime.jsx(Translation, { changeInProgress: changeInProgress, cdsLocations: cdsLocations, refData: refData, strand: strand, updateCDSLocation: updateCDSLocation, cdsMin: cdsMin, cdsMax: cdsMax, feature: feature, session: session }))] }));
|
|
7641
7841
|
});
|
|
7642
7842
|
|
|
7643
7843
|
const HeaderTableCell = styled(material.TableCell)(() => ({
|
|
@@ -7674,7 +7874,7 @@ const ApolloTranscriptDetailsWidget = mobxReact.observer(function ApolloTranscri
|
|
|
7674
7874
|
const { assembly, feature, refName } = model;
|
|
7675
7875
|
React.useEffect(() => {
|
|
7676
7876
|
setPanelState(DEFAULT_PANELS);
|
|
7677
|
-
// eslint-disable-next-line react
|
|
7877
|
+
// eslint-disable-next-line @eslint-react/exhaustive-deps
|
|
7678
7878
|
}, [feature]);
|
|
7679
7879
|
const session = util.getSession(model);
|
|
7680
7880
|
const { pluginManager } = util.getEnv(session);
|
|
@@ -7733,8 +7933,8 @@ function handleFeatureTypeChange(changeManager, feature, oldType, newType) {
|
|
|
7733
7933
|
typeName: 'TypeChange',
|
|
7734
7934
|
changedIds: [featureId],
|
|
7735
7935
|
featureId,
|
|
7736
|
-
oldType
|
|
7737
|
-
newType
|
|
7936
|
+
oldType,
|
|
7937
|
+
newType,
|
|
7738
7938
|
assembly: feature.assemblyId,
|
|
7739
7939
|
});
|
|
7740
7940
|
return changeManager.submit(change);
|
|
@@ -7830,7 +8030,7 @@ const NumberCell = mobxReact.observer(function NumberCell({ initialValue, notify
|
|
|
7830
8030
|
if (initialValue !== value) {
|
|
7831
8031
|
setValue(initialValue);
|
|
7832
8032
|
}
|
|
7833
|
-
// eslint-disable-next-line react
|
|
8033
|
+
// eslint-disable-next-line @eslint-react/exhaustive-deps
|
|
7834
8034
|
}, [initialValue]);
|
|
7835
8035
|
React.useEffect(() => {
|
|
7836
8036
|
if (blur) {
|
|
@@ -8671,8 +8871,9 @@ function draw$3(display, ctx, gene, row, rowInFeature, block) {
|
|
|
8671
8871
|
const [top, left, width] = getFeatureBox(display, gene, row, block);
|
|
8672
8872
|
const height = getRowCount$2(display, gene) * apolloRowHeight;
|
|
8673
8873
|
if (width > 2) {
|
|
8674
|
-
|
|
8675
|
-
|
|
8874
|
+
const selectedColor = configuration.readConfObject(session.getPluginConfiguration(), 'geneBackgroundColor', {
|
|
8875
|
+
featureType: gene.type,
|
|
8876
|
+
}) ?? material.alpha(theme.palette.background.paper, 0.6);
|
|
8676
8877
|
ctx.fillStyle = selectedColor;
|
|
8677
8878
|
ctx.fillRect(left, top, width, height);
|
|
8678
8879
|
}
|
|
@@ -8945,7 +9146,7 @@ function getLayout(display, feature) {
|
|
|
8945
9146
|
}
|
|
8946
9147
|
return layout;
|
|
8947
9148
|
}
|
|
8948
|
-
function getContextMenuItems$1(display,
|
|
9149
|
+
function getContextMenuItems$1(display, transcript) {
|
|
8949
9150
|
const { changeManager, regions, selectedFeature, session } = display;
|
|
8950
9151
|
const [region] = regions;
|
|
8951
9152
|
const currentAssemblyId = display.getAssemblyId(region.assemblyName);
|
|
@@ -8955,7 +9156,7 @@ function getContextMenuItems$1(display, feature) {
|
|
|
8955
9156
|
label: 'Open transcript editor',
|
|
8956
9157
|
onClick: () => {
|
|
8957
9158
|
const apolloTranscriptWidget = session.addWidget('ApolloTranscriptDetails', 'apolloTranscriptDetails', {
|
|
8958
|
-
feature,
|
|
9159
|
+
feature: transcript,
|
|
8959
9160
|
assembly: currentAssemblyId,
|
|
8960
9161
|
changeManager,
|
|
8961
9162
|
refName: region.refName,
|
|
@@ -8975,7 +9176,7 @@ function getContextMenuItems$1(display, feature) {
|
|
|
8975
9176
|
doneCallback();
|
|
8976
9177
|
},
|
|
8977
9178
|
changeManager,
|
|
8978
|
-
sourceFeature:
|
|
9179
|
+
sourceFeature: transcript,
|
|
8979
9180
|
sourceAssemblyId: currentAssemblyId,
|
|
8980
9181
|
selectedFeature,
|
|
8981
9182
|
setSelectedFeature: (feature) => {
|
|
@@ -8995,7 +9196,7 @@ function getContextMenuItems$1(display, feature) {
|
|
|
8995
9196
|
doneCallback();
|
|
8996
9197
|
},
|
|
8997
9198
|
changeManager,
|
|
8998
|
-
sourceFeature:
|
|
9199
|
+
sourceFeature: transcript,
|
|
8999
9200
|
sourceAssemblyId: currentAssemblyId,
|
|
9000
9201
|
selectedFeature,
|
|
9001
9202
|
setSelectedFeature: (feature) => {
|
|
@@ -9005,6 +9206,31 @@ function getContextMenuItems$1(display, feature) {
|
|
|
9005
9206
|
]);
|
|
9006
9207
|
},
|
|
9007
9208
|
});
|
|
9209
|
+
const { children } = transcript;
|
|
9210
|
+
if (!children) {
|
|
9211
|
+
return [];
|
|
9212
|
+
}
|
|
9213
|
+
const cdsChildren = [...children.values()].filter((child) => isCDSFeature(child, session));
|
|
9214
|
+
if (cdsChildren.length === 0) {
|
|
9215
|
+
menuItems.push({
|
|
9216
|
+
label: 'Add coding sequence',
|
|
9217
|
+
onClick: () => {
|
|
9218
|
+
session.queueDialog((doneCallback) => [
|
|
9219
|
+
AddCodingSequence,
|
|
9220
|
+
{
|
|
9221
|
+
session,
|
|
9222
|
+
handleClose: () => {
|
|
9223
|
+
doneCallback();
|
|
9224
|
+
},
|
|
9225
|
+
changeManager,
|
|
9226
|
+
sourceFeature: transcript,
|
|
9227
|
+
sourceAssemblyId: currentAssemblyId,
|
|
9228
|
+
refName: region.refName,
|
|
9229
|
+
},
|
|
9230
|
+
]);
|
|
9231
|
+
},
|
|
9232
|
+
});
|
|
9233
|
+
}
|
|
9008
9234
|
return menuItems;
|
|
9009
9235
|
}
|
|
9010
9236
|
// False positive here, none of these functions use "this"
|
|
@@ -9182,7 +9408,7 @@ function colorCode(letter, theme) {
|
|
|
9182
9408
|
letterUpper === 'C' ||
|
|
9183
9409
|
letterUpper === 'G' ||
|
|
9184
9410
|
letterUpper === 'T') {
|
|
9185
|
-
return theme.palette.bases[letterUpper].main
|
|
9411
|
+
return theme.palette.bases[letterUpper].main;
|
|
9186
9412
|
}
|
|
9187
9413
|
return 'lightgray';
|
|
9188
9414
|
}
|
|
@@ -9279,8 +9505,7 @@ function baseModelFactory$2(_pluginManager, configSchema) {
|
|
|
9279
9505
|
return internetAccounts.find((ia) => configuration.getConf(ia, 'internetAccountId') === internetAccountConfigId);
|
|
9280
9506
|
},
|
|
9281
9507
|
get changeManager() {
|
|
9282
|
-
return self.session.apolloDataStore
|
|
9283
|
-
.changeManager;
|
|
9508
|
+
return self.session.apolloDataStore.changeManager;
|
|
9284
9509
|
},
|
|
9285
9510
|
getAssemblyId(assemblyName) {
|
|
9286
9511
|
const { assemblyManager } = self.session;
|
|
@@ -9291,12 +9516,10 @@ function baseModelFactory$2(_pluginManager, configSchema) {
|
|
|
9291
9516
|
return assembly.name;
|
|
9292
9517
|
},
|
|
9293
9518
|
get selectedFeature() {
|
|
9294
|
-
return self.session
|
|
9295
|
-
.apolloSelectedFeature;
|
|
9519
|
+
return self.session.apolloSelectedFeature;
|
|
9296
9520
|
},
|
|
9297
9521
|
get hoveredFeature() {
|
|
9298
|
-
return self.session
|
|
9299
|
-
.apolloHoveredFeature;
|
|
9522
|
+
return self.session.apolloHoveredFeature;
|
|
9300
9523
|
},
|
|
9301
9524
|
}))
|
|
9302
9525
|
.actions((self) => ({
|
|
@@ -9341,8 +9564,11 @@ function baseModelFactory$2(_pluginManager, configSchema) {
|
|
|
9341
9564
|
const { filteredFeatureTypes, trackMenuItems: superTrackMenuItems } = self;
|
|
9342
9565
|
return {
|
|
9343
9566
|
trackMenuItems() {
|
|
9344
|
-
const { graphical, table, showCheckResults } = self;
|
|
9345
|
-
|
|
9567
|
+
const { graphical, table, showCheckResults, session } = self;
|
|
9568
|
+
const [region] = self.regions;
|
|
9569
|
+
const { assemblyName } = region;
|
|
9570
|
+
const backendDriver = session.apolloDataStore.getBackendDriver(assemblyName);
|
|
9571
|
+
const items = [
|
|
9346
9572
|
...superTrackMenuItems(),
|
|
9347
9573
|
{
|
|
9348
9574
|
type: 'subMenu',
|
|
@@ -9394,7 +9620,7 @@ function baseModelFactory$2(_pluginManager, configSchema) {
|
|
|
9394
9620
|
{
|
|
9395
9621
|
label: 'Filter features by type',
|
|
9396
9622
|
onClick: () => {
|
|
9397
|
-
const session = self
|
|
9623
|
+
const { session } = self;
|
|
9398
9624
|
self.session.queueDialog((doneCallback) => [
|
|
9399
9625
|
FilterFeatures,
|
|
9400
9626
|
{
|
|
@@ -9410,76 +9636,93 @@ function baseModelFactory$2(_pluginManager, configSchema) {
|
|
|
9410
9636
|
]);
|
|
9411
9637
|
},
|
|
9412
9638
|
},
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
|
|
9639
|
+
];
|
|
9640
|
+
if (backendDriver instanceof LocalDriver) {
|
|
9641
|
+
items.push({
|
|
9642
|
+
label: 'Import annotations',
|
|
9643
|
+
icon: InputIcon,
|
|
9416
9644
|
onClick: () => {
|
|
9417
|
-
const [region] = self.regions;
|
|
9418
|
-
const { assemblyName } = region;
|
|
9419
|
-
const assembly = self.getAssemblyId(assemblyName);
|
|
9420
|
-
if (!assembly) {
|
|
9421
|
-
return;
|
|
9422
|
-
}
|
|
9423
|
-
const session = self.session;
|
|
9424
9645
|
session.queueDialog((doneCallback) => [
|
|
9425
|
-
|
|
9646
|
+
ImportAnnotations,
|
|
9426
9647
|
{
|
|
9427
9648
|
session,
|
|
9649
|
+
assemblyName,
|
|
9428
9650
|
handleClose: () => {
|
|
9429
9651
|
doneCallback();
|
|
9430
9652
|
},
|
|
9431
|
-
assembly,
|
|
9432
9653
|
},
|
|
9433
9654
|
]);
|
|
9434
9655
|
},
|
|
9656
|
+
});
|
|
9657
|
+
}
|
|
9658
|
+
items.push({
|
|
9659
|
+
label: 'Export annotations',
|
|
9660
|
+
icon: Export,
|
|
9661
|
+
onClick: () => {
|
|
9662
|
+
const [region] = self.regions;
|
|
9663
|
+
const { assemblyName } = region;
|
|
9664
|
+
const assembly = self.getAssemblyId(assemblyName);
|
|
9665
|
+
if (!assembly) {
|
|
9666
|
+
return;
|
|
9667
|
+
}
|
|
9668
|
+
const { session } = self;
|
|
9669
|
+
session.queueDialog((doneCallback) => [
|
|
9670
|
+
DownloadGFF3,
|
|
9671
|
+
{
|
|
9672
|
+
session,
|
|
9673
|
+
handleClose: () => {
|
|
9674
|
+
doneCallback();
|
|
9675
|
+
},
|
|
9676
|
+
assembly,
|
|
9677
|
+
},
|
|
9678
|
+
]);
|
|
9435
9679
|
},
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
9453
|
-
},
|
|
9454
|
-
assembly,
|
|
9680
|
+
}, {
|
|
9681
|
+
label: 'View Change Log',
|
|
9682
|
+
icon: TrackChangesIcon,
|
|
9683
|
+
onClick: () => {
|
|
9684
|
+
const [region] = self.regions;
|
|
9685
|
+
const { assemblyName } = region;
|
|
9686
|
+
const assembly = self.getAssemblyId(assemblyName);
|
|
9687
|
+
if (!assembly) {
|
|
9688
|
+
return;
|
|
9689
|
+
}
|
|
9690
|
+
const { session } = self;
|
|
9691
|
+
session.queueDialog((doneCallback) => [
|
|
9692
|
+
ViewChangeLog,
|
|
9693
|
+
{
|
|
9694
|
+
session,
|
|
9695
|
+
handleClose: () => {
|
|
9696
|
+
doneCallback();
|
|
9455
9697
|
},
|
|
9456
|
-
|
|
9457
|
-
|
|
9698
|
+
assembly,
|
|
9699
|
+
},
|
|
9700
|
+
]);
|
|
9458
9701
|
},
|
|
9459
|
-
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
|
|
9464
|
-
|
|
9465
|
-
|
|
9466
|
-
|
|
9467
|
-
|
|
9468
|
-
|
|
9469
|
-
|
|
9470
|
-
|
|
9471
|
-
|
|
9472
|
-
|
|
9473
|
-
|
|
9474
|
-
|
|
9475
|
-
|
|
9476
|
-
},
|
|
9477
|
-
assembly,
|
|
9702
|
+
}, {
|
|
9703
|
+
label: 'View Check Results',
|
|
9704
|
+
icon: FactCheckIcon,
|
|
9705
|
+
onClick: () => {
|
|
9706
|
+
const [region] = self.regions;
|
|
9707
|
+
const { assemblyName } = region;
|
|
9708
|
+
const assembly = self.getAssemblyId(assemblyName);
|
|
9709
|
+
if (!assembly) {
|
|
9710
|
+
return;
|
|
9711
|
+
}
|
|
9712
|
+
const { session } = self;
|
|
9713
|
+
session.queueDialog((doneCallback) => [
|
|
9714
|
+
ViewCheckResults,
|
|
9715
|
+
{
|
|
9716
|
+
session,
|
|
9717
|
+
handleClose: () => {
|
|
9718
|
+
doneCallback();
|
|
9478
9719
|
},
|
|
9479
|
-
|
|
9480
|
-
|
|
9720
|
+
assembly,
|
|
9721
|
+
},
|
|
9722
|
+
]);
|
|
9481
9723
|
},
|
|
9482
|
-
|
|
9724
|
+
});
|
|
9725
|
+
return items;
|
|
9483
9726
|
},
|
|
9484
9727
|
};
|
|
9485
9728
|
})
|
|
@@ -10545,8 +10788,7 @@ function baseModelFactory$1(_pluginManager, configSchema) {
|
|
|
10545
10788
|
return internetAccounts.find((ia) => configuration.getConf(ia, 'internetAccountId') === internetAccountConfigId);
|
|
10546
10789
|
},
|
|
10547
10790
|
get changeManager() {
|
|
10548
|
-
return self.session.apolloDataStore
|
|
10549
|
-
.changeManager;
|
|
10791
|
+
return self.session.apolloDataStore.changeManager;
|
|
10550
10792
|
},
|
|
10551
10793
|
getAssemblyId(assemblyName) {
|
|
10552
10794
|
const { assemblyManager } = self.session;
|
|
@@ -10557,12 +10799,10 @@ function baseModelFactory$1(_pluginManager, configSchema) {
|
|
|
10557
10799
|
return assembly.name;
|
|
10558
10800
|
},
|
|
10559
10801
|
get selectedFeature() {
|
|
10560
|
-
return self.session
|
|
10561
|
-
.apolloSelectedFeature;
|
|
10802
|
+
return self.session.apolloSelectedFeature;
|
|
10562
10803
|
},
|
|
10563
10804
|
get hoveredFeature() {
|
|
10564
|
-
return self.session
|
|
10565
|
-
.apolloHoveredFeature;
|
|
10805
|
+
return self.session.apolloHoveredFeature;
|
|
10566
10806
|
},
|
|
10567
10807
|
get height() {
|
|
10568
10808
|
const { sequenceRowHeight } = self;
|
|
@@ -10774,11 +11014,9 @@ const FilterTranscripts = mobxReact.observer(function FilterTranscripts({ source
|
|
|
10774
11014
|
onUpdate(newForms);
|
|
10775
11015
|
setExcludedTranscripts(newForms);
|
|
10776
11016
|
};
|
|
10777
|
-
return (jsxRuntime.jsx(Dialog, { open: true, maxWidth: false, "data-testid": "filter-transcripts-dialog", title: "Filter transcripts by ID", handleClose: handleClose, children: jsxRuntime.jsxs(material.DialogContent, { children: [jsxRuntime.jsx(material.DialogContentText, { children: "Select the alternate transcripts you want to display in the apollo track" }), jsxRuntime.jsx(material.Grid, { container: true, spacing: 2, children: jsxRuntime.jsx(material.Grid, { size: 8, children: jsxRuntime.jsx(material.FormGroup, { children: allTranscripts.map((item) => (
|
|
10778
|
-
// eslint-disable-next-line react/jsx-key
|
|
10779
|
-
jsxRuntime.jsx(material.FormControlLabel, { control: jsxRuntime.jsx(material.Checkbox, { checked: !excludedTranscripts.includes(item), onChange: () => {
|
|
11017
|
+
return (jsxRuntime.jsx(Dialog, { open: true, maxWidth: false, "data-testid": "filter-transcripts-dialog", title: "Filter transcripts by ID", handleClose: handleClose, children: jsxRuntime.jsxs(material.DialogContent, { children: [jsxRuntime.jsx(material.DialogContentText, { children: "Select the alternate transcripts you want to display in the apollo track" }), jsxRuntime.jsx(material.Grid, { container: true, spacing: 2, children: jsxRuntime.jsx(material.Grid, { size: 8, children: jsxRuntime.jsx(material.FormGroup, { children: allTranscripts.map((item) => (jsxRuntime.jsx(material.FormControlLabel, { control: jsxRuntime.jsx(material.Checkbox, { checked: !excludedTranscripts.includes(item), onChange: () => {
|
|
10780
11018
|
handleChange(item);
|
|
10781
|
-
}, slotProps: { input: { 'aria-label': 'controlled' } } }), label: item }))) }) }) })] }) }));
|
|
11019
|
+
}, slotProps: { input: { 'aria-label': 'controlled' } } }), label: item }, item))) }) }) })] }) }));
|
|
10782
11020
|
});
|
|
10783
11021
|
|
|
10784
11022
|
let forwardFillLight = null;
|
|
@@ -11624,8 +11862,7 @@ function baseModelFactory(_pluginManager, configSchema) {
|
|
|
11624
11862
|
return internetAccounts.find((ia) => configuration.getConf(ia, 'internetAccountId') === internetAccountConfigId);
|
|
11625
11863
|
},
|
|
11626
11864
|
get changeManager() {
|
|
11627
|
-
return self.session.apolloDataStore
|
|
11628
|
-
.changeManager;
|
|
11865
|
+
return self.session.apolloDataStore.changeManager;
|
|
11629
11866
|
},
|
|
11630
11867
|
getAssemblyId(assemblyName) {
|
|
11631
11868
|
const { assemblyManager } = self.session;
|
|
@@ -11636,12 +11873,10 @@ function baseModelFactory(_pluginManager, configSchema) {
|
|
|
11636
11873
|
return assembly.name;
|
|
11637
11874
|
},
|
|
11638
11875
|
get selectedFeature() {
|
|
11639
|
-
return self.session
|
|
11640
|
-
.apolloSelectedFeature;
|
|
11876
|
+
return self.session.apolloSelectedFeature;
|
|
11641
11877
|
},
|
|
11642
11878
|
get hoveredFeature() {
|
|
11643
|
-
return self.session
|
|
11644
|
-
.apolloHoveredFeature;
|
|
11879
|
+
return self.session.apolloHoveredFeature;
|
|
11645
11880
|
},
|
|
11646
11881
|
}))
|
|
11647
11882
|
.actions((self) => ({
|
|
@@ -11769,7 +12004,7 @@ function baseModelFactory(_pluginManager, configSchema) {
|
|
|
11769
12004
|
{
|
|
11770
12005
|
label: 'Filter features by type',
|
|
11771
12006
|
onClick: () => {
|
|
11772
|
-
const session = self
|
|
12007
|
+
const { session } = self;
|
|
11773
12008
|
self.session.queueDialog((doneCallback) => [
|
|
11774
12009
|
FilterFeatures,
|
|
11775
12010
|
{
|
|
@@ -12207,17 +12442,12 @@ function mouseEventsModelIntermediateFactory(pluginManager, configSchema) {
|
|
|
12207
12442
|
if (!featureTypeOntology) {
|
|
12208
12443
|
throw new Error('featureTypeOntology is undefined');
|
|
12209
12444
|
}
|
|
12210
|
-
|
|
12211
|
-
|
|
12212
|
-
foundFeature = layoutRow.find((f) => f.feature.type == 'exon' &&
|
|
12445
|
+
const foundFeature = self.geneTrackRowNums.includes(row)
|
|
12446
|
+
? layoutRow.find((f) => f.feature.type == 'exon' &&
|
|
12213
12447
|
bp >= f.feature.min &&
|
|
12214
|
-
bp <= f.feature.max)
|
|
12215
|
-
|
|
12216
|
-
|
|
12217
|
-
}
|
|
12218
|
-
}
|
|
12219
|
-
else {
|
|
12220
|
-
foundFeature = layoutRow.find((f) => {
|
|
12448
|
+
bp <= f.feature.max) ??
|
|
12449
|
+
layoutRow.find((f) => bp >= f.feature.min && bp <= f.feature.max)
|
|
12450
|
+
: layoutRow.find((f) => {
|
|
12221
12451
|
const { feature } = f;
|
|
12222
12452
|
const featureID = feature.attributes.get('gff_id')?.toString();
|
|
12223
12453
|
const isTranscript = featureTypeOntology.isTypeOf(feature.type, 'transcript');
|
|
@@ -12239,7 +12469,6 @@ function mouseEventsModelIntermediateFactory(pluginManager, configSchema) {
|
|
|
12239
12469
|
}
|
|
12240
12470
|
return false;
|
|
12241
12471
|
});
|
|
12242
|
-
}
|
|
12243
12472
|
if (!foundFeature) {
|
|
12244
12473
|
return mousePosition;
|
|
12245
12474
|
}
|
|
@@ -12563,26 +12792,32 @@ function CreateApolloAnnotation({ annotationFeature, assembly, handleClose, refS
|
|
|
12563
12792
|
const apolloAssembly = apolloSessionModel.apolloDataStore.assemblies.get(assembly.name);
|
|
12564
12793
|
const refSeq = apolloAssembly?.refSeqs.get(refSeqId);
|
|
12565
12794
|
const features = refSeq?.getFeatures(region.start, region.end);
|
|
12566
|
-
const getDestinationFeatures = () => {
|
|
12567
|
-
const filteredFeatures = [];
|
|
12568
|
-
for (const f of features ?? []) {
|
|
12569
|
-
if (f.min > region.end || f.max < region.start) {
|
|
12570
|
-
continue;
|
|
12571
|
-
}
|
|
12572
|
-
// Destination feature should be of type gene
|
|
12573
|
-
if (featureTypeOntology?.isTypeOf(f.type, 'gene')) {
|
|
12574
|
-
const featureSnapshot = mobxStateTree.getSnapshot(f);
|
|
12575
|
-
filteredFeatures.push(featureSnapshot);
|
|
12576
|
-
}
|
|
12577
|
-
}
|
|
12578
|
-
return filteredFeatures;
|
|
12579
|
-
};
|
|
12580
12795
|
React.useEffect(() => {
|
|
12796
|
+
const getDestinationFeatures = () => {
|
|
12797
|
+
const filteredFeatures = [];
|
|
12798
|
+
for (const f of features ?? []) {
|
|
12799
|
+
if (f.min > region.end || f.max < region.start) {
|
|
12800
|
+
continue;
|
|
12801
|
+
}
|
|
12802
|
+
// Destination feature should be of type gene
|
|
12803
|
+
if (featureTypeOntology?.isTypeOf(f.type, 'gene')) {
|
|
12804
|
+
const featureSnapshot = mobxStateTree.getSnapshot(f);
|
|
12805
|
+
filteredFeatures.push(featureSnapshot);
|
|
12806
|
+
}
|
|
12807
|
+
}
|
|
12808
|
+
return filteredFeatures;
|
|
12809
|
+
};
|
|
12581
12810
|
setErrorMessage('');
|
|
12582
|
-
const
|
|
12583
|
-
setDestinationFeatures(
|
|
12584
|
-
setSelectedDestinationFeature(
|
|
12585
|
-
}, [
|
|
12811
|
+
const destinationFeatures = getDestinationFeatures();
|
|
12812
|
+
setDestinationFeatures(destinationFeatures);
|
|
12813
|
+
setSelectedDestinationFeature(destinationFeatures[0]);
|
|
12814
|
+
}, [
|
|
12815
|
+
checkedChildrens,
|
|
12816
|
+
featureTypeOntology,
|
|
12817
|
+
features,
|
|
12818
|
+
parentFeatureChecked,
|
|
12819
|
+
region,
|
|
12820
|
+
]);
|
|
12586
12821
|
const handleParentFeatureCheck = (event) => {
|
|
12587
12822
|
const isChecked = event.target.checked;
|
|
12588
12823
|
setParentFeatureChecked(isChecked);
|
|
@@ -12862,8 +13097,21 @@ function annotationFromPileup(pluggableElement) {
|
|
|
12862
13097
|
getAnnotationFeature(jbrowseFeature, refSeqId) {
|
|
12863
13098
|
const start = jbrowseFeature.get('start');
|
|
12864
13099
|
const end = jbrowseFeature.get('end');
|
|
12865
|
-
const strand = jbrowseFeature.get('strand');
|
|
12866
13100
|
const name = jbrowseFeature.get('name');
|
|
13101
|
+
let strand;
|
|
13102
|
+
const tags = jbrowseFeature.get('tags');
|
|
13103
|
+
const { ts, TS, XS } = tags;
|
|
13104
|
+
for (const tag of [ts, TS, XS]) {
|
|
13105
|
+
if (!strand && tag) {
|
|
13106
|
+
if ([1, '1', '+'].includes(tag)) {
|
|
13107
|
+
strand = 1;
|
|
13108
|
+
}
|
|
13109
|
+
else if ([-1, '-1', '-'].includes(tag)) {
|
|
13110
|
+
strand = -1;
|
|
13111
|
+
}
|
|
13112
|
+
}
|
|
13113
|
+
}
|
|
13114
|
+
strand ??= jbrowseFeature.get('strand');
|
|
12867
13115
|
const cigarData = jbrowseFeature.get('CIGAR');
|
|
12868
13116
|
const ops = parseCigar(cigarData);
|
|
12869
13117
|
let position = start;
|
|
@@ -12879,9 +13127,7 @@ function annotationFromPileup(pluggableElement) {
|
|
|
12879
13127
|
case 'M':
|
|
12880
13128
|
case '=':
|
|
12881
13129
|
case 'X': {
|
|
12882
|
-
|
|
12883
|
-
currentExonStart = position;
|
|
12884
|
-
}
|
|
13130
|
+
currentExonStart ??= position;
|
|
12885
13131
|
position += len;
|
|
12886
13132
|
break;
|
|
12887
13133
|
}
|
|
@@ -13295,7 +13541,7 @@ const LinearApolloDisplay = mobxReact.observer(function LinearApolloDisplay(prop
|
|
|
13295
13541
|
: undefined, menuItems: contextMenuItems })] }))] }));
|
|
13296
13542
|
});
|
|
13297
13543
|
|
|
13298
|
-
const TrackLines = mobxReact.observer(function TrackLines({ model, hrStyle = { margin: 0, top: 0, color: 'black' }, idx
|
|
13544
|
+
const TrackLines = mobxReact.observer(function TrackLines({ model, hrStyle = { margin: 0, top: 0, color: 'black' }, idx, }) {
|
|
13299
13545
|
const { apolloRowHeight, highestRow, showFeatureLabels } = model;
|
|
13300
13546
|
const featureLabelSpacer = showFeatureLabels ? 2 : 1;
|
|
13301
13547
|
return (jsxRuntime.jsx("div", { style: {
|
|
@@ -13367,8 +13613,7 @@ const LinearApolloSixFrameDisplay = mobxReact.observer(function LinearApolloSixF
|
|
|
13367
13613
|
regionNumber: idx,
|
|
13368
13614
|
})?.offsetPx ?? 0) - lgv.offsetPx;
|
|
13369
13615
|
const [feature] = checkResult.featureIds;
|
|
13370
|
-
if (!feature ||
|
|
13371
|
-
!feature.parent ||
|
|
13616
|
+
if (!feature?.parent ||
|
|
13372
13617
|
!looksLikeGene(feature.parent, session)) {
|
|
13373
13618
|
return null;
|
|
13374
13619
|
}
|
|
@@ -13620,18 +13865,15 @@ function addTopLevelMenus(rootModel) {
|
|
|
13620
13865
|
}
|
|
13621
13866
|
}
|
|
13622
13867
|
|
|
13623
|
-
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
13624
13868
|
/* eslint-disable @typescript-eslint/no-confusing-void-expression */
|
|
13625
13869
|
const ApolloJobModel = mobxStateTree.types
|
|
13626
13870
|
.model('JobsManager', {})
|
|
13627
13871
|
.views((self) => ({
|
|
13628
13872
|
get jobStatusWidget() {
|
|
13629
13873
|
const { widgets } = util.getSession(self);
|
|
13630
|
-
|
|
13631
|
-
if (!jobStatusWidget) {
|
|
13874
|
+
const jobStatusWidget = widgets.get('JobsList') ??
|
|
13632
13875
|
// @ts-expect-error: addWidget function not detected on the session
|
|
13633
|
-
|
|
13634
|
-
}
|
|
13876
|
+
util.getSession(self).addWidget('JobsListWidget', 'JobsList');
|
|
13635
13877
|
return jobStatusWidget;
|
|
13636
13878
|
},
|
|
13637
13879
|
}))
|
|
@@ -13910,19 +14152,15 @@ function clientDataStoreFactory(AnnotationFeatureExtended) {
|
|
|
13910
14152
|
continue;
|
|
13911
14153
|
}
|
|
13912
14154
|
const { assemblyName, refName } = region;
|
|
13913
|
-
|
|
13914
|
-
|
|
13915
|
-
assembly = self.assemblies.put({ _id: assemblyName, refSeqs: {} });
|
|
13916
|
-
}
|
|
14155
|
+
const assembly = self.assemblies.get(assemblyName) ??
|
|
14156
|
+
self.assemblies.put({ _id: assemblyName, refSeqs: {} });
|
|
13917
14157
|
const [firstFeature] = features;
|
|
13918
|
-
|
|
13919
|
-
|
|
13920
|
-
ref = assembly.refSeqs.put({
|
|
14158
|
+
const ref = assembly.refSeqs.get(firstFeature.refSeq) ??
|
|
14159
|
+
assembly.refSeqs.put({
|
|
13921
14160
|
_id: firstFeature.refSeq,
|
|
13922
14161
|
name: refName,
|
|
13923
14162
|
features: {},
|
|
13924
14163
|
});
|
|
13925
|
-
}
|
|
13926
14164
|
for (const feature of features) {
|
|
13927
14165
|
if (!ref.features.has(feature._id)) {
|
|
13928
14166
|
ref.features.put(feature);
|
|
@@ -13939,24 +14177,153 @@ function clientDataStoreFactory(AnnotationFeatureExtended) {
|
|
|
13939
14177
|
}
|
|
13940
14178
|
const { refSeq, seq } = yield backendDriver.getSequence(region);
|
|
13941
14179
|
const { assemblyName, end, refName, start } = region;
|
|
13942
|
-
|
|
13943
|
-
|
|
13944
|
-
|
|
13945
|
-
|
|
13946
|
-
let ref = assembly.refSeqs.get(refSeq);
|
|
13947
|
-
if (!ref) {
|
|
13948
|
-
ref = assembly.refSeqs.put({
|
|
13949
|
-
_id: refSeq,
|
|
13950
|
-
name: refName,
|
|
13951
|
-
sequence: [],
|
|
13952
|
-
});
|
|
13953
|
-
}
|
|
14180
|
+
const assembly = self.assemblies.get(assemblyName) ??
|
|
14181
|
+
self.assemblies.put({ _id: assemblyName, refSeqs: {} });
|
|
14182
|
+
const ref = assembly.refSeqs.get(refSeq) ??
|
|
14183
|
+
assembly.refSeqs.put({ _id: refSeq, name: refName, sequence: [] });
|
|
13954
14184
|
ref.addSequence({ start, stop: end, sequence: seq });
|
|
13955
14185
|
}
|
|
13956
14186
|
}),
|
|
13957
14187
|
}));
|
|
13958
14188
|
}
|
|
13959
14189
|
|
|
14190
|
+
function reassignIds(feature) {
|
|
14191
|
+
const newChildren = {};
|
|
14192
|
+
if (feature.children) {
|
|
14193
|
+
for (const child of Object.values(feature.children)) {
|
|
14194
|
+
const newChild = reassignIds(child);
|
|
14195
|
+
newChildren[newChild._id] = newChild;
|
|
14196
|
+
}
|
|
14197
|
+
}
|
|
14198
|
+
return {
|
|
14199
|
+
...feature,
|
|
14200
|
+
_id: new ObjectID().toHexString(),
|
|
14201
|
+
children: feature.children ? newChildren : undefined,
|
|
14202
|
+
};
|
|
14203
|
+
}
|
|
14204
|
+
function DuplicateFeatureDialog({ assemblyName, changeManager, existingFeature, featureSnapshot, handleClose, }) {
|
|
14205
|
+
const [choice, setChoice] = React.useState('keepExisting');
|
|
14206
|
+
const [errorMessage, setErrorMessage] = React.useState('');
|
|
14207
|
+
const [submitted, setSubmitted] = React.useState(false);
|
|
14208
|
+
const handleSubmit = async () => {
|
|
14209
|
+
setErrorMessage('');
|
|
14210
|
+
setSubmitted(true);
|
|
14211
|
+
try {
|
|
14212
|
+
if (choice === 'useNew') {
|
|
14213
|
+
const deleteChange = new shared.DeleteFeatureChange({
|
|
14214
|
+
typeName: 'DeleteFeatureChange',
|
|
14215
|
+
assembly: assemblyName,
|
|
14216
|
+
changedIds: [existingFeature._id],
|
|
14217
|
+
deletedFeature: existingFeature,
|
|
14218
|
+
});
|
|
14219
|
+
await changeManager.submit(deleteChange);
|
|
14220
|
+
const addChange = new shared.AddFeatureChange({
|
|
14221
|
+
typeName: 'AddFeatureChange',
|
|
14222
|
+
assembly: assemblyName,
|
|
14223
|
+
changedIds: [featureSnapshot._id],
|
|
14224
|
+
addedFeature: featureSnapshot,
|
|
14225
|
+
});
|
|
14226
|
+
await changeManager.submit(addChange);
|
|
14227
|
+
}
|
|
14228
|
+
else if (choice === 'keepBoth') {
|
|
14229
|
+
const newSnapshot = reassignIds(featureSnapshot);
|
|
14230
|
+
const addChange = new shared.AddFeatureChange({
|
|
14231
|
+
typeName: 'AddFeatureChange',
|
|
14232
|
+
assembly: assemblyName,
|
|
14233
|
+
changedIds: [newSnapshot._id],
|
|
14234
|
+
addedFeature: newSnapshot,
|
|
14235
|
+
});
|
|
14236
|
+
await changeManager.submit(addChange);
|
|
14237
|
+
}
|
|
14238
|
+
handleClose();
|
|
14239
|
+
}
|
|
14240
|
+
catch (error) {
|
|
14241
|
+
setErrorMessage(String(error));
|
|
14242
|
+
setSubmitted(false);
|
|
14243
|
+
}
|
|
14244
|
+
};
|
|
14245
|
+
return (jsxRuntime.jsxs(Dialog, { open: true, title: "Duplicate Feature Detected", handleClose: handleClose, maxWidth: "sm", fullWidth: true, children: [jsxRuntime.jsxs(material.DialogContent, { children: [jsxRuntime.jsxs(material.DialogContentText, { children: ["A feature with ID \u201C", featureSnapshot._id, "\u201D already exists. How would you like to resolve this conflict?"] }), jsxRuntime.jsxs(material.FormControl, { component: "fieldset", sx: { mt: 2 }, children: [jsxRuntime.jsx(material.FormLabel, { component: "legend", children: "Resolution" }), jsxRuntime.jsxs(material.RadioGroup, { value: choice, onChange: (e) => {
|
|
14246
|
+
setChoice(e.target.value);
|
|
14247
|
+
}, children: [jsxRuntime.jsx(material.FormControlLabel, { value: "keepExisting", control: jsxRuntime.jsx(material.Radio, {}), label: "Keep the existing feature" }), jsxRuntime.jsx(material.FormControlLabel, { value: "useNew", control: jsxRuntime.jsx(material.Radio, {}), label: "Replace with the new feature" }), jsxRuntime.jsx(material.FormControlLabel, { value: "keepBoth", control: jsxRuntime.jsx(material.Radio, {}), label: "Keep both (assign new IDs to the incoming feature)" })] })] }), errorMessage ? (jsxRuntime.jsx(material.DialogContentText, { color: "error", sx: { mt: 1 }, children: errorMessage })) : null] }), jsxRuntime.jsxs(material.DialogActions, { children: [jsxRuntime.jsx(material.Button, { variant: "contained", onClick: handleSubmit, disabled: submitted, children: "Confirm" }), jsxRuntime.jsx(material.Button, { onClick: handleClose, disabled: submitted, children: "Cancel" })] })] }));
|
|
14248
|
+
}
|
|
14249
|
+
|
|
14250
|
+
function fromUrlSafeBase64(urlSafeBase64) {
|
|
14251
|
+
let base64 = urlSafeBase64.replaceAll('-', '+').replaceAll('_', '/');
|
|
14252
|
+
const pad = base64.length % 4;
|
|
14253
|
+
if (pad) {
|
|
14254
|
+
base64 += '='.repeat(4 - pad);
|
|
14255
|
+
}
|
|
14256
|
+
return base64;
|
|
14257
|
+
}
|
|
14258
|
+
// Base64 decode + decompress
|
|
14259
|
+
async function decompress(encoded) {
|
|
14260
|
+
const binaryString = atob(encoded);
|
|
14261
|
+
// eslint-disable-next-line unicorn/prefer-code-point
|
|
14262
|
+
const bytes = Uint8Array.from(binaryString, (char) => char.charCodeAt(0));
|
|
14263
|
+
const stream = new Blob([bytes]).stream();
|
|
14264
|
+
const decompressionStream = new DecompressionStream('gzip');
|
|
14265
|
+
const decompressedStream = stream.pipeThrough(decompressionStream);
|
|
14266
|
+
const response = new Response(decompressedStream);
|
|
14267
|
+
const decompressed = await response.arrayBuffer();
|
|
14268
|
+
return JSON.parse(new TextDecoder().decode(decompressed));
|
|
14269
|
+
}
|
|
14270
|
+
async function handleApolloFeaturesUrlParam(encodedFeatures, apolloDataStore, assemblyManager, session) {
|
|
14271
|
+
const base64 = fromUrlSafeBase64(encodedFeatures);
|
|
14272
|
+
const decompressed = await decompress(base64);
|
|
14273
|
+
const featuresData = decompressed;
|
|
14274
|
+
for (const [assemblyName, featureSnapshots] of Object.entries(featuresData)) {
|
|
14275
|
+
const backendDriver = apolloDataStore.getBackendDriver(assemblyName);
|
|
14276
|
+
if (!(backendDriver instanceof LocalDriver)) {
|
|
14277
|
+
continue;
|
|
14278
|
+
}
|
|
14279
|
+
const assembly = (await assemblyManager.waitForAssembly(assemblyName));
|
|
14280
|
+
if (!assembly) {
|
|
14281
|
+
throw new Error(`Assembly not found: "${assemblyName}"`);
|
|
14282
|
+
}
|
|
14283
|
+
const { regions } = assembly;
|
|
14284
|
+
if (!regions) {
|
|
14285
|
+
throw new Error(`Assembly not found: "${assemblyName}"`);
|
|
14286
|
+
}
|
|
14287
|
+
const refNames = regions.map((r) => r.refName);
|
|
14288
|
+
const db = await openDb(assemblyName, refNames);
|
|
14289
|
+
for (const featureSnapshot of featureSnapshots) {
|
|
14290
|
+
const storeName = `features-${featureSnapshot.refSeq}`;
|
|
14291
|
+
const existing = (await db.get(storeName, featureSnapshot._id));
|
|
14292
|
+
if (existing === undefined) {
|
|
14293
|
+
const change = new shared.AddFeatureChange({
|
|
14294
|
+
typeName: 'AddFeatureChange',
|
|
14295
|
+
assembly: assemblyName,
|
|
14296
|
+
changedIds: [featureSnapshot._id],
|
|
14297
|
+
addedFeature: featureSnapshot,
|
|
14298
|
+
});
|
|
14299
|
+
await apolloDataStore.changeManager.submit(change);
|
|
14300
|
+
}
|
|
14301
|
+
else {
|
|
14302
|
+
// get rid of undefined values in JSON
|
|
14303
|
+
// eslint-disable-next-line unicorn/prefer-structured-clone
|
|
14304
|
+
const existingFeature = JSON.parse(JSON.stringify(existing));
|
|
14305
|
+
if (!equal(featureSnapshot, existingFeature)) {
|
|
14306
|
+
await new Promise((resolve) => {
|
|
14307
|
+
session.queueDialog((doneCallback) => [
|
|
14308
|
+
DuplicateFeatureDialog,
|
|
14309
|
+
{
|
|
14310
|
+
featureSnapshot,
|
|
14311
|
+
existingFeature,
|
|
14312
|
+
assemblyName,
|
|
14313
|
+
changeManager: apolloDataStore.changeManager,
|
|
14314
|
+
handleClose: () => {
|
|
14315
|
+
doneCallback();
|
|
14316
|
+
resolve();
|
|
14317
|
+
},
|
|
14318
|
+
},
|
|
14319
|
+
]);
|
|
14320
|
+
});
|
|
14321
|
+
}
|
|
14322
|
+
}
|
|
14323
|
+
}
|
|
14324
|
+
}
|
|
14325
|
+
}
|
|
14326
|
+
|
|
13960
14327
|
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
13961
14328
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
13962
14329
|
function extendSession(pluginManager, sessionModel) {
|
|
@@ -14042,8 +14409,7 @@ function extendSession(pluginManager, sessionModel) {
|
|
|
14042
14409
|
for (const block of dynamicBlocks.contentBlocks) {
|
|
14043
14410
|
const { assemblyName, end, refName, start } = block;
|
|
14044
14411
|
const assembly = self.apolloDataStore.assemblies.get(assemblyName);
|
|
14045
|
-
if (assembly
|
|
14046
|
-
assembly.backendDriverType === 'CollaborationServerDriver') {
|
|
14412
|
+
if (assembly?.backendDriverType === 'CollaborationServerDriver') {
|
|
14047
14413
|
locations.push({ assemblyName, refName, start, end });
|
|
14048
14414
|
}
|
|
14049
14415
|
}
|
|
@@ -14102,8 +14468,8 @@ function extendSession(pluginManager, sessionModel) {
|
|
|
14102
14468
|
for (const block of dynamicBlocks.contentBlocks) {
|
|
14103
14469
|
const { assemblyName, end, refName, start } = block;
|
|
14104
14470
|
const assembly = self.apolloDataStore.assemblies.get(assemblyName);
|
|
14105
|
-
if (assembly
|
|
14106
|
-
|
|
14471
|
+
if (assembly?.backendDriverType ===
|
|
14472
|
+
'CollaborationServerDriver') {
|
|
14107
14473
|
locations.push({ assemblyName, refName, start, end });
|
|
14108
14474
|
}
|
|
14109
14475
|
}
|
|
@@ -14223,6 +14589,37 @@ function extendSession(pluginManager, sessionModel) {
|
|
|
14223
14589
|
beforeDestroy() {
|
|
14224
14590
|
self.abortController.abort(new DOMException('Clean up Apollo session', 'AbortError'));
|
|
14225
14591
|
},
|
|
14592
|
+
}))
|
|
14593
|
+
.actions((self) => ({
|
|
14594
|
+
async afterCreate() {
|
|
14595
|
+
if (util.isElectron) {
|
|
14596
|
+
return;
|
|
14597
|
+
}
|
|
14598
|
+
const url = new URL(globalThis.location.href);
|
|
14599
|
+
const apolloFeatures = url.searchParams.get('apolloFeatures');
|
|
14600
|
+
if (!apolloFeatures) {
|
|
14601
|
+
return;
|
|
14602
|
+
}
|
|
14603
|
+
const { assemblyManager } = self;
|
|
14604
|
+
await handleApolloFeaturesUrlParam(apolloFeatures, self.apolloDataStore, assemblyManager, self);
|
|
14605
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
14606
|
+
const updatedURL = new URL(globalThis.location.href);
|
|
14607
|
+
updatedURL.searchParams.delete('apolloFeatures');
|
|
14608
|
+
globalThis.history.replaceState(null, '', updatedURL.toString());
|
|
14609
|
+
},
|
|
14610
|
+
afterAttach() {
|
|
14611
|
+
mobxStateTree.addDisposer(self, mobx.autorun((reaction) => {
|
|
14612
|
+
const { focusedViewId, activeWidgets } = self;
|
|
14613
|
+
if (!(focusedViewId && activeWidgets)) {
|
|
14614
|
+
return;
|
|
14615
|
+
}
|
|
14616
|
+
const trackSelector = activeWidgets.get('hierarchicalTrackSelector');
|
|
14617
|
+
// @ts-expect-error Don't have type for track selector
|
|
14618
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
14619
|
+
trackSelector?.setView(focusedViewId);
|
|
14620
|
+
reaction.dispose();
|
|
14621
|
+
}));
|
|
14622
|
+
},
|
|
14226
14623
|
}))
|
|
14227
14624
|
.views((self) => {
|
|
14228
14625
|
const superTrackActions = self
|
|
@@ -14354,7 +14751,6 @@ function extendSession(pluginManager, sessionModel) {
|
|
|
14354
14751
|
}
|
|
14355
14752
|
|
|
14356
14753
|
/* eslint-disable @typescript-eslint/unbound-method */
|
|
14357
|
-
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
14358
14754
|
/* eslint-disable @typescript-eslint/no-misused-promises */
|
|
14359
14755
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
14360
14756
|
function isApolloMessageData(data) {
|
|
@@ -14542,10 +14938,10 @@ class ApolloPlugin extends Plugin {
|
|
|
14542
14938
|
});
|
|
14543
14939
|
if (!inWebWorker) {
|
|
14544
14940
|
pluginManager.addToExtensionPoint('Core-extendWorker', (handle) => {
|
|
14545
|
-
if (!('on' in handle && handle.on)) {
|
|
14941
|
+
if (!('on' in handle.client && handle.client.on)) {
|
|
14546
14942
|
return handle;
|
|
14547
14943
|
}
|
|
14548
|
-
handle.on('apollo', async (event) => {
|
|
14944
|
+
handle.client.on('apollo', async (event) => {
|
|
14549
14945
|
if (!isApolloMessageData(event)) {
|
|
14550
14946
|
return;
|
|
14551
14947
|
}
|
|
@@ -14563,7 +14959,7 @@ class ApolloPlugin extends Plugin {
|
|
|
14563
14959
|
break;
|
|
14564
14960
|
}
|
|
14565
14961
|
const { seq: sequence } = await backendDriver.getSequence(region);
|
|
14566
|
-
handle.
|
|
14962
|
+
handle.worker.postMessage({
|
|
14567
14963
|
apollo,
|
|
14568
14964
|
messageId,
|
|
14569
14965
|
sequence,
|
|
@@ -14581,7 +14977,7 @@ class ApolloPlugin extends Plugin {
|
|
|
14581
14977
|
break;
|
|
14582
14978
|
}
|
|
14583
14979
|
const regions = await backendDriver.getRegions(assembly);
|
|
14584
|
-
handle.
|
|
14980
|
+
handle.worker.postMessage({
|
|
14585
14981
|
apollo,
|
|
14586
14982
|
messageId,
|
|
14587
14983
|
regions,
|
|
@@ -14599,7 +14995,7 @@ class ApolloPlugin extends Plugin {
|
|
|
14599
14995
|
break;
|
|
14600
14996
|
}
|
|
14601
14997
|
const refNameAliases = await backendDriver.getRefNameAliases(assembly);
|
|
14602
|
-
handle.
|
|
14998
|
+
handle.worker.postMessage({
|
|
14603
14999
|
apollo,
|
|
14604
15000
|
messageId,
|
|
14605
15001
|
refNameAliases,
|