@apollo-annotation/jbrowse-plugin-apollo 1.0.1 → 1.1.1

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.
Files changed (172) hide show
  1. package/dist/ApolloInternetAccount/components/AuthTypeSelector.d.ts +8 -2
  2. package/dist/ApolloInternetAccount/components/AuthTypeSelector.d.ts.map +1 -1
  3. package/dist/ApolloInternetAccount/components/LoginButtons.d.ts +8 -3
  4. package/dist/ApolloInternetAccount/components/LoginButtons.d.ts.map +1 -1
  5. package/dist/ApolloInternetAccount/configSchema.d.ts +44 -0
  6. package/dist/ApolloInternetAccount/configSchema.d.ts.map +1 -0
  7. package/dist/ApolloInternetAccount/model.d.ts +174 -0
  8. package/dist/ApolloInternetAccount/model.d.ts.map +1 -0
  9. package/dist/ApolloJobModel.d.ts.map +1 -1
  10. package/dist/ApolloRefNameAliasAdapter/configSchema.d.ts +8 -0
  11. package/dist/ApolloRefNameAliasAdapter/configSchema.d.ts.map +1 -0
  12. package/dist/ApolloSequenceAdapter/configSchema.d.ts +8 -0
  13. package/dist/ApolloSequenceAdapter/configSchema.d.ts.map +1 -0
  14. package/dist/ApolloTextSearchAdapter/configSchema.d.ts +20 -0
  15. package/dist/ApolloTextSearchAdapter/configSchema.d.ts.map +1 -0
  16. package/dist/BackendDrivers/CollaborationServerDriver.d.ts +1 -1
  17. package/dist/BackendDrivers/CollaborationServerDriver.d.ts.map +1 -1
  18. package/dist/BackendDrivers/LocalDriver/LocalDriver.d.ts.map +1 -1
  19. package/dist/FeatureDetailsWidget/ApolloFeatureDetailsWidget.d.ts.map +1 -1
  20. package/dist/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.d.ts.map +1 -1
  21. package/dist/FeatureDetailsWidget/Attributes.d.ts.map +1 -1
  22. package/dist/FeatureDetailsWidget/DefaultAttributeEditor.d.ts.map +1 -1
  23. package/dist/FeatureDetailsWidget/DefaultAttributeViewer.d.ts.map +1 -1
  24. package/dist/FeatureDetailsWidget/NumberTextField.d.ts.map +1 -1
  25. package/dist/FeatureDetailsWidget/StringTextField.d.ts.map +1 -1
  26. package/dist/FeatureDetailsWidget/TranscriptSequence.d.ts.map +1 -1
  27. package/dist/FeatureDetailsWidget/TranscriptWidgetEditLocation.d.ts.map +1 -1
  28. package/dist/FeatureDetailsWidget/Translation.d.ts +14 -0
  29. package/dist/FeatureDetailsWidget/Translation.d.ts.map +1 -0
  30. package/dist/LinearApolloDisplay/components/CheckResultWarnings.d.ts.map +1 -1
  31. package/dist/LinearApolloDisplay/configSchema.d.ts +2 -0
  32. package/dist/LinearApolloDisplay/configSchema.d.ts.map +1 -0
  33. package/dist/LinearApolloDisplay/glyphs/ExonGlyph.d.ts.map +1 -1
  34. package/dist/LinearApolloDisplay/glyphs/GeneGlyph.d.ts.map +1 -1
  35. package/dist/LinearApolloDisplay/glyphs/TranscriptGlyph.d.ts.map +1 -1
  36. package/dist/LinearApolloDisplay/stateModel/base.d.ts +1 -18
  37. package/dist/LinearApolloDisplay/stateModel/base.d.ts.map +1 -1
  38. package/dist/LinearApolloDisplay/stateModel/index.d.ts +1 -17
  39. package/dist/LinearApolloDisplay/stateModel/index.d.ts.map +1 -1
  40. package/dist/LinearApolloDisplay/stateModel/layouts.d.ts +2 -19
  41. package/dist/LinearApolloDisplay/stateModel/layouts.d.ts.map +1 -1
  42. package/dist/LinearApolloDisplay/stateModel/mouseEvents.d.ts +2 -34
  43. package/dist/LinearApolloDisplay/stateModel/mouseEvents.d.ts.map +1 -1
  44. package/dist/LinearApolloDisplay/stateModel/rendering.d.ts +2 -19
  45. package/dist/LinearApolloDisplay/stateModel/rendering.d.ts.map +1 -1
  46. package/dist/LinearApolloReferenceSequenceDisplay/configSchema.d.ts +2 -0
  47. package/dist/LinearApolloReferenceSequenceDisplay/configSchema.d.ts.map +1 -0
  48. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/base.d.ts +1 -1
  49. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/base.d.ts.map +1 -1
  50. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/index.d.ts +1 -1
  51. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/rendering.d.ts +1 -1
  52. package/dist/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.d.ts.map +1 -1
  53. package/dist/LinearApolloSixFrameDisplay/configSchema.d.ts +2 -0
  54. package/dist/LinearApolloSixFrameDisplay/configSchema.d.ts.map +1 -0
  55. package/dist/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.d.ts.map +1 -1
  56. package/dist/LinearApolloSixFrameDisplay/stateModel/base.d.ts +1 -1
  57. package/dist/LinearApolloSixFrameDisplay/stateModel/base.d.ts.map +1 -1
  58. package/dist/LinearApolloSixFrameDisplay/stateModel/index.d.ts +1 -1
  59. package/dist/LinearApolloSixFrameDisplay/stateModel/index.d.ts.map +1 -1
  60. package/dist/LinearApolloSixFrameDisplay/stateModel/layouts.d.ts +2 -3
  61. package/dist/LinearApolloSixFrameDisplay/stateModel/layouts.d.ts.map +1 -1
  62. package/dist/LinearApolloSixFrameDisplay/stateModel/mouseEvents.d.ts +2 -2
  63. package/dist/LinearApolloSixFrameDisplay/stateModel/mouseEvents.d.ts.map +1 -1
  64. package/dist/LinearApolloSixFrameDisplay/stateModel/rendering.d.ts +2 -3
  65. package/dist/LinearApolloSixFrameDisplay/stateModel/rendering.d.ts.map +1 -1
  66. package/dist/OntologyManager/OntologyStore/fulltext.d.ts.map +1 -1
  67. package/dist/OntologyManager/index.d.ts +734 -0
  68. package/dist/OntologyManager/index.d.ts.map +1 -0
  69. package/dist/TabularEditor/HybridGrid/NumberCell.d.ts.map +1 -1
  70. package/dist/components/AddCodingSequence.d.ts +14 -0
  71. package/dist/components/AddCodingSequence.d.ts.map +1 -0
  72. package/dist/components/AddRefSeqAliases.d.ts.map +1 -1
  73. package/dist/components/CopyFeature.d.ts.map +1 -1
  74. package/dist/components/CreateApolloAnnotation.d.ts.map +1 -1
  75. package/dist/components/DeleteFeature.d.ts.map +1 -1
  76. package/dist/components/DownloadGFF3.d.ts.map +1 -1
  77. package/dist/components/DuplicateFeatureDialog.d.ts +12 -0
  78. package/dist/components/DuplicateFeatureDialog.d.ts.map +1 -0
  79. package/dist/components/ImportAnnotations.d.ts +9 -0
  80. package/dist/components/ImportAnnotations.d.ts.map +1 -0
  81. package/dist/components/ManageUsers.d.ts.map +1 -1
  82. package/dist/components/OntologyTermMultiSelect.d.ts.map +1 -1
  83. package/dist/components/ViewChangeLog.d.ts.map +1 -1
  84. package/dist/components/index.d.ts +2 -0
  85. package/dist/components/index.d.ts.map +1 -1
  86. package/dist/extensions/annotationFromPileup.d.ts.map +1 -1
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.esm.js +835 -434
  89. package/dist/index.esm.js.map +1 -1
  90. package/dist/jbrowse-plugin-apollo.cjs.development.js +830 -429
  91. package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
  92. package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
  93. package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
  94. package/dist/jbrowse-plugin-apollo.umd.development.js +868 -468
  95. package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
  96. package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
  97. package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
  98. package/dist/session/ClientDataStore.d.ts +2194 -0
  99. package/dist/session/ClientDataStore.d.ts.map +1 -0
  100. package/dist/session/handleApolloFeaturesUrlParam.d.ts +8 -0
  101. package/dist/session/handleApolloFeaturesUrlParam.d.ts.map +1 -0
  102. package/dist/session/session.d.ts +6788 -0
  103. package/dist/session/session.d.ts.map +1 -0
  104. package/dist/util/displayUtils.d.ts +36 -0
  105. package/dist/util/displayUtils.d.ts.map +1 -0
  106. package/dist/util/sequenceUtils.d.ts +2 -0
  107. package/dist/util/sequenceUtils.d.ts.map +1 -0
  108. package/package.json +14 -14
  109. package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +73 -38
  110. package/src/ApolloInternetAccount/components/LoginButtons.tsx +31 -10
  111. package/src/ApolloInternetAccount/model.ts +8 -5
  112. package/src/ApolloJobModel.ts +3 -8
  113. package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +1 -1
  114. package/src/BackendDrivers/CollaborationServerDriver.ts +7 -11
  115. package/src/BackendDrivers/LocalDriver/LocalDriver.ts +9 -3
  116. package/src/FeatureDetailsWidget/ApolloFeatureDetailsWidget.tsx +1 -0
  117. package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +4 -2
  118. package/src/FeatureDetailsWidget/Attributes.tsx +1 -0
  119. package/src/FeatureDetailsWidget/DefaultAttributeEditor.tsx +1 -0
  120. package/src/FeatureDetailsWidget/DefaultAttributeViewer.tsx +1 -0
  121. package/src/FeatureDetailsWidget/NumberTextField.tsx +1 -0
  122. package/src/FeatureDetailsWidget/StringTextField.tsx +1 -0
  123. package/src/FeatureDetailsWidget/TranscriptSequence.tsx +2 -1
  124. package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +17 -354
  125. package/src/FeatureDetailsWidget/Translation.tsx +366 -0
  126. package/src/LinearApolloDisplay/components/CheckResultWarnings.tsx +4 -1
  127. package/src/LinearApolloDisplay/glyphs/ExonGlyph.ts +2 -5
  128. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +4 -6
  129. package/src/LinearApolloDisplay/glyphs/TranscriptGlyph.ts +39 -6
  130. package/src/LinearApolloDisplay/stateModel/base.ts +47 -23
  131. package/src/LinearApolloDisplay/stateModel/layouts.ts +7 -5
  132. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +4 -1
  133. package/src/LinearApolloDisplay/stateModel/rendering.ts +1 -5
  134. package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +10 -10
  135. package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +5 -3
  136. package/src/LinearApolloSixFrameDisplay/components/TrackLines.tsx +1 -1
  137. package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +2 -3
  138. package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +12 -20
  139. package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +10 -6
  140. package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +38 -45
  141. package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +1 -4
  142. package/src/OntologyManager/OntologyStore/fulltext.ts +0 -2
  143. package/src/OntologyManager/OntologyStore/index.ts +1 -1
  144. package/src/TabularEditor/HybridGrid/ChangeHandling.ts +2 -2
  145. package/src/TabularEditor/HybridGrid/NumberCell.tsx +2 -1
  146. package/src/components/AddAssemblyAliases.tsx +1 -1
  147. package/src/components/AddCodingSequence.tsx +255 -0
  148. package/src/components/AddFeature.tsx +1 -1
  149. package/src/components/AddRefSeqAliases.tsx +3 -2
  150. package/src/components/CopyFeature.tsx +9 -7
  151. package/src/components/CreateApolloAnnotation.tsx +32 -24
  152. package/src/components/DeleteAssembly.tsx +2 -2
  153. package/src/components/DeleteFeature.tsx +1 -4
  154. package/src/components/DownloadGFF3.tsx +4 -3
  155. package/src/components/DuplicateFeatureDialog.tsx +154 -0
  156. package/src/components/DuplicateTranscript.tsx +1 -1
  157. package/src/components/FilterTranscripts.tsx +1 -1
  158. package/src/components/ImportAnnotations.tsx +108 -0
  159. package/src/components/ManageUsers.tsx +1 -2
  160. package/src/components/OntologyTermAutocomplete.tsx +1 -3
  161. package/src/components/OntologyTermMultiSelect.tsx +3 -1
  162. package/src/components/ViewChangeLog.tsx +2 -1
  163. package/src/components/index.ts +2 -0
  164. package/src/extensions/annotationFromPileup.ts +34 -26
  165. package/src/index.ts +9 -8
  166. package/src/session/ClientDataStore.ts +16 -21
  167. package/src/session/handleApolloFeaturesUrlParam.ts +122 -0
  168. package/src/session/session.ts +49 -9
  169. package/src/util/annotationFeatureUtils.ts +1 -1
  170. package/src/util/displayUtils.ts +1 -1
  171. package/src/util/glyphUtils.ts +3 -3
  172. 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.0.1";
