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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +826 -433
  89. package/dist/index.esm.js.map +1 -1
  90. package/dist/jbrowse-plugin-apollo.cjs.development.js +821 -428
  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 +859 -467
  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 +358 -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 +28 -23
  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
package/dist/index.esm.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { isFeatureChange, checkRegistry, isAssemblySpecificChange, Change, changeRegistry } from '@apollo-annotation/common';
2
- import { AddFeatureChange, FeatureAttributeChange, DeleteFeatureChange, LocationStartChange, LocationEndChange, AddAssemblyFromExternalChange, AddAssemblyAndFeaturesFromFileChange, AddAssemblyFromFileChange, AddAssemblyAliasesChange, ValidationResultSet, isDeleteFeatureChange, stringifyAttributes, attributesToRecords, validationRegistry, makeUserSessionId, DeleteAssemblyChange, annotationFeatureToGFF3, AddFeaturesFromFileChange, UserChange, DeleteUserChange, MergeExonsChange, MergeTranscriptsChange, AddRefSeqAliasesChange, SplitExonChange, getDecodedToken, isGFFInternalAttribute, isGFFColumnInternal, internalToGFF, gffInternalToColumn, gffToInternal, gffColumnToInternal, TypeChange, StrandChange, splitStringIntoChunks, gff3ToAnnotationFeature, filterJBrowseConfig, ImportJBrowseConfigChange, changes, CDSCheck, TranscriptCheck, CoreValidation, ParentChildValidation } from '@apollo-annotation/shared';
2
+ import { AddFeatureChange, FeatureAttributeChange, DeleteFeatureChange, LocationStartChange, LocationEndChange, AddAssemblyFromExternalChange, AddAssemblyAndFeaturesFromFileChange, AddAssemblyFromFileChange, AddAssemblyAliasesChange, ValidationResultSet, isDeleteFeatureChange, stringifyAttributes, attributesToRecords, validationRegistry, makeUserSessionId, DeleteAssemblyChange, annotationFeatureToGFF3, gff3ToAnnotationFeature, AddFeaturesFromFileChange, UserChange, DeleteUserChange, MergeExonsChange, MergeTranscriptsChange, AddRefSeqAliasesChange, SplitExonChange, getDecodedToken, isGFFInternalAttribute, isGFFColumnInternal, internalToGFF, gffInternalToColumn, gffToInternal, gffColumnToInternal, TypeChange, StrandChange, splitStringIntoChunks, filterJBrowseConfig, ImportJBrowseConfigChange, changes, CDSCheck, TranscriptCheck, CoreValidation, ParentChildValidation } from '@apollo-annotation/shared';
3
3
  import Plugin from '@jbrowse/core/Plugin';
4
4
  import { ConfigurationSchema, readConfObject, getConf, ConfigurationReference } from '@jbrowse/core/configuration';
5
5
  import { BaseInternetAccountConfig, InternetAccount, TextSearchAdapterType, BaseDisplay, WidgetType, createBaseTrackConfig, TrackType, createBaseTrackModel, InternetAccountType, DisplayType } from '@jbrowse/core/pluggableElementTypes';
6
- import { isUriLocation, isLocalPathLocation, isSessionModelWithWidgets, getSession, getEnv, isElectron, isAbstractMenuManager, revcom, defaultCodonTable, getFrame, getContainingView, doesIntersect2, intersection2, measureText } from '@jbrowse/core/util';
6
+ import { isUriLocation, isLocalPathLocation, isSessionModelWithWidgets, revcom, getSession, getEnv, isElectron, isAbstractMenuManager, defaultCodonTable, getFrame, getContainingView, doesIntersect2, intersection2, measureText } from '@jbrowse/core/util';
7
7
  import AddIcon from '@mui/icons-material/Add';
8
- import { DialogTitle, IconButton, Autocomplete, TextField, DialogContent, DialogActions, Button, DialogContentText, Select, MenuItem, LinearProgress, Accordion, AccordionSummary, Typography, AccordionDetails, FormGroup, FormControlLabel, Box, Tooltip as Tooltip$1, Checkbox, Table, TableBody, TableRow, TableCell, InputAdornment, FormHelperText, FormControl, InputLabel, RadioGroup, Radio, SvgIcon, TableContainer, Paper, TableHead, Grid, Divider, Chip, List, ListItem, ListItemText, Menu, ListItemIcon, useTheme, alpha, createTheme, Alert, Badge, Avatar, CircularProgress } from '@mui/material';
8
+ import { DialogTitle, IconButton, Autocomplete, TextField, DialogContent, DialogActions, Button, DialogContentText, Select, MenuItem, LinearProgress, Accordion, AccordionSummary, Typography, AccordionDetails, FormGroup, FormControlLabel, Box, Tooltip as Tooltip$1, Checkbox, Table, TableBody, TableRow, TableCell, InputAdornment, FormHelperText, FormControl, RadioGroup, Radio, InputLabel, SvgIcon, TableContainer, Paper, TableHead, Grid, Divider, Chip, List, ListItem, ListItemText, Menu, ListItemIcon, useTheme, alpha, createTheme, Alert, Badge, Avatar, CircularProgress, FormLabel } from '@mui/material';
9
9
  import { types, addDisposer, getSnapshot, flow, isAlive, getRoot, getParent, cast, resolveIdentifier, getParentOfType, applySnapshot } from '@jbrowse/mobx-state-tree';
10
10
  import { autorun, entries, observable, flow as flow$1, when } from 'mobx';
11
11
  import { io } from 'socket.io-client';
@@ -33,10 +33,11 @@ import equal from 'fast-deep-equal/es6';
33
33
  import { Dialog as Dialog$1, Menu as Menu$1, BaseTooltip } from '@jbrowse/core/ui';
34
34
  import CloseIcon from '@mui/icons-material/Close';
35
35
  import { observer } from 'mobx-react';
36
- import { DataGrid, GridToolbar, GridActionsCellItem } from '@mui/x-data-grid';
37
- import { GFFFormattingTransformer } from '@gmod/gff';
36
+ import { DataGrid, GridActionsCellItem } from '@mui/x-data-grid';
37
+ import { GFFFormattingTransformer, GFFTransformer } from '@gmod/gff';
38
38
  import { saveAs } from 'file-saver';
39
39
  import AccountCircleIcon from '@mui/icons-material/AccountCircle';
40
+ import BusinessIcon from '@mui/icons-material/Business';
40
41
  import AdapterType from '@jbrowse/core/pluggableElementTypes/AdapterType';
41
42
  import { BaseAdapter, BaseSequenceAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
42
43
  import { nanoid } from 'nanoid';
@@ -49,9 +50,9 @@ import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
49
50
  import AddBoxIcon from '@mui/icons-material/AddBox';
50
51
  import { AnnotationFeatureModel, CheckResult, ApolloAssembly, ApolloRefSeq } from '@apollo-annotation/mst';
51
52
  import styled from '@emotion/styled';
53
+ import RemoveIcon from '@mui/icons-material/Remove';
52
54
  import ContentCopyIcon from '@mui/icons-material/ContentCopy';
53
55
  import ContentCutIcon from '@mui/icons-material/ContentCut';
54
- import RemoveIcon from '@mui/icons-material/Remove';
55
56
  import ClearIcon from '@mui/icons-material/Clear';
56
57
  import UnfoldLessIcon from '@mui/icons-material/UnfoldLess';
57
58
  import { getParentRenderProps } from '@jbrowse/core/util/tracks';
@@ -65,7 +66,7 @@ import RedoIcon from '@mui/icons-material/Redo';
65
66
  import UndoIcon from '@mui/icons-material/Undo';
66
67
  import SaveIcon from '@mui/icons-material/Save';
67
68
 
68
- var version = "1.0.1";
69
+ var version = "1.1.0";
69
70
 
70
71
  const ApolloConfigSchema = ConfigurationSchema('ApolloInternetAccount', {
71
72
  baseURL: {
@@ -1368,9 +1369,7 @@ async function getValidTerms(ontologyStore, fetchValidTerms, filterTerms, signal
1368
1369
  result = customTermList;
1369
1370
  }
1370
1371
  }
1371
- if (!result) {
1372
- result = await ontologyStore.getAllTerms();
1373
- }
1372
+ result ??= await ontologyStore.getAllTerms();
1374
1373
  return filterTerms ? result.filter((element) => filterTerms(element)) : result;
1375
1374
  }
1376
1375
 
@@ -1551,7 +1550,7 @@ function CopyFeature({ changeManager, handleClose, session, sourceAssemblyId, so
1551
1550
  setErrorMessage('No assemblies to copy to');
1552
1551
  return;
1553
1552
  }
1554
- const assembly = await assemblyManager.waitForAssembly(selectedAssemblyId);
1553
+ const assembly = (await assemblyManager.waitForAssembly(selectedAssemblyId));
1555
1554
  if (!assembly) {
1556
1555
  return;
1557
1556
  }
@@ -1580,7 +1579,7 @@ function CopyFeature({ changeManager, handleClose, session, sourceAssemblyId, so
1580
1579
  event.preventDefault();
1581
1580
  setErrorMessage('');
1582
1581
  const featureLength = sourceFeature.length;
1583
- const assembly = await assemblyManager.waitForAssembly(selectedAssemblyId);
1582
+ const assembly = (await assemblyManager.waitForAssembly(selectedAssemblyId));
1584
1583
  if (!assembly) {
1585
1584
  setErrorMessage(`Assembly not found: ${selectedAssemblyId}.`);
1586
1585
  return;
@@ -1918,8 +1917,7 @@ function DeleteFeature({ changeManager, handleClose, selectedFeature, session, s
1918
1917
  locationChanges.push(geneChange);
1919
1918
  }
1920
1919
  }
1921
- if (featureTypeOntology &&
1922
- featureTypeOntology.isTypeOf(sourceFeature.type, 'exon')) {
1920
+ if (featureTypeOntology?.isTypeOf(sourceFeature.type, 'exon')) {
1923
1921
  const cdsChange = trimCDS(sourceFeature);
1924
1922
  if (cdsChange) {
1925
1923
  if (cdsChange.typeName === 'DeleteFeatureChange') {
@@ -2172,7 +2170,7 @@ function getOverlappingEdge(feature, x, minMax) {
2172
2170
  return;
2173
2171
  }
2174
2172
  function isSelectedFeature(feature, selectedFeature) {
2175
- return Boolean(selectedFeature && feature._id === selectedFeature._id);
2173
+ return feature._id === selectedFeature?._id;
2176
2174
  }
2177
2175
  function makeFeatureLabel(feature) {
2178
2176
  let name;
@@ -2778,6 +2776,153 @@ function AddAssemblyAliases({ changeManager, handleClose, session, }) {
2778
2776
  }, pageSizeOptions: [5], processRowUpdate: processRowUpdate, disableRowSelectionOnClick: true }) }) }), errorMessage ? (jsx(DialogContent, { children: jsx(DialogContentText, { color: "error", children: errorMessage }) })) : null] }));
2779
2777
  }
2780
2778
 
