@apollo-annotation/jbrowse-plugin-apollo 1.0.1 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ApolloInternetAccount/components/AuthTypeSelector.d.ts +8 -2
- package/dist/ApolloInternetAccount/components/AuthTypeSelector.d.ts.map +1 -1
- package/dist/ApolloInternetAccount/components/LoginButtons.d.ts +8 -3
- package/dist/ApolloInternetAccount/components/LoginButtons.d.ts.map +1 -1
- package/dist/ApolloInternetAccount/configSchema.d.ts +44 -0
- package/dist/ApolloInternetAccount/configSchema.d.ts.map +1 -0
- package/dist/ApolloInternetAccount/model.d.ts +174 -0
- package/dist/ApolloInternetAccount/model.d.ts.map +1 -0
- package/dist/ApolloJobModel.d.ts.map +1 -1
- package/dist/ApolloRefNameAliasAdapter/configSchema.d.ts +8 -0
- package/dist/ApolloRefNameAliasAdapter/configSchema.d.ts.map +1 -0
- package/dist/ApolloSequenceAdapter/configSchema.d.ts +8 -0
- package/dist/ApolloSequenceAdapter/configSchema.d.ts.map +1 -0
- package/dist/ApolloTextSearchAdapter/configSchema.d.ts +20 -0
- package/dist/ApolloTextSearchAdapter/configSchema.d.ts.map +1 -0
- package/dist/BackendDrivers/CollaborationServerDriver.d.ts +1 -1
- package/dist/BackendDrivers/CollaborationServerDriver.d.ts.map +1 -1
- package/dist/BackendDrivers/LocalDriver/LocalDriver.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/ApolloFeatureDetailsWidget.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/Attributes.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/DefaultAttributeEditor.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/DefaultAttributeViewer.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/NumberTextField.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/StringTextField.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/TranscriptSequence.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/TranscriptWidgetEditLocation.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/Translation.d.ts +14 -0
- package/dist/FeatureDetailsWidget/Translation.d.ts.map +1 -0
- package/dist/LinearApolloDisplay/components/CheckResultWarnings.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/configSchema.d.ts +2 -0
- package/dist/LinearApolloDisplay/configSchema.d.ts.map +1 -0
- package/dist/LinearApolloDisplay/glyphs/ExonGlyph.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/glyphs/GeneGlyph.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/glyphs/TranscriptGlyph.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/stateModel/base.d.ts +1 -18
- package/dist/LinearApolloDisplay/stateModel/base.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/stateModel/index.d.ts +1 -17
- package/dist/LinearApolloDisplay/stateModel/index.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/stateModel/layouts.d.ts +2 -19
- package/dist/LinearApolloDisplay/stateModel/layouts.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/stateModel/mouseEvents.d.ts +2 -34
- package/dist/LinearApolloDisplay/stateModel/mouseEvents.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/stateModel/rendering.d.ts +2 -19
- package/dist/LinearApolloDisplay/stateModel/rendering.d.ts.map +1 -1
- package/dist/LinearApolloReferenceSequenceDisplay/configSchema.d.ts +2 -0
- package/dist/LinearApolloReferenceSequenceDisplay/configSchema.d.ts.map +1 -0
- package/dist/LinearApolloReferenceSequenceDisplay/stateModel/base.d.ts +1 -1
- package/dist/LinearApolloReferenceSequenceDisplay/stateModel/base.d.ts.map +1 -1
- package/dist/LinearApolloReferenceSequenceDisplay/stateModel/index.d.ts +1 -1
- package/dist/LinearApolloReferenceSequenceDisplay/stateModel/rendering.d.ts +1 -1
- package/dist/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.d.ts.map +1 -1
- package/dist/LinearApolloSixFrameDisplay/configSchema.d.ts +2 -0
- package/dist/LinearApolloSixFrameDisplay/configSchema.d.ts.map +1 -0
- package/dist/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.d.ts.map +1 -1
- package/dist/LinearApolloSixFrameDisplay/stateModel/base.d.ts +1 -1
- package/dist/LinearApolloSixFrameDisplay/stateModel/base.d.ts.map +1 -1
- package/dist/LinearApolloSixFrameDisplay/stateModel/index.d.ts +1 -1
- package/dist/LinearApolloSixFrameDisplay/stateModel/index.d.ts.map +1 -1
- package/dist/LinearApolloSixFrameDisplay/stateModel/layouts.d.ts +2 -3
- package/dist/LinearApolloSixFrameDisplay/stateModel/layouts.d.ts.map +1 -1
- package/dist/LinearApolloSixFrameDisplay/stateModel/mouseEvents.d.ts +2 -2
- package/dist/LinearApolloSixFrameDisplay/stateModel/mouseEvents.d.ts.map +1 -1
- package/dist/LinearApolloSixFrameDisplay/stateModel/rendering.d.ts +2 -3
- package/dist/LinearApolloSixFrameDisplay/stateModel/rendering.d.ts.map +1 -1
- package/dist/OntologyManager/OntologyStore/fulltext.d.ts.map +1 -1
- package/dist/OntologyManager/index.d.ts +734 -0
- package/dist/OntologyManager/index.d.ts.map +1 -0
- package/dist/TabularEditor/HybridGrid/NumberCell.d.ts.map +1 -1
- package/dist/components/AddCodingSequence.d.ts +14 -0
- package/dist/components/AddCodingSequence.d.ts.map +1 -0
- package/dist/components/AddRefSeqAliases.d.ts.map +1 -1
- package/dist/components/CopyFeature.d.ts.map +1 -1
- package/dist/components/CreateApolloAnnotation.d.ts.map +1 -1
- package/dist/components/DeleteFeature.d.ts.map +1 -1
- package/dist/components/DownloadGFF3.d.ts.map +1 -1
- package/dist/components/DuplicateFeatureDialog.d.ts +12 -0
- package/dist/components/DuplicateFeatureDialog.d.ts.map +1 -0
- package/dist/components/ImportAnnotations.d.ts +9 -0
- package/dist/components/ImportAnnotations.d.ts.map +1 -0
- package/dist/components/ManageUsers.d.ts.map +1 -1
- package/dist/components/OntologyTermMultiSelect.d.ts.map +1 -1
- package/dist/components/ViewChangeLog.d.ts.map +1 -1
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/extensions/annotationFromPileup.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +835 -434
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +830 -429
- package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.development.js +868 -468
- package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
- package/dist/session/ClientDataStore.d.ts +2194 -0
- package/dist/session/ClientDataStore.d.ts.map +1 -0
- package/dist/session/handleApolloFeaturesUrlParam.d.ts +8 -0
- package/dist/session/handleApolloFeaturesUrlParam.d.ts.map +1 -0
- package/dist/session/session.d.ts +6788 -0
- package/dist/session/session.d.ts.map +1 -0
- package/dist/util/displayUtils.d.ts +36 -0
- package/dist/util/displayUtils.d.ts.map +1 -0
- package/dist/util/sequenceUtils.d.ts +2 -0
- package/dist/util/sequenceUtils.d.ts.map +1 -0
- package/package.json +14 -14
- package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +73 -38
- package/src/ApolloInternetAccount/components/LoginButtons.tsx +31 -10
- package/src/ApolloInternetAccount/model.ts +8 -5
- package/src/ApolloJobModel.ts +3 -8
- package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +1 -1
- package/src/BackendDrivers/CollaborationServerDriver.ts +7 -11
- package/src/BackendDrivers/LocalDriver/LocalDriver.ts +9 -3
- package/src/FeatureDetailsWidget/ApolloFeatureDetailsWidget.tsx +1 -0
- package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +4 -2
- package/src/FeatureDetailsWidget/Attributes.tsx +1 -0
- package/src/FeatureDetailsWidget/DefaultAttributeEditor.tsx +1 -0
- package/src/FeatureDetailsWidget/DefaultAttributeViewer.tsx +1 -0
- package/src/FeatureDetailsWidget/NumberTextField.tsx +1 -0
- package/src/FeatureDetailsWidget/StringTextField.tsx +1 -0
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +2 -1
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +17 -354
- package/src/FeatureDetailsWidget/Translation.tsx +366 -0
- package/src/LinearApolloDisplay/components/CheckResultWarnings.tsx +4 -1
- package/src/LinearApolloDisplay/glyphs/ExonGlyph.ts +2 -5
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +4 -6
- package/src/LinearApolloDisplay/glyphs/TranscriptGlyph.ts +39 -6
- package/src/LinearApolloDisplay/stateModel/base.ts +47 -23
- package/src/LinearApolloDisplay/stateModel/layouts.ts +7 -5
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +4 -1
- package/src/LinearApolloDisplay/stateModel/rendering.ts +1 -5
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +10 -10
- package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +5 -3
- package/src/LinearApolloSixFrameDisplay/components/TrackLines.tsx +1 -1
- package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +2 -3
- package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +12 -20
- package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +10 -6
- package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +38 -45
- package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +1 -4
- package/src/OntologyManager/OntologyStore/fulltext.ts +0 -2
- package/src/OntologyManager/OntologyStore/index.ts +1 -1
- package/src/TabularEditor/HybridGrid/ChangeHandling.ts +2 -2
- package/src/TabularEditor/HybridGrid/NumberCell.tsx +2 -1
- package/src/components/AddAssemblyAliases.tsx +1 -1
- package/src/components/AddCodingSequence.tsx +255 -0
- package/src/components/AddFeature.tsx +1 -1
- package/src/components/AddRefSeqAliases.tsx +3 -2
- package/src/components/CopyFeature.tsx +9 -7
- package/src/components/CreateApolloAnnotation.tsx +32 -24
- package/src/components/DeleteAssembly.tsx +2 -2
- package/src/components/DeleteFeature.tsx +1 -4
- package/src/components/DownloadGFF3.tsx +4 -3
- package/src/components/DuplicateFeatureDialog.tsx +154 -0
- package/src/components/DuplicateTranscript.tsx +1 -1
- package/src/components/FilterTranscripts.tsx +1 -1
- package/src/components/ImportAnnotations.tsx +108 -0
- package/src/components/ManageUsers.tsx +1 -2
- package/src/components/OntologyTermAutocomplete.tsx +1 -3
- package/src/components/OntologyTermMultiSelect.tsx +3 -1
- package/src/components/ViewChangeLog.tsx +2 -1
- package/src/components/index.ts +2 -0
- package/src/extensions/annotationFromPileup.ts +34 -26
- package/src/index.ts +9 -8
- package/src/session/ClientDataStore.ts +16 -21
- package/src/session/handleApolloFeaturesUrlParam.ts +122 -0
- package/src/session/session.ts +49 -9
- package/src/util/annotationFeatureUtils.ts +1 -1
- package/src/util/displayUtils.ts +1 -1
- package/src/util/glyphUtils.ts +3 -3
- package/src/util/sequenceUtils.ts +29 -0
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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.
|
|
69
|
+
var version = "1.1.1";
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
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 =
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
|
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 {
|
|
5266
|
-
return (jsx(Button, { className: classes.loginButton, variant: "outlined", startIcon: jsx(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 {
|
|
5271
|
-
return (jsx(Button, { className: classes.loginButton, variant: "outlined", startIcon: jsx(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
|
-
|
|
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
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
}
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
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
|
|
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
|
|
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) => (
|
|
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
|
|
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
|
|
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,195 @@ 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
|
+
if (startCodonIndex === -1) {
|
|
7255
|
+
notify('Start codon not found', 'error');
|
|
7256
|
+
return;
|
|
7257
|
+
}
|
|
7258
|
+
const stopCodonIndex = proteinSequence.indexOf('*', startCodonIndex);
|
|
7259
|
+
if (stopCodonIndex === -1) {
|
|
7260
|
+
notify('Stop codon not found', 'error');
|
|
7261
|
+
return;
|
|
7262
|
+
}
|
|
7263
|
+
const startCodonPos = startCodonIndex * 3;
|
|
7264
|
+
const stopCodonPos = stopCodonIndex * 3;
|
|
7265
|
+
const startCodonGenomicLoc = getCodonGenomicLocation(startCodonPos);
|
|
7266
|
+
let stopCodonGenomicLoc = getCodonGenomicLocation(stopCodonPos);
|
|
7267
|
+
if (strand === 1) {
|
|
7268
|
+
if (startCodonGenomicLoc > stopCodonGenomicLoc) {
|
|
7269
|
+
notify('Start codon genomic location should be less than stop codon genomic location', 'error');
|
|
7270
|
+
return;
|
|
7271
|
+
}
|
|
7272
|
+
let promise;
|
|
7273
|
+
stopCodonGenomicLoc += 3; // move to end of stop codon
|
|
7274
|
+
if (startCodonGenomicLoc !== cdsMin) {
|
|
7275
|
+
promise = new Promise((resolve) => {
|
|
7276
|
+
updateCDSLocation(cdsMin, startCodonGenomicLoc, feature, true, () => {
|
|
7277
|
+
resolve(true);
|
|
7278
|
+
});
|
|
7279
|
+
});
|
|
7280
|
+
}
|
|
7281
|
+
if (stopCodonGenomicLoc !== cdsMax) {
|
|
7282
|
+
if (promise) {
|
|
7283
|
+
void promise.then(() => {
|
|
7284
|
+
updateCDSLocation(cdsMax, stopCodonGenomicLoc, feature, false);
|
|
7285
|
+
});
|
|
7286
|
+
}
|
|
7287
|
+
else {
|
|
7288
|
+
updateCDSLocation(cdsMax, stopCodonGenomicLoc, feature, false);
|
|
7289
|
+
}
|
|
7290
|
+
}
|
|
7291
|
+
}
|
|
7292
|
+
if (strand === -1) {
|
|
7293
|
+
// reverse strand
|
|
7294
|
+
if (startCodonGenomicLoc < stopCodonGenomicLoc) {
|
|
7295
|
+
notify('Start codon genomic location should be less than stop codon genomic location', 'error');
|
|
7296
|
+
return;
|
|
7297
|
+
}
|
|
7298
|
+
let promise;
|
|
7299
|
+
stopCodonGenomicLoc -= 3; // move to end of stop codon
|
|
7300
|
+
if (startCodonGenomicLoc !== cdsMax) {
|
|
7301
|
+
promise = new Promise((resolve) => {
|
|
7302
|
+
updateCDSLocation(cdsMax, startCodonGenomicLoc, feature, false, () => {
|
|
7303
|
+
resolve(true);
|
|
7304
|
+
});
|
|
7305
|
+
});
|
|
7306
|
+
}
|
|
7307
|
+
if (stopCodonGenomicLoc !== cdsMin) {
|
|
7308
|
+
if (promise) {
|
|
7309
|
+
void promise.then(() => {
|
|
7310
|
+
updateCDSLocation(cdsMin, stopCodonGenomicLoc, feature, true);
|
|
7311
|
+
});
|
|
7312
|
+
}
|
|
7313
|
+
else {
|
|
7314
|
+
updateCDSLocation(cdsMin, stopCodonGenomicLoc, feature, true);
|
|
7315
|
+
}
|
|
7316
|
+
}
|
|
7317
|
+
}
|
|
7318
|
+
notify('Translation sequence trimmed to start and stop codons', 'success');
|
|
7319
|
+
};
|
|
7320
|
+
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) => {
|
|
7321
|
+
const codonGenomicPos = idx * 3;
|
|
7322
|
+
if (protein === 'M') {
|
|
7323
|
+
return (jsx(Typography, { component: 'span', style: {
|
|
7324
|
+
backgroundColor: changeInProgress
|
|
7325
|
+
? 'lightgray'
|
|
7326
|
+
: 'yellow',
|
|
7327
|
+
cursor: 'pointer',
|
|
7328
|
+
border: '1px solid black',
|
|
7329
|
+
}, onClick: () => {
|
|
7330
|
+
if (changeInProgress) {
|
|
7331
|
+
return;
|
|
7332
|
+
}
|
|
7333
|
+
// NOTE: codonGenomicPos is important here for calculating the genomic location
|
|
7334
|
+
// of the start codon. We are using the codonGenomicPos as the key in the typography
|
|
7335
|
+
// elements to maintain the genomic postion of the codon start
|
|
7336
|
+
const startCodonGenomicLocation = getCodonGenomicLocation(codonGenomicPos);
|
|
7337
|
+
if (startCodonGenomicLocation !== cdsMin &&
|
|
7338
|
+
strand === 1) {
|
|
7339
|
+
updateCDSLocation(cdsMin, startCodonGenomicLocation, feature, true);
|
|
7340
|
+
}
|
|
7341
|
+
if (startCodonGenomicLocation !== cdsMax &&
|
|
7342
|
+
strand === -1) {
|
|
7343
|
+
updateCDSLocation(cdsMax, startCodonGenomicLocation, feature, false);
|
|
7344
|
+
}
|
|
7345
|
+
}, children: protein }, codonGenomicPos));
|
|
7346
|
+
}
|
|
7347
|
+
if (protein === '*') {
|
|
7348
|
+
return (jsx(Typography, { style: { backgroundColor: 'red', color: 'white' }, component: 'span', children: protein }, codonGenomicPos));
|
|
7349
|
+
}
|
|
7350
|
+
// Pass the codonGenomicPos as the key to maintain the genomic position of the codon
|
|
7351
|
+
return (jsx(Typography, { component: 'span', children: protein }, codonGenomicPos));
|
|
7352
|
+
}) }) }), jsxs("div", { style: {
|
|
7353
|
+
marginTop: 10,
|
|
7354
|
+
display: 'flex',
|
|
7355
|
+
flexDirection: 'row',
|
|
7356
|
+
alignItems: 'center',
|
|
7357
|
+
gap: 10,
|
|
7358
|
+
}, 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 } }) }) })] })] })] }) }));
|
|
7359
|
+
}
|
|
7360
|
+
|
|
7361
|
+
const StyledTextField = styled(NumberTextField)(() => ({
|
|
7362
|
+
'&.MuiFormControl-root': {
|
|
7363
|
+
marginTop: 0,
|
|
7364
|
+
marginBottom: 0,
|
|
7365
|
+
width: '100%',
|
|
7366
|
+
},
|
|
7367
|
+
'& .MuiInputBase-input': {
|
|
7368
|
+
fontSize: 12,
|
|
7369
|
+
height: 20,
|
|
7370
|
+
padding: 1,
|
|
7371
|
+
paddingLeft: 10,
|
|
7372
|
+
},
|
|
7373
|
+
}));
|
|
6990
7374
|
const Strand = (props) => {
|
|
6991
7375
|
const { strand } = props;
|
|
6992
7376
|
return (jsx("div", { children: strand === 1 ? (jsx(AddIcon, {})) : strand === -1 ? (jsx(RemoveIcon, {})) : (jsx(Typography, { component: 'span', children: "N/A" })) }));
|
|
@@ -7005,7 +7389,6 @@ const TranscriptWidgetEditLocation = observer(function TranscriptWidgetEditLocat
|
|
|
7005
7389
|
const currentAssembly = session.apolloDataStore.assemblies.get(assembly);
|
|
7006
7390
|
const refData = currentAssembly?.getByRefName(refName);
|
|
7007
7391
|
const { changeManager } = session.apolloDataStore;
|
|
7008
|
-
const seqRef = useRef(null);
|
|
7009
7392
|
const { changeInProgress } = session;
|
|
7010
7393
|
if (!refData) {
|
|
7011
7394
|
return null;
|
|
@@ -7432,184 +7815,6 @@ const TranscriptWidgetEditLocation = observer(function TranscriptWidgetEditLocat
|
|
|
7432
7815
|
},
|
|
7433
7816
|
];
|
|
7434
7817
|
};
|
|
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
7818
|
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
7819
|
return updateCDSLocation(cdsMin, newLocation - 1, feature, true);
|
|
7615
7820
|
}, 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 +7824,12 @@ const TranscriptWidgetEditLocation = observer(function TranscriptWidgetEditLocat
|
|
|
7619
7824
|
}, 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
7825
|
return updateCDSLocation(cdsMin, newLocation - 1, feature, true);
|
|
7621
7826
|
}, 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 (
|
|
7623
|
-
|
|
7827
|
+
return (
|
|
7828
|
+
// eslint-disable-next-line @eslint-react/no-array-index-key
|
|
7829
|
+
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 &&
|
|
7830
|
+
getFivePrimeSpliceSite(loc, index).map((site, idx) => (jsx(Typography
|
|
7831
|
+
// eslint-disable-next-line @eslint-react/no-array-index-key
|
|
7832
|
+
, { 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
7833
|
return handleExonLocationChange(loc.min, newLocation - 1, feature, true);
|
|
7625
7834
|
}, disabled: changeInProgress }) })) : (jsx(Grid, { size: 4, style: { padding: 0 }, children: jsx(StyledTextField, { margin: "dense", variant: "outlined", value: loc.max, onChangeCommitted: (newLocation) => {
|
|
7626
7835
|
return handleExonLocationChange(loc.max, newLocation, feature, false);
|
|
@@ -7629,14 +7838,10 @@ const TranscriptWidgetEditLocation = observer(function TranscriptWidgetEditLocat
|
|
|
7629
7838
|
}, disabled: changeInProgress }) })) : (jsx(Grid, { size: 4, style: { padding: 0 }, children: jsx(StyledTextField, { margin: "dense", variant: "outlined", value: loc.min + 1, onChangeCommitted: (newLocation) => {
|
|
7630
7839
|
return handleExonLocationChange(loc.min, newLocation - 1, feature, true);
|
|
7631
7840
|
}, disabled: changeInProgress }) })), jsx(Grid, { size: 1, children: index !== transcriptExonParts.length - 1 &&
|
|
7632
|
-
getThreePrimeSpliceSite(loc, index).map((site, idx) => (jsx(Typography
|
|
7633
|
-
|
|
7634
|
-
|
|
7635
|
-
|
|
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 } }) }) })] })] })] }) }))] }));
|
|
7841
|
+
getThreePrimeSpliceSite(loc, index).map((site, idx) => (jsx(Typography
|
|
7842
|
+
// eslint-disable-next-line @eslint-react/no-array-index-key
|
|
7843
|
+
, { component: 'span', color: site.color, children: site.spliceSite }, idx))) })] })) }, index));
|
|
7844
|
+
})] }), cdsPresent && (jsx(Translation, { changeInProgress: changeInProgress, cdsLocations: cdsLocations, refData: refData, strand: strand, updateCDSLocation: updateCDSLocation, cdsMin: cdsMin, cdsMax: cdsMax, feature: feature, session: session }))] }));
|
|
7640
7845
|
});
|
|
7641
7846
|
|
|
7642
7847
|
const HeaderTableCell = styled(TableCell)(() => ({
|
|
@@ -7673,7 +7878,7 @@ const ApolloTranscriptDetailsWidget = observer(function ApolloTranscriptDetails(
|
|
|
7673
7878
|
const { assembly, feature, refName } = model;
|
|
7674
7879
|
useEffect(() => {
|
|
7675
7880
|
setPanelState(DEFAULT_PANELS);
|
|
7676
|
-
// eslint-disable-next-line react
|
|
7881
|
+
// eslint-disable-next-line @eslint-react/exhaustive-deps
|
|
7677
7882
|
}, [feature]);
|
|
7678
7883
|
const session = getSession(model);
|
|
7679
7884
|
const { pluginManager } = getEnv(session);
|
|
@@ -7732,8 +7937,8 @@ function handleFeatureTypeChange(changeManager, feature, oldType, newType) {
|
|
|
7732
7937
|
typeName: 'TypeChange',
|
|
7733
7938
|
changedIds: [featureId],
|
|
7734
7939
|
featureId,
|
|
7735
|
-
oldType
|
|
7736
|
-
newType
|
|
7940
|
+
oldType,
|
|
7941
|
+
newType,
|
|
7737
7942
|
assembly: feature.assemblyId,
|
|
7738
7943
|
});
|
|
7739
7944
|
return changeManager.submit(change);
|
|
@@ -7829,7 +8034,7 @@ const NumberCell = observer(function NumberCell({ initialValue, notifyError, onC
|
|
|
7829
8034
|
if (initialValue !== value) {
|
|
7830
8035
|
setValue(initialValue);
|
|
7831
8036
|
}
|
|
7832
|
-
// eslint-disable-next-line react
|
|
8037
|
+
// eslint-disable-next-line @eslint-react/exhaustive-deps
|
|
7833
8038
|
}, [initialValue]);
|
|
7834
8039
|
useEffect(() => {
|
|
7835
8040
|
if (blur) {
|
|
@@ -8670,8 +8875,9 @@ function draw$3(display, ctx, gene, row, rowInFeature, block) {
|
|
|
8670
8875
|
const [top, left, width] = getFeatureBox(display, gene, row, block);
|
|
8671
8876
|
const height = getRowCount$2(display, gene) * apolloRowHeight;
|
|
8672
8877
|
if (width > 2) {
|
|
8673
|
-
|
|
8674
|
-
|
|
8878
|
+
const selectedColor = readConfObject(session.getPluginConfiguration(), 'geneBackgroundColor', {
|
|
8879
|
+
featureType: gene.type,
|
|
8880
|
+
}) ?? alpha(theme.palette.background.paper, 0.6);
|
|
8675
8881
|
ctx.fillStyle = selectedColor;
|
|
8676
8882
|
ctx.fillRect(left, top, width, height);
|
|
8677
8883
|
}
|
|
@@ -8944,7 +9150,7 @@ function getLayout(display, feature) {
|
|
|
8944
9150
|
}
|
|
8945
9151
|
return layout;
|
|
8946
9152
|
}
|
|
8947
|
-
function getContextMenuItems$1(display,
|
|
9153
|
+
function getContextMenuItems$1(display, transcript) {
|
|
8948
9154
|
const { changeManager, regions, selectedFeature, session } = display;
|
|
8949
9155
|
const [region] = regions;
|
|
8950
9156
|
const currentAssemblyId = display.getAssemblyId(region.assemblyName);
|
|
@@ -8954,7 +9160,7 @@ function getContextMenuItems$1(display, feature) {
|
|
|
8954
9160
|
label: 'Open transcript editor',
|
|
8955
9161
|
onClick: () => {
|
|
8956
9162
|
const apolloTranscriptWidget = session.addWidget('ApolloTranscriptDetails', 'apolloTranscriptDetails', {
|
|
8957
|
-
feature,
|
|
9163
|
+
feature: transcript,
|
|
8958
9164
|
assembly: currentAssemblyId,
|
|
8959
9165
|
changeManager,
|
|
8960
9166
|
refName: region.refName,
|
|
@@ -8974,7 +9180,7 @@ function getContextMenuItems$1(display, feature) {
|
|
|
8974
9180
|
doneCallback();
|
|
8975
9181
|
},
|
|
8976
9182
|
changeManager,
|
|
8977
|
-
sourceFeature:
|
|
9183
|
+
sourceFeature: transcript,
|
|
8978
9184
|
sourceAssemblyId: currentAssemblyId,
|
|
8979
9185
|
selectedFeature,
|
|
8980
9186
|
setSelectedFeature: (feature) => {
|
|
@@ -8994,7 +9200,7 @@ function getContextMenuItems$1(display, feature) {
|
|
|
8994
9200
|
doneCallback();
|
|
8995
9201
|
},
|
|
8996
9202
|
changeManager,
|
|
8997
|
-
sourceFeature:
|
|
9203
|
+
sourceFeature: transcript,
|
|
8998
9204
|
sourceAssemblyId: currentAssemblyId,
|
|
8999
9205
|
selectedFeature,
|
|
9000
9206
|
setSelectedFeature: (feature) => {
|
|
@@ -9004,6 +9210,31 @@ function getContextMenuItems$1(display, feature) {
|
|
|
9004
9210
|
]);
|
|
9005
9211
|
},
|
|
9006
9212
|
});
|
|
9213
|
+
const { children } = transcript;
|
|
9214
|
+
if (!children) {
|
|
9215
|
+
return [];
|
|
9216
|
+
}
|
|
9217
|
+
const cdsChildren = [...children.values()].filter((child) => isCDSFeature(child, session));
|
|
9218
|
+
if (cdsChildren.length === 0) {
|
|
9219
|
+
menuItems.push({
|
|
9220
|
+
label: 'Add coding sequence',
|
|
9221
|
+
onClick: () => {
|
|
9222
|
+
session.queueDialog((doneCallback) => [
|
|
9223
|
+
AddCodingSequence,
|
|
9224
|
+
{
|
|
9225
|
+
session,
|
|
9226
|
+
handleClose: () => {
|
|
9227
|
+
doneCallback();
|
|
9228
|
+
},
|
|
9229
|
+
changeManager,
|
|
9230
|
+
sourceFeature: transcript,
|
|
9231
|
+
sourceAssemblyId: currentAssemblyId,
|
|
9232
|
+
refName: region.refName,
|
|
9233
|
+
},
|
|
9234
|
+
]);
|
|
9235
|
+
},
|
|
9236
|
+
});
|
|
9237
|
+
}
|
|
9007
9238
|
return menuItems;
|
|
9008
9239
|
}
|
|
9009
9240
|
// False positive here, none of these functions use "this"
|
|
@@ -9181,7 +9412,7 @@ function colorCode(letter, theme) {
|
|
|
9181
9412
|
letterUpper === 'C' ||
|
|
9182
9413
|
letterUpper === 'G' ||
|
|
9183
9414
|
letterUpper === 'T') {
|
|
9184
|
-
return theme.palette.bases[letterUpper].main
|
|
9415
|
+
return theme.palette.bases[letterUpper].main;
|
|
9185
9416
|
}
|
|
9186
9417
|
return 'lightgray';
|
|
9187
9418
|
}
|
|
@@ -9278,8 +9509,7 @@ function baseModelFactory$2(_pluginManager, configSchema) {
|
|
|
9278
9509
|
return internetAccounts.find((ia) => getConf(ia, 'internetAccountId') === internetAccountConfigId);
|
|
9279
9510
|
},
|
|
9280
9511
|
get changeManager() {
|
|
9281
|
-
return self.session.apolloDataStore
|
|
9282
|
-
.changeManager;
|
|
9512
|
+
return self.session.apolloDataStore.changeManager;
|
|
9283
9513
|
},
|
|
9284
9514
|
getAssemblyId(assemblyName) {
|
|
9285
9515
|
const { assemblyManager } = self.session;
|
|
@@ -9290,12 +9520,10 @@ function baseModelFactory$2(_pluginManager, configSchema) {
|
|
|
9290
9520
|
return assembly.name;
|
|
9291
9521
|
},
|
|
9292
9522
|
get selectedFeature() {
|
|
9293
|
-
return self.session
|
|
9294
|
-
.apolloSelectedFeature;
|
|
9523
|
+
return self.session.apolloSelectedFeature;
|
|
9295
9524
|
},
|
|
9296
9525
|
get hoveredFeature() {
|
|
9297
|
-
return self.session
|
|
9298
|
-
.apolloHoveredFeature;
|
|
9526
|
+
return self.session.apolloHoveredFeature;
|
|
9299
9527
|
},
|
|
9300
9528
|
}))
|
|
9301
9529
|
.actions((self) => ({
|
|
@@ -9340,8 +9568,11 @@ function baseModelFactory$2(_pluginManager, configSchema) {
|
|
|
9340
9568
|
const { filteredFeatureTypes, trackMenuItems: superTrackMenuItems } = self;
|
|
9341
9569
|
return {
|
|
9342
9570
|
trackMenuItems() {
|
|
9343
|
-
const { graphical, table, showCheckResults } = self;
|
|
9344
|
-
|
|
9571
|
+
const { graphical, table, showCheckResults, session } = self;
|
|
9572
|
+
const [region] = self.regions;
|
|
9573
|
+
const { assemblyName } = region;
|
|
9574
|
+
const backendDriver = session.apolloDataStore.getBackendDriver(assemblyName);
|
|
9575
|
+
const items = [
|
|
9345
9576
|
...superTrackMenuItems(),
|
|
9346
9577
|
{
|
|
9347
9578
|
type: 'subMenu',
|
|
@@ -9393,7 +9624,7 @@ function baseModelFactory$2(_pluginManager, configSchema) {
|
|
|
9393
9624
|
{
|
|
9394
9625
|
label: 'Filter features by type',
|
|
9395
9626
|
onClick: () => {
|
|
9396
|
-
const session = self
|
|
9627
|
+
const { session } = self;
|
|
9397
9628
|
self.session.queueDialog((doneCallback) => [
|
|
9398
9629
|
FilterFeatures,
|
|
9399
9630
|
{
|
|
@@ -9409,76 +9640,93 @@ function baseModelFactory$2(_pluginManager, configSchema) {
|
|
|
9409
9640
|
]);
|
|
9410
9641
|
},
|
|
9411
9642
|
},
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
|
|
9643
|
+
];
|
|
9644
|
+
if (backendDriver instanceof LocalDriver) {
|
|
9645
|
+
items.push({
|
|
9646
|
+
label: 'Import annotations',
|
|
9647
|
+
icon: InputIcon,
|
|
9415
9648
|
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
9649
|
session.queueDialog((doneCallback) => [
|
|
9424
|
-
|
|
9650
|
+
ImportAnnotations,
|
|
9425
9651
|
{
|
|
9426
9652
|
session,
|
|
9653
|
+
assemblyName,
|
|
9427
9654
|
handleClose: () => {
|
|
9428
9655
|
doneCallback();
|
|
9429
9656
|
},
|
|
9430
|
-
assembly,
|
|
9431
9657
|
},
|
|
9432
9658
|
]);
|
|
9433
9659
|
},
|
|
9660
|
+
});
|
|
9661
|
+
}
|
|
9662
|
+
items.push({
|
|
9663
|
+
label: 'Export annotations',
|
|
9664
|
+
icon: Export,
|
|
9665
|
+
onClick: () => {
|
|
9666
|
+
const [region] = self.regions;
|
|
9667
|
+
const { assemblyName } = region;
|
|
9668
|
+
const assembly = self.getAssemblyId(assemblyName);
|
|
9669
|
+
if (!assembly) {
|
|
9670
|
+
return;
|
|
9671
|
+
}
|
|
9672
|
+
const { session } = self;
|
|
9673
|
+
session.queueDialog((doneCallback) => [
|
|
9674
|
+
DownloadGFF3,
|
|
9675
|
+
{
|
|
9676
|
+
session,
|
|
9677
|
+
handleClose: () => {
|
|
9678
|
+
doneCallback();
|
|
9679
|
+
},
|
|
9680
|
+
assembly,
|
|
9681
|
+
},
|
|
9682
|
+
]);
|
|
9434
9683
|
},
|
|
9435
|
-
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
},
|
|
9453
|
-
assembly,
|
|
9684
|
+
}, {
|
|
9685
|
+
label: 'View Change Log',
|
|
9686
|
+
icon: TrackChangesIcon,
|
|
9687
|
+
onClick: () => {
|
|
9688
|
+
const [region] = self.regions;
|
|
9689
|
+
const { assemblyName } = region;
|
|
9690
|
+
const assembly = self.getAssemblyId(assemblyName);
|
|
9691
|
+
if (!assembly) {
|
|
9692
|
+
return;
|
|
9693
|
+
}
|
|
9694
|
+
const { session } = self;
|
|
9695
|
+
session.queueDialog((doneCallback) => [
|
|
9696
|
+
ViewChangeLog,
|
|
9697
|
+
{
|
|
9698
|
+
session,
|
|
9699
|
+
handleClose: () => {
|
|
9700
|
+
doneCallback();
|
|
9454
9701
|
},
|
|
9455
|
-
|
|
9456
|
-
|
|
9702
|
+
assembly,
|
|
9703
|
+
},
|
|
9704
|
+
]);
|
|
9457
9705
|
},
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
|
|
9464
|
-
|
|
9465
|
-
|
|
9466
|
-
|
|
9467
|
-
|
|
9468
|
-
|
|
9469
|
-
|
|
9470
|
-
|
|
9471
|
-
|
|
9472
|
-
|
|
9473
|
-
|
|
9474
|
-
|
|
9475
|
-
},
|
|
9476
|
-
assembly,
|
|
9706
|
+
}, {
|
|
9707
|
+
label: 'View Check Results',
|
|
9708
|
+
icon: FactCheckIcon,
|
|
9709
|
+
onClick: () => {
|
|
9710
|
+
const [region] = self.regions;
|
|
9711
|
+
const { assemblyName } = region;
|
|
9712
|
+
const assembly = self.getAssemblyId(assemblyName);
|
|
9713
|
+
if (!assembly) {
|
|
9714
|
+
return;
|
|
9715
|
+
}
|
|
9716
|
+
const { session } = self;
|
|
9717
|
+
session.queueDialog((doneCallback) => [
|
|
9718
|
+
ViewCheckResults,
|
|
9719
|
+
{
|
|
9720
|
+
session,
|
|
9721
|
+
handleClose: () => {
|
|
9722
|
+
doneCallback();
|
|
9477
9723
|
},
|
|
9478
|
-
|
|
9479
|
-
|
|
9724
|
+
assembly,
|
|
9725
|
+
},
|
|
9726
|
+
]);
|
|
9480
9727
|
},
|
|
9481
|
-
|
|
9728
|
+
});
|
|
9729
|
+
return items;
|
|
9482
9730
|
},
|
|
9483
9731
|
};
|
|
9484
9732
|
})
|
|
@@ -10544,8 +10792,7 @@ function baseModelFactory$1(_pluginManager, configSchema) {
|
|
|
10544
10792
|
return internetAccounts.find((ia) => getConf(ia, 'internetAccountId') === internetAccountConfigId);
|
|
10545
10793
|
},
|
|
10546
10794
|
get changeManager() {
|
|
10547
|
-
return self.session.apolloDataStore
|
|
10548
|
-
.changeManager;
|
|
10795
|
+
return self.session.apolloDataStore.changeManager;
|
|
10549
10796
|
},
|
|
10550
10797
|
getAssemblyId(assemblyName) {
|
|
10551
10798
|
const { assemblyManager } = self.session;
|
|
@@ -10556,12 +10803,10 @@ function baseModelFactory$1(_pluginManager, configSchema) {
|
|
|
10556
10803
|
return assembly.name;
|
|
10557
10804
|
},
|
|
10558
10805
|
get selectedFeature() {
|
|
10559
|
-
return self.session
|
|
10560
|
-
.apolloSelectedFeature;
|
|
10806
|
+
return self.session.apolloSelectedFeature;
|
|
10561
10807
|
},
|
|
10562
10808
|
get hoveredFeature() {
|
|
10563
|
-
return self.session
|
|
10564
|
-
.apolloHoveredFeature;
|
|
10809
|
+
return self.session.apolloHoveredFeature;
|
|
10565
10810
|
},
|
|
10566
10811
|
get height() {
|
|
10567
10812
|
const { sequenceRowHeight } = self;
|
|
@@ -10773,11 +11018,9 @@ const FilterTranscripts = observer(function FilterTranscripts({ sourceFeature, f
|
|
|
10773
11018
|
onUpdate(newForms);
|
|
10774
11019
|
setExcludedTranscripts(newForms);
|
|
10775
11020
|
};
|
|
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: () => {
|
|
11021
|
+
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
11022
|
handleChange(item);
|
|
10780
|
-
}, slotProps: { input: { 'aria-label': 'controlled' } } }), label: item }))) }) }) })] }) }));
|
|
11023
|
+
}, slotProps: { input: { 'aria-label': 'controlled' } } }), label: item }, item))) }) }) })] }) }));
|
|
10781
11024
|
});
|
|
10782
11025
|
|
|
10783
11026
|
let forwardFillLight = null;
|
|
@@ -11623,8 +11866,7 @@ function baseModelFactory(_pluginManager, configSchema) {
|
|
|
11623
11866
|
return internetAccounts.find((ia) => getConf(ia, 'internetAccountId') === internetAccountConfigId);
|
|
11624
11867
|
},
|
|
11625
11868
|
get changeManager() {
|
|
11626
|
-
return self.session.apolloDataStore
|
|
11627
|
-
.changeManager;
|
|
11869
|
+
return self.session.apolloDataStore.changeManager;
|
|
11628
11870
|
},
|
|
11629
11871
|
getAssemblyId(assemblyName) {
|
|
11630
11872
|
const { assemblyManager } = self.session;
|
|
@@ -11635,12 +11877,10 @@ function baseModelFactory(_pluginManager, configSchema) {
|
|
|
11635
11877
|
return assembly.name;
|
|
11636
11878
|
},
|
|
11637
11879
|
get selectedFeature() {
|
|
11638
|
-
return self.session
|
|
11639
|
-
.apolloSelectedFeature;
|
|
11880
|
+
return self.session.apolloSelectedFeature;
|
|
11640
11881
|
},
|
|
11641
11882
|
get hoveredFeature() {
|
|
11642
|
-
return self.session
|
|
11643
|
-
.apolloHoveredFeature;
|
|
11883
|
+
return self.session.apolloHoveredFeature;
|
|
11644
11884
|
},
|
|
11645
11885
|
}))
|
|
11646
11886
|
.actions((self) => ({
|
|
@@ -11768,7 +12008,7 @@ function baseModelFactory(_pluginManager, configSchema) {
|
|
|
11768
12008
|
{
|
|
11769
12009
|
label: 'Filter features by type',
|
|
11770
12010
|
onClick: () => {
|
|
11771
|
-
const session = self
|
|
12011
|
+
const { session } = self;
|
|
11772
12012
|
self.session.queueDialog((doneCallback) => [
|
|
11773
12013
|
FilterFeatures,
|
|
11774
12014
|
{
|
|
@@ -12206,17 +12446,12 @@ function mouseEventsModelIntermediateFactory(pluginManager, configSchema) {
|
|
|
12206
12446
|
if (!featureTypeOntology) {
|
|
12207
12447
|
throw new Error('featureTypeOntology is undefined');
|
|
12208
12448
|
}
|
|
12209
|
-
|
|
12210
|
-
|
|
12211
|
-
foundFeature = layoutRow.find((f) => f.feature.type == 'exon' &&
|
|
12449
|
+
const foundFeature = self.geneTrackRowNums.includes(row)
|
|
12450
|
+
? layoutRow.find((f) => f.feature.type == 'exon' &&
|
|
12212
12451
|
bp >= f.feature.min &&
|
|
12213
|
-
bp <= f.feature.max)
|
|
12214
|
-
|
|
12215
|
-
|
|
12216
|
-
}
|
|
12217
|
-
}
|
|
12218
|
-
else {
|
|
12219
|
-
foundFeature = layoutRow.find((f) => {
|
|
12452
|
+
bp <= f.feature.max) ??
|
|
12453
|
+
layoutRow.find((f) => bp >= f.feature.min && bp <= f.feature.max)
|
|
12454
|
+
: layoutRow.find((f) => {
|
|
12220
12455
|
const { feature } = f;
|
|
12221
12456
|
const featureID = feature.attributes.get('gff_id')?.toString();
|
|
12222
12457
|
const isTranscript = featureTypeOntology.isTypeOf(feature.type, 'transcript');
|
|
@@ -12238,7 +12473,6 @@ function mouseEventsModelIntermediateFactory(pluginManager, configSchema) {
|
|
|
12238
12473
|
}
|
|
12239
12474
|
return false;
|
|
12240
12475
|
});
|
|
12241
|
-
}
|
|
12242
12476
|
if (!foundFeature) {
|
|
12243
12477
|
return mousePosition;
|
|
12244
12478
|
}
|
|
@@ -12561,27 +12795,33 @@ function CreateApolloAnnotation({ annotationFeature, assembly, handleClose, refS
|
|
|
12561
12795
|
const [selectedDestinationFeature, setSelectedDestinationFeature] = useState();
|
|
12562
12796
|
const apolloAssembly = apolloSessionModel.apolloDataStore.assemblies.get(assembly.name);
|
|
12563
12797
|
const refSeq = apolloAssembly?.refSeqs.get(refSeqId);
|
|
12564
|
-
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
|
-
};
|
|
12798
|
+
const features = useMemo(() => refSeq?.getFeatures(region.start, region.end), [refSeq, region.start, region.end]);
|
|
12579
12799
|
useEffect(() => {
|
|
12800
|
+
const getDestinationFeatures = () => {
|
|
12801
|
+
const filteredFeatures = [];
|
|
12802
|
+
for (const f of features ?? []) {
|
|
12803
|
+
if (f.min > region.end || f.max < region.start) {
|
|
12804
|
+
continue;
|
|
12805
|
+
}
|
|
12806
|
+
// Destination feature should be of type gene
|
|
12807
|
+
if (featureTypeOntology?.isTypeOf(f.type, 'gene')) {
|
|
12808
|
+
const featureSnapshot = getSnapshot(f);
|
|
12809
|
+
filteredFeatures.push(featureSnapshot);
|
|
12810
|
+
}
|
|
12811
|
+
}
|
|
12812
|
+
return filteredFeatures;
|
|
12813
|
+
};
|
|
12580
12814
|
setErrorMessage('');
|
|
12581
|
-
const
|
|
12582
|
-
setDestinationFeatures(
|
|
12583
|
-
setSelectedDestinationFeature(
|
|
12584
|
-
}, [
|
|
12815
|
+
const destinationFeatures = getDestinationFeatures();
|
|
12816
|
+
setDestinationFeatures(destinationFeatures);
|
|
12817
|
+
setSelectedDestinationFeature(destinationFeatures[0]);
|
|
12818
|
+
}, [
|
|
12819
|
+
checkedChildrens,
|
|
12820
|
+
featureTypeOntology,
|
|
12821
|
+
features,
|
|
12822
|
+
parentFeatureChecked,
|
|
12823
|
+
region,
|
|
12824
|
+
]);
|
|
12585
12825
|
const handleParentFeatureCheck = (event) => {
|
|
12586
12826
|
const isChecked = event.target.checked;
|
|
12587
12827
|
setParentFeatureChecked(isChecked);
|
|
@@ -12861,8 +13101,21 @@ function annotationFromPileup(pluggableElement) {
|
|
|
12861
13101
|
getAnnotationFeature(jbrowseFeature, refSeqId) {
|
|
12862
13102
|
const start = jbrowseFeature.get('start');
|
|
12863
13103
|
const end = jbrowseFeature.get('end');
|
|
12864
|
-
const strand = jbrowseFeature.get('strand');
|
|
12865
13104
|
const name = jbrowseFeature.get('name');
|
|
13105
|
+
let strand;
|
|
13106
|
+
const tags = jbrowseFeature.get('tags');
|
|
13107
|
+
const { ts, TS, XS } = tags;
|
|
13108
|
+
for (const tag of [ts, TS, XS]) {
|
|
13109
|
+
if (!strand && tag) {
|
|
13110
|
+
if ([1, '1', '+'].includes(tag)) {
|
|
13111
|
+
strand = 1;
|
|
13112
|
+
}
|
|
13113
|
+
else if ([-1, '-1', '-'].includes(tag)) {
|
|
13114
|
+
strand = -1;
|
|
13115
|
+
}
|
|
13116
|
+
}
|
|
13117
|
+
}
|
|
13118
|
+
strand ??= jbrowseFeature.get('strand');
|
|
12866
13119
|
const cigarData = jbrowseFeature.get('CIGAR');
|
|
12867
13120
|
const ops = parseCigar(cigarData);
|
|
12868
13121
|
let position = start;
|
|
@@ -12878,9 +13131,7 @@ function annotationFromPileup(pluggableElement) {
|
|
|
12878
13131
|
case 'M':
|
|
12879
13132
|
case '=':
|
|
12880
13133
|
case 'X': {
|
|
12881
|
-
|
|
12882
|
-
currentExonStart = position;
|
|
12883
|
-
}
|
|
13134
|
+
currentExonStart ??= position;
|
|
12884
13135
|
position += len;
|
|
12885
13136
|
break;
|
|
12886
13137
|
}
|
|
@@ -13294,7 +13545,7 @@ const LinearApolloDisplay = observer(function LinearApolloDisplay(props) {
|
|
|
13294
13545
|
: undefined, menuItems: contextMenuItems })] }))] }));
|
|
13295
13546
|
});
|
|
13296
13547
|
|
|
13297
|
-
const TrackLines = observer(function TrackLines({ model, hrStyle = { margin: 0, top: 0, color: 'black' }, idx
|
|
13548
|
+
const TrackLines = observer(function TrackLines({ model, hrStyle = { margin: 0, top: 0, color: 'black' }, idx, }) {
|
|
13298
13549
|
const { apolloRowHeight, highestRow, showFeatureLabels } = model;
|
|
13299
13550
|
const featureLabelSpacer = showFeatureLabels ? 2 : 1;
|
|
13300
13551
|
return (jsx("div", { style: {
|
|
@@ -13366,8 +13617,7 @@ const LinearApolloSixFrameDisplay = observer(function LinearApolloSixFrameDispla
|
|
|
13366
13617
|
regionNumber: idx,
|
|
13367
13618
|
})?.offsetPx ?? 0) - lgv.offsetPx;
|
|
13368
13619
|
const [feature] = checkResult.featureIds;
|
|
13369
|
-
if (!feature ||
|
|
13370
|
-
!feature.parent ||
|
|
13620
|
+
if (!feature?.parent ||
|
|
13371
13621
|
!looksLikeGene(feature.parent, session)) {
|
|
13372
13622
|
return null;
|
|
13373
13623
|
}
|
|
@@ -13619,18 +13869,15 @@ function addTopLevelMenus(rootModel) {
|
|
|
13619
13869
|
}
|
|
13620
13870
|
}
|
|
13621
13871
|
|
|
13622
|
-
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
13623
13872
|
/* eslint-disable @typescript-eslint/no-confusing-void-expression */
|
|
13624
13873
|
const ApolloJobModel = types
|
|
13625
13874
|
.model('JobsManager', {})
|
|
13626
13875
|
.views((self) => ({
|
|
13627
13876
|
get jobStatusWidget() {
|
|
13628
13877
|
const { widgets } = getSession(self);
|
|
13629
|
-
|
|
13630
|
-
if (!jobStatusWidget) {
|
|
13878
|
+
const jobStatusWidget = widgets.get('JobsList') ??
|
|
13631
13879
|
// @ts-expect-error: addWidget function not detected on the session
|
|
13632
|
-
|
|
13633
|
-
}
|
|
13880
|
+
getSession(self).addWidget('JobsListWidget', 'JobsList');
|
|
13634
13881
|
return jobStatusWidget;
|
|
13635
13882
|
},
|
|
13636
13883
|
}))
|
|
@@ -13909,19 +14156,15 @@ function clientDataStoreFactory(AnnotationFeatureExtended) {
|
|
|
13909
14156
|
continue;
|
|
13910
14157
|
}
|
|
13911
14158
|
const { assemblyName, refName } = region;
|
|
13912
|
-
|
|
13913
|
-
|
|
13914
|
-
assembly = self.assemblies.put({ _id: assemblyName, refSeqs: {} });
|
|
13915
|
-
}
|
|
14159
|
+
const assembly = self.assemblies.get(assemblyName) ??
|
|
14160
|
+
self.assemblies.put({ _id: assemblyName, refSeqs: {} });
|
|
13916
14161
|
const [firstFeature] = features;
|
|
13917
|
-
|
|
13918
|
-
|
|
13919
|
-
ref = assembly.refSeqs.put({
|
|
14162
|
+
const ref = assembly.refSeqs.get(firstFeature.refSeq) ??
|
|
14163
|
+
assembly.refSeqs.put({
|
|
13920
14164
|
_id: firstFeature.refSeq,
|
|
13921
14165
|
name: refName,
|
|
13922
14166
|
features: {},
|
|
13923
14167
|
});
|
|
13924
|
-
}
|
|
13925
14168
|
for (const feature of features) {
|
|
13926
14169
|
if (!ref.features.has(feature._id)) {
|
|
13927
14170
|
ref.features.put(feature);
|
|
@@ -13938,24 +14181,153 @@ function clientDataStoreFactory(AnnotationFeatureExtended) {
|
|
|
13938
14181
|
}
|
|
13939
14182
|
const { refSeq, seq } = yield backendDriver.getSequence(region);
|
|
13940
14183
|
const { assemblyName, end, refName, start } = region;
|
|
13941
|
-
|
|
13942
|
-
|
|
13943
|
-
|
|
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
|
-
}
|
|
14184
|
+
const assembly = self.assemblies.get(assemblyName) ??
|
|
14185
|
+
self.assemblies.put({ _id: assemblyName, refSeqs: {} });
|
|
14186
|
+
const ref = assembly.refSeqs.get(refSeq) ??
|
|
14187
|
+
assembly.refSeqs.put({ _id: refSeq, name: refName, sequence: [] });
|
|
13953
14188
|
ref.addSequence({ start, stop: end, sequence: seq });
|
|
13954
14189
|
}
|
|
13955
14190
|
}),
|
|
13956
14191
|
}));
|
|
13957
14192
|
}
|
|
13958
14193
|
|
|
14194
|
+
function reassignIds(feature) {
|
|
14195
|
+
const newChildren = {};
|
|
14196
|
+
if (feature.children) {
|
|
14197
|
+
for (const child of Object.values(feature.children)) {
|
|
14198
|
+
const newChild = reassignIds(child);
|
|
14199
|
+
newChildren[newChild._id] = newChild;
|
|
14200
|
+
}
|
|
14201
|
+
}
|
|
14202
|
+
return {
|
|
14203
|
+
...feature,
|
|
14204
|
+
_id: new ObjectID().toHexString(),
|
|
14205
|
+
children: feature.children ? newChildren : undefined,
|
|
14206
|
+
};
|
|
14207
|
+
}
|
|
14208
|
+
function DuplicateFeatureDialog({ assemblyName, changeManager, existingFeature, featureSnapshot, handleClose, }) {
|
|
14209
|
+
const [choice, setChoice] = useState('keepExisting');
|
|
14210
|
+
const [errorMessage, setErrorMessage] = useState('');
|
|
14211
|
+
const [submitted, setSubmitted] = useState(false);
|
|
14212
|
+
const handleSubmit = async () => {
|
|
14213
|
+
setErrorMessage('');
|
|
14214
|
+
setSubmitted(true);
|
|
14215
|
+
try {
|
|
14216
|
+
if (choice === 'useNew') {
|
|
14217
|
+
const deleteChange = new DeleteFeatureChange({
|
|
14218
|
+
typeName: 'DeleteFeatureChange',
|
|
14219
|
+
assembly: assemblyName,
|
|
14220
|
+
changedIds: [existingFeature._id],
|
|
14221
|
+
deletedFeature: existingFeature,
|
|
14222
|
+
});
|
|
14223
|
+
await changeManager.submit(deleteChange);
|
|
14224
|
+
const addChange = new AddFeatureChange({
|
|
14225
|
+
typeName: 'AddFeatureChange',
|
|
14226
|
+
assembly: assemblyName,
|
|
14227
|
+
changedIds: [featureSnapshot._id],
|
|
14228
|
+
addedFeature: featureSnapshot,
|
|
14229
|
+
});
|
|
14230
|
+
await changeManager.submit(addChange);
|
|
14231
|
+
}
|
|
14232
|
+
else if (choice === 'keepBoth') {
|
|
14233
|
+
const newSnapshot = reassignIds(featureSnapshot);
|
|
14234
|
+
const addChange = new AddFeatureChange({
|
|
14235
|
+
typeName: 'AddFeatureChange',
|
|
14236
|
+
assembly: assemblyName,
|
|
14237
|
+
changedIds: [newSnapshot._id],
|
|
14238
|
+
addedFeature: newSnapshot,
|
|
14239
|
+
});
|
|
14240
|
+
await changeManager.submit(addChange);
|
|
14241
|
+
}
|
|
14242
|
+
handleClose();
|
|
14243
|
+
}
|
|
14244
|
+
catch (error) {
|
|
14245
|
+
setErrorMessage(String(error));
|
|
14246
|
+
setSubmitted(false);
|
|
14247
|
+
}
|
|
14248
|
+
};
|
|
14249
|
+
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) => {
|
|
14250
|
+
setChoice(e.target.value);
|
|
14251
|
+
}, 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" })] })] }));
|
|
14252
|
+
}
|
|
14253
|
+
|
|
14254
|
+
function fromUrlSafeBase64(urlSafeBase64) {
|
|
14255
|
+
let base64 = urlSafeBase64.replaceAll('-', '+').replaceAll('_', '/');
|
|
14256
|
+
const pad = base64.length % 4;
|
|
14257
|
+
if (pad) {
|
|
14258
|
+
base64 += '='.repeat(4 - pad);
|
|
14259
|
+
}
|
|
14260
|
+
return base64;
|
|
14261
|
+
}
|
|
14262
|
+
// Base64 decode + decompress
|
|
14263
|
+
async function decompress(encoded) {
|
|
14264
|
+
const binaryString = atob(encoded);
|
|
14265
|
+
// eslint-disable-next-line unicorn/prefer-code-point
|
|
14266
|
+
const bytes = Uint8Array.from(binaryString, (char) => char.charCodeAt(0));
|
|
14267
|
+
const stream = new Blob([bytes]).stream();
|
|
14268
|
+
const decompressionStream = new DecompressionStream('gzip');
|
|
14269
|
+
const decompressedStream = stream.pipeThrough(decompressionStream);
|
|
14270
|
+
const response = new Response(decompressedStream);
|
|
14271
|
+
const decompressed = await response.arrayBuffer();
|
|
14272
|
+
return JSON.parse(new TextDecoder().decode(decompressed));
|
|
14273
|
+
}
|
|
14274
|
+
async function handleApolloFeaturesUrlParam(encodedFeatures, apolloDataStore, assemblyManager, session) {
|
|
14275
|
+
const base64 = fromUrlSafeBase64(encodedFeatures);
|
|
14276
|
+
const decompressed = await decompress(base64);
|
|
14277
|
+
const featuresData = decompressed;
|
|
14278
|
+
for (const [assemblyName, featureSnapshots] of Object.entries(featuresData)) {
|
|
14279
|
+
const backendDriver = apolloDataStore.getBackendDriver(assemblyName);
|
|
14280
|
+
if (!(backendDriver instanceof LocalDriver)) {
|
|
14281
|
+
continue;
|
|
14282
|
+
}
|
|
14283
|
+
const assembly = (await assemblyManager.waitForAssembly(assemblyName));
|
|
14284
|
+
if (!assembly) {
|
|
14285
|
+
throw new Error(`Assembly not found: "${assemblyName}"`);
|
|
14286
|
+
}
|
|
14287
|
+
const { regions } = assembly;
|
|
14288
|
+
if (!regions) {
|
|
14289
|
+
throw new Error(`Assembly not found: "${assemblyName}"`);
|
|
14290
|
+
}
|
|
14291
|
+
const refNames = regions.map((r) => r.refName);
|
|
14292
|
+
const db = await openDb(assemblyName, refNames);
|
|
14293
|
+
for (const featureSnapshot of featureSnapshots) {
|
|
14294
|
+
const storeName = `features-${featureSnapshot.refSeq}`;
|
|
14295
|
+
const existing = (await db.get(storeName, featureSnapshot._id));
|
|
14296
|
+
if (existing === undefined) {
|
|
14297
|
+
const change = new AddFeatureChange({
|
|
14298
|
+
typeName: 'AddFeatureChange',
|
|
14299
|
+
assembly: assemblyName,
|
|
14300
|
+
changedIds: [featureSnapshot._id],
|
|
14301
|
+
addedFeature: featureSnapshot,
|
|
14302
|
+
});
|
|
14303
|
+
await apolloDataStore.changeManager.submit(change);
|
|
14304
|
+
}
|
|
14305
|
+
else {
|
|
14306
|
+
// get rid of undefined values in JSON
|
|
14307
|
+
// eslint-disable-next-line unicorn/prefer-structured-clone
|
|
14308
|
+
const existingFeature = JSON.parse(JSON.stringify(existing));
|
|
14309
|
+
if (!equal(featureSnapshot, existingFeature)) {
|
|
14310
|
+
await new Promise((resolve) => {
|
|
14311
|
+
session.queueDialog((doneCallback) => [
|
|
14312
|
+
DuplicateFeatureDialog,
|
|
14313
|
+
{
|
|
14314
|
+
featureSnapshot,
|
|
14315
|
+
existingFeature,
|
|
14316
|
+
assemblyName,
|
|
14317
|
+
changeManager: apolloDataStore.changeManager,
|
|
14318
|
+
handleClose: () => {
|
|
14319
|
+
doneCallback();
|
|
14320
|
+
resolve();
|
|
14321
|
+
},
|
|
14322
|
+
},
|
|
14323
|
+
]);
|
|
14324
|
+
});
|
|
14325
|
+
}
|
|
14326
|
+
}
|
|
14327
|
+
}
|
|
14328
|
+
}
|
|
14329
|
+
}
|
|
14330
|
+
|
|
13959
14331
|
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
13960
14332
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
13961
14333
|
function extendSession(pluginManager, sessionModel) {
|
|
@@ -14041,8 +14413,7 @@ function extendSession(pluginManager, sessionModel) {
|
|
|
14041
14413
|
for (const block of dynamicBlocks.contentBlocks) {
|
|
14042
14414
|
const { assemblyName, end, refName, start } = block;
|
|
14043
14415
|
const assembly = self.apolloDataStore.assemblies.get(assemblyName);
|
|
14044
|
-
if (assembly
|
|
14045
|
-
assembly.backendDriverType === 'CollaborationServerDriver') {
|
|
14416
|
+
if (assembly?.backendDriverType === 'CollaborationServerDriver') {
|
|
14046
14417
|
locations.push({ assemblyName, refName, start, end });
|
|
14047
14418
|
}
|
|
14048
14419
|
}
|
|
@@ -14101,8 +14472,8 @@ function extendSession(pluginManager, sessionModel) {
|
|
|
14101
14472
|
for (const block of dynamicBlocks.contentBlocks) {
|
|
14102
14473
|
const { assemblyName, end, refName, start } = block;
|
|
14103
14474
|
const assembly = self.apolloDataStore.assemblies.get(assemblyName);
|
|
14104
|
-
if (assembly
|
|
14105
|
-
|
|
14475
|
+
if (assembly?.backendDriverType ===
|
|
14476
|
+
'CollaborationServerDriver') {
|
|
14106
14477
|
locations.push({ assemblyName, refName, start, end });
|
|
14107
14478
|
}
|
|
14108
14479
|
}
|
|
@@ -14222,6 +14593,37 @@ function extendSession(pluginManager, sessionModel) {
|
|
|
14222
14593
|
beforeDestroy() {
|
|
14223
14594
|
self.abortController.abort(new DOMException('Clean up Apollo session', 'AbortError'));
|
|
14224
14595
|
},
|
|
14596
|
+
}))
|
|
14597
|
+
.actions((self) => ({
|
|
14598
|
+
async afterCreate() {
|
|
14599
|
+
if (isElectron) {
|
|
14600
|
+
return;
|
|
14601
|
+
}
|
|
14602
|
+
const url = new URL(globalThis.location.href);
|
|
14603
|
+
const apolloFeatures = url.searchParams.get('apolloFeatures');
|
|
14604
|
+
if (!apolloFeatures) {
|
|
14605
|
+
return;
|
|
14606
|
+
}
|
|
14607
|
+
const { assemblyManager } = self;
|
|
14608
|
+
await handleApolloFeaturesUrlParam(apolloFeatures, self.apolloDataStore, assemblyManager, self);
|
|
14609
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
14610
|
+
const updatedURL = new URL(globalThis.location.href);
|
|
14611
|
+
updatedURL.searchParams.delete('apolloFeatures');
|
|
14612
|
+
globalThis.history.replaceState(null, '', updatedURL.toString());
|
|
14613
|
+
},
|
|
14614
|
+
afterAttach() {
|
|
14615
|
+
addDisposer(self, autorun((reaction) => {
|
|
14616
|
+
const { focusedViewId, activeWidgets } = self;
|
|
14617
|
+
if (!(focusedViewId && activeWidgets)) {
|
|
14618
|
+
return;
|
|
14619
|
+
}
|
|
14620
|
+
const trackSelector = activeWidgets.get('hierarchicalTrackSelector');
|
|
14621
|
+
// @ts-expect-error Don't have type for track selector
|
|
14622
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
14623
|
+
trackSelector?.setView(focusedViewId);
|
|
14624
|
+
reaction.dispose();
|
|
14625
|
+
}));
|
|
14626
|
+
},
|
|
14225
14627
|
}))
|
|
14226
14628
|
.views((self) => {
|
|
14227
14629
|
const superTrackActions = self
|
|
@@ -14353,7 +14755,6 @@ function extendSession(pluginManager, sessionModel) {
|
|
|
14353
14755
|
}
|
|
14354
14756
|
|
|
14355
14757
|
/* eslint-disable @typescript-eslint/unbound-method */
|
|
14356
|
-
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
14357
14758
|
/* eslint-disable @typescript-eslint/no-misused-promises */
|
|
14358
14759
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
14359
14760
|
function isApolloMessageData(data) {
|
|
@@ -14541,10 +14942,10 @@ class ApolloPlugin extends Plugin {
|
|
|
14541
14942
|
});
|
|
14542
14943
|
if (!inWebWorker) {
|
|
14543
14944
|
pluginManager.addToExtensionPoint('Core-extendWorker', (handle) => {
|
|
14544
|
-
if (!('on' in handle && handle.on)) {
|
|
14945
|
+
if (!('on' in handle.client && handle.client.on)) {
|
|
14545
14946
|
return handle;
|
|
14546
14947
|
}
|
|
14547
|
-
handle.on('apollo', async (event) => {
|
|
14948
|
+
handle.client.on('apollo', async (event) => {
|
|
14548
14949
|
if (!isApolloMessageData(event)) {
|
|
14549
14950
|
return;
|
|
14550
14951
|
}
|
|
@@ -14562,7 +14963,7 @@ class ApolloPlugin extends Plugin {
|
|
|
14562
14963
|
break;
|
|
14563
14964
|
}
|
|
14564
14965
|
const { seq: sequence } = await backendDriver.getSequence(region);
|
|
14565
|
-
handle.
|
|
14966
|
+
handle.worker.postMessage({
|
|
14566
14967
|
apollo,
|
|
14567
14968
|
messageId,
|
|
14568
14969
|
sequence,
|
|
@@ -14580,7 +14981,7 @@ class ApolloPlugin extends Plugin {
|
|
|
14580
14981
|
break;
|
|
14581
14982
|
}
|
|
14582
14983
|
const regions = await backendDriver.getRegions(assembly);
|
|
14583
|
-
handle.
|
|
14984
|
+
handle.worker.postMessage({
|
|
14584
14985
|
apollo,
|
|
14585
14986
|
messageId,
|
|
14586
14987
|
regions,
|
|
@@ -14598,7 +14999,7 @@ class ApolloPlugin extends Plugin {
|
|
|
14598
14999
|
break;
|
|
14599
15000
|
}
|
|
14600
15001
|
const refNameAliases = await backendDriver.getRefNameAliases(assembly);
|
|
14601
|
-
handle.
|
|
15002
|
+
handle.worker.postMessage({
|
|
14602
15003
|
apollo,
|
|
14603
15004
|
messageId,
|
|
14604
15005
|
refNameAliases,
|