73
+ var version = "1.1.1";
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
- if (!result) {
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 Boolean(selectedFeature && feature._id === selectedFeature._id);
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
  }
@@ -2885,7 +3030,7 @@ class LocalDriver extends BackendDriver {
2885
3030
  async getRegions(assemblyName) {
2886
3031
  const session = util.getSession(this.clientStore);
2887
3032
  const { assemblyManager } = session;
2888
- const assembly = await assemblyManager.waitForAssembly(assemblyName);
3033
+ const assembly = (await assemblyManager.waitForAssembly(assemblyName));
2889
3034
  if (!assembly) {
2890
3035
  throw new Error(`Assembly not found: "${assemblyName}"`);
2891
3036
  }
@@ -2901,7 +3046,7 @@ class LocalDriver extends BackendDriver {
2901
3046
  async getRefNameAliases(assemblyName) {
2902
3047
  const session = util.getSession(this.clientStore);
2903
3048
  const { assemblyManager } = session;
2904
- const assembly = await assemblyManager.waitForAssembly(assemblyName);
3049
+ const assembly = (await assemblyManager.waitForAssembly(assemblyName));
2905
3050
  if (!assembly) {
2906
3051
  throw new Error(`Assembly not found: "${assemblyName}"`);
2907
3052
  }
@@ -3560,7 +3705,7 @@ class CollaborationServerDriver extends BackendDriver {
3560
3705
  const localSessionId = shared.makeUserSessionId(token);
3561
3706
  const changeManager = new ChangeManager(this.clientStore);
3562
3707
  // Save server last change sequence into session storage
3563
- internetAccount.setLastChangeSequenceNumber(Number(message.changeSequence));
3708
+ internetAccount.setLastChangeSequenceNumber(message.changeSequence);
3564
3709
  if (message.userSessionId === localSessionId) {
3565
3710
  return; // we did this change, no need to apply it again
3566
3711
  }
@@ -3608,14 +3753,10 @@ class CollaborationServerDriver extends BackendDriver {
3608
3753
  const seq = await inFlightPromise;
3609
3754
  return { seq, refSeq };
3610
3755
  }
3611
- let apolloAssembly = this.clientStore.assemblies.get(assemblyName);
3612
- if (!apolloAssembly) {
3613
- apolloAssembly = this.clientStore.addAssembly(assemblyName);
3614
- }
3615
- let apolloRefSeq = apolloAssembly.refSeqs.get(refSeq);
3616
- if (!apolloRefSeq) {
3617
- apolloRefSeq = apolloAssembly.addRefSeq(refSeq, refName);
3618
- }
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);
3619
3760
  const clientStoreSequence = apolloRefSeq.getSequence(start, end);
3620
3761
  if (clientStoreSequence.length === end - start) {
3621
3762
  return { seq: clientStoreSequence, refSeq };
@@ -3972,7 +4113,7 @@ function AddFeature({ changeManager, handleClose, region, session, }) {
3972
4113
  setType(NewFeature[value]);
3973
4114
  }
3974
4115
  };
3975
- let submitDisabled = Boolean(error) || !(start && end && type);
4116
+ let submitDisabled = error || !(start && end && type);
3976
4117
  if ((type === NewFeature.CUSTOM && !customType) ||
3977
4118
  (!strand && type === NewFeature.GENE_AND_SUBFEATURES) ||
3978
4119
  (!strand && type === NewFeature.TRANSCRIPT_AND_SUBFEATURES)) {
@@ -3993,7 +4134,7 @@ function AddFeature({ changeManager, handleClose, region, session, }) {
3993
4134
  function DeleteAssembly({ changeManager, handleClose, session, }) {
3994
4135
  const { internetAccounts } = mobxStateTree.getRoot(session);
3995
4136
  const [errorMessage, setErrorMessage] = React.useState('');
3996
- const [confirmDelete, setconfirmDelete] = React.useState(false);
4137
+ const [confirmDelete, setConfirmDelete] = React.useState(false);
3997
4138
  const [submitted, setSubmitted] = React.useState(false);
3998
4139
  const apolloInternetAccounts = internetAccounts.filter((ia) => ia.type === 'ApolloInternetAccount');
3999
4140
  if (apolloInternetAccounts.length === 0) {
@@ -4034,7 +4175,7 @@ function DeleteAssembly({ changeManager, handleClose, session, }) {
4034
4175
  event.preventDefault();
4035
4176
  }
4036
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: () => {
4037
- setconfirmDelete(!confirmDelete);
4178
+ setConfirmDelete(!confirmDelete);
4038
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] }));
4039
4180
  }
4040
4181
 
@@ -4043,7 +4184,7 @@ function Export(props) {
4043
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" }) }));
4044
4185
  }
4045
4186
  function DownloadGFF3({ handleClose, session, assembly: assemblyName, }) {
4046
- const [includeFASTA, setincludeFASTA] = React.useState(false);
4187
+ const [includeFASTA, setIncludeFASTA] = React.useState(false);
4047
4188
  const [errorMessage, setErrorMessage] = React.useState('');
4048
4189
  const { getInternetAccount } = session.apolloDataStore;
4049
4190
  const { assemblyManager } = session;
@@ -4092,7 +4233,7 @@ function DownloadGFF3({ handleClose, session, assembly: assemblyName, }) {
4092
4233
  window.open(exportUri, '_blank');
4093
4234
  }
4094
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: () => {
4095
- setincludeFASTA(!includeFASTA);
4236
+ setIncludeFASTA(!includeFASTA);
4096
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] }));
4097
4238
  }
4098
4239
  function getAssemblyGFF3Stream(assemblyName) {
@@ -4131,6 +4272,49 @@ async function* getFeaturesForAssembly(assemblyName) {
4131
4272
  db.close();
4132
4273
  }
4133
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
+
4134
4318
  function ImportFeatures({ changeManager, handleClose, session, }) {
4135
4319
  const { apolloDataStore } = session;
4136
4320
  const [file, setFile] = React.useState();
@@ -4534,7 +4718,7 @@ function ManageUsers({ changeManager, handleClose, session, }) {
4534
4718
  });
4535
4719
  return newRow;
4536
4720
  }
4537
- 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, slots: { toolbar: xDataGrid.GridToolbar }, getRowHeight: () => 'auto', isCellEditable: (params) => !isCurrentUser(params.id), processRowUpdate: processRowUpdate, onProcessRowUpdateError: (error) => {
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) => {
4538
4722
  setErrorMessage(String(error));
4539
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] }));
4540
4724
  }