2779
+ const START_CODON = 'ATG';
2780
+ const STOP_CODONS = new Set(['TAA', 'TAG', 'TGA']);
2781
+ function findLongestOrf(sequence) {
2782
+ const starts = [
2783
+ null,
2784
+ null,
2785
+ null,
2786
+ ];
2787
+ let longest;
2788
+ for (let start = 0; start < sequence.length - 2; start++) {
2789
+ const codon = sequence.slice(start, start + 3).toUpperCase();
2790
+ const frame = (start % 3);
2791
+ if (codon === START_CODON && starts[frame] === null) {
2792
+ starts[frame] = start;
2793
+ }
2794
+ else if (STOP_CODONS.has(codon)) {
2795
+ const inFrameStart = starts[frame];
2796
+ if (inFrameStart === null) {
2797
+ continue;
2798
+ }
2799
+ const length = start - inFrameStart;
2800
+ if (!longest || longest[1] - longest[0] < length) {
2801
+ longest = [inFrameStart, start];
2802
+ }
2803
+ starts[frame] = null;
2804
+ }
2805
+ }
2806
+ return longest;
2807
+ }
2808
+
2809
+ // Returns the genomic half-open boundary for stitched position `pos`.
2810
+ // For strand +1: the inclusive start (min) of the nucleotide.
2811
+ // For strand -1: the exclusive end (max) of the nucleotide.
2812
+ // Symmetric usage:
2813
+ // plus: cdsMin = helper(orf[0]), cdsMax = helper(orf[1]+3)
2814
+ // minus: cdsMax = helper(orf[0]), cdsMin = helper(orf[1]+3)
2815
+ function stitchedToGenomicBound(pos, exons, strand) {
2816
+ let cum = 0;
2817
+ for (const exon of exons) {
2818
+ const len = exon.max - exon.min;
2819
+ if (pos <= cum + len) {
2820
+ const off = pos - cum;
2821
+ return strand === 1 ? exon.min + off : exon.max - off;
2822
+ }
2823
+ cum += len;
2824
+ }
2825
+ const last = exons.at(-1);
2826
+ if (!last) {
2827
+ throw new Error('No exons found');
2828
+ }
2829
+ return strand === 1 ? last.max : last.min;
2830
+ }
2831
+ function AddCodingSequence({ changeManager, handleClose, refName, session, sourceAssemblyId, sourceFeature, }) {
2832
+ const [method, setMethod] = useState('longest-orf');
2833
+ const [minInput, setMinInput] = useState('');
2834
+ const [maxInput, setMaxInput] = useState('');
2835
+ const [errorMessage, setErrorMessage] = useState('');
2836
+ async function onSubmit(event) {
2837
+ event.preventDefault();
2838
+ setErrorMessage('');
2839
+ let cdsMin;
2840
+ let cdsMax;
2841
+ try {
2842
+ if (method === 'longest-orf') {
2843
+ const exonParts = sourceFeature.transcriptExonParts.filter((p) => p.type === 'exon');
2844
+ if (exonParts.length === 0) {
2845
+ setErrorMessage('No exons found in this transcript');
2846
+ return;
2847
+ }
2848
+ const backendDriver = session.apolloDataStore.getBackendDriver(sourceAssemblyId);
2849
+ if (!backendDriver) {
2850
+ setErrorMessage('No backend driver found for this assembly');
2851
+ return;
2852
+ }
2853
+ let stitchedSequence = '';
2854
+ for (const exon of exonParts) {
2855
+ const { seq } = await backendDriver.getSequence({
2856
+ assemblyName: sourceAssemblyId,
2857
+ refName,
2858
+ start: exon.min,
2859
+ end: exon.max,
2860
+ });
2861
+ stitchedSequence += sourceFeature.strand === -1 ? revcom(seq) : seq;
2862
+ }
2863
+ const orf = findLongestOrf(stitchedSequence);
2864
+ if (!orf) {
2865
+ ;
2866
+ session.notify('No open reading frame found in this transcript', 'error');
2867
+ handleClose();
2868
+ return;
2869
+ }
2870
+ const strand = sourceFeature.strand ?? 1;
2871
+ if (strand === 1) {
2872
+ cdsMin = stitchedToGenomicBound(orf[0], exonParts, 1);
2873
+ cdsMax = stitchedToGenomicBound(orf[1] + 3, exonParts, 1);
2874
+ }
2875
+ else {
2876
+ cdsMax = stitchedToGenomicBound(orf[0], exonParts, -1);
2877
+ cdsMin = stitchedToGenomicBound(orf[1] + 3, exonParts, -1);
2878
+ }
2879
+ }
2880
+ else {
2881
+ cdsMin = Number(minInput) - 1;
2882
+ cdsMax = Number(maxInput);
2883
+ }
2884
+ const _id = new ObjectID().toHexString();
2885
+ const addedFeature = {
2886
+ _id,
2887
+ refSeq: sourceFeature.refSeq,
2888
+ min: cdsMin,
2889
+ max: cdsMax,
2890
+ type: 'CDS',
2891
+ };
2892
+ if (sourceFeature.strand) {
2893
+ addedFeature.strand = sourceFeature.strand;
2894
+ }
2895
+ const change = new AddFeatureChange({
2896
+ changedIds: [sourceFeature._id],
2897
+ typeName: 'AddFeatureChange',
2898
+ assembly: sourceAssemblyId,
2899
+ addedFeature,
2900
+ parentFeatureId: sourceFeature._id,
2901
+ });
2902
+ await changeManager.submit(change);
2903
+ session.apolloSetSelectedFeature(_id);
2904
+ handleClose();
2905
+ }
2906
+ catch (error) {
2907
+ setErrorMessage(error instanceof Error ? error.message : 'An unknown error occurred');
2908
+ }
2909
+ }
2910
+ const manualError = method === 'manual' &&
2911
+ Boolean(minInput) &&
2912
+ Boolean(maxInput) &&
2913
+ Number(maxInput) <= Number(minInput);
2914
+ const submitDisabled = method === 'manual' ? !(minInput && maxInput) || manualError : false;
2915
+ return (jsxs(Dialog, { open: true, title: "Add coding sequence", handleClose: handleClose, maxWidth: false, "data-testid": "add-coding-sequence", children: [jsxs("form", { onSubmit: (event) => {
2916
+ void onSubmit(event);
2917
+ }, children: [jsxs(DialogContent, { style: { display: 'flex', flexDirection: 'column' }, children: [jsx(FormControl, { children: jsxs(RadioGroup, { value: method, onChange: (e) => {
2918
+ setMethod(e.target.value);
2919
+ }, children: [jsx(FormControlLabel, { value: "longest-orf", control: jsx(Radio, {}), label: "Calculate longest open reading frame" }), jsx(FormControlLabel, { value: "manual", control: jsx(Radio, {}), label: "Manual" })] }) }), method === 'manual' ? (jsxs(Fragment, { children: [jsx(TextField, { margin: "dense", id: "cds-min", label: "Min", type: "number", fullWidth: true, variant: "outlined", value: minInput, onChange: (e) => {
2920
+ setMinInput(e.target.value);
2921
+ } }), jsx(TextField, { margin: "dense", id: "cds-max", label: "Max", type: "number", fullWidth: true, variant: "outlined", value: maxInput, onChange: (e) => {
2922
+ setMaxInput(e.target.value);
2923
+ }, error: manualError, helperText: manualError ? '"Max" must be greater than "Min"' : null })] })) : null] }), jsxs(DialogActions, { children: [jsx(Button, { variant: "contained", type: "submit", disabled: submitDisabled, children: "Submit" }), jsx(Button, { variant: "outlined", type: "button", onClick: handleClose, children: "Cancel" })] })] }), errorMessage ? (jsx(DialogContent, { children: jsx(DialogContentText, { color: "error", children: errorMessage }) })) : null] }));
2924
+ }
2925
+
2781
2926
  class BackendDriver {
2782
2927
  clientStore;
2783
2928
  constructor(clientStore) {
@@ -2850,7 +2995,7 @@ class LocalDriver extends BackendDriver {
2850
2995
  async getSequence(region) {
2851
2996
  const session = getSession(this.clientStore);
2852
2997
  const { assemblyManager } = session;
2853
- const assembly = await assemblyManager.waitForAssembly(region.assemblyName);
2998
+ const assembly = (await assemblyManager.waitForAssembly(region.assemblyName));
2854
2999
  if (!assembly) {
2855
3000
  throw new Error(`Assembly not found: "${region.assemblyName}"`);
2856
3001
  }
@@ -2881,7 +3026,7 @@ class LocalDriver extends BackendDriver {
2881
3026
  async getRegions(assemblyName) {
2882
3027
  const session = getSession(this.clientStore);
2883
3028
  const { assemblyManager } = session;
2884
- const assembly = await assemblyManager.waitForAssembly(assemblyName);
3029
+ const assembly = (await assemblyManager.waitForAssembly(assemblyName));
2885
3030
  if (!assembly) {
2886
3031
  throw new Error(`Assembly not found: "${assemblyName}"`);
2887
3032
  }
@@ -2897,7 +3042,7 @@ class LocalDriver extends BackendDriver {
2897
3042
  async getRefNameAliases(assemblyName) {
2898
3043
  const session = getSession(this.clientStore);
2899
3044
  const { assemblyManager } = session;
2900
- const assembly = await assemblyManager.waitForAssembly(assemblyName);
3045
+ const assembly = (await assemblyManager.waitForAssembly(assemblyName));
2901
3046
  if (!assembly) {
2902
3047
  throw new Error(`Assembly not found: "${assemblyName}"`);
2903
3048
  }
@@ -3556,7 +3701,7 @@ class CollaborationServerDriver extends BackendDriver {
3556
3701
  const localSessionId = makeUserSessionId(token);
3557
3702
  const changeManager = new ChangeManager(this.clientStore);
3558
3703
  // Save server last change sequence into session storage
3559
- internetAccount.setLastChangeSequenceNumber(Number(message.changeSequence));
3704
+ internetAccount.setLastChangeSequenceNumber(message.changeSequence);
3560
3705
  if (message.userSessionId === localSessionId) {
3561
3706
  return; // we did this change, no need to apply it again
3562
3707
  }
@@ -3604,14 +3749,10 @@ class CollaborationServerDriver extends BackendDriver {
3604
3749
  const seq = await inFlightPromise;
3605
3750
  return { seq, refSeq };
3606
3751
  }
3607
- let apolloAssembly = this.clientStore.assemblies.get(assemblyName);
3608
- if (!apolloAssembly) {
3609
- apolloAssembly = this.clientStore.addAssembly(assemblyName);
3610
- }
3611
- let apolloRefSeq = apolloAssembly.refSeqs.get(refSeq);
3612
- if (!apolloRefSeq) {
3613
- apolloRefSeq = apolloAssembly.addRefSeq(refSeq, refName);
3614
- }
3752
+ const apolloAssembly = this.clientStore.assemblies.get(assemblyName) ??
3753
+ this.clientStore.addAssembly(assemblyName);
3754
+ const apolloRefSeq = apolloAssembly.refSeqs.get(refSeq) ??
3755
+ apolloAssembly.addRefSeq(refSeq, refName);
3615
3756
  const clientStoreSequence = apolloRefSeq.getSequence(start, end);
3616
3757
  if (clientStoreSequence.length === end - start) {
3617
3758
  return { seq: clientStoreSequence, refSeq };
@@ -3968,7 +4109,7 @@ function AddFeature({ changeManager, handleClose, region, session, }) {
3968
4109
  setType(NewFeature[value]);
3969
4110
  }
3970
4111
  };
3971
- let submitDisabled = Boolean(error) || !(start && end && type);
4112
+ let submitDisabled = error || !(start && end && type);
3972
4113
  if ((type === NewFeature.CUSTOM && !customType) ||
3973
4114
  (!strand && type === NewFeature.GENE_AND_SUBFEATURES) ||
3974
4115
  (!strand && type === NewFeature.TRANSCRIPT_AND_SUBFEATURES)) {
@@ -3989,7 +4130,7 @@ function AddFeature({ changeManager, handleClose, region, session, }) {
3989
4130
  function DeleteAssembly({ changeManager, handleClose, session, }) {
3990
4131
  const { internetAccounts } = getRoot(session);
3991
4132
  const [errorMessage, setErrorMessage] = useState('');
3992
- const [confirmDelete, setconfirmDelete] = useState(false);
4133
+ const [confirmDelete, setConfirmDelete] = useState(false);
3993
4134
  const [submitted, setSubmitted] = useState(false);
3994
4135
  const apolloInternetAccounts = internetAccounts.filter((ia) => ia.type === 'ApolloInternetAccount');
3995
4136
  if (apolloInternetAccounts.length === 0) {
@@ -4030,7 +4171,7 @@ function DeleteAssembly({ changeManager, handleClose, session, }) {
4030
4171
  event.preventDefault();
4031
4172
  }
4032
4173
  return (jsxs(Dialog, { open: true, title: "Delete Assembly", handleClose: handleClose, maxWidth: false, "data-testid": "delete-assembly", children: [jsxs("form", { onSubmit: onSubmit, children: [jsxs(DialogContent, { style: { display: 'flex', flexDirection: 'column' }, children: [apolloInternetAccounts.length > 1 ? (jsxs(Fragment, { children: [jsx(DialogContentText, { children: "Select account" }), jsx(Select, { value: selectedInternetAccount.internetAccountId, onChange: handleChangeInternetAccount, disabled: submitted && !errorMessage, children: internetAccounts.map((option) => (jsx(MenuItem, { value: option.internetAccountId, children: option.name }, option.id))) })] })) : null, jsx(DialogContentText, { children: "Select assembly" }), jsx(Select, { labelId: "label", value: selectedAssembly?.name ?? '', onChange: handleChangeAssembly, disabled: assemblies.length === 0, children: assemblies.map((option) => (jsx(MenuItem, { value: option.name, children: option.displayName }, option.name))) }), jsx(DialogContentText, { children: jsx("strong", { style: { color: 'red' }, children: "NOTE: All assembly data will be deleted and this operation cannot be undone!" }) }), jsx(FormGroup, { children: jsx(FormControlLabel, { control: jsx(Checkbox, { checked: confirmDelete, onChange: () => {
4033
- setconfirmDelete(!confirmDelete);
4174
+ setConfirmDelete(!confirmDelete);
4034
4175
  } }), label: "I understand that all assembly data will be deleted" }) })] }), jsxs(DialogActions, { children: [jsx(Button, { disabled: !selectedAssembly || !confirmDelete, variant: "contained", type: "submit", children: "Delete" }), jsx(Button, { variant: "outlined", type: "submit", onClick: handleClose, children: "Cancel" })] })] }), errorMessage ? (jsx(DialogContent, { children: jsx(DialogContentText, { color: "error", children: errorMessage }) })) : null] }));
4035
4176
  }
4036
4177
 
@@ -4039,7 +4180,7 @@ function Export(props) {
4039
4180
  return (jsx(SvgIcon, { viewBox: "0 0 24 24", ...props, children: 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" }) }));
4040
4181
  }
4041
4182
  function DownloadGFF3({ handleClose, session, assembly: assemblyName, }) {
4042
- const [includeFASTA, setincludeFASTA] = useState(false);
4183
+ const [includeFASTA, setIncludeFASTA] = useState(false);
4043
4184
  const [errorMessage, setErrorMessage] = useState('');
4044
4185
  const { getInternetAccount } = session.apolloDataStore;
4045
4186
  const { assemblyManager } = session;
@@ -4088,7 +4229,7 @@ function DownloadGFF3({ handleClose, session, assembly: assemblyName, }) {
4088
4229
  window.open(exportUri, '_blank');
4089
4230
  }
4090
4231
  return (jsxs(Dialog, { open: true, title: "Export annotations", handleClose: handleClose, maxWidth: false, "data-testid": "download-gff3", children: [jsxs("form", { onSubmit: onSubmit, children: [jsxs(DialogContent, { style: { display: 'flex', flexDirection: 'column' }, children: [jsxs(DialogContentText, { children: ["Exporting annotations for ", assemblyName] }), jsx(FormGroup, { children: jsx(FormControlLabel, { "data-testid": "include-fasta-checkbox", control: jsx(Checkbox, { checked: includeFASTA, onChange: () => {
4091
- setincludeFASTA(!includeFASTA);
4232
+ setIncludeFASTA(!includeFASTA);
4092
4233
  }, disabled: !internetAccountConfigId }), label: "Include fasta sequence in GFF output" }) })] }), jsxs(DialogActions, { children: [jsx(Button, { variant: "contained", type: "submit", children: "Download" }), jsx(Button, { variant: "outlined", type: "submit", onClick: handleClose, children: "Cancel" })] })] }), errorMessage ? (jsx(DialogContent, { children: jsx(DialogContentText, { color: "error", children: errorMessage }) })) : null] }));
4093
4234
  }
4094
4235
  function getAssemblyGFF3Stream(assemblyName) {
@@ -4127,6 +4268,49 @@ async function* getFeaturesForAssembly(assemblyName) {
4127
4268
  db.close();
4128
4269
  }
4129
4270
 
4271
+ function ImportAnnotations({ assemblyName, handleClose, session, }) {
4272
+ const { changeManager } = session.apolloDataStore;
4273
+ const [file, setFile] = useState();
4274
+ const [errorMessage, setErrorMessage] = useState('');
4275
+ const [loading, setLoading] = useState(false);
4276
+ function handleChangeFile(e) {
4277
+ if (e.target.files) {
4278
+ setFile(e.target.files[0]);
4279
+ }
4280
+ }
4281
+ async function onSubmit(event) {
4282
+ event.preventDefault();
4283
+ if (!file) {
4284
+ return;
4285
+ }
4286
+ setLoading(true);
4287
+ setErrorMessage('');
4288
+ try {
4289
+ const stream = file
4290
+ .stream()
4291
+ .pipeThrough(new TransformStream(new GFFTransformer({ parseSequences: false })));
4292
+ for await (const value of stream) {
4293
+ const annotationFeature = gff3ToAnnotationFeature(value);
4294
+ const change = new AddFeatureChange({
4295
+ changedIds: [annotationFeature._id],
4296
+ typeName: 'AddFeatureChange',
4297
+ assembly: assemblyName,
4298
+ addedFeature: annotationFeature,
4299
+ });
4300
+ await changeManager.submit(change);
4301
+ }
4302
+ handleClose();
4303
+ }
4304
+ catch (error) {
4305
+ setErrorMessage(String(error));
4306
+ }
4307
+ finally {
4308
+ setLoading(false);
4309
+ }
4310
+ }
4311
+ return (jsxs(Dialog, { open: true, title: "Import annotations", handleClose: handleClose, children: [loading ? jsx(LinearProgress, {}) : null, jsxs("form", { onSubmit: onSubmit, children: [jsxs(DialogContent, { children: [jsx(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." }), jsx("input", { type: "file", accept: ".gff,.gff3", onChange: handleChangeFile, disabled: loading }), errorMessage ? (jsx(DialogContentText, { color: "error", children: errorMessage })) : null] }), jsxs(DialogActions, { children: [jsx(Button, { variant: "contained", type: "submit", disabled: !file || loading, children: "Import" }), jsx(Button, { variant: "outlined", onClick: handleClose, children: "Cancel" })] })] })] }));
4312
+ }
4313
+
4130
4314
  function ImportFeatures({ changeManager, handleClose, session, }) {
4131
4315
  const { apolloDataStore } = session;
4132
4316
  const [file, setFile] = useState();
@@ -4530,7 +4714,7 @@ function ManageUsers({ changeManager, handleClose, session, }) {
4530
4714
  });
4531
4715
  return newRow;
4532
4716
  }
4533
- return (jsxs(Dialog, { open: true, fullScreen: true, title: "Manage users", handleClose: handleClose, "data-testid": "manage-users", children: [jsxs(DialogContent, { children: [apolloInternetAccounts.length > 1 ? (jsxs(Fragment, { children: [jsx(DialogContentText, { children: "Select account" }), jsx(Select, { value: selectedInternetAccount.internetAccountId, onChange: handleChangeInternetAccount, disabled: !errorMessage, children: internetAccounts.map((option) => (jsx(MenuItem, { value: option.internetAccountId, children: option.name }, option.id))) })] })) : null, jsx("div", { style: { height: '100%', width: '100%' }, children: jsx(DataGrid, { pagination: true, rows: users, columns: gridColumns, getRowId: (row) => row._id, slots: { toolbar: GridToolbar }, getRowHeight: () => 'auto', isCellEditable: (params) => !isCurrentUser(params.id), processRowUpdate: processRowUpdate, onProcessRowUpdateError: (error) => {
4717
+ return (jsxs(Dialog, { open: true, fullScreen: true, title: "Manage users", handleClose: handleClose, "data-testid": "manage-users", children: [jsxs(DialogContent, { children: [apolloInternetAccounts.length > 1 ? (jsxs(Fragment, { children: [jsx(DialogContentText, { children: "Select account" }), jsx(Select, { value: selectedInternetAccount.internetAccountId, onChange: handleChangeInternetAccount, disabled: !errorMessage, children: internetAccounts.map((option) => (jsx(MenuItem, { value: option.internetAccountId, children: option.name }, option.id))) })] })) : null, jsx("div", { style: { height: '100%', width: '100%' }, children: jsx(DataGrid, { pagination: true, rows: users, columns: gridColumns, getRowId: (row) => row._id, showToolbar: true, getRowHeight: () => 'auto', isCellEditable: (params) => !isCurrentUser(params.id), processRowUpdate: processRowUpdate, onProcessRowUpdateError: (error) => {
4534
4718
  setErrorMessage(String(error));
4535
4719
  } }) })] }), jsx(DialogActions, { children: jsx(Button, { variant: "outlined", type: "submit", onClick: handleClose, children: "Close" }) }), errorMessage ? (jsx(DialogContent, { children: jsx(DialogContentText, { color: "error", children: errorMessage }) })) : null] }));
4536
4720
  }
@@ -4840,12 +5024,13 @@ const AddRefSeqAliases = observer(function AddRefSeqAliases({ changeManager, han
4840
5024
  const [enableSubmit, setEnableSubmit] = useState(false);
4841
5025
  const [selectedAssembly, setSelectedAssembly] = useState();
4842
5026
  const [selectedRows, setSelectedRows] = useState([]);
4843
- const [refNameAliasMap, setRefNameAliasMap] = useState(new Map());
5027
+ const [refNameAliasMap, setRefNameAliasMap] = useState(() => new Map());
4844
5028
  const { apolloDataStore } = session;
4845
5029
  const { collaborationServerDriver } = apolloDataStore;
4846
5030
  const assemblies = collaborationServerDriver.getAssemblies();
4847
5031
  useEffect(() => {
4848
5032
  if (assemblies.length > 0) {
5033
+ // eslint-disable-next-line @eslint-react/set-state-in-effect
4849
5034
  setSelectedAssembly(assemblies[0]);
4850
5035
  collaborationServerDriver
4851
5036
  .getRefNameAliases(assemblies[0].name)
@@ -4857,7 +5042,7 @@ const AddRefSeqAliases = observer(function AddRefSeqAliases({ changeManager, han
4857
5042
  setErrorMessage('Error fetching refName aliases for assembly');
4858
5043
  });
4859
5044
  }
4860
- // eslint-disable-next-line react-hooks/exhaustive-deps
5045
+ // eslint-disable-next-line @eslint-react/exhaustive-deps
4861
5046
  }, []);
4862
5047
  const initializeRefNameAliasMap = (refNameAliasesList) => {
4863
5048
  const initialMap = new Map();
@@ -5262,17 +5447,23 @@ const useStyles$d = makeStyles()((theme) => ({
5262
5447
  }));
5263
5448
  function GoogleButton(props) {
5264
5449
  const { classes } = useStyles$d();
5265
- const { disabled } = props;
5266
- return (jsx(Button, { className: classes.loginButton, variant: "outlined", startIcon: jsx(Google, { color: disabled ? 'disabled' : undefined }), ...props, children: "Sign in with Google" }));
5450
+ const { message } = props;
5451
+ return (jsx(Button, { className: classes.loginButton, variant: "outlined", startIcon: jsx(Google, {}), ...props, children: message }));
5267
5452
  }
5268
5453
  function MicrosoftButton(props) {
5269
5454
  const { classes } = useStyles$d();
5270
- const { disabled } = props;
5271
- return (jsx(Button, { className: classes.loginButton, variant: "outlined", startIcon: jsx(Microsoft, { color: disabled ? 'disabled' : undefined }), ...props, children: "Sign in with Microsoft" }));
5455
+ const { message } = props;
5456
+ return (jsx(Button, { className: classes.loginButton, variant: "outlined", startIcon: jsx(Microsoft, {}), ...props, children: message }));
5272
5457
  }
5273
5458
  function GuestButton(props) {
5274
5459
  const { classes } = useStyles$d();
5275
- return (jsx(Button, { className: classes.loginButton, variant: "outlined", startIcon: jsx(AccountCircleIcon, { fontSize: "small" }), ...props, children: "Continue as Guest" }));
5460
+ const { message } = props;
5461
+ return (jsx(Button, { className: classes.loginButton, variant: "outlined", startIcon: jsx(AccountCircleIcon, { fontSize: "small" }), ...props, children: message }));
5462
+ }
5463
+ function GenericButton(props) {
5464
+ const { classes } = useStyles$d();
5465
+ const { message } = props;
5466
+ return (jsx(Button, { className: classes.loginButton, variant: "outlined", startIcon: jsx(BusinessIcon, { fontSize: "small" }), ...props, children: message }));
5276
5467
  }
5277
5468
 
5278
5469
  const useStyles$c = makeStyles()((theme) => ({
@@ -5284,7 +5475,7 @@ const useStyles$c = makeStyles()((theme) => ({
5284
5475
  const AuthTypeSelector = ({ baseURL, handleClose, name, }) => {
5285
5476
  const { classes } = useStyles$c();
5286
5477
  const [errorMessage, setErrorMessage] = useState('');
5287
- const [loginTypes, setLoginTypes] = useState([]);
5478
+ const [loginTypes, setLoginTypes] = useState(null);
5288
5479
  useEffect(() => {
5289
5480
  const controller = new AbortController();
5290
5481
  const { signal } = controller;
@@ -5298,6 +5489,9 @@ const AuthTypeSelector = ({ baseURL, handleClose, name, }) => {
5298
5489
  }
5299
5490
  const data = (await response.json());
5300
5491
  setLoginTypes(data);
5492
+ if (data.length === 0) {
5493
+ setErrorMessage('No login types configured');
5494
+ }
5301
5495
  }
5302
5496
  getAuthTypes().catch((error) => {
5303
5497
  if (!isAbortException(error)) {
@@ -5309,26 +5503,35 @@ const AuthTypeSelector = ({ baseURL, handleClose, name, }) => {
5309
5503
  };
5310
5504
  }, [baseURL]);
5311
5505
  function handleClick(authType) {
5312
- if (authType === 'google') {
5313
- handleClose('google');
5314
- }
5315
- else if (authType === 'microsoft') {
5316
- handleClose('microsoft');
5317
- }
5318
- else {
5319
- handleClose('guest');
5320
- }
5321
- }
5322
- const allowGoogle = loginTypes.includes('google');
5323
- const allowMicrosoft = loginTypes.includes('microsoft');
5324
- const allowGuest = loginTypes.includes('guest');
5325
- return (jsxs(Dialog, { open: true, title: `Log in to ${name}`, handleClose: handleClose, maxWidth: false, "data-testid": "login-apollo", children: [jsxs(DialogContent, { style: { display: 'flex', flexDirection: 'column', paddingTop: 8 }, children: [allowGoogle ? (jsx(GoogleButton, { disabled: !allowGoogle, onClick: () => {
5326
- handleClick('google');
5327
- } })) : null, allowMicrosoft ? (jsx(MicrosoftButton, { disabled: !allowMicrosoft, onClick: () => {
5328
- handleClick('microsoft');
5329
- } })) : null, allowGuest ? (jsxs(Fragment, { children: [jsx(Divider, { className: classes.divider }), jsx(GuestButton, { onClick: () => {
5330
- handleClick('guest');
5331
- } })] })) : null] }), jsx(DialogActions, { children: jsx(Button, { variant: "outlined", type: "submit", onClick: () => {
5506
+ handleClose(authType);
5507
+ }
5508
+ if (loginTypes === null) {
5509
+ return 'Loading…';
5510
+ }
5511
+ const firstLoginType = loginTypes.at(0);
5512
+ if (firstLoginType && loginTypes.length === 1 && !firstLoginType.needsPopup) {
5513
+ handleClick(firstLoginType);
5514
+ }
5515
+ return (jsxs(Dialog, { open: true, title: `Log in to ${name}`, handleClose: handleClose, maxWidth: false, "data-testid": "login-apollo", children: [jsx(DialogContent, { style: { display: 'flex', flexDirection: 'column', paddingTop: 8 }, children: loginTypes.map((loginType) => {
5516
+ if (loginType.name === 'google') {
5517
+ return (jsx(GoogleButton, { message: loginType.message, onClick: () => {
5518
+ handleClick(loginType);
5519
+ } }, loginType.name));
5520
+ }
5521
+ if (loginType.name === 'microsoft') {
5522
+ return (jsx(MicrosoftButton, { message: loginType.message, onClick: () => {
5523
+ handleClick(loginType);
5524
+ } }, loginType.name));
5525
+ }
5526
+ if (loginType.name === 'guest') {
5527
+ return (jsxs(React.Fragment, { children: [jsx(Divider, { className: classes.divider }), jsx(GuestButton, { message: loginType.message, onClick: () => {
5528
+ handleClick(loginType);
5529
+ } })] }, loginType.name));
5530
+ }
5531
+ return (jsx(GenericButton, { message: loginType.message, onClick: () => {
5532
+ handleClick(loginType);
5533
+ } }, loginType.name));
5534
+ }) }), jsx(DialogActions, { children: jsx(Button, { variant: "outlined", type: "submit", onClick: () => {
5332
5535
  handleClose();
5333
5536
  }, children: "Cancel" }) }), errorMessage ? (jsx(DialogContent, { children: jsx(DialogContentText, { color: "error", children: errorMessage }) })) : null] }));
5334
5537
  };
@@ -5524,13 +5727,13 @@ const stateModelFactory$3 = (configSchema) => {
5524
5727
  },
5525
5728
  ]);
5526
5729
  });
5527
- if (authType !== 'guest') {
5730
+ if (authType.needsPopup) {
5528
5731
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
5529
- self.openAuthWindow(authType, resolve, reject);
5732
+ self.openAuthWindow(authType.name, resolve, reject);
5530
5733
  return;
5531
5734
  }
5532
5735
  const url = new URL('auth/login', baseURL);
5533
- const searchParams = new URLSearchParams({ type: authType });
5736
+ const searchParams = new URLSearchParams({ type: authType.name });
5534
5737
  url.search = searchParams.toString();
5535
5738
  const uri = url.toString();
5536
5739
  const response = await fetch(uri, { signal: self.controller.signal });
@@ -5651,7 +5854,7 @@ const stateModelFactory$3 = (configSchema) => {
5651
5854
  socket.on('COMMON', (message) => {
5652
5855
  if ('checkResult' in message) {
5653
5856
  if (message.deleted) {
5654
- deleteCheckResult(message.checkResult._id.toString());
5857
+ deleteCheckResult(message.checkResult._id);
5655
5858
  }
5656
5859
  else {
5657
5860
  addCheckResult(message.checkResult);
@@ -6271,7 +6474,9 @@ const DefaultAttributeEditor = observer(function DefaultAttributeEditor({ attrib
6271
6474
  return newValues;
6272
6475
  });
6273
6476
  }
6274
- return (jsxs(Fragment, { children: [newValues.map((value, idx) => (jsxs("div", { style: { display: 'flex' }, children: [jsx(StringTextField, { value: value, onChangeCommitted: (editedValue) => {
6477
+ return (jsxs(Fragment, { children: [newValues.map((value, idx) => (
6478
+ // eslint-disable-next-line @eslint-react/no-array-index-key
6479
+ jsxs("div", { style: { display: 'flex' }, children: [jsx(StringTextField, { value: value, onChangeCommitted: (editedValue) => {
6275
6480
  updateValue(idx, editedValue);
6276
6481
  }, variant: "outlined", fullWidth: true }), jsx(IconButton, { "aria-label": "delete", size: "medium", edge: "end", onClick: () => {
6277
6482
  deleteValue(idx);
@@ -6283,7 +6488,9 @@ const DefaultAttributeEditor = observer(function DefaultAttributeEditor({ attrib
6283
6488
  });
6284
6489
 
6285
6490
  function DefaultAttributeViewer({ values }) {
6286
- return (jsx(Fragment, { children: values?.map((value, idx) => (jsx(Typography, { variant: "body2", color: "textSecondary", children: value }, `${idx}.${value}`))) }));
6491
+ return (jsx(Fragment, { children: values?.map((value, idx) => (jsx(Typography
6492
+ // eslint-disable-next-line @eslint-react/no-array-index-key
6493
+ , { variant: "body2", color: "textSecondary", children: value }, `${idx}.${value}`))) }));
6287
6494
  }
6288
6495
 
6289
6496
  const useStyles$a = makeStyles()((theme) => ({
@@ -6589,6 +6796,7 @@ const ApolloFeatureDetailsWidget = observer(function ApolloFeatureDetailsWidget(
6589
6796
  const { classes } = useStyles$8();
6590
6797
  const [panelState, setPanelState] = useState(['attributes']);
6591
6798
  useEffect(() => {
6799
+ // eslint-disable-next-line @eslint-react/set-state-in-effect
6592
6800
  setPanelState(['attributes']);
6593
6801
  }, [feature]);
6594
6802
  if (!(feature && currentAssembly)) {
@@ -6890,7 +7098,7 @@ const TranscriptSequence = observer(function TranscriptSequence({ assembly, feat
6890
7098
  else {
6891
7099
  setSequenceOptions(defaultSequenceOptions);
6892
7100
  }
6893
- // eslint-disable-next-line react-hooks/exhaustive-deps
7101
+ // eslint-disable-next-line @eslint-react/exhaustive-deps
6894
7102
  }, [feature]);
6895
7103
  if (!(currentAssembly && refData)) {
6896
7104
  return null;
@@ -6953,19 +7161,6 @@ const TranscriptSequence = observer(function TranscriptSequence({ assembly, feat
6953
7161
  .join(';'), "(strand=", feature.strand === 1 ? '+' : '-', ";length=", getSequenceLength(sequenceSegments), ")", jsx("br", {}), wrapSequence(sequenceSegments, SEQUENCE_WRAP_LENGTH)] })] }));
6954
7162
  });
6955
7163
 
6956
- const StyledTextField = styled(NumberTextField)(() => ({
6957
- '&.MuiFormControl-root': {
6958
- marginTop: 0,
6959
- marginBottom: 0,
6960
- width: '100%',
6961
- },
6962
- '& .MuiInputBase-input': {
6963
- fontSize: 12,
6964
- height: 20,
6965
- padding: 1,
6966
- paddingLeft: 10,
6967
- },
6968
- }));
6969
7164
  const SequenceContainer = styled('div')({
6970
7165
  display: 'flex',
6971
7166
  justifyContent: 'center',
@@ -6987,6 +7182,187 @@ const StyledAccordionSummary$1 = styled(AccordionSummary)(() => ({
6987
7182
  maxHeight: 30,
6988
7183
  },
6989
7184
  }));
7185
+ function Translation({ changeInProgress, cdsLocations, refData, strand,
7186
+ // eslint-disable-next-line @typescript-eslint/unbound-method
7187
+ updateCDSLocation, cdsMin, cdsMax, feature, session, }) {
7188
+ const seqRef = useRef(null);
7189
+ const { notify } = session;
7190
+ const cdsSequences = [];
7191
+ const [firstLocation] = cdsLocations;
7192
+ for (const loc of firstLocation) {
7193
+ const seq = refData.getSequence(loc.min, loc.max);
7194
+ cdsSequences.push(strand === -1 ? revcom(seq) : seq);
7195
+ }
7196
+ const cdsSequence = cdsSequences.join('');
7197
+ const proteinSequence = [];
7198
+ for (let codonGenomicPos = 0; codonGenomicPos < cdsSequence.length; codonGenomicPos += 3) {
7199
+ const codonSeq = cdsSequence
7200
+ .slice(codonGenomicPos, codonGenomicPos + 3)
7201
+ .toUpperCase();
7202
+ const protein = defaultCodonTable[codonSeq] || '&';
7203
+ proteinSequence.push(protein);
7204
+ }
7205
+ const onCopyClick = () => {
7206
+ const seqDiv = seqRef.current;
7207
+ if (!seqDiv) {
7208
+ return;
7209
+ }
7210
+ void copyToClipboard(seqDiv);
7211
+ };
7212
+ // Codon position is the index of the start codon in the CDS genomic sequence
7213
+ // Calculate the genomic location of the start codon based on the codon position in the CDS
7214
+ const getCodonGenomicLocation = (codonGenomicPosition) => {
7215
+ const [firstLocation] = cdsLocations;
7216
+ let cdsLen = 0;
7217
+ const sortedCDSLocations = firstLocation.toSorted(({ min: a }, { min: b }) => a - b);
7218
+ // Suppose CDS locations are [{min: 0, max: 10}, {min: 20, max: 30}, {min: 40, max: 50}]
7219
+ // and codonGenomicPosition is 25
7220
+ // ((10 - 0) + (30 - 20) + (50 - 40)) > 25
7221
+ // So, start codon is in (40, 50)
7222
+ // 40 + (25-20) = 45 is the genomic location of the start codon
7223
+ if (strand === 1) {
7224
+ for (const loc of sortedCDSLocations) {
7225
+ const locLength = loc.max - loc.min;
7226
+ if (cdsLen + locLength > codonGenomicPosition) {
7227
+ return loc.min + (codonGenomicPosition - cdsLen);
7228
+ }
7229
+ cdsLen += locLength;
7230
+ }
7231
+ }
7232
+ else if (strand === -1) {
7233
+ for (let i = sortedCDSLocations.length - 1; i >= 0; i--) {
7234
+ const loc = sortedCDSLocations[i];
7235
+ const locLength = loc.max - loc.min;
7236
+ if (cdsLen + locLength > codonGenomicPosition) {
7237
+ return loc.max - (codonGenomicPosition - cdsLen);
7238
+ }
7239
+ cdsLen += locLength;
7240
+ }
7241
+ }
7242
+ if (strand === 1) {
7243
+ return cdsMin;
7244
+ }
7245
+ return cdsMax;
7246
+ };
7247
+ const trimTranslationSequence = () => {
7248
+ if ((proteinSequence.at(0) === 'M' && proteinSequence.at(-1) === '*') ||
7249
+ proteinSequence.length === 0) {
7250
+ return;
7251
+ }
7252
+ // Trim any sequence before first start codon and after stop codon
7253
+ const startCodonIndex = proteinSequence.indexOf('M');
7254
+ const stopCodonIndex = proteinSequence.lastIndexOf('*');
7255
+ const startCodonPos = startCodonIndex * 3;
7256
+ const stopCodonPos = stopCodonIndex * 3;
7257
+ const startCodonGenomicLoc = getCodonGenomicLocation(startCodonPos);
7258
+ let stopCodonGenomicLoc = getCodonGenomicLocation(stopCodonPos);
7259
+ if (strand === 1) {
7260
+ if (startCodonGenomicLoc > stopCodonGenomicLoc) {
7261
+ notify('Start codon genomic location should be less than stop codon genomic location', 'error');
7262
+ return;
7263
+ }
7264
+ let promise;
7265
+ stopCodonGenomicLoc += 3; // move to end of stop codon
7266
+ if (startCodonGenomicLoc !== cdsMin) {
7267
+ promise = new Promise((resolve) => {
7268
+ updateCDSLocation(cdsMin, startCodonGenomicLoc, feature, true, () => {
7269
+ resolve(true);
7270
+ });
7271
+ });
7272
+ }
7273
+ if (stopCodonGenomicLoc !== cdsMax) {
7274
+ if (promise) {
7275
+ void promise.then(() => {
7276
+ updateCDSLocation(cdsMax, stopCodonGenomicLoc, feature, false);
7277
+ });
7278
+ }
7279
+ else {
7280
+ updateCDSLocation(cdsMax, stopCodonGenomicLoc, feature, false);
7281
+ }
7282
+ }
7283
+ }
7284
+ if (strand === -1) {
7285
+ // reverse strand
7286
+ if (startCodonGenomicLoc < stopCodonGenomicLoc) {
7287
+ notify('Start codon genomic location should be less than stop codon genomic location', 'error');
7288
+ return;
7289
+ }
7290
+ let promise;
7291
+ stopCodonGenomicLoc -= 3; // move to end of stop codon
7292
+ if (startCodonGenomicLoc !== cdsMax) {
7293
+ promise = new Promise((resolve) => {
7294
+ updateCDSLocation(cdsMax, startCodonGenomicLoc, feature, false, () => {
7295
+ resolve(true);
7296
+ });
7297
+ });
7298
+ }
7299
+ if (stopCodonGenomicLoc !== cdsMin) {
7300
+ if (promise) {
7301
+ void promise.then(() => {
7302
+ updateCDSLocation(cdsMin, stopCodonGenomicLoc, feature, true);
7303
+ });
7304
+ }
7305
+ else {
7306
+ updateCDSLocation(cdsMin, stopCodonGenomicLoc, feature, true);
7307
+ }
7308
+ }
7309
+ }
7310
+ notify('Translation sequence trimmed to start and stop codons', 'success');
7311
+ };
7312
+ return (jsx("div", { children: jsxs(Accordion, { children: [jsx(StyledAccordionSummary$1, { expandIcon: jsx(ExpandMoreIcon, { style: { color: 'white' } }), "aria-controls": "panel1-content", id: "panel1-header", children: jsx(Typography, { component: "span", fontWeight: 'bold', children: "Translation" }) }), jsxs(AccordionDetails, { children: [jsx(SequenceContainer, { children: jsx(Typography, { component: 'span', ref: seqRef, style: { maxHeight: 120, overflowY: 'scroll' }, children: proteinSequence.map((protein, idx) => {
7313
+ const codonGenomicPos = idx * 3;
7314
+ if (protein === 'M') {
7315
+ return (jsx(Typography, { component: 'span', style: {
7316
+ backgroundColor: changeInProgress
7317
+ ? 'lightgray'
7318
+ : 'yellow',
7319
+ cursor: 'pointer',
7320
+ border: '1px solid black',
7321
+ }, onClick: () => {
7322
+ if (changeInProgress) {
7323
+ return;
7324
+ }
7325
+ // NOTE: codonGenomicPos is important here for calculating the genomic location
7326
+ // of the start codon. We are using the codonGenomicPos as the key in the typography
7327
+ // elements to maintain the genomic postion of the codon start
7328
+ const startCodonGenomicLocation = getCodonGenomicLocation(codonGenomicPos);
7329
+ if (startCodonGenomicLocation !== cdsMin &&
7330
+ strand === 1) {
7331
+ updateCDSLocation(cdsMin, startCodonGenomicLocation, feature, true);
7332
+ }
7333
+ if (startCodonGenomicLocation !== cdsMax &&
7334
+ strand === -1) {
7335
+ updateCDSLocation(cdsMax, startCodonGenomicLocation, feature, false);
7336
+ }
7337
+ }, children: protein }, codonGenomicPos));
7338
+ }
7339
+ if (protein === '*') {
7340
+ return (jsx(Typography, { style: { backgroundColor: 'red', color: 'white' }, component: 'span', children: protein }, codonGenomicPos));
7341
+ }
7342
+ // Pass the codonGenomicPos as the key to maintain the genomic position of the codon
7343
+ return (jsx(Typography, { component: 'span', children: protein }, codonGenomicPos));
7344
+ }) }) }), jsxs("div", { style: {
7345
+ marginTop: 10,
7346
+ display: 'flex',
7347
+ flexDirection: 'row',
7348
+ alignItems: 'center',
7349
+ gap: 10,
7350
+ }, children: [jsx(Tooltip$1, { title: "Copy", children: jsx("button", { onClick: onCopyClick, style: { border: 'none', background: 'none', padding: 0 }, disabled: changeInProgress, children: jsx(ContentCopyIcon, { style: { fontSize: 15 } }) }) }), jsx(Tooltip$1, { title: "Trim", children: jsx("button", { onClick: trimTranslationSequence, style: { border: 'none', background: 'none', padding: 0 }, disabled: changeInProgress, children: jsx(ContentCutIcon, { style: { fontSize: 15 } }) }) })] })] })] }) }));
7351
+ }
7352
+
7353
+ const StyledTextField = styled(NumberTextField)(() => ({
7354
+ '&.MuiFormControl-root': {
7355
+ marginTop: 0,
7356
+ marginBottom: 0,
7357
+ width: '100%',
7358
+ },
7359
+ '& .MuiInputBase-input': {
7360
+ fontSize: 12,
7361
+ height: 20,
7362
+ padding: 1,
7363
+ paddingLeft: 10,
7364
+ },
7365
+ }));
6990
7366
  const Strand = (props) => {
6991
7367
  const { strand } = props;
6992
7368
  return (jsx("div", { children: strand === 1 ? (jsx(AddIcon, {})) : strand === -1 ? (jsx(RemoveIcon, {})) : (jsx(Typography, { component: 'span', children: "N/A" })) }));
@@ -7005,7 +7381,6 @@ const TranscriptWidgetEditLocation = observer(function TranscriptWidgetEditLocat
7005
7381
  const currentAssembly = session.apolloDataStore.assemblies.get(assembly);
7006
7382
  const refData = currentAssembly?.getByRefName(refName);
7007
7383
  const { changeManager } = session.apolloDataStore;
7008
- const seqRef = useRef(null);
7009
7384
  const { changeInProgress } = session;
7010
7385
  if (!refData) {
7011
7386
  return null;
@@ -7432,184 +7807,6 @@ const TranscriptWidgetEditLocation = observer(function TranscriptWidgetEditLocat
7432
7807
  },
7433
7808
  ];
7434
7809
  };
7435
- const getTranslationSequence = () => {
7436
- let wholeSequence = '';
7437
- const [firstLocation] = cdsLocations;
7438
- const sortedCDSLocations = firstLocation.toSorted(({ min: a }, { min: b }) => a - b);
7439
- for (const loc of sortedCDSLocations) {
7440
- wholeSequence += refData.getSequence(loc.min, loc.max);
7441
- }
7442
- if (strand === -1) {
7443
- // Original: ACGCAT
7444
- // Complement: TGCGTA
7445
- // Reverse complement: ATGCGT
7446
- wholeSequence = revcom(wholeSequence);
7447
- }
7448
- const elements = [];
7449
- for (let codonGenomicPos = 0; codonGenomicPos < wholeSequence.length; codonGenomicPos += 3) {
7450
- const codonSeq = wholeSequence
7451
- .slice(codonGenomicPos, codonGenomicPos + 3)
7452
- .toUpperCase();
7453
- const protein = defaultCodonTable[codonSeq] || '&';
7454
- // highlight start codon and stop codons
7455
- if (codonSeq === 'ATG') {
7456
- elements.push(jsx(Typography, { component: 'span', style: {
7457
- backgroundColor: changeInProgress ? 'lightgray' : 'yellow',
7458
- cursor: 'pointer',
7459
- border: '1px solid black',
7460
- }, onClick: () => {
7461
- if (changeInProgress) {
7462
- return;
7463
- }
7464
- // NOTE: codonGenomicPos is important here for calculating the genomic location
7465
- // of the start codon. We are using the codonGenomicPos as the key in the typography
7466
- // elements to maintain the genomic postion of the codon start
7467
- const startCodonGenomicLocation = getCodonGenomicLocation(codonGenomicPos);
7468
- if (startCodonGenomicLocation !== cdsMin && strand === 1) {
7469
- updateCDSLocation(cdsMin, startCodonGenomicLocation, feature, true);
7470
- }
7471
- if (startCodonGenomicLocation !== cdsMax && strand === -1) {
7472
- updateCDSLocation(cdsMax, startCodonGenomicLocation, feature, false);
7473
- }
7474
- }, children: protein }, codonGenomicPos));
7475
- }
7476
- else if (['TAA', 'TAG', 'TGA'].includes(codonSeq)) {
7477
- elements.push(jsx(Typography, { style: { backgroundColor: 'red', color: 'white' }, component: 'span', children: protein }, codonGenomicPos));
7478
- }
7479
- else {
7480
- elements.push(
7481
- // Pass the codonGenomicPos as the key to maintain the genomic position of the codon
7482
- jsx(Typography, { component: 'span', children: protein }, codonGenomicPos));
7483
- }
7484
- }
7485
- return elements;
7486
- };
7487
- // Codon position is the index of the start codon in the CDS genomic sequence
7488
- // Calculate the genomic location of the start codon based on the codon position in the CDS
7489
- const getCodonGenomicLocation = (codonGenomicPosition) => {
7490
- const [firstLocation] = cdsLocations;
7491
- let cdsLen = 0;
7492
- const sortedCDSLocations = firstLocation.toSorted(({ min: a }, { min: b }) => a - b);
7493
- // Suppose CDS locations are [{min: 0, max: 10}, {min: 20, max: 30}, {min: 40, max: 50}]
7494
- // and codonGenomicPosition is 25
7495
- // ((10 - 0) + (30 - 20) + (50 - 40)) > 25
7496
- // So, start codon is in (40, 50)
7497
- // 40 + (25-20) = 45 is the genomic location of the start codon
7498
- if (strand === 1) {
7499
- for (const loc of sortedCDSLocations) {
7500
- const locLength = loc.max - loc.min;
7501
- if (cdsLen + locLength > codonGenomicPosition) {
7502
- return loc.min + (codonGenomicPosition - cdsLen);
7503
- }
7504
- cdsLen += locLength;
7505
- }
7506
- }
7507
- else if (strand === -1) {
7508
- for (let i = sortedCDSLocations.length - 1; i >= 0; i--) {
7509
- const loc = sortedCDSLocations[i];
7510
- const locLength = loc.max - loc.min;
7511
- if (cdsLen + locLength > codonGenomicPosition) {
7512
- return loc.max - (codonGenomicPosition - cdsLen);
7513
- }
7514
- cdsLen += locLength;
7515
- }
7516
- }
7517
- if (strand === 1) {
7518
- return cdsMin;
7519
- }
7520
- return cdsMax;
7521
- };
7522
- const trimTranslationSequence = () => {
7523
- const sequenceElements = getTranslationSequence();
7524
- const translationSequence = sequenceElements
7525
- .map((el) => el.props.children)
7526
- .join('');
7527
- if (translationSequence.startsWith('M') &&
7528
- translationSequence.endsWith('*')) {
7529
- return;
7530
- }
7531
- // NOTE: We are maintaining the genomic location of the codon start as the "key"
7532
- // in typography elements. See getTranslationSequence function
7533
- const translSeqCodonStartGenomicPosArr = [];
7534
- for (const el of sequenceElements) {
7535
- translSeqCodonStartGenomicPosArr.push({
7536
- codonGenomicPos: el.key,
7537
- sequenceLetter: el.props.children,
7538
- });
7539
- }
7540
- if (translSeqCodonStartGenomicPosArr.length === 0) {
7541
- return;
7542
- }
7543
- // Trim any sequence before first start codon and after stop codon
7544
- const startCodonIndex = translationSequence.indexOf('M');
7545
- const stopCodonIndex = translationSequence.indexOf('*');
7546
- const startCodonPos = translSeqCodonStartGenomicPosArr[startCodonIndex].codonGenomicPos;
7547
- const stopCodonPos = translSeqCodonStartGenomicPosArr[stopCodonIndex].codonGenomicPos;
7548
- if (!startCodonPos || !stopCodonPos) {
7549
- return;
7550
- }
7551
- const startCodonGenomicLoc = getCodonGenomicLocation(startCodonPos);
7552
- let stopCodonGenomicLoc = getCodonGenomicLocation(stopCodonPos);
7553
- if (strand === 1) {
7554
- if (startCodonGenomicLoc > stopCodonGenomicLoc) {
7555
- notify('Start codon genomic location should be less than stop codon genomic location', 'error');
7556
- return;
7557
- }
7558
- let promise;
7559
- stopCodonGenomicLoc += 3; // move to end of stop codon
7560
- if (startCodonGenomicLoc !== cdsMin) {
7561
- promise = new Promise((resolve) => {
7562
- updateCDSLocation(cdsMin, startCodonGenomicLoc, feature, true, () => {
7563
- resolve(true);
7564
- });
7565
- });
7566
- }
7567
- if (stopCodonGenomicLoc !== cdsMax) {
7568
- if (promise) {
7569
- void promise.then(() => {
7570
- updateCDSLocation(cdsMax, stopCodonGenomicLoc, feature, false);
7571
- });
7572
- }
7573
- else {
7574
- updateCDSLocation(cdsMax, stopCodonGenomicLoc, feature, false);
7575
- }
7576
- }
7577
- }
7578
- if (strand === -1) {
7579
- // reverse strand
7580
- if (startCodonGenomicLoc < stopCodonGenomicLoc) {
7581
- notify('Start codon genomic location should be less than stop codon genomic location', 'error');
7582
- return;
7583
- }
7584
- let promise;
7585
- stopCodonGenomicLoc -= 3; // move to end of stop codon
7586
- if (startCodonGenomicLoc !== cdsMax) {
7587
- promise = new Promise((resolve) => {
7588
- updateCDSLocation(cdsMax, startCodonGenomicLoc, feature, false, () => {
7589
- resolve(true);
7590
- });
7591
- });
7592
- }
7593
- if (stopCodonGenomicLoc !== cdsMin) {
7594
- if (promise) {
7595
- void promise.then(() => {
7596
- updateCDSLocation(cdsMin, stopCodonGenomicLoc, feature, true);
7597
- });
7598
- }
7599
- else {
7600
- updateCDSLocation(cdsMin, stopCodonGenomicLoc, feature, true);
7601
- }
7602
- }
7603
- }
7604
- notify('Translation sequence trimmed to start and stop codons', 'success');
7605
- };
7606
- const onCopyClick = () => {
7607
- const seqDiv = seqRef.current;
7608
- if (!seqDiv) {
7609
- return;
7610
- }
7611
- void copyToClipboard(seqDiv);
7612
- };
7613
7810
  return (jsxs("div", { children: [cdsPresent && (jsx("div", { children: jsxs(Grid, { container: true, justifyContent: "center", alignItems: "center", style: { textAlign: 'center' }, children: [jsx(Grid, { size: 1 }), strand === 1 ? (jsx(Grid, { size: 4, children: jsx(StyledTextField, { margin: "dense", variant: "outlined", value: cdsMin + 1, onChangeCommitted: (newLocation) => {
7614
7811
  return updateCDSLocation(cdsMin, newLocation - 1, feature, true);
7615
7812
  }, style: { border: '1px solid black', borderRadius: 5 }, disabled: changeInProgress }) })) : (jsx(Grid, { size: 4, children: jsx(StyledTextField, { margin: "dense", variant: "outlined", value: cdsMax, onChangeCommitted: (newLocation) => {
@@ -7619,8 +7816,12 @@ const TranscriptWidgetEditLocation = observer(function TranscriptWidgetEditLocat
7619
7816
  }, style: { border: '1px solid black', borderRadius: 5 }, disabled: changeInProgress }) })) : (jsx(Grid, { size: 4, children: jsx(StyledTextField, { margin: "dense", variant: "outlined", value: cdsMin + 1, onChangeCommitted: (newLocation) => {
7620
7817
  return updateCDSLocation(cdsMin, newLocation - 1, feature, true);
7621
7818
  }, style: { border: '1px solid black', borderRadius: 5 }, disabled: changeInProgress }) })), jsx(Grid, { size: 1 })] }) })), jsxs("div", { style: { marginTop: 5, marginBottom: 10 }, children: [jsx("div", { style: { textAlign: 'center' }, children: jsx(Typography, { children: "Exons" }) }), transcriptExonParts.map((loc, index) => {
7622
- return (jsx("div", { children: loc.type === 'exon' && (jsxs(Grid, { container: true, justifyContent: "center", alignItems: "center", style: { textAlign: 'center' }, children: [jsx(Grid, { size: 1, children: index !== 0 &&
7623
- getFivePrimeSpliceSite(loc, index).map((site, idx) => (jsx(Typography, { component: 'span', color: site.color, children: site.spliceSite }, idx))) }), strand === 1 ? (jsx(Grid, { size: 4, style: { padding: 0 }, children: jsx(StyledTextField, { margin: "dense", variant: "outlined", value: loc.min + 1, onChangeCommitted: (newLocation) => {
7819
+ return (
7820
+ // eslint-disable-next-line @eslint-react/no-array-index-key
7821
+ jsx("div", { children: loc.type === 'exon' && (jsxs(Grid, { container: true, justifyContent: "center", alignItems: "center", style: { textAlign: 'center' }, children: [jsx(Grid, { size: 1, children: index !== 0 &&
7822
+ getFivePrimeSpliceSite(loc, index).map((site, idx) => (jsx(Typography
7823
+ // eslint-disable-next-line @eslint-react/no-array-index-key
7824
+ , { component: 'span', color: site.color, children: site.spliceSite }, idx))) }), strand === 1 ? (jsx(Grid, { size: 4, style: { padding: 0 }, children: jsx(StyledTextField, { margin: "dense", variant: "outlined", value: loc.min + 1, onChangeCommitted: (newLocation) => {
7624
7825
  return handleExonLocationChange(loc.min, newLocation - 1, feature, true);
7625
7826
  }, disabled: changeInProgress }) })) : (jsx(Grid, { size: 4, style: { padding: 0 }, children: jsx(StyledTextField, { margin: "dense", variant: "outlined", value: loc.max, onChangeCommitted: (newLocation) => {
7626
7827
  return handleExonLocationChange(loc.max, newLocation, feature, false);
@@ -7629,14 +7830,10 @@ const TranscriptWidgetEditLocation = observer(function TranscriptWidgetEditLocat
7629
7830
  }, disabled: changeInProgress }) })) : (jsx(Grid, { size: 4, style: { padding: 0 }, children: jsx(StyledTextField, { margin: "dense", variant: "outlined", value: loc.min + 1, onChangeCommitted: (newLocation) => {
7630
7831
  return handleExonLocationChange(loc.min, newLocation - 1, feature, true);
7631
7832
  }, disabled: changeInProgress }) })), jsx(Grid, { size: 1, children: index !== transcriptExonParts.length - 1 &&
7632
- getThreePrimeSpliceSite(loc, index).map((site, idx) => (jsx(Typography, { component: 'span', color: site.color, children: site.spliceSite }, idx))) })] })) }, index));
7633
- })] }), cdsPresent && (jsx("div", { children: jsxs(Accordion, { children: [jsx(StyledAccordionSummary$1, { expandIcon: jsx(ExpandMoreIcon, { style: { color: 'white' } }), "aria-controls": "panel1-content", id: "panel1-header", children: jsx(Typography, { component: "span", fontWeight: 'bold', children: "Translation" }) }), jsxs(AccordionDetails, { children: [jsx(SequenceContainer, { children: jsx(Typography, { component: 'span', ref: seqRef, style: { maxHeight: 120, overflowY: 'scroll' }, children: getTranslationSequence() }) }), jsxs("div", { style: {
7634
- marginTop: 10,
7635
- display: 'flex',
7636
- flexDirection: 'row',
7637
- alignItems: 'center',
7638
- gap: 10,
7639
- }, children: [jsx(Tooltip$1, { title: "Copy", children: jsx("button", { onClick: onCopyClick, style: { border: 'none', background: 'none', padding: 0 }, disabled: changeInProgress, children: jsx(ContentCopyIcon, { style: { fontSize: 15 } }) }) }), jsx(Tooltip$1, { title: "Trim", children: jsx("button", { onClick: trimTranslationSequence, style: { border: 'none', background: 'none', padding: 0 }, disabled: changeInProgress, children: jsx(ContentCutIcon, { style: { fontSize: 15 } }) }) })] })] })] }) }))] }));
7833
+ getThreePrimeSpliceSite(loc, index).map((site, idx) => (jsx(Typography
7834
+ // eslint-disable-next-line @eslint-react/no-array-index-key
7835
+ , { component: 'span', color: site.color, children: site.spliceSite }, idx))) })] })) }, index));
7836
+ })] }), cdsPresent && (jsx(Translation, { changeInProgress: changeInProgress, cdsLocations: cdsLocations, refData: refData, strand: strand, updateCDSLocation: updateCDSLocation, cdsMin: cdsMin, cdsMax: cdsMax, feature: feature, session: session }))] }));
7640
7837
  });
7641
7838
 
7642
7839
  const HeaderTableCell = styled(TableCell)(() => ({
@@ -7673,7 +7870,7 @@ const ApolloTranscriptDetailsWidget = observer(function ApolloTranscriptDetails(
7673
7870
  const { assembly, feature, refName } = model;
7674
7871
  useEffect(() => {
7675
7872
  setPanelState(DEFAULT_PANELS);
7676
- // eslint-disable-next-line react-hooks/exhaustive-deps
7873
+ // eslint-disable-next-line @eslint-react/exhaustive-deps
7677
7874
  }, [feature]);
7678
7875
  const session = getSession(model);
7679
7876
  const { pluginManager } = getEnv(session);
@@ -7732,8 +7929,8 @@ function handleFeatureTypeChange(changeManager, feature, oldType, newType) {
7732
7929
  typeName: 'TypeChange',
7733
7930
  changedIds: [featureId],
7734
7931
  featureId,
7735
- oldType: String(oldType),
7736
- newType: String(newType),
7932
+ oldType,
7933
+ newType,
7737
7934
  assembly: feature.assemblyId,
7738
7935
  });
7739
7936
  return changeManager.submit(change);
@@ -7829,7 +8026,7 @@ const NumberCell = observer(function NumberCell({ initialValue, notifyError, onC
7829
8026
  if (initialValue !== value) {
7830
8027
  setValue(initialValue);
7831
8028
  }
7832
- // eslint-disable-next-line react-hooks/exhaustive-deps
8029
+ // eslint-disable-next-line @eslint-react/exhaustive-deps
7833
8030
  }, [initialValue]);
7834
8031
  useEffect(() => {
7835
8032
  if (blur) {
@@ -8670,8 +8867,9 @@ function draw$3(display, ctx, gene, row, rowInFeature, block) {
8670
8867
  const [top, left, width] = getFeatureBox(display, gene, row, block);
8671
8868
  const height = getRowCount$2(display, gene) * apolloRowHeight;
8672
8869
  if (width > 2) {
8673
- let selectedColor = readConfObject(session.getPluginConfiguration(), 'geneBackgroundColor', { featureType: gene.type });
8674
- selectedColor = alpha(theme.palette.background.paper, 0.6);
8870
+ const selectedColor = readConfObject(session.getPluginConfiguration(), 'geneBackgroundColor', {
8871
+ featureType: gene.type,
8872
+ }) ?? alpha(theme.palette.background.paper, 0.6);
8675
8873
  ctx.fillStyle = selectedColor;
8676
8874
  ctx.fillRect(left, top, width, height);
8677
8875
  }
@@ -8944,7 +9142,7 @@ function getLayout(display, feature) {
8944
9142
  }
8945
9143
  return layout;
8946
9144
  }
8947
- function getContextMenuItems$1(display, feature) {
9145
+ function getContextMenuItems$1(display, transcript) {
8948
9146
  const { changeManager, regions, selectedFeature, session } = display;
8949
9147
  const [region] = regions;
8950
9148
  const currentAssemblyId = display.getAssemblyId(region.assemblyName);
@@ -8954,7 +9152,7 @@ function getContextMenuItems$1(display, feature) {
8954
9152
  label: 'Open transcript editor',
8955
9153
  onClick: () => {
8956
9154
  const apolloTranscriptWidget = session.addWidget('ApolloTranscriptDetails', 'apolloTranscriptDetails', {
8957
- feature,
9155
+ feature: transcript,
8958
9156
  assembly: currentAssemblyId,
8959
9157
  changeManager,
8960
9158
  refName: region.refName,
@@ -8974,7 +9172,7 @@ function getContextMenuItems$1(display, feature) {
8974
9172
  doneCallback();
8975
9173
  },
8976
9174
  changeManager,
8977
- sourceFeature: feature,
9175
+ sourceFeature: transcript,
8978
9176
  sourceAssemblyId: currentAssemblyId,
8979
9177
  selectedFeature,
8980
9178
  setSelectedFeature: (feature) => {
@@ -8994,7 +9192,7 @@ function getContextMenuItems$1(display, feature) {
8994
9192
  doneCallback();
8995
9193
  },
8996
9194
  changeManager,
8997
- sourceFeature: feature,
9195
+ sourceFeature: transcript,
8998
9196
  sourceAssemblyId: currentAssemblyId,
8999
9197
  selectedFeature,
9000
9198
  setSelectedFeature: (feature) => {
@@ -9004,6 +9202,31 @@ function getContextMenuItems$1(display, feature) {
9004
9202
  ]);
9005
9203
  },
9006
9204
  });
9205
+ const { children } = transcript;
9206
+ if (!children) {
9207
+ return [];
9208
+ }
9209
+ const cdsChildren = [...children.values()].filter((child) => isCDSFeature(child, session));
9210
+ if (cdsChildren.length === 0) {
9211
+ menuItems.push({
9212
+ label: 'Add coding sequence',
9213
+ onClick: () => {
9214
+ session.queueDialog((doneCallback) => [
9215
+ AddCodingSequence,
9216
+ {
9217
+ session,
9218
+ handleClose: () => {
9219
+ doneCallback();
9220
+ },
9221
+ changeManager,
9222
+ sourceFeature: transcript,
9223
+ sourceAssemblyId: currentAssemblyId,
9224
+ refName: region.refName,
9225
+ },
9226
+ ]);
9227
+ },
9228
+ });
9229
+ }
9007
9230
  return menuItems;
9008
9231
  }
9009
9232
  // False positive here, none of these functions use "this"
@@ -9181,7 +9404,7 @@ function colorCode(letter, theme) {
9181
9404
  letterUpper === 'C' ||
9182
9405
  letterUpper === 'G' ||
9183
9406
  letterUpper === 'T') {
9184
- return theme.palette.bases[letterUpper].main.toString();
9407
+ return theme.palette.bases[letterUpper].main;
9185
9408
  }
9186
9409
  return 'lightgray';
9187
9410
  }
@@ -9278,8 +9501,7 @@ function baseModelFactory$2(_pluginManager, configSchema) {
9278
9501
  return internetAccounts.find((ia) => getConf(ia, 'internetAccountId') === internetAccountConfigId);
9279
9502
  },
9280
9503
  get changeManager() {
9281
- return self.session.apolloDataStore
9282
- .changeManager;
9504
+ return self.session.apolloDataStore.changeManager;
9283
9505
  },
9284
9506
  getAssemblyId(assemblyName) {
9285
9507
  const { assemblyManager } = self.session;
@@ -9290,12 +9512,10 @@ function baseModelFactory$2(_pluginManager, configSchema) {
9290
9512
  return assembly.name;
9291
9513
  },
9292
9514
  get selectedFeature() {
9293
- return self.session
9294
- .apolloSelectedFeature;
9515
+ return self.session.apolloSelectedFeature;
9295
9516
  },
9296
9517
  get hoveredFeature() {
9297
- return self.session
9298
- .apolloHoveredFeature;
9518
+ return self.session.apolloHoveredFeature;
9299
9519
  },
9300
9520
  }))
9301
9521
  .actions((self) => ({
@@ -9340,8 +9560,11 @@ function baseModelFactory$2(_pluginManager, configSchema) {
9340
9560
  const { filteredFeatureTypes, trackMenuItems: superTrackMenuItems } = self;
9341
9561
  return {
9342
9562
  trackMenuItems() {
9343
- const { graphical, table, showCheckResults } = self;
9344
- return [
9563
+ const { graphical, table, showCheckResults, session } = self;
9564
+ const [region] = self.regions;
9565
+ const { assemblyName } = region;
9566
+ const backendDriver = session.apolloDataStore.getBackendDriver(assemblyName);
9567
+ const items = [
9345
9568
  ...superTrackMenuItems(),
9346
9569
  {
9347
9570
  type: 'subMenu',
@@ -9393,7 +9616,7 @@ function baseModelFactory$2(_pluginManager, configSchema) {
9393
9616
  {
9394
9617
  label: 'Filter features by type',
9395
9618
  onClick: () => {
9396
- const session = self.session;
9619
+ const { session } = self;
9397
9620
  self.session.queueDialog((doneCallback) => [
9398
9621
  FilterFeatures,
9399
9622
  {
@@ -9409,76 +9632,93 @@ function baseModelFactory$2(_pluginManager, configSchema) {
9409
9632
  ]);
9410
9633
  },
9411
9634
  },
9412
- {
9413
- label: 'Export annotations',
9414
- icon: Export,
9635
+ ];
9636
+ if (backendDriver instanceof LocalDriver) {
9637
+ items.push({
9638
+ label: 'Import annotations',
9639
+ icon: InputIcon,
9415
9640
  onClick: () => {
9416
- const [region] = self.regions;
9417
- const { assemblyName } = region;
9418
- const assembly = self.getAssemblyId(assemblyName);
9419
- if (!assembly) {
9420
- return;
9421
- }
9422
- const session = self.session;
9423
9641
  session.queueDialog((doneCallback) => [
9424
- DownloadGFF3,
9642
+ ImportAnnotations,
9425
9643
  {
9426
9644
  session,
9645
+ assemblyName,
9427
9646
  handleClose: () => {
9428
9647
  doneCallback();
9429
9648
  },
9430
- assembly,
9431
9649
  },
9432
9650
  ]);
9433
9651
  },
9652
+ });
9653
+ }
9654
+ items.push({
9655
+ label: 'Export annotations',
9656
+ icon: Export,
9657
+ onClick: () => {
9658
+ const [region] = self.regions;
9659
+ const { assemblyName } = region;
9660
+ const assembly = self.getAssemblyId(assemblyName);
9661
+ if (!assembly) {
9662
+ return;
9663
+ }
9664
+ const { session } = self;
9665
+ session.queueDialog((doneCallback) => [
9666
+ DownloadGFF3,
9667
+ {
9668
+ session,
9669
+ handleClose: () => {
9670
+ doneCallback();
9671
+ },
9672
+ assembly,
9673
+ },
9674
+ ]);
9434
9675
  },
9435
- {
9436
- label: 'View Change Log',
9437
- icon: TrackChangesIcon,
9438
- onClick: () => {
9439
- const [region] = self.regions;
9440
- const { assemblyName } = region;
9441
- const assembly = self.getAssemblyId(assemblyName);
9442
- if (!assembly) {
9443
- return;
9444
- }
9445
- const session = self.session;
9446
- session.queueDialog((doneCallback) => [
9447
- ViewChangeLog,
9448
- {
9449
- session,
9450
- handleClose: () => {
9451
- doneCallback();
9452
- },
9453
- assembly,
9676
+ }, {
9677
+ label: 'View Change Log',
9678
+ icon: TrackChangesIcon,
9679
+ onClick: () => {
9680
+ const [region] = self.regions;
9681
+ const { assemblyName } = region;
9682
+ const assembly = self.getAssemblyId(assemblyName);
9683
+ if (!assembly) {
9684
+ return;
9685
+ }
9686
+ const { session } = self;
9687
+ session.queueDialog((doneCallback) => [
9688
+ ViewChangeLog,
9689
+ {
9690
+ session,
9691
+ handleClose: () => {
9692
+ doneCallback();
9454
9693
  },
9455
- ]);
9456
- },
9694
+ assembly,
9695
+ },
9696
+ ]);
9457
9697
  },
9458
- {
9459
- label: 'View Check Results',
9460
- icon: FactCheckIcon,
9461
- onClick: () => {
9462
- const [region] = self.regions;
9463
- const { assemblyName } = region;
9464
- const assembly = self.getAssemblyId(assemblyName);
9465
- if (!assembly) {
9466
- return;
9467
- }
9468
- const session = self.session;
9469
- session.queueDialog((doneCallback) => [
9470
- ViewCheckResults,
9471
- {
9472
- session,
9473
- handleClose: () => {
9474
- doneCallback();
9475
- },
9476
- assembly,
9698
+ }, {
9699
+ label: 'View Check Results',
9700
+ icon: FactCheckIcon,
9701
+ onClick: () => {
9702
+ const [region] = self.regions;
9703
+ const { assemblyName } = region;
9704
+ const assembly = self.getAssemblyId(assemblyName);
9705
+ if (!assembly) {
9706
+ return;
9707
+ }
9708
+ const { session } = self;
9709
+ session.queueDialog((doneCallback) => [
9710
+ ViewCheckResults,
9711
+ {
9712
+ session,
9713
+ handleClose: () => {
9714
+ doneCallback();
9477
9715
  },
9478
- ]);
9479
- },
9716
+ assembly,
9717
+ },
9718
+ ]);
9480
9719
  },
9481
- ];
9720
+ });
9721
+ return items;
9482
9722
  },
9483
9723
  };
9484
9724
  })
@@ -10544,8 +10784,7 @@ function baseModelFactory$1(_pluginManager, configSchema) {
10544
10784
  return internetAccounts.find((ia) => getConf(ia, 'internetAccountId') === internetAccountConfigId);
10545
10785
  },
10546
10786
  get changeManager() {
10547
- return self.session.apolloDataStore
10548
- .changeManager;
10787
+ return self.session.apolloDataStore.changeManager;
10549
10788
  },
10550
10789
  getAssemblyId(assemblyName) {
10551
10790
  const { assemblyManager } = self.session;
@@ -10556,12 +10795,10 @@ function baseModelFactory$1(_pluginManager, configSchema) {
10556
10795
  return assembly.name;
10557
10796
  },
10558
10797
  get selectedFeature() {
10559
- return self.session
10560
- .apolloSelectedFeature;
10798
+ return self.session.apolloSelectedFeature;
10561
10799
  },
10562
10800
  get hoveredFeature() {
10563
- return self.session
10564
- .apolloHoveredFeature;
10801
+ return self.session.apolloHoveredFeature;
10565
10802
  },
10566
10803
  get height() {
10567
10804
  const { sequenceRowHeight } = self;
@@ -10773,11 +11010,9 @@ const FilterTranscripts = observer(function FilterTranscripts({ sourceFeature, f
10773
11010
  onUpdate(newForms);
10774
11011
  setExcludedTranscripts(newForms);
10775
11012
  };
10776
- return (jsx(Dialog, { open: true, maxWidth: false, "data-testid": "filter-transcripts-dialog", title: "Filter transcripts by ID", handleClose: handleClose, children: jsxs(DialogContent, { children: [jsx(DialogContentText, { children: "Select the alternate transcripts you want to display in the apollo track" }), jsx(Grid, { container: true, spacing: 2, children: jsx(Grid, { size: 8, children: jsx(FormGroup, { children: allTranscripts.map((item) => (
10777
- // eslint-disable-next-line react/jsx-key
10778
- jsx(FormControlLabel, { control: jsx(Checkbox, { checked: !excludedTranscripts.includes(item), onChange: () => {
11013
+ return (jsx(Dialog, { open: true, maxWidth: false, "data-testid": "filter-transcripts-dialog", title: "Filter transcripts by ID", handleClose: handleClose, children: jsxs(DialogContent, { children: [jsx(DialogContentText, { children: "Select the alternate transcripts you want to display in the apollo track" }), jsx(Grid, { container: true, spacing: 2, children: jsx(Grid, { size: 8, children: jsx(FormGroup, { children: allTranscripts.map((item) => (jsx(FormControlLabel, { control: jsx(Checkbox, { checked: !excludedTranscripts.includes(item), onChange: () => {
10779
11014
  handleChange(item);
10780
- }, slotProps: { input: { 'aria-label': 'controlled' } } }), label: item }))) }) }) })] }) }));
11015
+ }, slotProps: { input: { 'aria-label': 'controlled' } } }), label: item }, item))) }) }) })] }) }));
10781
11016
  });
10782
11017
 
10783
11018
  let forwardFillLight = null;
@@ -11623,8 +11858,7 @@ function baseModelFactory(_pluginManager, configSchema) {
11623
11858
  return internetAccounts.find((ia) => getConf(ia, 'internetAccountId') === internetAccountConfigId);
11624
11859
  },
11625
11860
  get changeManager() {
11626
- return self.session.apolloDataStore
11627
- .changeManager;
11861
+ return self.session.apolloDataStore.changeManager;
11628
11862
  },
11629
11863
  getAssemblyId(assemblyName) {
11630
11864
  const { assemblyManager } = self.session;
@@ -11635,12 +11869,10 @@ function baseModelFactory(_pluginManager, configSchema) {
11635
11869
  return assembly.name;
11636
11870
  },
11637
11871
  get selectedFeature() {
11638
- return self.session
11639
- .apolloSelectedFeature;
11872
+ return self.session.apolloSelectedFeature;
11640
11873
  },
11641
11874
  get hoveredFeature() {
11642
- return self.session
11643
- .apolloHoveredFeature;
11875
+ return self.session.apolloHoveredFeature;
11644
11876
  },
11645
11877
  }))
11646
11878
  .actions((self) => ({
@@ -11768,7 +12000,7 @@ function baseModelFactory(_pluginManager, configSchema) {
11768
12000
  {
11769
12001
  label: 'Filter features by type',
11770
12002
  onClick: () => {
11771
- const session = self.session;
12003
+ const { session } = self;
11772
12004
  self.session.queueDialog((doneCallback) => [
11773
12005
  FilterFeatures,
11774
12006
  {
@@ -12206,17 +12438,12 @@ function mouseEventsModelIntermediateFactory(pluginManager, configSchema) {
12206
12438
  if (!featureTypeOntology) {
12207
12439
  throw new Error('featureTypeOntology is undefined');
12208
12440
  }
12209
- let foundFeature;
12210
- if (self.geneTrackRowNums.includes(row)) {
12211
- foundFeature = layoutRow.find((f) => f.feature.type == 'exon' &&
12441
+ const foundFeature = self.geneTrackRowNums.includes(row)
12442
+ ? layoutRow.find((f) => f.feature.type == 'exon' &&
12212
12443
  bp >= f.feature.min &&
12213
- bp <= f.feature.max);
12214
- if (!foundFeature) {
12215
- foundFeature = layoutRow.find((f) => bp >= f.feature.min && bp <= f.feature.max);
12216
- }
12217
- }
12218
- else {
12219
- foundFeature = layoutRow.find((f) => {
12444
+ bp <= f.feature.max) ??
12445
+ layoutRow.find((f) => bp >= f.feature.min && bp <= f.feature.max)
12446
+ : layoutRow.find((f) => {
12220
12447
  const { feature } = f;
12221
12448
  const featureID = feature.attributes.get('gff_id')?.toString();
12222
12449
  const isTranscript = featureTypeOntology.isTypeOf(feature.type, 'transcript');
@@ -12238,7 +12465,6 @@ function mouseEventsModelIntermediateFactory(pluginManager, configSchema) {
12238
12465
  }
12239
12466
  return false;
12240
12467
  });
12241
- }
12242
12468
  if (!foundFeature) {
12243
12469
  return mousePosition;
12244
12470
  }
@@ -12562,26 +12788,32 @@ function CreateApolloAnnotation({ annotationFeature, assembly, handleClose, refS
12562
12788
  const apolloAssembly = apolloSessionModel.apolloDataStore.assemblies.get(assembly.name);
12563
12789
  const refSeq = apolloAssembly?.refSeqs.get(refSeqId);
12564
12790
  const features = refSeq?.getFeatures(region.start, region.end);
12565
- const getDestinationFeatures = () => {
12566
- const filteredFeatures = [];
12567
- for (const f of features ?? []) {
12568
- if (f.min > region.end || f.max < region.start) {
12569
- continue;
12570
- }
12571
- // Destination feature should be of type gene
12572
- if (featureTypeOntology?.isTypeOf(f.type, 'gene')) {
12573
- const featureSnapshot = getSnapshot(f);
12574
- filteredFeatures.push(featureSnapshot);
12575
- }
12576
- }
12577
- return filteredFeatures;
12578
- };
12579
12791
  useEffect(() => {
12792
+ const getDestinationFeatures = () => {
12793
+ const filteredFeatures = [];
12794
+ for (const f of features ?? []) {
12795
+ if (f.min > region.end || f.max < region.start) {
12796
+ continue;
12797
+ }
12798
+ // Destination feature should be of type gene
12799
+ if (featureTypeOntology?.isTypeOf(f.type, 'gene')) {
12800
+ const featureSnapshot = getSnapshot(f);
12801
+ filteredFeatures.push(featureSnapshot);
12802
+ }
12803
+ }
12804
+ return filteredFeatures;
12805
+ };
12580
12806
  setErrorMessage('');
12581
- const features = getDestinationFeatures();
12582
- setDestinationFeatures(features);
12583
- setSelectedDestinationFeature(features[0]);
12584
- }, [checkedChildrens, parentFeatureChecked, region]);
12807
+ const destinationFeatures = getDestinationFeatures();
12808
+ setDestinationFeatures(destinationFeatures);
12809
+ setSelectedDestinationFeature(destinationFeatures[0]);
12810
+ }, [
12811
+ checkedChildrens,
12812
+ featureTypeOntology,
12813
+ features,
12814
+ parentFeatureChecked,
12815
+ region,
12816
+ ]);
12585
12817
  const handleParentFeatureCheck = (event) => {
12586
12818
  const isChecked = event.target.checked;
12587
12819
  setParentFeatureChecked(isChecked);
@@ -12861,8 +13093,21 @@ function annotationFromPileup(pluggableElement) {
12861
13093
  getAnnotationFeature(jbrowseFeature, refSeqId) {
12862
13094
  const start = jbrowseFeature.get('start');
12863
13095
  const end = jbrowseFeature.get('end');
12864
- const strand = jbrowseFeature.get('strand');
12865
13096
  const name = jbrowseFeature.get('name');
13097
+ let strand;
13098
+ const tags = jbrowseFeature.get('tags');
13099
+ const { ts, TS, XS } = tags;
13100
+ for (const tag of [ts, TS, XS]) {
13101
+ if (!strand && tag) {
13102
+ if ([1, '1', '+'].includes(tag)) {
13103
+ strand = 1;
13104
+ }
13105
+ else if ([-1, '-1', '-'].includes(tag)) {
13106
+ strand = -1;
13107
+ }
13108
+ }
13109
+ }
13110
+ strand ??= jbrowseFeature.get('strand');
12866
13111
  const cigarData = jbrowseFeature.get('CIGAR');
12867
13112
  const ops = parseCigar(cigarData);
12868
13113
  let position = start;
@@ -12878,9 +13123,7 @@ function annotationFromPileup(pluggableElement) {
12878
13123
  case 'M':
12879
13124
  case '=':
12880
13125
  case 'X': {
12881
- if (currentExonStart === undefined) {
12882
- currentExonStart = position;
12883
- }
13126
+ currentExonStart ??= position;
12884
13127
  position += len;
12885
13128
  break;
12886
13129
  }
@@ -13294,7 +13537,7 @@ const LinearApolloDisplay = observer(function LinearApolloDisplay(props) {
13294
13537
  : undefined, menuItems: contextMenuItems })] }))] }));
13295
13538
  });
13296
13539
 
13297
- const TrackLines = observer(function TrackLines({ model, hrStyle = { margin: 0, top: 0, color: 'black' }, idx = 0, }) {
13540
+ const TrackLines = observer(function TrackLines({ model, hrStyle = { margin: 0, top: 0, color: 'black' }, idx, }) {
13298
13541
  const { apolloRowHeight, highestRow, showFeatureLabels } = model;
13299
13542
  const featureLabelSpacer = showFeatureLabels ? 2 : 1;
13300
13543
  return (jsx("div", { style: {
@@ -13366,8 +13609,7 @@ const LinearApolloSixFrameDisplay = observer(function LinearApolloSixFrameDispla
13366
13609
  regionNumber: idx,
13367
13610
  })?.offsetPx ?? 0) - lgv.offsetPx;
13368
13611
  const [feature] = checkResult.featureIds;
13369
- if (!feature ||
13370
- !feature.parent ||
13612
+ if (!feature?.parent ||
13371
13613
  !looksLikeGene(feature.parent, session)) {
13372
13614
  return null;
13373
13615
  }
@@ -13619,18 +13861,15 @@ function addTopLevelMenus(rootModel) {
13619
13861
  }
13620
13862
  }
13621
13863
 
13622
- /* eslint-disable @typescript-eslint/no-unsafe-assignment */
13623
13864
  /* eslint-disable @typescript-eslint/no-confusing-void-expression */
13624
13865
  const ApolloJobModel = types
13625
13866
  .model('JobsManager', {})
13626
13867
  .views((self) => ({
13627
13868
  get jobStatusWidget() {
13628
13869
  const { widgets } = getSession(self);
13629
- let jobStatusWidget = widgets.get('JobsList');
13630
- if (!jobStatusWidget) {
13870
+ const jobStatusWidget = widgets.get('JobsList') ??
13631
13871
  // @ts-expect-error: addWidget function not detected on the session
13632
- jobStatusWidget = getSession(self).addWidget('JobsListWidget', 'JobsList');
13633
- }
13872
+ getSession(self).addWidget('JobsListWidget', 'JobsList');
13634
13873
  return jobStatusWidget;
13635
13874
  },
13636
13875
  }))
@@ -13909,19 +14148,15 @@ function clientDataStoreFactory(AnnotationFeatureExtended) {
13909
14148
  continue;
13910
14149
  }
13911
14150
  const { assemblyName, refName } = region;
13912
- let assembly = self.assemblies.get(assemblyName);
13913
- if (!assembly) {
13914
- assembly = self.assemblies.put({ _id: assemblyName, refSeqs: {} });
13915
- }
14151
+ const assembly = self.assemblies.get(assemblyName) ??
14152
+ self.assemblies.put({ _id: assemblyName, refSeqs: {} });
13916
14153
  const [firstFeature] = features;
13917
- let ref = assembly.refSeqs.get(firstFeature.refSeq);
13918
- if (!ref) {
13919
- ref = assembly.refSeqs.put({
14154
+ const ref = assembly.refSeqs.get(firstFeature.refSeq) ??
14155
+ assembly.refSeqs.put({
13920
14156
  _id: firstFeature.refSeq,
13921
14157
  name: refName,
13922
14158
  features: {},
13923
14159
  });
13924
- }
13925
14160
  for (const feature of features) {
13926
14161
  if (!ref.features.has(feature._id)) {
13927
14162
  ref.features.put(feature);
@@ -13938,24 +14173,153 @@ function clientDataStoreFactory(AnnotationFeatureExtended) {
13938
14173
  }
13939
14174
  const { refSeq, seq } = yield backendDriver.getSequence(region);
13940
14175
  const { assemblyName, end, refName, start } = region;
13941
- let assembly = self.assemblies.get(assemblyName);
13942
- if (!assembly) {
13943
- assembly = self.assemblies.put({ _id: assemblyName, refSeqs: {} });
13944
- }
13945
- let ref = assembly.refSeqs.get(refSeq);
13946
- if (!ref) {
13947
- ref = assembly.refSeqs.put({
13948
- _id: refSeq,
13949
- name: refName,
13950
- sequence: [],
13951
- });
13952
- }
14176
+ const assembly = self.assemblies.get(assemblyName) ??
14177
+ self.assemblies.put({ _id: assemblyName, refSeqs: {} });
14178
+ const ref = assembly.refSeqs.get(refSeq) ??
14179
+ assembly.refSeqs.put({ _id: refSeq, name: refName, sequence: [] });
13953
14180
  ref.addSequence({ start, stop: end, sequence: seq });
13954
14181
  }
13955
14182
  }),
13956
14183
  }));
13957
14184
  }
13958
14185
 
14186
+ function reassignIds(feature) {
14187
+ const newChildren = {};
14188
+ if (feature.children) {
14189
+ for (const child of Object.values(feature.children)) {
14190
+ const newChild = reassignIds(child);
14191
+ newChildren[newChild._id] = newChild;
14192
+ }
14193
+ }
14194
+ return {
14195
+ ...feature,
14196
+ _id: new ObjectID().toHexString(),
14197
+ children: feature.children ? newChildren : undefined,
14198
+ };
14199
+ }
14200
+ function DuplicateFeatureDialog({ assemblyName, changeManager, existingFeature, featureSnapshot, handleClose, }) {
14201
+ const [choice, setChoice] = useState('keepExisting');
14202
+ const [errorMessage, setErrorMessage] = useState('');
14203
+ const [submitted, setSubmitted] = useState(false);
14204
+ const handleSubmit = async () => {
14205
+ setErrorMessage('');
14206
+ setSubmitted(true);
14207
+ try {
14208
+ if (choice === 'useNew') {
14209
+ const deleteChange = new DeleteFeatureChange({
14210
+ typeName: 'DeleteFeatureChange',
14211
+ assembly: assemblyName,
14212
+ changedIds: [existingFeature._id],
14213
+ deletedFeature: existingFeature,
14214
+ });
14215
+ await changeManager.submit(deleteChange);
14216
+ const addChange = new AddFeatureChange({
14217
+ typeName: 'AddFeatureChange',
14218
+ assembly: assemblyName,
14219
+ changedIds: [featureSnapshot._id],
14220
+ addedFeature: featureSnapshot,
14221
+ });
14222
+ await changeManager.submit(addChange);
14223
+ }
14224
+ else if (choice === 'keepBoth') {
14225
+ const newSnapshot = reassignIds(featureSnapshot);
14226
+ const addChange = new AddFeatureChange({
14227
+ typeName: 'AddFeatureChange',
14228
+ assembly: assemblyName,
14229
+ changedIds: [newSnapshot._id],
14230
+ addedFeature: newSnapshot,
14231
+ });
14232
+ await changeManager.submit(addChange);
14233
+ }
14234
+ handleClose();
14235
+ }
14236
+ catch (error) {
14237
+ setErrorMessage(String(error));
14238
+ setSubmitted(false);
14239
+ }
14240
+ };
14241
+ return (jsxs(Dialog, { open: true, title: "Duplicate Feature Detected", handleClose: handleClose, maxWidth: "sm", fullWidth: true, children: [jsxs(DialogContent, { children: [jsxs(DialogContentText, { children: ["A feature with ID \u201C", featureSnapshot._id, "\u201D already exists. How would you like to resolve this conflict?"] }), jsxs(FormControl, { component: "fieldset", sx: { mt: 2 }, children: [jsx(FormLabel, { component: "legend", children: "Resolution" }), jsxs(RadioGroup, { value: choice, onChange: (e) => {
14242
+ setChoice(e.target.value);
14243
+ }, children: [jsx(FormControlLabel, { value: "keepExisting", control: jsx(Radio, {}), label: "Keep the existing feature" }), jsx(FormControlLabel, { value: "useNew", control: jsx(Radio, {}), label: "Replace with the new feature" }), jsx(FormControlLabel, { value: "keepBoth", control: jsx(Radio, {}), label: "Keep both (assign new IDs to the incoming feature)" })] })] }), errorMessage ? (jsx(DialogContentText, { color: "error", sx: { mt: 1 }, children: errorMessage })) : null] }), jsxs(DialogActions, { children: [jsx(Button, { variant: "contained", onClick: handleSubmit, disabled: submitted, children: "Confirm" }), jsx(Button, { onClick: handleClose, disabled: submitted, children: "Cancel" })] })] }));
14244
+ }
14245
+
14246
+ function fromUrlSafeBase64(urlSafeBase64) {
14247
+ let base64 = urlSafeBase64.replaceAll('-', '+').replaceAll('_', '/');
14248
+ const pad = base64.length % 4;
14249
+ if (pad) {
14250
+ base64 += '='.repeat(4 - pad);
14251
+ }
14252
+ return base64;
14253
+ }
14254
+ // Base64 decode + decompress
14255
+ async function decompress(encoded) {
14256
+ const binaryString = atob(encoded);
14257
+ // eslint-disable-next-line unicorn/prefer-code-point
14258
+ const bytes = Uint8Array.from(binaryString, (char) => char.charCodeAt(0));
14259
+ const stream = new Blob([bytes]).stream();
14260
+ const decompressionStream = new DecompressionStream('gzip');
14261
+ const decompressedStream = stream.pipeThrough(decompressionStream);
14262
+ const response = new Response(decompressedStream);
14263
+ const decompressed = await response.arrayBuffer();
14264
+ return JSON.parse(new TextDecoder().decode(decompressed));
14265
+ }
14266
+ async function handleApolloFeaturesUrlParam(encodedFeatures, apolloDataStore, assemblyManager, session) {
14267
+ const base64 = fromUrlSafeBase64(encodedFeatures);
14268
+ const decompressed = await decompress(base64);
14269
+ const featuresData = decompressed;
14270
+ for (const [assemblyName, featureSnapshots] of Object.entries(featuresData)) {
14271
+ const backendDriver = apolloDataStore.getBackendDriver(assemblyName);
14272
+ if (!(backendDriver instanceof LocalDriver)) {
14273
+ continue;
14274
+ }
14275
+ const assembly = (await assemblyManager.waitForAssembly(assemblyName));
14276
+ if (!assembly) {
14277
+ throw new Error(`Assembly not found: "${assemblyName}"`);
14278
+ }
14279
+ const { regions } = assembly;
14280
+ if (!regions) {
14281
+ throw new Error(`Assembly not found: "${assemblyName}"`);
14282
+ }
14283
+ const refNames = regions.map((r) => r.refName);
14284
+ const db = await openDb(assemblyName, refNames);
14285
+ for (const featureSnapshot of featureSnapshots) {
14286
+ const storeName = `features-${featureSnapshot.refSeq}`;
14287
+ const existing = (await db.get(storeName, featureSnapshot._id));
14288
+ if (existing === undefined) {
14289
+ const change = new AddFeatureChange({
14290
+ typeName: 'AddFeatureChange',
14291
+ assembly: assemblyName,
14292
+ changedIds: [featureSnapshot._id],
14293
+ addedFeature: featureSnapshot,
14294
+ });
14295
+ await apolloDataStore.changeManager.submit(change);
14296
+ }
14297
+ else {
14298
+ // get rid of undefined values in JSON
14299
+ // eslint-disable-next-line unicorn/prefer-structured-clone
14300
+ const existingFeature = JSON.parse(JSON.stringify(existing));
14301
+ if (!equal(featureSnapshot, existingFeature)) {
14302
+ await new Promise((resolve) => {
14303
+ session.queueDialog((doneCallback) => [
14304
+ DuplicateFeatureDialog,
14305
+ {
14306
+ featureSnapshot,
14307
+ existingFeature,
14308
+ assemblyName,
14309
+ changeManager: apolloDataStore.changeManager,
14310
+ handleClose: () => {
14311
+ doneCallback();
14312
+ resolve();
14313
+ },
14314
+ },
14315
+ ]);
14316
+ });
14317
+ }
14318
+ }
14319
+ }
14320
+ }
14321
+ }
14322
+
13959
14323
  /* eslint-disable @typescript-eslint/no-unnecessary-condition */
13960
14324
  /* eslint-disable @typescript-eslint/no-unsafe-assignment */
13961
14325
  function extendSession(pluginManager, sessionModel) {
@@ -14041,8 +14405,7 @@ function extendSession(pluginManager, sessionModel) {
14041
14405
  for (const block of dynamicBlocks.contentBlocks) {
14042
14406
  const { assemblyName, end, refName, start } = block;
14043
14407
  const assembly = self.apolloDataStore.assemblies.get(assemblyName);
14044
- if (assembly &&
14045
- assembly.backendDriverType === 'CollaborationServerDriver') {
14408
+ if (assembly?.backendDriverType === 'CollaborationServerDriver') {
14046
14409
  locations.push({ assemblyName, refName, start, end });
14047
14410
  }
14048
14411
  }
@@ -14101,8 +14464,8 @@ function extendSession(pluginManager, sessionModel) {
14101
14464
  for (const block of dynamicBlocks.contentBlocks) {
14102
14465
  const { assemblyName, end, refName, start } = block;
14103
14466
  const assembly = self.apolloDataStore.assemblies.get(assemblyName);
14104
- if (assembly &&
14105
- assembly.backendDriverType === 'CollaborationServerDriver') {
14467
+ if (assembly?.backendDriverType ===
14468
+ 'CollaborationServerDriver') {
14106
14469
  locations.push({ assemblyName, refName, start, end });
14107
14470
  }
14108
14471
  }
@@ -14222,6 +14585,37 @@ function extendSession(pluginManager, sessionModel) {
14222
14585
  beforeDestroy() {
14223
14586
  self.abortController.abort(new DOMException('Clean up Apollo session', 'AbortError'));
14224
14587
  },
14588
+ }))
14589
+ .actions((self) => ({
14590
+ async afterCreate() {
14591
+ if (isElectron) {
14592
+ return;
14593
+ }
14594
+ const url = new URL(globalThis.location.href);
14595
+ const apolloFeatures = url.searchParams.get('apolloFeatures');
14596
+ if (!apolloFeatures) {
14597
+ return;
14598
+ }
14599
+ const { assemblyManager } = self;
14600
+ await handleApolloFeaturesUrlParam(apolloFeatures, self.apolloDataStore, assemblyManager, self);
14601
+ await new Promise((resolve) => setTimeout(resolve, 2000));
14602
+ const updatedURL = new URL(globalThis.location.href);
14603
+ updatedURL.searchParams.delete('apolloFeatures');
14604
+ globalThis.history.replaceState(null, '', updatedURL.toString());
14605
+ },
14606
+ afterAttach() {
14607
+ addDisposer(self, autorun((reaction) => {
14608
+ const { focusedViewId, activeWidgets } = self;
14609
+ if (!(focusedViewId && activeWidgets)) {
14610
+ return;
14611
+ }
14612
+ const trackSelector = activeWidgets.get('hierarchicalTrackSelector');
14613
+ // @ts-expect-error Don't have type for track selector
14614
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
14615
+ trackSelector?.setView(focusedViewId);
14616
+ reaction.dispose();
14617
+ }));
14618
+ },
14225
14619
  }))
14226
14620
  .views((self) => {
14227
14621
  const superTrackActions = self
@@ -14353,7 +14747,6 @@ function extendSession(pluginManager, sessionModel) {
14353
14747
  }
14354
14748
 
14355
14749
  /* eslint-disable @typescript-eslint/unbound-method */
14356
- /* eslint-disable @typescript-eslint/no-unnecessary-condition */
14357
14750
  /* eslint-disable @typescript-eslint/no-misused-promises */
14358
14751
  /* eslint-disable @typescript-eslint/no-explicit-any */
14359
14752
  function isApolloMessageData(data) {
@@ -14541,10 +14934,10 @@ class ApolloPlugin extends Plugin {
14541
14934
  });
14542
14935
  if (!inWebWorker) {
14543
14936
  pluginManager.addToExtensionPoint('Core-extendWorker', (handle) => {
14544
- if (!('on' in handle && handle.on)) {
14937
+ if (!('on' in handle.client && handle.client.on)) {
14545
14938
  return handle;
14546
14939
  }
14547
- handle.on('apollo', async (event) => {
14940
+ handle.client.on('apollo', async (event) => {
14548
14941
  if (!isApolloMessageData(event)) {
14549
14942
  return;
14550
14943
  }
@@ -14562,7 +14955,7 @@ class ApolloPlugin extends Plugin {
14562
14955
  break;
14563
14956
  }
14564
14957
  const { seq: sequence } = await backendDriver.getSequence(region);
14565
- handle.workers[0].postMessage({
14958
+ handle.worker.postMessage({
14566
14959
  apollo,
14567
14960
  messageId,
14568
14961
  sequence,
@@ -14580,7 +14973,7 @@ class ApolloPlugin extends Plugin {
14580
14973
  break;
14581
14974
  }
14582
14975
  const regions = await backendDriver.getRegions(assembly);
14583
- handle.workers[0].postMessage({
14976
+ handle.worker.postMessage({
14584
14977
  apollo,
14585
14978
  messageId,
14586
14979
  regions,
@@ -14598,7 +14991,7 @@ class ApolloPlugin extends Plugin {
14598
14991
  break;
14599
14992
  }
14600
14993
  const refNameAliases = await backendDriver.getRefNameAliases(assembly);
14601
- handle.workers[0].postMessage({
14994
+ handle.worker.postMessage({
14602
14995
  apollo,
14603
14996
  messageId,
14604
14997
  refNameAliases,