@@ -4844,12 +5028,13 @@ const AddRefSeqAliases = mobxReact.observer(function AddRefSeqAliases({ changeMa
4844
5028
  const [enableSubmit, setEnableSubmit] = React.useState(false);
4845
5029
  const [selectedAssembly, setSelectedAssembly] = React.useState();
4846
5030
  const [selectedRows, setSelectedRows] = React.useState([]);
4847
- const [refNameAliasMap, setRefNameAliasMap] = React.useState(new Map());
5031
+ const [refNameAliasMap, setRefNameAliasMap] = React.useState(() => new Map());
4848
5032
  const { apolloDataStore } = session;
4849
5033
  const { collaborationServerDriver } = apolloDataStore;
4850
5034
  const assemblies = collaborationServerDriver.getAssemblies();
4851
5035
  React.useEffect(() => {
4852
5036
  if (assemblies.length > 0) {
5037
+ // eslint-disable-next-line @eslint-react/set-state-in-effect
4853
5038
  setSelectedAssembly(assemblies[0]);
4854
5039
  collaborationServerDriver
4855
5040
  .getRefNameAliases(assemblies[0].name)
@@ -4861,7 +5046,7 @@ const AddRefSeqAliases = mobxReact.observer(function AddRefSeqAliases({ changeMa
4861
5046
  setErrorMessage('Error fetching refName aliases for assembly');
4862
5047
  });
4863
5048
  }
4864
- // eslint-disable-next-line react-hooks/exhaustive-deps
5049
+ // eslint-disable-next-line @eslint-react/exhaustive-deps
4865
5050
  }, []);
4866
5051
  const initializeRefNameAliasMap = (refNameAliasesList) => {
4867
5052
  const initialMap = new Map();
@@ -5266,17 +5451,23 @@ const useStyles$d = tssReact.makeStyles()((theme) => ({
5266
5451
  }));
5267
5452
  function GoogleButton(props) {
5268
5453
  const { classes } = useStyles$d();
5269
- const { disabled } = props;
5270
- return (jsxRuntime.jsx(material.Button, { className: classes.loginButton, variant: "outlined", startIcon: jsxRuntime.jsx(Google, { color: disabled ? 'disabled' : undefined }), ...props, children: "Sign in with Google" }));
5454
+ const { message } = props;
5455
+ return (jsxRuntime.jsx(material.Button, { className: classes.loginButton, variant: "outlined", startIcon: jsxRuntime.jsx(Google, {}), ...props, children: message }));
5271
5456
  }
5272
5457
  function MicrosoftButton(props) {
5273
5458
  const { classes } = useStyles$d();
5274
- const { disabled } = props;
5275
- return (jsxRuntime.jsx(material.Button, { className: classes.loginButton, variant: "outlined", startIcon: jsxRuntime.jsx(Microsoft, { color: disabled ? 'disabled' : undefined }), ...props, children: "Sign in with Microsoft" }));
5459
+ const { message } = props;
5460
+ return (jsxRuntime.jsx(material.Button, { className: classes.loginButton, variant: "outlined", startIcon: jsxRuntime.jsx(Microsoft, {}), ...props, children: message }));
5276
5461
  }
5277
5462
  function GuestButton(props) {
5278
5463
  const { classes } = useStyles$d();
5279
- return (jsxRuntime.jsx(material.Button, { className: classes.loginButton, variant: "outlined", startIcon: jsxRuntime.jsx(AccountCircleIcon, { fontSize: "small" }), ...props, children: "Continue as Guest" }));
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 }));
5280
5471
  }
5281
5472
 
5282
5473
  const useStyles$c = tssReact.makeStyles()((theme) => ({
@@ -5288,7 +5479,7 @@ const useStyles$c = tssReact.makeStyles()((theme) => ({
5288
5479
  const AuthTypeSelector = ({ baseURL, handleClose, name, }) => {
5289
5480
  const { classes } = useStyles$c();
5290
5481
  const [errorMessage, setErrorMessage] = React.useState('');
5291
- const [loginTypes, setLoginTypes] = React.useState([]);
5482
+ const [loginTypes, setLoginTypes] = React.useState(null);
5292
5483
  React.useEffect(() => {
5293
5484
  const controller = new AbortController();
5294
5485
  const { signal } = controller;
@@ -5302,6 +5493,9 @@ const AuthTypeSelector = ({ baseURL, handleClose, name, }) => {
5302
5493
  }
5303
5494
  const data = (await response.json());
5304
5495
  setLoginTypes(data);
5496
+ if (data.length === 0) {
5497
+ setErrorMessage('No login types configured');
5498
+ }
5305
5499
  }
5306
5500
  getAuthTypes().catch((error) => {
5307
5501
  if (!aborting.isAbortException(error)) {
@@ -5313,26 +5507,35 @@ const AuthTypeSelector = ({ baseURL, handleClose, name, }) => {
5313
5507
  };
5314
5508
  }, [baseURL]);
5315
5509
  function handleClick(authType) {
5316
- if (authType === 'google') {
5317
- handleClose('google');
5318
- }
5319
- else if (authType === 'microsoft') {
5320
- handleClose('microsoft');
5321
- }
5322
- else {
5323
- handleClose('guest');
5324
- }
5325
- }
5326
- const allowGoogle = loginTypes.includes('google');
5327
- const allowMicrosoft = loginTypes.includes('microsoft');
5328
- const allowGuest = loginTypes.includes('guest');
5329
- return (jsxRuntime.jsxs(Dialog, { open: true, title: `Log in to ${name}`, handleClose: handleClose, maxWidth: false, "data-testid": "login-apollo", children: [jsxRuntime.jsxs(material.DialogContent, { style: { display: 'flex', flexDirection: 'column', paddingTop: 8 }, children: [allowGoogle ? (jsxRuntime.jsx(GoogleButton, { disabled: !allowGoogle, onClick: () => {
5330
- handleClick('google');
5331
- } })) : null, allowMicrosoft ? (jsxRuntime.jsx(MicrosoftButton, { disabled: !allowMicrosoft, onClick: () => {
5332
- handleClick('microsoft');
5333
- } })) : null, allowGuest ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(material.Divider, { className: classes.divider }), jsxRuntime.jsx(GuestButton, { onClick: () => {
5334
- handleClick('guest');
5335
- } })] })) : null] }), jsxRuntime.jsx(material.DialogActions, { children: jsxRuntime.jsx(material.Button, { variant: "outlined", type: "submit", onClick: () => {
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: () => {
5336
5539
  handleClose();
5337
5540
  }, children: "Cancel" }) }), errorMessage ? (jsxRuntime.jsx(material.DialogContent, { children: jsxRuntime.jsx(material.DialogContentText, { color: "error", children: errorMessage }) })) : null] }));
5338
5541
  };
@@ -5528,13 +5731,13 @@ const stateModelFactory$3 = (configSchema) => {
5528
5731
  },
5529
5732
  ]);
5530
5733
  });
5531
- if (authType !== 'guest') {
5734
+ if (authType.needsPopup) {
5532
5735
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
5533
- self.openAuthWindow(authType, resolve, reject);
5736
+ self.openAuthWindow(authType.name, resolve, reject);
5534
5737
  return;
5535
5738
  }
5536
5739
  const url = new URL('auth/login', baseURL);
5537
- const searchParams = new URLSearchParams({ type: authType });
5740
+ const searchParams = new URLSearchParams({ type: authType.name });
5538
5741
  url.search = searchParams.toString();
5539
5742
  const uri = url.toString();
5540
5743
  const response = await fetch(uri, { signal: self.controller.signal });
@@ -5655,7 +5858,7 @@ const stateModelFactory$3 = (configSchema) => {
5655
5858
  socket.on('COMMON', (message) => {
5656
5859
  if ('checkResult' in message) {
5657
5860
  if (message.deleted) {
5658
- deleteCheckResult(message.checkResult._id.toString());
5861
+ deleteCheckResult(message.checkResult._id);
5659
5862
  }
5660
5863
  else {
5661
5864
  addCheckResult(message.checkResult);
@@ -6275,7 +6478,9 @@ const DefaultAttributeEditor = mobxReact.observer(function DefaultAttributeEdito
6275
6478
  return newValues;
6276
6479
  });
6277
6480
  }
6278
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [newValues.map((value, idx) => (jsxRuntime.jsxs("div", { style: { display: 'flex' }, children: [jsxRuntime.jsx(StringTextField, { value: value, onChangeCommitted: (editedValue) => {
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) => {
6279
6484
  updateValue(idx, editedValue);
6280
6485
  }, variant: "outlined", fullWidth: true }), jsxRuntime.jsx(material.IconButton, { "aria-label": "delete", size: "medium", edge: "end", onClick: () => {
6281
6486
  deleteValue(idx);
@@ -6287,7 +6492,9 @@ const DefaultAttributeEditor = mobxReact.observer(function DefaultAttributeEdito
6287
6492
  });
6288
6493
 
6289
6494
  function DefaultAttributeViewer({ values }) {
6290
- return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: values?.map((value, idx) => (jsxRuntime.jsx(material.Typography, { variant: "body2", color: "textSecondary", children: value }, `${idx}.${value}`))) }));
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}`))) }));
6291
6498
  }
6292
6499
 
6293
6500
  const useStyles$a = tssReact.makeStyles()((theme) => ({
@@ -6593,6 +6800,7 @@ const ApolloFeatureDetailsWidget = mobxReact.observer(function ApolloFeatureDeta
6593
6800
  const { classes } = useStyles$8();
6594
6801
  const [panelState, setPanelState] = React.useState(['attributes']);
6595
6802
  React.useEffect(() => {
6803
+ // eslint-disable-next-line @eslint-react/set-state-in-effect
6596
6804
  setPanelState(['attributes']);
6597
6805
  }, [feature]);
6598
6806
  if (!(feature && currentAssembly)) {
@@ -6894,7 +7102,7 @@ const TranscriptSequence = mobxReact.observer(function TranscriptSequence({ asse
6894
7102
  else {
6895
7103
  setSequenceOptions(defaultSequenceOptions);
6896
7104
  }
6897
- // eslint-disable-next-line react-hooks/exhaustive-deps
7105
+ // eslint-disable-next-line @eslint-react/exhaustive-deps
6898
7106
  }, [feature]);
6899
7107
  if (!(currentAssembly && refData)) {
6900
7108
  return null;
@@ -6957,19 +7165,6 @@ const TranscriptSequence = mobxReact.observer(function TranscriptSequence({ asse
6957
7165
  .join(';'), "(strand=", feature.strand === 1 ? '+' : '-', ";length=", getSequenceLength(sequenceSegments), ")", jsxRuntime.jsx("br", {}), wrapSequence(sequenceSegments, SEQUENCE_WRAP_LENGTH)] })] }));
6958
7166
  });
6959
7167
 
6960
- const StyledTextField = styled(NumberTextField)(() => ({
6961
- '&.MuiFormControl-root': {
6962
- marginTop: 0,
6963
- marginBottom: 0,
6964
- width: '100%',
6965
- },
6966
- '& .MuiInputBase-input': {
6967
- fontSize: 12,
6968
- height: 20,
6969
- padding: 1,
6970
- paddingLeft: 10,
6971
- },
6972
- }));
6973
7168
  const SequenceContainer = styled('div')({
6974
7169
  display: 'flex',
6975
7170
  justifyContent: 'center',
@@ -6991,6 +7186,195 @@ const StyledAccordionSummary$1 = styled(material.AccordionSummary)(() => ({
6991
7186
  maxHeight: 30,
6992
7187
  },
6993
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
+ if (startCodonIndex === -1) {
7259
+ notify('Start codon not found', 'error');
7260
+ return;
7261
+ }
7262
+ const stopCodonIndex = proteinSequence.indexOf('*', startCodonIndex);
7263
+ if (stopCodonIndex === -1) {
7264
+ notify('Stop codon not found', 'error');
7265
+ return;
7266
+ }
7267
+ const startCodonPos = startCodonIndex * 3;
7268
+ const stopCodonPos = stopCodonIndex * 3;
7269
+ const startCodonGenomicLoc = getCodonGenomicLocation(startCodonPos);
7270
+ let stopCodonGenomicLoc = getCodonGenomicLocation(stopCodonPos);
7271
+ if (strand === 1) {
7272
+ if (startCodonGenomicLoc > stopCodonGenomicLoc) {
7273
+ notify('Start codon genomic location should be less than stop codon genomic location', 'error');
7274
+ return;
7275
+ }
7276
+ let promise;
7277
+ stopCodonGenomicLoc += 3; // move to end of stop codon
7278
+ if (startCodonGenomicLoc !== cdsMin) {
7279
+ promise = new Promise((resolve) => {
7280
+ updateCDSLocation(cdsMin, startCodonGenomicLoc, feature, true, () => {
7281
+ resolve(true);
7282
+ });
7283
+ });
7284
+ }
7285
+ if (stopCodonGenomicLoc !== cdsMax) {
7286
+ if (promise) {
7287
+ void promise.then(() => {
7288
+ updateCDSLocation(cdsMax, stopCodonGenomicLoc, feature, false);
7289
+ });
7290
+ }
7291
+ else {
7292
+ updateCDSLocation(cdsMax, stopCodonGenomicLoc, feature, false);
7293
+ }
7294
+ }
7295
+ }
7296
+ if (strand === -1) {
7297
+ // reverse strand
7298
+ if (startCodonGenomicLoc < stopCodonGenomicLoc) {
7299
+ notify('Start codon genomic location should be less than stop codon genomic location', 'error');
7300
+ return;
7301
+ }
7302
+ let promise;
7303
+ stopCodonGenomicLoc -= 3; // move to end of stop codon
7304
+ if (startCodonGenomicLoc !== cdsMax) {
7305
+ promise = new Promise((resolve) => {
7306
+ updateCDSLocation(cdsMax, startCodonGenomicLoc, feature, false, () => {
7307
+ resolve(true);
7308
+ });
7309
+ });
7310
+ }
7311
+ if (stopCodonGenomicLoc !== cdsMin) {
7312
+ if (promise) {
7313
+ void promise.then(() => {
7314
+ updateCDSLocation(cdsMin, stopCodonGenomicLoc, feature, true);
7315
+ });
7316
+ }
7317
+ else {
7318
+ updateCDSLocation(cdsMin, stopCodonGenomicLoc, feature, true);
7319
+ }
7320
+ }
7321
+ }
7322
+ notify('Translation sequence trimmed to start and stop codons', 'success');
7323
+ };
7324
+ 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) => {
7325
+ const codonGenomicPos = idx * 3;
7326
+ if (protein === 'M') {
7327
+ return (jsxRuntime.jsx(material.Typography, { component: 'span', style: {
7328
+ backgroundColor: changeInProgress
7329
+ ? 'lightgray'
7330
+ : 'yellow',
7331
+ cursor: 'pointer',
7332
+ border: '1px solid black',
7333
+ }, onClick: () => {
7334
+ if (changeInProgress) {
7335
+ return;
7336
+ }
7337
+ // NOTE: codonGenomicPos is important here for calculating the genomic location
7338
+ // of the start codon. We are using the codonGenomicPos as the key in the typography
7339
+ // elements to maintain the genomic postion of the codon start
7340
+ const startCodonGenomicLocation = getCodonGenomicLocation(codonGenomicPos);
7341
+ if (startCodonGenomicLocation !== cdsMin &&
7342
+ strand === 1) {
7343
+ updateCDSLocation(cdsMin, startCodonGenomicLocation, feature, true);
7344
+ }
7345
+ if (startCodonGenomicLocation !== cdsMax &&
7346
+ strand === -1) {
7347
+ updateCDSLocation(cdsMax, startCodonGenomicLocation, feature, false);
7348
+ }
7349
+ }, children: protein }, codonGenomicPos));
7350
+ }
7351
+ if (protein === '*') {
7352
+ return (jsxRuntime.jsx(material.Typography, { style: { backgroundColor: 'red', color: 'white' }, component: 'span', children: protein }, codonGenomicPos));
7353
+ }
7354
+ // Pass the codonGenomicPos as the key to maintain the genomic position of the codon
7355
+ return (jsxRuntime.jsx(material.Typography, { component: 'span', children: protein }, codonGenomicPos));
7356
+ }) }) }), jsxRuntime.jsxs("div", { style: {
7357
+ marginTop: 10,
7358
+ display: 'flex',
7359
+ flexDirection: 'row',
7360
+ alignItems: 'center',
7361
+ gap: 10,
7362
+ }, 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 } }) }) })] })] })] }) }));
7363
+ }
7364
+
7365
+ const StyledTextField = styled(NumberTextField)(() => ({
7366
+ '&.MuiFormControl-root': {
7367
+ marginTop: 0,
7368
+ marginBottom: 0,
7369
+ width: '100%',
7370
+ },
7371
+ '& .MuiInputBase-input': {
7372
+ fontSize: 12,
7373
+ height: 20,
7374
+ padding: 1,
7375
+ paddingLeft: 10,
7376
+ },
7377
+ }));
6994
7378
  const Strand = (props) => {
6995
7379
  const { strand } = props;
6996
7380
  return (jsxRuntime.jsx("div", { children: strand === 1 ? (jsxRuntime.jsx(AddIcon, {})) : strand === -1 ? (jsxRuntime.jsx(RemoveIcon, {})) : (jsxRuntime.jsx(material.Typography, { component: 'span', children: "N/A" })) }));
@@ -7009,7 +7393,6 @@ const TranscriptWidgetEditLocation = mobxReact.observer(function TranscriptWidge
7009
7393
  const currentAssembly = session.apolloDataStore.assemblies.get(assembly);
7010
7394
  const refData = currentAssembly?.getByRefName(refName);
7011
7395
  const { changeManager } = session.apolloDataStore;
7012
- const seqRef = React.useRef(null);
7013
7396
  const { changeInProgress } = session;
7014
7397
  if (!refData) {
7015
7398
  return null;
@@ -7436,184 +7819,6 @@ const TranscriptWidgetEditLocation = mobxReact.observer(function TranscriptWidge
7436
7819
  },
7437
7820
  ];
7438
7821
  };
7439
- const getTranslationSequence = () => {
7440
- let wholeSequence = '';
7441
- const [firstLocation] = cdsLocations;
7442
- const sortedCDSLocations = firstLocation.toSorted(({ min: a }, { min: b }) => a - b);
7443
- for (const loc of sortedCDSLocations) {
7444
- wholeSequence += refData.getSequence(loc.min, loc.max);
7445
- }
7446
- if (strand === -1) {
7447
- // Original: ACGCAT
7448
- // Complement: TGCGTA
7449
- // Reverse complement: ATGCGT
7450
- wholeSequence = util.revcom(wholeSequence);
7451
- }
7452
- const elements = [];
7453
- for (let codonGenomicPos = 0; codonGenomicPos < wholeSequence.length; codonGenomicPos += 3) {
7454
- const codonSeq = wholeSequence
7455
- .slice(codonGenomicPos, codonGenomicPos + 3)
7456
- .toUpperCase();
7457
- const protein = util.defaultCodonTable[codonSeq] || '&';
7458
- // highlight start codon and stop codons
7459
- if (codonSeq === 'ATG') {
7460
- elements.push(jsxRuntime.jsx(material.Typography, { component: 'span', style: {
7461
- backgroundColor: changeInProgress ? 'lightgray' : 'yellow',
7462
- cursor: 'pointer',
7463
- border: '1px solid black',
7464
- }, onClick: () => {
7465
- if (changeInProgress) {
7466
- return;
7467
- }
7468
- // NOTE: codonGenomicPos is important here for calculating the genomic location
7469
- // of the start codon. We are using the codonGenomicPos as the key in the typography
7470
- // elements to maintain the genomic postion of the codon start
7471
- const startCodonGenomicLocation = getCodonGenomicLocation(codonGenomicPos);
7472
- if (startCodonGenomicLocation !== cdsMin && strand === 1) {
7473
- updateCDSLocation(cdsMin, startCodonGenomicLocation, feature, true);
7474
- }
7475
- if (startCodonGenomicLocation !== cdsMax && strand === -1) {
7476
- updateCDSLocation(cdsMax, startCodonGenomicLocation, feature, false);
7477
- }
7478
- }, children: protein }, codonGenomicPos));
7479
- }
7480
- else if (['TAA', 'TAG', 'TGA'].includes(codonSeq)) {
7481
- elements.push(jsxRuntime.jsx(material.Typography, { style: { backgroundColor: 'red', color: 'white' }, component: 'span', children: protein }, codonGenomicPos));
7482
- }
7483
- else {
7484
- elements.push(
7485
- // Pass the codonGenomicPos as the key to maintain the genomic position of the codon
7486
- jsxRuntime.jsx(material.Typography, { component: 'span', children: protein }, codonGenomicPos));
7487
- }
7488
- }
7489
- return elements;
7490
- };
7491
- // Codon position is the index of the start codon in the CDS genomic sequence
7492
- // Calculate the genomic location of the start codon based on the codon position in the CDS
7493
- const getCodonGenomicLocation = (codonGenomicPosition) => {
7494
- const [firstLocation] = cdsLocations;
7495
- let cdsLen = 0;
7496
- const sortedCDSLocations = firstLocation.toSorted(({ min: a }, { min: b }) => a - b);
7497
- // Suppose CDS locations are [{min: 0, max: 10}, {min: 20, max: 30}, {min: 40, max: 50}]
7498
- // and codonGenomicPosition is 25
7499
- // ((10 - 0) + (30 - 20) + (50 - 40)) > 25
7500
- // So, start codon is in (40, 50)
7501
- // 40 + (25-20) = 45 is the genomic location of the start codon
7502
- if (strand === 1) {
7503
- for (const loc of sortedCDSLocations) {
7504
- const locLength = loc.max - loc.min;
7505
- if (cdsLen + locLength > codonGenomicPosition) {
7506
- return loc.min + (codonGenomicPosition - cdsLen);
7507
- }
7508
- cdsLen += locLength;
7509
- }
7510
- }
7511
- else if (strand === -1) {
7512
- for (let i = sortedCDSLocations.length - 1; i >= 0; i--) {
7513
- const loc = sortedCDSLocations[i];
7514
- const locLength = loc.max - loc.min;
7515
- if (cdsLen + locLength > codonGenomicPosition) {
7516
- return loc.max - (codonGenomicPosition - cdsLen);
7517
- }
7518
- cdsLen += locLength;
7519
- }
7520
- }
7521
- if (strand === 1) {
7522
- return cdsMin;
7523
- }
7524
- return cdsMax;
7525
- };
7526
- const trimTranslationSequence = () => {
7527
- const sequenceElements = getTranslationSequence();
7528
- const translationSequence = sequenceElements
7529
- .map((el) => el.props.children)
7530
- .join('');
7531
- if (translationSequence.startsWith('M') &&
7532
- translationSequence.endsWith('*')) {
7533
- return;
7534
- }
7535
- // NOTE: We are maintaining the genomic location of the codon start as the "key"
7536
- // in typography elements. See getTranslationSequence function
7537
- const translSeqCodonStartGenomicPosArr = [];
7538
- for (const el of sequenceElements) {
7539
- translSeqCodonStartGenomicPosArr.push({
7540
- codonGenomicPos: el.key,
7541
- sequenceLetter: el.props.children,
7542
- });
7543
- }
7544
- if (translSeqCodonStartGenomicPosArr.length === 0) {
7545
- return;
7546
- }
7547
- // Trim any sequence before first start codon and after stop codon
7548
- const startCodonIndex = translationSequence.indexOf('M');
7549
- const stopCodonIndex = translationSequence.indexOf('*');
7550
- const startCodonPos = translSeqCodonStartGenomicPosArr[startCodonIndex].codonGenomicPos;
7551
- const stopCodonPos = translSeqCodonStartGenomicPosArr[stopCodonIndex].codonGenomicPos;
7552
- if (!startCodonPos || !stopCodonPos) {
7553
- return;
7554
- }
7555
- const startCodonGenomicLoc = getCodonGenomicLocation(startCodonPos);
7556
- let stopCodonGenomicLoc = getCodonGenomicLocation(stopCodonPos);
7557
- if (strand === 1) {
7558
- if (startCodonGenomicLoc > stopCodonGenomicLoc) {
7559
- notify('Start codon genomic location should be less than stop codon genomic location', 'error');
7560
- return;
7561
- }
7562
- let promise;
7563
- stopCodonGenomicLoc += 3; // move to end of stop codon
7564
- if (startCodonGenomicLoc !== cdsMin) {
7565
- promise = new Promise((resolve) => {
7566
- updateCDSLocation(cdsMin, startCodonGenomicLoc, feature, true, () => {
7567
- resolve(true);
7568
- });
7569
- });
7570
- }
7571
- if (stopCodonGenomicLoc !== cdsMax) {
7572
- if (promise) {
7573
- void promise.then(() => {
7574
- updateCDSLocation(cdsMax, stopCodonGenomicLoc, feature, false);
7575
- });
7576
- }
7577
- else {
7578
- updateCDSLocation(cdsMax, stopCodonGenomicLoc, feature, false);
7579
- }
7580
- }
7581
- }
7582
- if (strand === -1) {
7583
- // reverse strand
7584
- if (startCodonGenomicLoc < stopCodonGenomicLoc) {
7585
- notify('Start codon genomic location should be less than stop codon genomic location', 'error');
7586
- return;
7587
- }
7588
- let promise;
7589
- stopCodonGenomicLoc -= 3; // move to end of stop codon
7590
- if (startCodonGenomicLoc !== cdsMax) {
7591
- promise = new Promise((resolve) => {
7592
- updateCDSLocation(cdsMax, startCodonGenomicLoc, feature, false, () => {
7593
- resolve(true);
7594
- });
7595
- });
7596
- }
7597
- if (stopCodonGenomicLoc !== cdsMin) {
7598
- if (promise) {
7599
- void promise.then(() => {
7600
- updateCDSLocation(cdsMin, stopCodonGenomicLoc, feature, true);
7601
- });
7602
- }
7603
- else {
7604
- updateCDSLocation(cdsMin, stopCodonGenomicLoc, feature, true);
7605
- }
7606
- }
7607
- }
7608
- notify('Translation sequence trimmed to start and stop codons', 'success');
7609
- };
7610
- const onCopyClick = () => {
7611
- const seqDiv = seqRef.current;
7612
- if (!seqDiv) {
7613
- return;
7614
- }
7615
- void copyToClipboard(seqDiv);
7616
- };
7617
7822
  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) => {
7618
7823
  return updateCDSLocation(cdsMin, newLocation - 1, feature, true);
7619
7824
  }, 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) => {
@@ -7623,8 +7828,12 @@ const TranscriptWidgetEditLocation = mobxReact.observer(function TranscriptWidge
7623
7828
  }, 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) => {
7624
7829
  return updateCDSLocation(cdsMin, newLocation - 1, feature, true);
7625
7830
  }, 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) => {
7626
- return (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 &&
7627
- getFivePrimeSpliceSite(loc, index).map((site, idx) => (jsxRuntime.jsx(material.Typography, { 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) => {
7831
+ return (
7832
+ // eslint-disable-next-line @eslint-react/no-array-index-key
7833
+ 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 &&
7834
+ getFivePrimeSpliceSite(loc, index).map((site, idx) => (jsxRuntime.jsx(material.Typography
7835
+ // eslint-disable-next-line @eslint-react/no-array-index-key
7836
+ , { 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) => {
7628
7837
  return handleExonLocationChange(loc.min, newLocation - 1, feature, true);
7629
7838
  }, disabled: changeInProgress }) })) : (jsxRuntime.jsx(material.Grid, { size: 4, style: { padding: 0 }, children: jsxRuntime.jsx(StyledTextField, { margin: "dense", variant: "outlined", value: loc.max, onChangeCommitted: (newLocation) => {
7630
7839
  return handleExonLocationChange(loc.max, newLocation, feature, false);
@@ -7633,14 +7842,10 @@ const TranscriptWidgetEditLocation = mobxReact.observer(function TranscriptWidge
7633
7842
  }, 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) => {
7634
7843
  return handleExonLocationChange(loc.min, newLocation - 1, feature, true);
7635
7844
  }, disabled: changeInProgress }) })), jsxRuntime.jsx(material.Grid, { size: 1, children: index !== transcriptExonParts.length - 1 &&
7636
- getThreePrimeSpliceSite(loc, index).map((site, idx) => (jsxRuntime.jsx(material.Typography, { component: 'span', color: site.color, children: site.spliceSite }, idx))) })] })) }, index));
7637
- })] }), cdsPresent && (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: getTranslationSequence() }) }), jsxRuntime.jsxs("div", { style: {
7638
- marginTop: 10,
7639
- display: 'flex',
7640
- flexDirection: 'row',
7641
- alignItems: 'center',
7642
- gap: 10,
7643
- }, 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 } }) }) })] })] })] }) }))] }));
7845
+ getThreePrimeSpliceSite(loc, index).map((site, idx) => (jsxRuntime.jsx(material.Typography
7846
+ // eslint-disable-next-line @eslint-react/no-array-index-key
7847
+ , { component: 'span', color: site.color, children: site.spliceSite }, idx))) })] })) }, index));
7848
+ })] }), cdsPresent && (jsxRuntime.jsx(Translation, { changeInProgress: changeInProgress, cdsLocations: cdsLocations, refData: refData, strand: strand, updateCDSLocation: updateCDSLocation, cdsMin: cdsMin, cdsMax: cdsMax, feature: feature, session: session }))] }));
7644
7849
  });
7645
7850
 
7646
7851
  const HeaderTableCell = styled(material.TableCell)(() => ({
@@ -7677,7 +7882,7 @@ const ApolloTranscriptDetailsWidget = mobxReact.observer(function ApolloTranscri
7677
7882
  const { assembly, feature, refName } = model;
7678
7883
  React.useEffect(() => {
7679
7884
  setPanelState(DEFAULT_PANELS);
7680
- // eslint-disable-next-line react-hooks/exhaustive-deps
7885
+ // eslint-disable-next-line @eslint-react/exhaustive-deps
7681
7886
  }, [feature]);
7682
7887
  const session = util.getSession(model);
7683
7888
  const { pluginManager } = util.getEnv(session);
@@ -7736,8 +7941,8 @@ function handleFeatureTypeChange(changeManager, feature, oldType, newType) {
7736
7941
  typeName: 'TypeChange',
7737
7942
  changedIds: [featureId],
7738
7943
  featureId,
7739
- oldType: String(oldType),
7740
- newType: String(newType),
7944
+ oldType,
7945
+ newType,
7741
7946
  assembly: feature.assemblyId,
7742
7947
  });
7743
7948
  return changeManager.submit(change);
@@ -7833,7 +8038,7 @@ const NumberCell = mobxReact.observer(function NumberCell({ initialValue, notify
7833
8038
  if (initialValue !== value) {
7834
8039
  setValue(initialValue);
7835
8040
  }
7836
- // eslint-disable-next-line react-hooks/exhaustive-deps
8041
+ // eslint-disable-next-line @eslint-react/exhaustive-deps
7837
8042
  }, [initialValue]);
7838
8043
  React.useEffect(() => {
7839
8044
  if (blur) {
@@ -8674,8 +8879,9 @@ function draw$3(display, ctx, gene, row, rowInFeature, block) {
8674
8879
  const [top, left, width] = getFeatureBox(display, gene, row, block);
8675
8880
  const height = getRowCount$2(display, gene) * apolloRowHeight;
8676
8881
  if (width > 2) {
8677
- let selectedColor = configuration.readConfObject(session.getPluginConfiguration(), 'geneBackgroundColor', { featureType: gene.type });
8678
- selectedColor = material.alpha(theme.palette.background.paper, 0.6);
8882
+ const selectedColor = configuration.readConfObject(session.getPluginConfiguration(), 'geneBackgroundColor', {
8883
+ featureType: gene.type,
8884
+ }) ?? material.alpha(theme.palette.background.paper, 0.6);
8679
8885
  ctx.fillStyle = selectedColor;
8680
8886
  ctx.fillRect(left, top, width, height);
8681
8887
  }
@@ -8948,7 +9154,7 @@ function getLayout(display, feature) {
8948
9154
  }
8949
9155
  return layout;
8950
9156
  }
8951
- function getContextMenuItems$1(display, feature) {
9157
+ function getContextMenuItems$1(display, transcript) {
8952
9158
  const { changeManager, regions, selectedFeature, session } = display;
8953
9159
  const [region] = regions;
8954
9160
  const currentAssemblyId = display.getAssemblyId(region.assemblyName);
@@ -8958,7 +9164,7 @@ function getContextMenuItems$1(display, feature) {
8958
9164
  label: 'Open transcript editor',
8959
9165
  onClick: () => {
8960
9166
  const apolloTranscriptWidget = session.addWidget('ApolloTranscriptDetails', 'apolloTranscriptDetails', {
8961
- feature,
9167
+ feature: transcript,
8962
9168
  assembly: currentAssemblyId,
8963
9169
  changeManager,
8964
9170
  refName: region.refName,
@@ -8978,7 +9184,7 @@ function getContextMenuItems$1(display, feature) {
8978
9184
  doneCallback();
8979
9185
  },
8980
9186
  changeManager,
8981
- sourceFeature: feature,
9187
+ sourceFeature: transcript,
8982
9188
  sourceAssemblyId: currentAssemblyId,
8983
9189
  selectedFeature,
8984
9190
  setSelectedFeature: (feature) => {
@@ -8998,7 +9204,7 @@ function getContextMenuItems$1(display, feature) {
8998
9204
  doneCallback();
8999
9205
  },
9000
9206
  changeManager,
9001
- sourceFeature: feature,
9207
+ sourceFeature: transcript,
9002
9208
  sourceAssemblyId: currentAssemblyId,
9003
9209
  selectedFeature,
9004
9210
  setSelectedFeature: (feature) => {
@@ -9008,6 +9214,31 @@ function getContextMenuItems$1(display, feature) {
9008
9214
  ]);
9009
9215
  },
9010
9216
  });
9217
+ const { children } = transcript;
9218
+ if (!children) {
9219
+ return [];
9220
+ }
9221
+ const cdsChildren = [...children.values()].filter((child) => isCDSFeature(child, session));
9222
+ if (cdsChildren.length === 0) {
9223
+ menuItems.push({
9224
+ label: 'Add coding sequence',
9225
+ onClick: () => {
9226
+ session.queueDialog((doneCallback) => [
9227
+ AddCodingSequence,
9228
+ {
9229
+ session,
9230
+ handleClose: () => {
9231
+ doneCallback();
9232
+ },
9233
+ changeManager,
9234
+ sourceFeature: transcript,
9235
+ sourceAssemblyId: currentAssemblyId,
9236
+ refName: region.refName,
9237
+ },
9238
+ ]);
9239
+ },
9240
+ });
9241
+ }
9011
9242
  return menuItems;
9012
9243
  }
9013
9244
  // False positive here, none of these functions use "this"
@@ -9185,7 +9416,7 @@ function colorCode(letter, theme) {
9185
9416
  letterUpper === 'C' ||
9186
9417
  letterUpper === 'G' ||
9187
9418
  letterUpper === 'T') {
9188
- return theme.palette.bases[letterUpper].main.toString();
9419
+ return theme.palette.bases[letterUpper].main;
9189
9420
  }
9190
9421
  return 'lightgray';
9191
9422
  }
@@ -9282,8 +9513,7 @@ function baseModelFactory$2(_pluginManager, configSchema) {
9282
9513
  return internetAccounts.find((ia) => configuration.getConf(ia, 'internetAccountId') === internetAccountConfigId);
9283
9514
  },
9284
9515
  get changeManager() {
9285
- return self.session.apolloDataStore
9286
- .changeManager;
9516
+ return self.session.apolloDataStore.changeManager;
9287
9517
  },
9288
9518
  getAssemblyId(assemblyName) {
9289
9519
  const { assemblyManager } = self.session;
@@ -9294,12 +9524,10 @@ function baseModelFactory$2(_pluginManager, configSchema) {
9294
9524
  return assembly.name;
9295
9525
  },
9296
9526
  get selectedFeature() {
9297
- return self.session
9298
- .apolloSelectedFeature;
9527
+ return self.session.apolloSelectedFeature;
9299
9528
  },
9300
9529
  get hoveredFeature() {
9301
- return self.session
9302
- .apolloHoveredFeature;
9530
+ return self.session.apolloHoveredFeature;
9303
9531
  },
9304
9532
  }))
9305
9533
  .actions((self) => ({
@@ -9344,8 +9572,11 @@ function baseModelFactory$2(_pluginManager, configSchema) {
9344
9572
  const { filteredFeatureTypes, trackMenuItems: superTrackMenuItems } = self;
9345
9573
  return {
9346
9574
  trackMenuItems() {
9347
- const { graphical, table, showCheckResults } = self;
9348
- return [
9575
+ const { graphical, table, showCheckResults, session } = self;
9576
+ const [region] = self.regions;
9577
+ const { assemblyName } = region;
9578
+ const backendDriver = session.apolloDataStore.getBackendDriver(assemblyName);
9579
+ const items = [
9349
9580
  ...superTrackMenuItems(),
9350
9581
  {
9351
9582
  type: 'subMenu',
@@ -9397,7 +9628,7 @@ function baseModelFactory$2(_pluginManager, configSchema) {
9397
9628
  {
9398
9629
  label: 'Filter features by type',
9399
9630
  onClick: () => {
9400
- const session = self.session;
9631
+ const { session } = self;
9401
9632
  self.session.queueDialog((doneCallback) => [
9402
9633
  FilterFeatures,
9403
9634
  {
@@ -9413,76 +9644,93 @@ function baseModelFactory$2(_pluginManager, configSchema) {
9413
9644
  ]);
9414
9645
  },
9415
9646
  },
9416
- {
9417
- label: 'Export annotations',
9418
- icon: Export,
9647
+ ];
9648
+ if (backendDriver instanceof LocalDriver) {
9649
+ items.push({
9650
+ label: 'Import annotations',
9651
+ icon: InputIcon,
9419
9652
  onClick: () => {
9420
- const [region] = self.regions;
9421
- const { assemblyName } = region;
9422
- const assembly = self.getAssemblyId(assemblyName);
9423
- if (!assembly) {
9424
- return;
9425
- }
9426
- const session = self.session;
9427
9653
  session.queueDialog((doneCallback) => [
9428
- DownloadGFF3,
9654
+ ImportAnnotations,
9429
9655
  {
9430
9656
  session,
9657
+ assemblyName,
9431
9658
  handleClose: () => {
9432
9659
  doneCallback();
9433
9660
  },
9434
- assembly,
9435
9661
  },
9436
9662
  ]);
9437
9663
  },
9664
+ });
9665
+ }
9666
+ items.push({
9667
+ label: 'Export annotations',
9668
+ icon: Export,
9669
+ onClick: () => {
9670
+ const [region] = self.regions;
9671
+ const { assemblyName } = region;
9672
+ const assembly = self.getAssemblyId(assemblyName);
9673
+ if (!assembly) {
9674
+ return;
9675
+ }
9676
+ const { session } = self;
9677
+ session.queueDialog((doneCallback) => [
9678
+ DownloadGFF3,
9679
+ {
9680
+ session,
9681
+ handleClose: () => {
9682
+ doneCallback();
9683
+ },
9684
+ assembly,
9685
+ },
9686
+ ]);
9438
9687
  },
9439
- {
9440
- label: 'View Change Log',
9441
- icon: TrackChangesIcon,
9442
- onClick: () => {
9443
- const [region] = self.regions;
9444
- const { assemblyName } = region;
9445
- const assembly = self.getAssemblyId(assemblyName);
9446
- if (!assembly) {
9447
- return;
9448
- }
9449
- const session = self.session;
9450
- session.queueDialog((doneCallback) => [
9451
- ViewChangeLog,
9452
- {
9453
- session,
9454
- handleClose: () => {
9455
- doneCallback();
9456
- },
9457
- assembly,
9688
+ }, {
9689
+ label: 'View Change Log',
9690
+ icon: TrackChangesIcon,
9691
+ onClick: () => {
9692
+ const [region] = self.regions;
9693
+ const { assemblyName } = region;
9694
+ const assembly = self.getAssemblyId(assemblyName);
9695
+ if (!assembly) {
9696
+ return;
9697
+ }
9698
+ const { session } = self;
9699
+ session.queueDialog((doneCallback) => [
9700
+ ViewChangeLog,
9701
+ {
9702
+ session,
9703
+ handleClose: () => {
9704
+ doneCallback();
9458
9705
  },
9459
- ]);
9460
- },
9706
+ assembly,
9707
+ },
9708
+ ]);
9461
9709
  },
9462
- {
9463
- label: 'View Check Results',
9464
- icon: FactCheckIcon,
9465
- onClick: () => {
9466
- const [region] = self.regions;
9467
- const { assemblyName } = region;
9468
- const assembly = self.getAssemblyId(assemblyName);
9469
- if (!assembly) {
9470
- return;
9471
- }
9472
- const session = self.session;
9473
- session.queueDialog((doneCallback) => [
9474
- ViewCheckResults,
9475
- {
9476
- session,
9477
- handleClose: () => {
9478
- doneCallback();
9479
- },
9480
- assembly,
9710
+ }, {
9711
+ label: 'View Check Results',
9712
+ icon: FactCheckIcon,
9713
+ onClick: () => {
9714
+ const [region] = self.regions;
9715
+ const { assemblyName } = region;
9716
+ const assembly = self.getAssemblyId(assemblyName);
9717
+ if (!assembly) {
9718
+ return;
9719
+ }
9720
+ const { session } = self;
9721
+ session.queueDialog((doneCallback) => [
9722
+ ViewCheckResults,
9723
+ {
9724
+ session,
9725
+ handleClose: () => {
9726
+ doneCallback();
9481
9727
  },
9482
- ]);
9483
- },
9728
+ assembly,
9729
+ },
9730
+ ]);
9484
9731
  },
9485
- ];
9732
+ });
9733
+ return items;
9486
9734
  },
9487
9735
  };
9488
9736
  })
@@ -10548,8 +10796,7 @@ function baseModelFactory$1(_pluginManager, configSchema) {
10548
10796
  return internetAccounts.find((ia) => configuration.getConf(ia, 'internetAccountId') === internetAccountConfigId);
10549
10797
  },
10550
10798
  get changeManager() {
10551
- return self.session.apolloDataStore
10552
- .changeManager;
10799
+ return self.session.apolloDataStore.changeManager;
10553
10800
  },
10554
10801
  getAssemblyId(assemblyName) {
10555
10802
  const { assemblyManager } = self.session;
@@ -10560,12 +10807,10 @@ function baseModelFactory$1(_pluginManager, configSchema) {
10560
10807
  return assembly.name;
10561
10808
  },
10562
10809
  get selectedFeature() {
10563
- return self.session
10564
- .apolloSelectedFeature;
10810
+ return self.session.apolloSelectedFeature;
10565
10811
  },
10566
10812
  get hoveredFeature() {
10567
- return self.session
10568
- .apolloHoveredFeature;
10813
+ return self.session.apolloHoveredFeature;
10569
10814
  },
10570
10815
  get height() {
10571
10816
  const { sequenceRowHeight } = self;
@@ -10777,11 +11022,9 @@ const FilterTranscripts = mobxReact.observer(function FilterTranscripts({ source
10777
11022
  onUpdate(newForms);
10778
11023
  setExcludedTranscripts(newForms);
10779
11024
  };
10780
- 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) => (
10781
- // eslint-disable-next-line react/jsx-key
10782
- jsxRuntime.jsx(material.FormControlLabel, { control: jsxRuntime.jsx(material.Checkbox, { checked: !excludedTranscripts.includes(item), onChange: () => {
11025
+ 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: () => {
10783
11026
  handleChange(item);
10784
- }, slotProps: { input: { 'aria-label': 'controlled' } } }), label: item }))) }) }) })] }) }));
11027
+ }, slotProps: { input: { 'aria-label': 'controlled' } } }), label: item }, item))) }) }) })] }) }));
10785
11028
  });
10786
11029
 
10787
11030
  let forwardFillLight = null;
@@ -11627,8 +11870,7 @@ function baseModelFactory(_pluginManager, configSchema) {
11627
11870
  return internetAccounts.find((ia) => configuration.getConf(ia, 'internetAccountId') === internetAccountConfigId);
11628
11871
  },
11629
11872
  get changeManager() {
11630
- return self.session.apolloDataStore
11631
- .changeManager;
11873
+ return self.session.apolloDataStore.changeManager;
11632
11874
  },
11633
11875
  getAssemblyId(assemblyName) {
11634
11876
  const { assemblyManager } = self.session;
@@ -11639,12 +11881,10 @@ function baseModelFactory(_pluginManager, configSchema) {
11639
11881
  return assembly.name;
11640
11882
  },
11641
11883
  get selectedFeature() {
11642
- return self.session
11643
- .apolloSelectedFeature;
11884
+ return self.session.apolloSelectedFeature;
11644
11885
  },
11645
11886
  get hoveredFeature() {
11646
- return self.session
11647
- .apolloHoveredFeature;
11887
+ return self.session.apolloHoveredFeature;
11648
11888
  },
11649
11889
  }))
11650
11890
  .actions((self) => ({
@@ -11772,7 +12012,7 @@ function baseModelFactory(_pluginManager, configSchema) {
11772
12012
  {
11773
12013
  label: 'Filter features by type',
11774
12014
  onClick: () => {
11775
- const session = self.session;
12015
+ const { session } = self;
11776
12016
  self.session.queueDialog((doneCallback) => [
11777
12017
  FilterFeatures,
11778
12018
  {
@@ -12210,17 +12450,12 @@ function mouseEventsModelIntermediateFactory(pluginManager, configSchema) {
12210
12450
  if (!featureTypeOntology) {
12211
12451
  throw new Error('featureTypeOntology is undefined');
12212
12452
  }
12213
- let foundFeature;
12214
- if (self.geneTrackRowNums.includes(row)) {
12215
- foundFeature = layoutRow.find((f) => f.feature.type == 'exon' &&
12453
+ const foundFeature = self.geneTrackRowNums.includes(row)
12454
+ ? layoutRow.find((f) => f.feature.type == 'exon' &&
12216
12455
  bp >= f.feature.min &&
12217
- bp <= f.feature.max);
12218
- if (!foundFeature) {
12219
- foundFeature = layoutRow.find((f) => bp >= f.feature.min && bp <= f.feature.max);
12220
- }
12221
- }
12222
- else {
12223
- foundFeature = layoutRow.find((f) => {
12456
+ bp <= f.feature.max) ??
12457
+ layoutRow.find((f) => bp >= f.feature.min && bp <= f.feature.max)
12458
+ : layoutRow.find((f) => {
12224
12459
  const { feature } = f;
12225
12460
  const featureID = feature.attributes.get('gff_id')?.toString();
12226
12461
  const isTranscript = featureTypeOntology.isTypeOf(feature.type, 'transcript');
@@ -12242,7 +12477,6 @@ function mouseEventsModelIntermediateFactory(pluginManager, configSchema) {
12242
12477
  }
12243
12478
  return false;
12244
12479
  });
12245
- }
12246
12480
  if (!foundFeature) {
12247
12481
  return mousePosition;
12248
12482
  }
@@ -12565,27 +12799,33 @@ function CreateApolloAnnotation({ annotationFeature, assembly, handleClose, refS
12565
12799
  const [selectedDestinationFeature, setSelectedDestinationFeature] = React.useState();
12566
12800
  const apolloAssembly = apolloSessionModel.apolloDataStore.assemblies.get(assembly.name);
12567
12801
  const refSeq = apolloAssembly?.refSeqs.get(refSeqId);
12568
- const features = refSeq?.getFeatures(region.start, region.end);
12569
- const getDestinationFeatures = () => {
12570
- const filteredFeatures = [];
12571
- for (const f of features ?? []) {
12572
- if (f.min > region.end || f.max < region.start) {
12573
- continue;
12574
- }
12575
- // Destination feature should be of type gene
12576
- if (featureTypeOntology?.isTypeOf(f.type, 'gene')) {
12577
- const featureSnapshot = mobxStateTree.getSnapshot(f);
12578
- filteredFeatures.push(featureSnapshot);
12579
- }
12580
- }
12581
- return filteredFeatures;
12582
- };
12802
+ const features = React.useMemo(() => refSeq?.getFeatures(region.start, region.end), [refSeq, region.start, region.end]);
12583
12803
  React.useEffect(() => {
12804
+ const getDestinationFeatures = () => {
12805
+ const filteredFeatures = [];
12806
+ for (const f of features ?? []) {
12807
+ if (f.min > region.end || f.max < region.start) {
12808
+ continue;
12809
+ }
12810
+ // Destination feature should be of type gene
12811
+ if (featureTypeOntology?.isTypeOf(f.type, 'gene')) {
12812
+ const featureSnapshot = mobxStateTree.getSnapshot(f);
12813
+ filteredFeatures.push(featureSnapshot);
12814
+ }
12815
+ }
12816
+ return filteredFeatures;
12817
+ };
12584
12818
  setErrorMessage('');
12585
- const features = getDestinationFeatures();
12586
- setDestinationFeatures(features);
12587
- setSelectedDestinationFeature(features[0]);
12588
- }, [checkedChildrens, parentFeatureChecked, region]);
12819
+ const destinationFeatures = getDestinationFeatures();
12820
+ setDestinationFeatures(destinationFeatures);
12821
+ setSelectedDestinationFeature(destinationFeatures[0]);
12822
+ }, [
12823
+ checkedChildrens,
12824
+ featureTypeOntology,
12825
+ features,
12826
+ parentFeatureChecked,
12827
+ region,
12828
+ ]);
12589
12829
  const handleParentFeatureCheck = (event) => {
12590
12830
  const isChecked = event.target.checked;
12591
12831
  setParentFeatureChecked(isChecked);
@@ -12865,8 +13105,21 @@ function annotationFromPileup(pluggableElement) {
12865
13105
  getAnnotationFeature(jbrowseFeature, refSeqId) {
12866
13106
  const start = jbrowseFeature.get('start');
12867
13107
  const end = jbrowseFeature.get('end');
12868
- const strand = jbrowseFeature.get('strand');
12869
13108
  const name = jbrowseFeature.get('name');
13109
+ let strand;
13110
+ const tags = jbrowseFeature.get('tags');
13111
+ const { ts, TS, XS } = tags;
13112
+ for (const tag of [ts, TS, XS]) {
13113
+ if (!strand && tag) {
13114
+ if ([1, '1', '+'].includes(tag)) {
13115
+ strand = 1;
13116
+ }
13117
+ else if ([-1, '-1', '-'].includes(tag)) {
13118
+ strand = -1;
13119
+ }
13120
+ }
13121
+ }
13122
+ strand ??= jbrowseFeature.get('strand');
12870
13123
  const cigarData = jbrowseFeature.get('CIGAR');
12871
13124
  const ops = parseCigar(cigarData);
12872
13125
  let position = start;
@@ -12882,9 +13135,7 @@ function annotationFromPileup(pluggableElement) {
12882
13135
  case 'M':
12883
13136
  case '=':
12884
13137
  case 'X': {
12885
- if (currentExonStart === undefined) {
12886
- currentExonStart = position;
12887
- }
13138
+ currentExonStart ??= position;
12888
13139
  position += len;
12889
13140
  break;
12890
13141
  }
@@ -13298,7 +13549,7 @@ const LinearApolloDisplay = mobxReact.observer(function LinearApolloDisplay(prop
13298
13549
  : undefined, menuItems: contextMenuItems })] }))] }));
13299
13550
  });
13300
13551
 
13301
- const TrackLines = mobxReact.observer(function TrackLines({ model, hrStyle = { margin: 0, top: 0, color: 'black' }, idx = 0, }) {
13552
+ const TrackLines = mobxReact.observer(function TrackLines({ model, hrStyle = { margin: 0, top: 0, color: 'black' }, idx, }) {
13302
13553
  const { apolloRowHeight, highestRow, showFeatureLabels } = model;
13303
13554
  const featureLabelSpacer = showFeatureLabels ? 2 : 1;
13304
13555
  return (jsxRuntime.jsx("div", { style: {
@@ -13370,8 +13621,7 @@ const LinearApolloSixFrameDisplay = mobxReact.observer(function LinearApolloSixF
13370
13621
  regionNumber: idx,
13371
13622
  })?.offsetPx ?? 0) - lgv.offsetPx;
13372
13623
  const [feature] = checkResult.featureIds;
13373
- if (!feature ||
13374
- !feature.parent ||
13624
+ if (!feature?.parent ||
13375
13625
  !looksLikeGene(feature.parent, session)) {
13376
13626
  return null;
13377
13627
  }
@@ -13623,18 +13873,15 @@ function addTopLevelMenus(rootModel) {
13623
13873
  }
13624
13874
  }
13625
13875
 
13626
- /* eslint-disable @typescript-eslint/no-unsafe-assignment */
13627
13876
  /* eslint-disable @typescript-eslint/no-confusing-void-expression */
13628
13877
  const ApolloJobModel = mobxStateTree.types
13629
13878
  .model('JobsManager', {})
13630
13879
  .views((self) => ({
13631
13880
  get jobStatusWidget() {
13632
13881
  const { widgets } = util.getSession(self);
13633
- let jobStatusWidget = widgets.get('JobsList');
13634
- if (!jobStatusWidget) {
13882
+ const jobStatusWidget = widgets.get('JobsList') ??
13635
13883
  // @ts-expect-error: addWidget function not detected on the session
13636
- jobStatusWidget = util.getSession(self).addWidget('JobsListWidget', 'JobsList');
13637
- }
13884
+ util.getSession(self).addWidget('JobsListWidget', 'JobsList');
13638
13885
  return jobStatusWidget;
13639
13886
  },
13640
13887
  }))
@@ -13913,19 +14160,15 @@ function clientDataStoreFactory(AnnotationFeatureExtended) {
13913
14160
  continue;
13914
14161
  }
13915
14162
  const { assemblyName, refName } = region;
13916
- let assembly = self.assemblies.get(assemblyName);
13917
- if (!assembly) {
13918
- assembly = self.assemblies.put({ _id: assemblyName, refSeqs: {} });
13919
- }
14163
+ const assembly = self.assemblies.get(assemblyName) ??
14164
+ self.assemblies.put({ _id: assemblyName, refSeqs: {} });
13920
14165
  const [firstFeature] = features;
13921
- let ref = assembly.refSeqs.get(firstFeature.refSeq);
13922
- if (!ref) {
13923
- ref = assembly.refSeqs.put({
14166
+ const ref = assembly.refSeqs.get(firstFeature.refSeq) ??
14167
+ assembly.refSeqs.put({
13924
14168
  _id: firstFeature.refSeq,
13925
14169
  name: refName,
13926
14170
  features: {},
13927
14171
  });
13928
- }
13929
14172
  for (const feature of features) {
13930
14173
  if (!ref.features.has(feature._id)) {
13931
14174
  ref.features.put(feature);
@@ -13942,24 +14185,153 @@ function clientDataStoreFactory(AnnotationFeatureExtended) {
13942
14185
  }
13943
14186
  const { refSeq, seq } = yield backendDriver.getSequence(region);
13944
14187
  const { assemblyName, end, refName, start } = region;
13945
- let assembly = self.assemblies.get(assemblyName);
13946
- if (!assembly) {
13947
- assembly = self.assemblies.put({ _id: assemblyName, refSeqs: {} });
13948
- }
13949
- let ref = assembly.refSeqs.get(refSeq);
13950
- if (!ref) {
13951
- ref = assembly.refSeqs.put({
13952
- _id: refSeq,
13953
- name: refName,
13954
- sequence: [],
13955
- });
13956
- }
14188
+ const assembly = self.assemblies.get(assemblyName) ??
14189
+ self.assemblies.put({ _id: assemblyName, refSeqs: {} });
14190
+ const ref = assembly.refSeqs.get(refSeq) ??
14191
+ assembly.refSeqs.put({ _id: refSeq, name: refName, sequence: [] });
13957
14192
  ref.addSequence({ start, stop: end, sequence: seq });
13958
14193
  }
13959
14194
  }),
13960
14195
  }));
13961
14196
  }
13962
14197
 
14198
+ function reassignIds(feature) {
14199
+ const newChildren = {};
14200
+ if (feature.children) {
14201
+ for (const child of Object.values(feature.children)) {
14202
+ const newChild = reassignIds(child);
14203
+ newChildren[newChild._id] = newChild;
14204
+ }
14205
+ }
14206
+ return {
14207
+ ...feature,
14208
+ _id: new ObjectID().toHexString(),
14209
+ children: feature.children ? newChildren : undefined,
14210
+ };
14211
+ }
14212
+ function DuplicateFeatureDialog({ assemblyName, changeManager, existingFeature, featureSnapshot, handleClose, }) {
14213
+ const [choice, setChoice] = React.useState('keepExisting');
14214
+ const [errorMessage, setErrorMessage] = React.useState('');
14215
+ const [submitted, setSubmitted] = React.useState(false);
14216
+ const handleSubmit = async () => {
14217
+ setErrorMessage('');
14218
+ setSubmitted(true);
14219
+ try {
14220
+ if (choice === 'useNew') {
14221
+ const deleteChange = new shared.DeleteFeatureChange({
14222
+ typeName: 'DeleteFeatureChange',
14223
+ assembly: assemblyName,
14224
+ changedIds: [existingFeature._id],
14225
+ deletedFeature: existingFeature,
14226
+ });
14227
+ await changeManager.submit(deleteChange);
14228
+ const addChange = new shared.AddFeatureChange({
14229
+ typeName: 'AddFeatureChange',
14230
+ assembly: assemblyName,
14231
+ changedIds: [featureSnapshot._id],
14232
+ addedFeature: featureSnapshot,
14233
+ });
14234
+ await changeManager.submit(addChange);
14235
+ }
14236
+ else if (choice === 'keepBoth') {
14237
+ const newSnapshot = reassignIds(featureSnapshot);
14238
+ const addChange = new shared.AddFeatureChange({
14239
+ typeName: 'AddFeatureChange',
14240
+ assembly: assemblyName,
14241
+ changedIds: [newSnapshot._id],
14242
+ addedFeature: newSnapshot,
14243
+ });
14244
+ await changeManager.submit(addChange);
14245
+ }
14246
+ handleClose();
14247
+ }
14248
+ catch (error) {
14249
+ setErrorMessage(String(error));
14250
+ setSubmitted(false);
14251
+ }
14252
+ };
14253
+ 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) => {
14254
+ setChoice(e.target.value);
14255
+ }, 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" })] })] }));
14256
+ }
14257
+
14258
+ function fromUrlSafeBase64(urlSafeBase64) {
14259
+ let base64 = urlSafeBase64.replaceAll('-', '+').replaceAll('_', '/');
14260
+ const pad = base64.length % 4;
14261
+ if (pad) {
14262
+ base64 += '='.repeat(4 - pad);
14263
+ }
14264
+ return base64;
14265
+ }
14266
+ // Base64 decode + decompress
14267
+ async function decompress(encoded) {
14268
+ const binaryString = atob(encoded);
14269
+ // eslint-disable-next-line unicorn/prefer-code-point
14270
+ const bytes = Uint8Array.from(binaryString, (char) => char.charCodeAt(0));
14271
+ const stream = new Blob([bytes]).stream();
14272
+ const decompressionStream = new DecompressionStream('gzip');
14273
+ const decompressedStream = stream.pipeThrough(decompressionStream);
14274
+ const response = new Response(decompressedStream);
14275
+ const decompressed = await response.arrayBuffer();
14276
+ return JSON.parse(new TextDecoder().decode(decompressed));
14277
+ }
14278
+ async function handleApolloFeaturesUrlParam(encodedFeatures, apolloDataStore, assemblyManager, session) {
14279
+ const base64 = fromUrlSafeBase64(encodedFeatures);
14280
+ const decompressed = await decompress(base64);
14281
+ const featuresData = decompressed;
14282
+ for (const [assemblyName, featureSnapshots] of Object.entries(featuresData)) {
14283
+ const backendDriver = apolloDataStore.getBackendDriver(assemblyName);
14284
+ if (!(backendDriver instanceof LocalDriver)) {
14285
+ continue;
14286
+ }
14287
+ const assembly = (await assemblyManager.waitForAssembly(assemblyName));
14288
+ if (!assembly) {
14289
+ throw new Error(`Assembly not found: "${assemblyName}"`);
14290
+ }
14291
+ const { regions } = assembly;
14292
+ if (!regions) {
14293
+ throw new Error(`Assembly not found: "${assemblyName}"`);
14294
+ }
14295
+ const refNames = regions.map((r) => r.refName);
14296
+ const db = await openDb(assemblyName, refNames);
14297
+ for (const featureSnapshot of featureSnapshots) {
14298
+ const storeName = `features-${featureSnapshot.refSeq}`;
14299
+ const existing = (await db.get(storeName, featureSnapshot._id));
14300
+ if (existing === undefined) {
14301
+ const change = new shared.AddFeatureChange({
14302
+ typeName: 'AddFeatureChange',
14303
+ assembly: assemblyName,
14304
+ changedIds: [featureSnapshot._id],
14305
+ addedFeature: featureSnapshot,
14306
+ });
14307
+ await apolloDataStore.changeManager.submit(change);
14308
+ }
14309
+ else {
14310
+ // get rid of undefined values in JSON
14311
+ // eslint-disable-next-line unicorn/prefer-structured-clone
14312
+ const existingFeature = JSON.parse(JSON.stringify(existing));
14313
+ if (!equal(featureSnapshot, existingFeature)) {
14314
+ await new Promise((resolve) => {
14315
+ session.queueDialog((doneCallback) => [
14316
+ DuplicateFeatureDialog,
14317
+ {
14318
+ featureSnapshot,
14319
+ existingFeature,
14320
+ assemblyName,
14321
+ changeManager: apolloDataStore.changeManager,
14322
+ handleClose: () => {
14323
+ doneCallback();
14324
+ resolve();
14325
+ },
14326
+ },
14327
+ ]);
14328
+ });
14329
+ }
14330
+ }
14331
+ }
14332
+ }
14333
+ }
14334
+
13963
14335
  /* eslint-disable @typescript-eslint/no-unnecessary-condition */
13964
14336
  /* eslint-disable @typescript-eslint/no-unsafe-assignment */
13965
14337
  function extendSession(pluginManager, sessionModel) {
@@ -14045,8 +14417,7 @@ function extendSession(pluginManager, sessionModel) {
14045
14417
  for (const block of dynamicBlocks.contentBlocks) {
14046
14418
  const { assemblyName, end, refName, start } = block;
14047
14419
  const assembly = self.apolloDataStore.assemblies.get(assemblyName);
14048
- if (assembly &&
14049
- assembly.backendDriverType === 'CollaborationServerDriver') {
14420
+ if (assembly?.backendDriverType === 'CollaborationServerDriver') {
14050
14421
  locations.push({ assemblyName, refName, start, end });
14051
14422
  }
14052
14423
  }
@@ -14105,8 +14476,8 @@ function extendSession(pluginManager, sessionModel) {
14105
14476
  for (const block of dynamicBlocks.contentBlocks) {
14106
14477
  const { assemblyName, end, refName, start } = block;
14107
14478
  const assembly = self.apolloDataStore.assemblies.get(assemblyName);
14108
- if (assembly &&
14109
- assembly.backendDriverType === 'CollaborationServerDriver') {
14479
+ if (assembly?.backendDriverType ===
14480
+ 'CollaborationServerDriver') {
14110
14481
  locations.push({ assemblyName, refName, start, end });
14111
14482
  }
14112
14483
  }
@@ -14226,6 +14597,37 @@ function extendSession(pluginManager, sessionModel) {
14226
14597
  beforeDestroy() {
14227
14598
  self.abortController.abort(new DOMException('Clean up Apollo session', 'AbortError'));
14228
14599
  },
14600
+ }))
14601
+ .actions((self) => ({
14602
+ async afterCreate() {
14603
+ if (util.isElectron) {
14604
+ return;
14605
+ }
14606
+ const url = new URL(globalThis.location.href);
14607
+ const apolloFeatures = url.searchParams.get('apolloFeatures');
14608
+ if (!apolloFeatures) {
14609
+ return;
14610
+ }
14611
+ const { assemblyManager } = self;
14612
+ await handleApolloFeaturesUrlParam(apolloFeatures, self.apolloDataStore, assemblyManager, self);
14613
+ await new Promise((resolve) => setTimeout(resolve, 2000));
14614
+ const updatedURL = new URL(globalThis.location.href);
14615
+ updatedURL.searchParams.delete('apolloFeatures');
14616
+ globalThis.history.replaceState(null, '', updatedURL.toString());
14617
+ },
14618
+ afterAttach() {
14619
+ mobxStateTree.addDisposer(self, mobx.autorun((reaction) => {
14620
+ const { focusedViewId, activeWidgets } = self;
14621
+ if (!(focusedViewId && activeWidgets)) {
14622
+ return;
14623
+ }
14624
+ const trackSelector = activeWidgets.get('hierarchicalTrackSelector');
14625
+ // @ts-expect-error Don't have type for track selector
14626
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
14627
+ trackSelector?.setView(focusedViewId);
14628
+ reaction.dispose();
14629
+ }));
14630
+ },
14229
14631
  }))
14230
14632
  .views((self) => {
14231
14633
  const superTrackActions = self
@@ -14357,7 +14759,6 @@ function extendSession(pluginManager, sessionModel) {
14357
14759
  }
14358
14760
 
14359
14761
  /* eslint-disable @typescript-eslint/unbound-method */
14360
- /* eslint-disable @typescript-eslint/no-unnecessary-condition */
14361
14762
  /* eslint-disable @typescript-eslint/no-misused-promises */
14362
14763
  /* eslint-disable @typescript-eslint/no-explicit-any */
14363
14764
  function isApolloMessageData(data) {
@@ -14545,10 +14946,10 @@ class ApolloPlugin extends Plugin {
14545
14946
  });
14546
14947
  if (!inWebWorker) {
14547
14948
  pluginManager.addToExtensionPoint('Core-extendWorker', (handle) => {
14548
- if (!('on' in handle && handle.on)) {
14949
+ if (!('on' in handle.client && handle.client.on)) {
14549
14950
  return handle;
14550
14951
  }
14551
- handle.on('apollo', async (event) => {
14952
+ handle.client.on('apollo', async (event) => {
14552
14953
  if (!isApolloMessageData(event)) {
14553
14954
  return;
14554
14955
  }
@@ -14566,7 +14967,7 @@ class ApolloPlugin extends Plugin {
14566
14967
  break;
14567
14968
  }
14568
14969
  const { seq: sequence } = await backendDriver.getSequence(region);
14569
- handle.workers[0].postMessage({
14970
+ handle.worker.postMessage({
14570
14971
  apollo,
14571
14972
  messageId,
14572
14973
  sequence,
@@ -14584,7 +14985,7 @@ class ApolloPlugin extends Plugin {
14584
14985
  break;
14585
14986
  }
14586
14987
  const regions = await backendDriver.getRegions(assembly);
14587
- handle.workers[0].postMessage({
14988
+ handle.worker.postMessage({
14588
14989
  apollo,
14589
14990
  messageId,
14590
14991
  regions,
@@ -14602,7 +15003,7 @@ class ApolloPlugin extends Plugin {
14602
15003
  break;
14603
15004
  }
14604
15005
  const refNameAliases = await backendDriver.getRefNameAliases(assembly);
14605
- handle.workers[0].postMessage({
15006
+ handle.worker.postMessage({
14606
15007
  apollo,
14607
15008
  messageId,
14608
15009
  refNameAliases,