@apollo-annotation/jbrowse-plugin-apollo 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ApolloInternetAccount/components/AuthTypeSelector.d.ts +8 -2
- package/dist/ApolloInternetAccount/components/AuthTypeSelector.d.ts.map +1 -1
- package/dist/ApolloInternetAccount/components/LoginButtons.d.ts +8 -3
- package/dist/ApolloInternetAccount/components/LoginButtons.d.ts.map +1 -1
- package/dist/ApolloInternetAccount/configSchema.d.ts +44 -0
- package/dist/ApolloInternetAccount/configSchema.d.ts.map +1 -0
- package/dist/ApolloInternetAccount/model.d.ts +174 -0
- package/dist/ApolloInternetAccount/model.d.ts.map +1 -0
- package/dist/ApolloJobModel.d.ts.map +1 -1
- package/dist/ApolloRefNameAliasAdapter/configSchema.d.ts +8 -0
- package/dist/ApolloRefNameAliasAdapter/configSchema.d.ts.map +1 -0
- package/dist/ApolloSequenceAdapter/configSchema.d.ts +8 -0
- package/dist/ApolloSequenceAdapter/configSchema.d.ts.map +1 -0
- package/dist/ApolloTextSearchAdapter/configSchema.d.ts +20 -0
- package/dist/ApolloTextSearchAdapter/configSchema.d.ts.map +1 -0
- package/dist/BackendDrivers/CollaborationServerDriver.d.ts +1 -1
- package/dist/BackendDrivers/CollaborationServerDriver.d.ts.map +1 -1
- package/dist/BackendDrivers/LocalDriver/LocalDriver.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/ApolloFeatureDetailsWidget.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/Attributes.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/DefaultAttributeEditor.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/DefaultAttributeViewer.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/NumberTextField.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/StringTextField.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/TranscriptSequence.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/TranscriptWidgetEditLocation.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/Translation.d.ts +14 -0
- package/dist/FeatureDetailsWidget/Translation.d.ts.map +1 -0
- package/dist/LinearApolloDisplay/components/CheckResultWarnings.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/configSchema.d.ts +2 -0
- package/dist/LinearApolloDisplay/configSchema.d.ts.map +1 -0
- package/dist/LinearApolloDisplay/glyphs/ExonGlyph.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/glyphs/GeneGlyph.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/glyphs/TranscriptGlyph.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/stateModel/base.d.ts +1 -18
- package/dist/LinearApolloDisplay/stateModel/base.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/stateModel/index.d.ts +1 -17
- package/dist/LinearApolloDisplay/stateModel/index.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/stateModel/layouts.d.ts +2 -19
- package/dist/LinearApolloDisplay/stateModel/layouts.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/stateModel/mouseEvents.d.ts +2 -34
- package/dist/LinearApolloDisplay/stateModel/mouseEvents.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/stateModel/rendering.d.ts +2 -19
- package/dist/LinearApolloDisplay/stateModel/rendering.d.ts.map +1 -1
- package/dist/LinearApolloReferenceSequenceDisplay/configSchema.d.ts +2 -0
- package/dist/LinearApolloReferenceSequenceDisplay/configSchema.d.ts.map +1 -0
- package/dist/LinearApolloReferenceSequenceDisplay/stateModel/base.d.ts +1 -1
- package/dist/LinearApolloReferenceSequenceDisplay/stateModel/base.d.ts.map +1 -1
- package/dist/LinearApolloReferenceSequenceDisplay/stateModel/index.d.ts +1 -1
- package/dist/LinearApolloReferenceSequenceDisplay/stateModel/rendering.d.ts +1 -1
- package/dist/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.d.ts.map +1 -1
- package/dist/LinearApolloSixFrameDisplay/configSchema.d.ts +2 -0
- package/dist/LinearApolloSixFrameDisplay/configSchema.d.ts.map +1 -0
- package/dist/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.d.ts.map +1 -1
- package/dist/LinearApolloSixFrameDisplay/stateModel/base.d.ts +1 -1
- package/dist/LinearApolloSixFrameDisplay/stateModel/base.d.ts.map +1 -1
- package/dist/LinearApolloSixFrameDisplay/stateModel/index.d.ts +1 -1
- package/dist/LinearApolloSixFrameDisplay/stateModel/index.d.ts.map +1 -1
- package/dist/LinearApolloSixFrameDisplay/stateModel/layouts.d.ts +2 -3
- package/dist/LinearApolloSixFrameDisplay/stateModel/layouts.d.ts.map +1 -1
- package/dist/LinearApolloSixFrameDisplay/stateModel/mouseEvents.d.ts +2 -2
- package/dist/LinearApolloSixFrameDisplay/stateModel/mouseEvents.d.ts.map +1 -1
- package/dist/LinearApolloSixFrameDisplay/stateModel/rendering.d.ts +2 -3
- package/dist/LinearApolloSixFrameDisplay/stateModel/rendering.d.ts.map +1 -1
- package/dist/OntologyManager/OntologyStore/fulltext.d.ts.map +1 -1
- package/dist/OntologyManager/index.d.ts +734 -0
- package/dist/OntologyManager/index.d.ts.map +1 -0
- package/dist/TabularEditor/HybridGrid/NumberCell.d.ts.map +1 -1
- package/dist/components/AddCodingSequence.d.ts +14 -0
- package/dist/components/AddCodingSequence.d.ts.map +1 -0
- package/dist/components/AddRefSeqAliases.d.ts.map +1 -1
- package/dist/components/CopyFeature.d.ts.map +1 -1
- package/dist/components/CreateApolloAnnotation.d.ts.map +1 -1
- package/dist/components/DeleteFeature.d.ts.map +1 -1
- package/dist/components/DownloadGFF3.d.ts.map +1 -1
- package/dist/components/DuplicateFeatureDialog.d.ts +12 -0
- package/dist/components/DuplicateFeatureDialog.d.ts.map +1 -0
- package/dist/components/ImportAnnotations.d.ts +9 -0
- package/dist/components/ImportAnnotations.d.ts.map +1 -0
- package/dist/components/ManageUsers.d.ts.map +1 -1
- package/dist/components/OntologyTermMultiSelect.d.ts.map +1 -1
- package/dist/components/ViewChangeLog.d.ts.map +1 -1
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/extensions/annotationFromPileup.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +830 -434
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +825 -429
- package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.development.js +863 -468
- package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
- package/dist/session/ClientDataStore.d.ts +2194 -0
- package/dist/session/ClientDataStore.d.ts.map +1 -0
- package/dist/session/handleApolloFeaturesUrlParam.d.ts +8 -0
- package/dist/session/handleApolloFeaturesUrlParam.d.ts.map +1 -0
- package/dist/session/session.d.ts +6788 -0
- package/dist/session/session.d.ts.map +1 -0
- package/dist/util/displayUtils.d.ts +36 -0
- package/dist/util/displayUtils.d.ts.map +1 -0
- package/dist/util/sequenceUtils.d.ts +2 -0
- package/dist/util/sequenceUtils.d.ts.map +1 -0
- package/package.json +14 -14
- package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +73 -38
- package/src/ApolloInternetAccount/components/LoginButtons.tsx +31 -10
- package/src/ApolloInternetAccount/model.ts +8 -5
- package/src/ApolloJobModel.ts +3 -8
- package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +1 -1
- package/src/BackendDrivers/CollaborationServerDriver.ts +7 -11
- package/src/BackendDrivers/LocalDriver/LocalDriver.ts +13 -4
- package/src/FeatureDetailsWidget/ApolloFeatureDetailsWidget.tsx +1 -0
- package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +4 -2
- package/src/FeatureDetailsWidget/Attributes.tsx +1 -0
- package/src/FeatureDetailsWidget/DefaultAttributeEditor.tsx +1 -0
- package/src/FeatureDetailsWidget/DefaultAttributeViewer.tsx +1 -0
- package/src/FeatureDetailsWidget/NumberTextField.tsx +1 -0
- package/src/FeatureDetailsWidget/StringTextField.tsx +1 -0
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +2 -1
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +17 -354
- package/src/FeatureDetailsWidget/Translation.tsx +358 -0
- package/src/LinearApolloDisplay/components/CheckResultWarnings.tsx +4 -1
- package/src/LinearApolloDisplay/glyphs/ExonGlyph.ts +2 -5
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +4 -6
- package/src/LinearApolloDisplay/glyphs/TranscriptGlyph.ts +39 -6
- package/src/LinearApolloDisplay/stateModel/base.ts +47 -23
- package/src/LinearApolloDisplay/stateModel/layouts.ts +7 -5
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +4 -1
- package/src/LinearApolloDisplay/stateModel/rendering.ts +1 -5
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +10 -10
- package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +5 -3
- package/src/LinearApolloSixFrameDisplay/components/TrackLines.tsx +1 -1
- package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +2 -3
- package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +12 -20
- package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +10 -6
- package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +38 -45
- package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +1 -4
- package/src/OntologyManager/OntologyStore/fulltext.ts +0 -2
- package/src/OntologyManager/OntologyStore/index.ts +1 -1
- package/src/TabularEditor/HybridGrid/ChangeHandling.ts +2 -2
- package/src/TabularEditor/HybridGrid/NumberCell.tsx +2 -1
- package/src/components/AddAssemblyAliases.tsx +1 -1
- package/src/components/AddCodingSequence.tsx +255 -0
- package/src/components/AddFeature.tsx +1 -1
- package/src/components/AddRefSeqAliases.tsx +3 -2
- package/src/components/CopyFeature.tsx +9 -7
- package/src/components/CreateApolloAnnotation.tsx +28 -23
- package/src/components/DeleteAssembly.tsx +2 -2
- package/src/components/DeleteFeature.tsx +1 -4
- package/src/components/DownloadGFF3.tsx +4 -3
- package/src/components/DuplicateFeatureDialog.tsx +154 -0
- package/src/components/DuplicateTranscript.tsx +1 -1
- package/src/components/FilterTranscripts.tsx +1 -1
- package/src/components/ImportAnnotations.tsx +108 -0
- package/src/components/ManageUsers.tsx +1 -2
- package/src/components/OntologyTermAutocomplete.tsx +1 -3
- package/src/components/OntologyTermMultiSelect.tsx +3 -1
- package/src/components/ViewChangeLog.tsx +2 -1
- package/src/components/index.ts +2 -0
- package/src/extensions/annotationFromPileup.ts +34 -26
- package/src/index.ts +9 -8
- package/src/session/ClientDataStore.ts +16 -21
- package/src/session/handleApolloFeaturesUrlParam.ts +122 -0
- package/src/session/session.ts +49 -9
- package/src/util/annotationFeatureUtils.ts +1 -1
- package/src/util/displayUtils.ts +1 -1
- package/src/util/glyphUtils.ts +3 -3
- package/src/util/sequenceUtils.ts +29 -0
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.0";
|
|
69
70
|
|
|
70
71
|
const ApolloConfigSchema = ConfigurationSchema('ApolloInternetAccount', {
|
|
71
72
|
baseURL: {
|
|
@@ -1368,9 +1369,7 @@ async function getValidTerms(ontologyStore, fetchValidTerms, filterTerms, signal
|
|
|
1368
1369
|
result = customTermList;
|
|
1369
1370
|
}
|
|
1370
1371
|
}
|
|
1371
|
-
|
|
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
|
}
|
|
@@ -2869,7 +3014,10 @@ class LocalDriver extends BackendDriver {
|
|
|
2869
3014
|
const adapter = new CLASS(
|
|
2870
3015
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
2871
3016
|
adapterConf, undefined, pluginManager);
|
|
2872
|
-
const seq = await adapter.getSequence(
|
|
3017
|
+
const seq = await adapter.getSequence({
|
|
3018
|
+
...region,
|
|
3019
|
+
refName: assembly.getSeqAdapterRefName(region.refName),
|
|
3020
|
+
});
|
|
2873
3021
|
if (!seq) {
|
|
2874
3022
|
throw new Error(`Sequence not found: ${JSON.stringify(region)}`);
|
|
2875
3023
|
}
|
|
@@ -2878,7 +3026,7 @@ class LocalDriver extends BackendDriver {
|
|
|
2878
3026
|
async getRegions(assemblyName) {
|
|
2879
3027
|
const session = getSession(this.clientStore);
|
|
2880
3028
|
const { assemblyManager } = session;
|
|
2881
|
-
const assembly = await assemblyManager.waitForAssembly(assemblyName);
|
|
3029
|
+
const assembly = (await assemblyManager.waitForAssembly(assemblyName));
|
|
2882
3030
|
if (!assembly) {
|
|
2883
3031
|
throw new Error(`Assembly not found: "${assemblyName}"`);
|
|
2884
3032
|
}
|
|
@@ -2894,7 +3042,7 @@ class LocalDriver extends BackendDriver {
|
|
|
2894
3042
|
async getRefNameAliases(assemblyName) {
|
|
2895
3043
|
const session = getSession(this.clientStore);
|
|
2896
3044
|
const { assemblyManager } = session;
|
|
2897
|
-
const assembly = await assemblyManager.waitForAssembly(assemblyName);
|
|
3045
|
+
const assembly = (await assemblyManager.waitForAssembly(assemblyName));
|
|
2898
3046
|
if (!assembly) {
|
|
2899
3047
|
throw new Error(`Assembly not found: "${assemblyName}"`);
|
|
2900
3048
|
}
|
|
@@ -3553,7 +3701,7 @@ class CollaborationServerDriver extends BackendDriver {
|
|
|
3553
3701
|
const localSessionId = makeUserSessionId(token);
|
|
3554
3702
|
const changeManager = new ChangeManager(this.clientStore);
|
|
3555
3703
|
// Save server last change sequence into session storage
|
|
3556
|
-
internetAccount.setLastChangeSequenceNumber(
|
|
3704
|
+
internetAccount.setLastChangeSequenceNumber(message.changeSequence);
|
|
3557
3705
|
if (message.userSessionId === localSessionId) {
|
|
3558
3706
|
return; // we did this change, no need to apply it again
|
|
3559
3707
|
}
|
|
@@ -3601,14 +3749,10 @@ class CollaborationServerDriver extends BackendDriver {
|
|
|
3601
3749
|
const seq = await inFlightPromise;
|
|
3602
3750
|
return { seq, refSeq };
|
|
3603
3751
|
}
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
let apolloRefSeq = apolloAssembly.refSeqs.get(refSeq);
|
|
3609
|
-
if (!apolloRefSeq) {
|
|
3610
|
-
apolloRefSeq = apolloAssembly.addRefSeq(refSeq, refName);
|
|
3611
|
-
}
|
|
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);
|
|
3612
3756
|
const clientStoreSequence = apolloRefSeq.getSequence(start, end);
|
|
3613
3757
|
if (clientStoreSequence.length === end - start) {
|
|
3614
3758
|
return { seq: clientStoreSequence, refSeq };
|
|
@@ -3965,7 +4109,7 @@ function AddFeature({ changeManager, handleClose, region, session, }) {
|
|
|
3965
4109
|
setType(NewFeature[value]);
|
|
3966
4110
|
}
|
|
3967
4111
|
};
|
|
3968
|
-
let submitDisabled =
|
|
4112
|
+
let submitDisabled = error || !(start && end && type);
|
|
3969
4113
|
if ((type === NewFeature.CUSTOM && !customType) ||
|
|
3970
4114
|
(!strand && type === NewFeature.GENE_AND_SUBFEATURES) ||
|
|
3971
4115
|
(!strand && type === NewFeature.TRANSCRIPT_AND_SUBFEATURES)) {
|
|
@@ -3986,7 +4130,7 @@ function AddFeature({ changeManager, handleClose, region, session, }) {
|
|
|
3986
4130
|
function DeleteAssembly({ changeManager, handleClose, session, }) {
|
|
3987
4131
|
const { internetAccounts } = getRoot(session);
|
|
3988
4132
|
const [errorMessage, setErrorMessage] = useState('');
|
|
3989
|
-
const [confirmDelete,
|
|
4133
|
+
const [confirmDelete, setConfirmDelete] = useState(false);
|
|
3990
4134
|
const [submitted, setSubmitted] = useState(false);
|
|
3991
4135
|
const apolloInternetAccounts = internetAccounts.filter((ia) => ia.type === 'ApolloInternetAccount');
|
|
3992
4136
|
if (apolloInternetAccounts.length === 0) {
|
|
@@ -4027,7 +4171,7 @@ function DeleteAssembly({ changeManager, handleClose, session, }) {
|
|
|
4027
4171
|
event.preventDefault();
|
|
4028
4172
|
}
|
|
4029
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: () => {
|
|
4030
|
-
|
|
4174
|
+
setConfirmDelete(!confirmDelete);
|
|
4031
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] }));
|
|
4032
4176
|
}
|
|
4033
4177
|
|
|
@@ -4036,7 +4180,7 @@ function Export(props) {
|
|
|
4036
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" }) }));
|
|
4037
4181
|
}
|
|
4038
4182
|
function DownloadGFF3({ handleClose, session, assembly: assemblyName, }) {
|
|
4039
|
-
const [includeFASTA,
|
|
4183
|
+
const [includeFASTA, setIncludeFASTA] = useState(false);
|
|
4040
4184
|
const [errorMessage, setErrorMessage] = useState('');
|
|
4041
4185
|
const { getInternetAccount } = session.apolloDataStore;
|
|
4042
4186
|
const { assemblyManager } = session;
|
|
@@ -4085,7 +4229,7 @@ function DownloadGFF3({ handleClose, session, assembly: assemblyName, }) {
|
|
|
4085
4229
|
window.open(exportUri, '_blank');
|
|
4086
4230
|
}
|
|
4087
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: () => {
|
|
4088
|
-
|
|
4232
|
+
setIncludeFASTA(!includeFASTA);
|
|
4089
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] }));
|
|
4090
4234
|
}
|
|
4091
4235
|
function getAssemblyGFF3Stream(assemblyName) {
|
|
@@ -4124,6 +4268,49 @@ async function* getFeaturesForAssembly(assemblyName) {
|
|
|
4124
4268
|
db.close();
|
|
4125
4269
|
}
|
|
4126
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
|
+
|
|
4127
4314
|
function ImportFeatures({ changeManager, handleClose, session, }) {
|
|
4128
4315
|
const { apolloDataStore } = session;
|
|
4129
4316
|
const [file, setFile] = useState();
|
|
@@ -4527,7 +4714,7 @@ function ManageUsers({ changeManager, handleClose, session, }) {
|
|
|
4527
4714
|
});
|
|
4528
4715
|
return newRow;
|
|
4529
4716
|
}
|
|
4530
|
-
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) => {
|
|
4531
4718
|
setErrorMessage(String(error));
|
|
4532
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] }));
|
|
4533
4720
|
}
|
|
@@ -4837,12 +5024,13 @@ const AddRefSeqAliases = observer(function AddRefSeqAliases({ changeManager, han
|
|
|
4837
5024
|
const [enableSubmit, setEnableSubmit] = useState(false);
|
|
4838
5025
|
const [selectedAssembly, setSelectedAssembly] = useState();
|
|
4839
5026
|
const [selectedRows, setSelectedRows] = useState([]);
|
|
4840
|
-
const [refNameAliasMap, setRefNameAliasMap] = useState(new Map());
|
|
5027
|
+
const [refNameAliasMap, setRefNameAliasMap] = useState(() => new Map());
|
|
4841
5028
|
const { apolloDataStore } = session;
|
|
4842
5029
|
const { collaborationServerDriver } = apolloDataStore;
|
|
4843
5030
|
const assemblies = collaborationServerDriver.getAssemblies();
|
|
4844
5031
|
useEffect(() => {
|
|
4845
5032
|
if (assemblies.length > 0) {
|
|
5033
|
+
// eslint-disable-next-line @eslint-react/set-state-in-effect
|
|
4846
5034
|
setSelectedAssembly(assemblies[0]);
|
|
4847
5035
|
collaborationServerDriver
|
|
4848
5036
|
.getRefNameAliases(assemblies[0].name)
|
|
@@ -4854,7 +5042,7 @@ const AddRefSeqAliases = observer(function AddRefSeqAliases({ changeManager, han
|
|
|
4854
5042
|
setErrorMessage('Error fetching refName aliases for assembly');
|
|
4855
5043
|
});
|
|
4856
5044
|
}
|
|
4857
|
-
// eslint-disable-next-line react
|
|
5045
|
+
// eslint-disable-next-line @eslint-react/exhaustive-deps
|
|
4858
5046
|
}, []);
|
|
4859
5047
|
const initializeRefNameAliasMap = (refNameAliasesList) => {
|
|
4860
5048
|
const initialMap = new Map();
|
|
@@ -5259,17 +5447,23 @@ const useStyles$d = makeStyles()((theme) => ({
|
|
|
5259
5447
|
}));
|
|
5260
5448
|
function GoogleButton(props) {
|
|
5261
5449
|
const { classes } = useStyles$d();
|
|
5262
|
-
const {
|
|
5263
|
-
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 }));
|
|
5264
5452
|
}
|
|
5265
5453
|
function MicrosoftButton(props) {
|
|
5266
5454
|
const { classes } = useStyles$d();
|
|
5267
|
-
const {
|
|
5268
|
-
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 }));
|
|
5269
5457
|
}
|
|
5270
5458
|
function GuestButton(props) {
|
|
5271
5459
|
const { classes } = useStyles$d();
|
|
5272
|
-
|
|
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 }));
|
|
5273
5467
|
}
|
|
5274
5468
|
|
|
5275
5469
|
const useStyles$c = makeStyles()((theme) => ({
|
|
@@ -5281,7 +5475,7 @@ const useStyles$c = makeStyles()((theme) => ({
|
|
|
5281
5475
|
const AuthTypeSelector = ({ baseURL, handleClose, name, }) => {
|
|
5282
5476
|
const { classes } = useStyles$c();
|
|
5283
5477
|
const [errorMessage, setErrorMessage] = useState('');
|
|
5284
|
-
const [loginTypes, setLoginTypes] = useState(
|
|
5478
|
+
const [loginTypes, setLoginTypes] = useState(null);
|
|
5285
5479
|
useEffect(() => {
|
|
5286
5480
|
const controller = new AbortController();
|
|
5287
5481
|
const { signal } = controller;
|
|
@@ -5295,6 +5489,9 @@ const AuthTypeSelector = ({ baseURL, handleClose, name, }) => {
|
|
|
5295
5489
|
}
|
|
5296
5490
|
const data = (await response.json());
|
|
5297
5491
|
setLoginTypes(data);
|
|
5492
|
+
if (data.length === 0) {
|
|
5493
|
+
setErrorMessage('No login types configured');
|
|
5494
|
+
}
|
|
5298
5495
|
}
|
|
5299
5496
|
getAuthTypes().catch((error) => {
|
|
5300
5497
|
if (!isAbortException(error)) {
|
|
@@ -5306,26 +5503,35 @@ const AuthTypeSelector = ({ baseURL, handleClose, name, }) => {
|
|
|
5306
5503
|
};
|
|
5307
5504
|
}, [baseURL]);
|
|
5308
5505
|
function handleClick(authType) {
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
}
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
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: () => {
|
|
5329
5535
|
handleClose();
|
|
5330
5536
|
}, children: "Cancel" }) }), errorMessage ? (jsx(DialogContent, { children: jsx(DialogContentText, { color: "error", children: errorMessage }) })) : null] }));
|
|
5331
5537
|
};
|
|
@@ -5521,13 +5727,13 @@ const stateModelFactory$3 = (configSchema) => {
|
|
|
5521
5727
|
},
|
|
5522
5728
|
]);
|
|
5523
5729
|
});
|
|
5524
|
-
if (authType
|
|
5730
|
+
if (authType.needsPopup) {
|
|
5525
5731
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
5526
|
-
self.openAuthWindow(authType, resolve, reject);
|
|
5732
|
+
self.openAuthWindow(authType.name, resolve, reject);
|
|
5527
5733
|
return;
|
|
5528
5734
|
}
|
|
5529
5735
|
const url = new URL('auth/login', baseURL);
|
|
5530
|
-
const searchParams = new URLSearchParams({ type: authType });
|
|
5736
|
+
const searchParams = new URLSearchParams({ type: authType.name });
|
|
5531
5737
|
url.search = searchParams.toString();
|
|
5532
5738
|
const uri = url.toString();
|
|
5533
5739
|
const response = await fetch(uri, { signal: self.controller.signal });
|
|
@@ -5648,7 +5854,7 @@ const stateModelFactory$3 = (configSchema) => {
|
|
|
5648
5854
|
socket.on('COMMON', (message) => {
|
|
5649
5855
|
if ('checkResult' in message) {
|
|
5650
5856
|
if (message.deleted) {
|
|
5651
|
-
deleteCheckResult(message.checkResult._id
|
|
5857
|
+
deleteCheckResult(message.checkResult._id);
|
|
5652
5858
|
}
|
|
5653
5859
|
else {
|
|
5654
5860
|
addCheckResult(message.checkResult);
|
|
@@ -6268,7 +6474,9 @@ const DefaultAttributeEditor = observer(function DefaultAttributeEditor({ attrib
|
|
|
6268
6474
|
return newValues;
|
|
6269
6475
|
});
|
|
6270
6476
|
}
|
|
6271
|
-
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) => {
|
|
6272
6480
|
updateValue(idx, editedValue);
|
|
6273
6481
|
}, variant: "outlined", fullWidth: true }), jsx(IconButton, { "aria-label": "delete", size: "medium", edge: "end", onClick: () => {
|
|
6274
6482
|
deleteValue(idx);
|
|
@@ -6280,7 +6488,9 @@ const DefaultAttributeEditor = observer(function DefaultAttributeEditor({ attrib
|
|
|
6280
6488
|
});
|
|
6281
6489
|
|
|
6282
6490
|
function DefaultAttributeViewer({ values }) {
|
|
6283
|
-
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}`))) }));
|
|
6284
6494
|
}
|
|
6285
6495
|
|
|
6286
6496
|
const useStyles$a = makeStyles()((theme) => ({
|
|
@@ -6586,6 +6796,7 @@ const ApolloFeatureDetailsWidget = observer(function ApolloFeatureDetailsWidget(
|
|
|
6586
6796
|
const { classes } = useStyles$8();
|
|
6587
6797
|
const [panelState, setPanelState] = useState(['attributes']);
|
|
6588
6798
|
useEffect(() => {
|
|
6799
|
+
// eslint-disable-next-line @eslint-react/set-state-in-effect
|
|
6589
6800
|
setPanelState(['attributes']);
|
|
6590
6801
|
}, [feature]);
|
|
6591
6802
|
if (!(feature && currentAssembly)) {
|
|
@@ -6887,7 +7098,7 @@ const TranscriptSequence = observer(function TranscriptSequence({ assembly, feat
|
|
|
6887
7098
|
else {
|
|
6888
7099
|
setSequenceOptions(defaultSequenceOptions);
|
|
6889
7100
|
}
|
|
6890
|
-
// eslint-disable-next-line react
|
|
7101
|
+
// eslint-disable-next-line @eslint-react/exhaustive-deps
|
|
6891
7102
|
}, [feature]);
|
|
6892
7103
|
if (!(currentAssembly && refData)) {
|
|
6893
7104
|
return null;
|
|
@@ -6950,19 +7161,6 @@ const TranscriptSequence = observer(function TranscriptSequence({ assembly, feat
|
|
|
6950
7161
|
.join(';'), "(strand=", feature.strand === 1 ? '+' : '-', ";length=", getSequenceLength(sequenceSegments), ")", jsx("br", {}), wrapSequence(sequenceSegments, SEQUENCE_WRAP_LENGTH)] })] }));
|
|
6951
7162
|
});
|
|
6952
7163
|
|
|
6953
|
-
const StyledTextField = styled(NumberTextField)(() => ({
|
|
6954
|
-
'&.MuiFormControl-root': {
|
|
6955
|
-
marginTop: 0,
|
|
6956
|
-
marginBottom: 0,
|
|
6957
|
-
width: '100%',
|
|
6958
|
-
},
|
|
6959
|
-
'& .MuiInputBase-input': {
|
|
6960
|
-
fontSize: 12,
|
|
6961
|
-
height: 20,
|
|
6962
|
-
padding: 1,
|
|
6963
|
-
paddingLeft: 10,
|
|
6964
|
-
},
|
|
6965
|
-
}));
|
|
6966
7164
|
const SequenceContainer = styled('div')({
|
|
6967
7165
|
display: 'flex',
|
|
6968
7166
|
justifyContent: 'center',
|
|
@@ -6984,6 +7182,187 @@ const StyledAccordionSummary$1 = styled(AccordionSummary)(() => ({
|
|
|
6984
7182
|
maxHeight: 30,
|
|
6985
7183
|
},
|
|
6986
7184
|
}));
|
|
7185
|
+
function Translation({ changeInProgress, cdsLocations, refData, strand,
|
|
7186
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
7187
|
+
updateCDSLocation, cdsMin, cdsMax, feature, session, }) {
|
|
7188
|
+
const seqRef = useRef(null);
|
|
7189
|
+
const { notify } = session;
|
|
7190
|
+
const cdsSequences = [];
|
|
7191
|
+
const [firstLocation] = cdsLocations;
|
|
7192
|
+
for (const loc of firstLocation) {
|
|
7193
|
+
const seq = refData.getSequence(loc.min, loc.max);
|
|
7194
|
+
cdsSequences.push(strand === -1 ? revcom(seq) : seq);
|
|
7195
|
+
}
|
|
7196
|
+
const cdsSequence = cdsSequences.join('');
|
|
7197
|
+
const proteinSequence = [];
|
|
7198
|
+
for (let codonGenomicPos = 0; codonGenomicPos < cdsSequence.length; codonGenomicPos += 3) {
|
|
7199
|
+
const codonSeq = cdsSequence
|
|
7200
|
+
.slice(codonGenomicPos, codonGenomicPos + 3)
|
|
7201
|
+
.toUpperCase();
|
|
7202
|
+
const protein = defaultCodonTable[codonSeq] || '&';
|
|
7203
|
+
proteinSequence.push(protein);
|
|
7204
|
+
}
|
|
7205
|
+
const onCopyClick = () => {
|
|
7206
|
+
const seqDiv = seqRef.current;
|
|
7207
|
+
if (!seqDiv) {
|
|
7208
|
+
return;
|
|
7209
|
+
}
|
|
7210
|
+
void copyToClipboard(seqDiv);
|
|
7211
|
+
};
|
|
7212
|
+
// Codon position is the index of the start codon in the CDS genomic sequence
|
|
7213
|
+
// Calculate the genomic location of the start codon based on the codon position in the CDS
|
|
7214
|
+
const getCodonGenomicLocation = (codonGenomicPosition) => {
|
|
7215
|
+
const [firstLocation] = cdsLocations;
|
|
7216
|
+
let cdsLen = 0;
|
|
7217
|
+
const sortedCDSLocations = firstLocation.toSorted(({ min: a }, { min: b }) => a - b);
|
|
7218
|
+
// Suppose CDS locations are [{min: 0, max: 10}, {min: 20, max: 30}, {min: 40, max: 50}]
|
|
7219
|
+
// and codonGenomicPosition is 25
|
|
7220
|
+
// ((10 - 0) + (30 - 20) + (50 - 40)) > 25
|
|
7221
|
+
// So, start codon is in (40, 50)
|
|
7222
|
+
// 40 + (25-20) = 45 is the genomic location of the start codon
|
|
7223
|
+
if (strand === 1) {
|
|
7224
|
+
for (const loc of sortedCDSLocations) {
|
|
7225
|
+
const locLength = loc.max - loc.min;
|
|
7226
|
+
if (cdsLen + locLength > codonGenomicPosition) {
|
|
7227
|
+
return loc.min + (codonGenomicPosition - cdsLen);
|
|
7228
|
+
}
|
|
7229
|
+
cdsLen += locLength;
|
|
7230
|
+
}
|
|
7231
|
+
}
|
|
7232
|
+
else if (strand === -1) {
|
|
7233
|
+
for (let i = sortedCDSLocations.length - 1; i >= 0; i--) {
|
|
7234
|
+
const loc = sortedCDSLocations[i];
|
|
7235
|
+
const locLength = loc.max - loc.min;
|
|
7236
|
+
if (cdsLen + locLength > codonGenomicPosition) {
|
|
7237
|
+
return loc.max - (codonGenomicPosition - cdsLen);
|
|
7238
|
+
}
|
|
7239
|
+
cdsLen += locLength;
|
|
7240
|
+
}
|
|
7241
|
+
}
|
|
7242
|
+
if (strand === 1) {
|
|
7243
|
+
return cdsMin;
|
|
7244
|
+
}
|
|
7245
|
+
return cdsMax;
|
|
7246
|
+
};
|
|
7247
|
+
const trimTranslationSequence = () => {
|
|
7248
|
+
if ((proteinSequence.at(0) === 'M' && proteinSequence.at(-1) === '*') ||
|
|
7249
|
+
proteinSequence.length === 0) {
|
|
7250
|
+
return;
|
|
7251
|
+
}
|
|
7252
|
+
// Trim any sequence before first start codon and after stop codon
|
|
7253
|
+
const startCodonIndex = proteinSequence.indexOf('M');
|
|
7254
|
+
const stopCodonIndex = proteinSequence.lastIndexOf('*');
|
|
7255
|
+
const startCodonPos = startCodonIndex * 3;
|
|
7256
|
+
const stopCodonPos = stopCodonIndex * 3;
|
|
7257
|
+
const startCodonGenomicLoc = getCodonGenomicLocation(startCodonPos);
|
|
7258
|
+
let stopCodonGenomicLoc = getCodonGenomicLocation(stopCodonPos);
|
|
7259
|
+
if (strand === 1) {
|
|
7260
|
+
if (startCodonGenomicLoc > stopCodonGenomicLoc) {
|
|
7261
|
+
notify('Start codon genomic location should be less than stop codon genomic location', 'error');
|
|
7262
|
+
return;
|
|
7263
|
+
}
|
|
7264
|
+
let promise;
|
|
7265
|
+
stopCodonGenomicLoc += 3; // move to end of stop codon
|
|
7266
|
+
if (startCodonGenomicLoc !== cdsMin) {
|
|
7267
|
+
promise = new Promise((resolve) => {
|
|
7268
|
+
updateCDSLocation(cdsMin, startCodonGenomicLoc, feature, true, () => {
|
|
7269
|
+
resolve(true);
|
|
7270
|
+
});
|
|
7271
|
+
});
|
|
7272
|
+
}
|
|
7273
|
+
if (stopCodonGenomicLoc !== cdsMax) {
|
|
7274
|
+
if (promise) {
|
|
7275
|
+
void promise.then(() => {
|
|
7276
|
+
updateCDSLocation(cdsMax, stopCodonGenomicLoc, feature, false);
|
|
7277
|
+
});
|
|
7278
|
+
}
|
|
7279
|
+
else {
|
|
7280
|
+
updateCDSLocation(cdsMax, stopCodonGenomicLoc, feature, false);
|
|
7281
|
+
}
|
|
7282
|
+
}
|
|
7283
|
+
}
|
|
7284
|
+
if (strand === -1) {
|
|
7285
|
+
// reverse strand
|
|
7286
|
+
if (startCodonGenomicLoc < stopCodonGenomicLoc) {
|
|
7287
|
+
notify('Start codon genomic location should be less than stop codon genomic location', 'error');
|
|
7288
|
+
return;
|
|
7289
|
+
}
|
|
7290
|
+
let promise;
|
|
7291
|
+
stopCodonGenomicLoc -= 3; // move to end of stop codon
|
|
7292
|
+
if (startCodonGenomicLoc !== cdsMax) {
|
|
7293
|
+
promise = new Promise((resolve) => {
|
|
7294
|
+
updateCDSLocation(cdsMax, startCodonGenomicLoc, feature, false, () => {
|
|
7295
|
+
resolve(true);
|
|
7296
|
+
});
|
|
7297
|
+
});
|
|
7298
|
+
}
|
|
7299
|
+
if (stopCodonGenomicLoc !== cdsMin) {
|
|
7300
|
+
if (promise) {
|
|
7301
|
+
void promise.then(() => {
|
|
7302
|
+
updateCDSLocation(cdsMin, stopCodonGenomicLoc, feature, true);
|
|
7303
|
+
});
|
|
7304
|
+
}
|
|
7305
|
+
else {
|
|
7306
|
+
updateCDSLocation(cdsMin, stopCodonGenomicLoc, feature, true);
|
|
7307
|
+
}
|
|
7308
|
+
}
|
|
7309
|
+
}
|
|
7310
|
+
notify('Translation sequence trimmed to start and stop codons', 'success');
|
|
7311
|
+
};
|
|
7312
|
+
return (jsx("div", { children: jsxs(Accordion, { children: [jsx(StyledAccordionSummary$1, { expandIcon: jsx(ExpandMoreIcon, { style: { color: 'white' } }), "aria-controls": "panel1-content", id: "panel1-header", children: jsx(Typography, { component: "span", fontWeight: 'bold', children: "Translation" }) }), jsxs(AccordionDetails, { children: [jsx(SequenceContainer, { children: jsx(Typography, { component: 'span', ref: seqRef, style: { maxHeight: 120, overflowY: 'scroll' }, children: proteinSequence.map((protein, idx) => {
|
|
7313
|
+
const codonGenomicPos = idx * 3;
|
|
7314
|
+
if (protein === 'M') {
|
|
7315
|
+
return (jsx(Typography, { component: 'span', style: {
|
|
7316
|
+
backgroundColor: changeInProgress
|
|
7317
|
+
? 'lightgray'
|
|
7318
|
+
: 'yellow',
|
|
7319
|
+
cursor: 'pointer',
|
|
7320
|
+
border: '1px solid black',
|
|
7321
|
+
}, onClick: () => {
|
|
7322
|
+
if (changeInProgress) {
|
|
7323
|
+
return;
|
|
7324
|
+
}
|
|
7325
|
+
// NOTE: codonGenomicPos is important here for calculating the genomic location
|
|
7326
|
+
// of the start codon. We are using the codonGenomicPos as the key in the typography
|
|
7327
|
+
// elements to maintain the genomic postion of the codon start
|
|
7328
|
+
const startCodonGenomicLocation = getCodonGenomicLocation(codonGenomicPos);
|
|
7329
|
+
if (startCodonGenomicLocation !== cdsMin &&
|
|
7330
|
+
strand === 1) {
|
|
7331
|
+
updateCDSLocation(cdsMin, startCodonGenomicLocation, feature, true);
|
|
7332
|
+
}
|
|
7333
|
+
if (startCodonGenomicLocation !== cdsMax &&
|
|
7334
|
+
strand === -1) {
|
|
7335
|
+
updateCDSLocation(cdsMax, startCodonGenomicLocation, feature, false);
|
|
7336
|
+
}
|
|
7337
|
+
}, children: protein }, codonGenomicPos));
|
|
7338
|
+
}
|
|
7339
|
+
if (protein === '*') {
|
|
7340
|
+
return (jsx(Typography, { style: { backgroundColor: 'red', color: 'white' }, component: 'span', children: protein }, codonGenomicPos));
|
|
7341
|
+
}
|
|
7342
|
+
// Pass the codonGenomicPos as the key to maintain the genomic position of the codon
|
|
7343
|
+
return (jsx(Typography, { component: 'span', children: protein }, codonGenomicPos));
|
|
7344
|
+
}) }) }), jsxs("div", { style: {
|
|
7345
|
+
marginTop: 10,
|
|
7346
|
+
display: 'flex',
|
|
7347
|
+
flexDirection: 'row',
|
|
7348
|
+
alignItems: 'center',
|
|
7349
|
+
gap: 10,
|
|
7350
|
+
}, children: [jsx(Tooltip$1, { title: "Copy", children: jsx("button", { onClick: onCopyClick, style: { border: 'none', background: 'none', padding: 0 }, disabled: changeInProgress, children: jsx(ContentCopyIcon, { style: { fontSize: 15 } }) }) }), jsx(Tooltip$1, { title: "Trim", children: jsx("button", { onClick: trimTranslationSequence, style: { border: 'none', background: 'none', padding: 0 }, disabled: changeInProgress, children: jsx(ContentCutIcon, { style: { fontSize: 15 } }) }) })] })] })] }) }));
|
|
7351
|
+
}
|
|
7352
|
+
|
|
7353
|
+
const StyledTextField = styled(NumberTextField)(() => ({
|
|
7354
|
+
'&.MuiFormControl-root': {
|
|
7355
|
+
marginTop: 0,
|
|
7356
|
+
marginBottom: 0,
|
|
7357
|
+
width: '100%',
|
|
7358
|
+
},
|
|
7359
|
+
'& .MuiInputBase-input': {
|
|
7360
|
+
fontSize: 12,
|
|
7361
|
+
height: 20,
|
|
7362
|
+
padding: 1,
|
|
7363
|
+
paddingLeft: 10,
|
|
7364
|
+
},
|
|
7365
|
+
}));
|
|
6987
7366
|
const Strand = (props) => {
|
|
6988
7367
|
const { strand } = props;
|
|
6989
7368
|
return (jsx("div", { children: strand === 1 ? (jsx(AddIcon, {})) : strand === -1 ? (jsx(RemoveIcon, {})) : (jsx(Typography, { component: 'span', children: "N/A" })) }));
|
|
@@ -7002,7 +7381,6 @@ const TranscriptWidgetEditLocation = observer(function TranscriptWidgetEditLocat
|
|
|
7002
7381
|
const currentAssembly = session.apolloDataStore.assemblies.get(assembly);
|
|
7003
7382
|
const refData = currentAssembly?.getByRefName(refName);
|
|
7004
7383
|
const { changeManager } = session.apolloDataStore;
|
|
7005
|
-
const seqRef = useRef(null);
|
|
7006
7384
|
const { changeInProgress } = session;
|
|
7007
7385
|
if (!refData) {
|
|
7008
7386
|
return null;
|
|
@@ -7429,184 +7807,6 @@ const TranscriptWidgetEditLocation = observer(function TranscriptWidgetEditLocat
|
|
|
7429
7807
|
},
|
|
7430
7808
|
];
|
|
7431
7809
|
};
|
|
7432
|
-
const getTranslationSequence = () => {
|
|
7433
|
-
let wholeSequence = '';
|
|
7434
|
-
const [firstLocation] = cdsLocations;
|
|
7435
|
-
const sortedCDSLocations = firstLocation.toSorted(({ min: a }, { min: b }) => a - b);
|
|
7436
|
-
for (const loc of sortedCDSLocations) {
|
|
7437
|
-
wholeSequence += refData.getSequence(loc.min, loc.max);
|
|
7438
|
-
}
|
|
7439
|
-
if (strand === -1) {
|
|
7440
|
-
// Original: ACGCAT
|
|
7441
|
-
// Complement: TGCGTA
|
|
7442
|
-
// Reverse complement: ATGCGT
|
|
7443
|
-
wholeSequence = revcom(wholeSequence);
|
|
7444
|
-
}
|
|
7445
|
-
const elements = [];
|
|
7446
|
-
for (let codonGenomicPos = 0; codonGenomicPos < wholeSequence.length; codonGenomicPos += 3) {
|
|
7447
|
-
const codonSeq = wholeSequence
|
|
7448
|
-
.slice(codonGenomicPos, codonGenomicPos + 3)
|
|
7449
|
-
.toUpperCase();
|
|
7450
|
-
const protein = defaultCodonTable[codonSeq] || '&';
|
|
7451
|
-
// highlight start codon and stop codons
|
|
7452
|
-
if (codonSeq === 'ATG') {
|
|
7453
|
-
elements.push(jsx(Typography, { component: 'span', style: {
|
|
7454
|
-
backgroundColor: changeInProgress ? 'lightgray' : 'yellow',
|
|
7455
|
-
cursor: 'pointer',
|
|
7456
|
-
border: '1px solid black',
|
|
7457
|
-
}, onClick: () => {
|
|
7458
|
-
if (changeInProgress) {
|
|
7459
|
-
return;
|
|
7460
|
-
}
|
|
7461
|
-
// NOTE: codonGenomicPos is important here for calculating the genomic location
|
|
7462
|
-
// of the start codon. We are using the codonGenomicPos as the key in the typography
|
|
7463
|
-
// elements to maintain the genomic postion of the codon start
|
|
7464
|
-
const startCodonGenomicLocation = getCodonGenomicLocation(codonGenomicPos);
|
|
7465
|
-
if (startCodonGenomicLocation !== cdsMin && strand === 1) {
|
|
7466
|
-
updateCDSLocation(cdsMin, startCodonGenomicLocation, feature, true);
|
|
7467
|
-
}
|
|
7468
|
-
if (startCodonGenomicLocation !== cdsMax && strand === -1) {
|
|
7469
|
-
updateCDSLocation(cdsMax, startCodonGenomicLocation, feature, false);
|
|
7470
|
-
}
|
|
7471
|
-
}, children: protein }, codonGenomicPos));
|
|
7472
|
-
}
|
|
7473
|
-
else if (['TAA', 'TAG', 'TGA'].includes(codonSeq)) {
|
|
7474
|
-
elements.push(jsx(Typography, { style: { backgroundColor: 'red', color: 'white' }, component: 'span', children: protein }, codonGenomicPos));
|
|
7475
|
-
}
|
|
7476
|
-
else {
|
|
7477
|
-
elements.push(
|
|
7478
|
-
// Pass the codonGenomicPos as the key to maintain the genomic position of the codon
|
|
7479
|
-
jsx(Typography, { component: 'span', children: protein }, codonGenomicPos));
|
|
7480
|
-
}
|
|
7481
|
-
}
|
|
7482
|
-
return elements;
|
|
7483
|
-
};
|
|
7484
|
-
// Codon position is the index of the start codon in the CDS genomic sequence
|
|
7485
|
-
// Calculate the genomic location of the start codon based on the codon position in the CDS
|
|
7486
|
-
const getCodonGenomicLocation = (codonGenomicPosition) => {
|
|
7487
|
-
const [firstLocation] = cdsLocations;
|
|
7488
|
-
let cdsLen = 0;
|
|
7489
|
-
const sortedCDSLocations = firstLocation.toSorted(({ min: a }, { min: b }) => a - b);
|
|
7490
|
-
// Suppose CDS locations are [{min: 0, max: 10}, {min: 20, max: 30}, {min: 40, max: 50}]
|
|
7491
|
-
// and codonGenomicPosition is 25
|
|
7492
|
-
// ((10 - 0) + (30 - 20) + (50 - 40)) > 25
|
|
7493
|
-
// So, start codon is in (40, 50)
|
|
7494
|
-
// 40 + (25-20) = 45 is the genomic location of the start codon
|
|
7495
|
-
if (strand === 1) {
|
|
7496
|
-
for (const loc of sortedCDSLocations) {
|
|
7497
|
-
const locLength = loc.max - loc.min;
|
|
7498
|
-
if (cdsLen + locLength > codonGenomicPosition) {
|
|
7499
|
-
return loc.min + (codonGenomicPosition - cdsLen);
|
|
7500
|
-
}
|
|
7501
|
-
cdsLen += locLength;
|
|
7502
|
-
}
|
|
7503
|
-
}
|
|
7504
|
-
else if (strand === -1) {
|
|
7505
|
-
for (let i = sortedCDSLocations.length - 1; i >= 0; i--) {
|
|
7506
|
-
const loc = sortedCDSLocations[i];
|
|
7507
|
-
const locLength = loc.max - loc.min;
|
|
7508
|
-
if (cdsLen + locLength > codonGenomicPosition) {
|
|
7509
|
-
return loc.max - (codonGenomicPosition - cdsLen);
|
|
7510
|
-
}
|
|
7511
|
-
cdsLen += locLength;
|
|
7512
|
-
}
|
|
7513
|
-
}
|
|
7514
|
-
if (strand === 1) {
|
|
7515
|
-
return cdsMin;
|
|
7516
|
-
}
|
|
7517
|
-
return cdsMax;
|
|
7518
|
-
};
|
|
7519
|
-
const trimTranslationSequence = () => {
|
|
7520
|
-
const sequenceElements = getTranslationSequence();
|
|
7521
|
-
const translationSequence = sequenceElements
|
|
7522
|
-
.map((el) => el.props.children)
|
|
7523
|
-
.join('');
|
|
7524
|
-
if (translationSequence.startsWith('M') &&
|
|
7525
|
-
translationSequence.endsWith('*')) {
|
|
7526
|
-
return;
|
|
7527
|
-
}
|
|
7528
|
-
// NOTE: We are maintaining the genomic location of the codon start as the "key"
|
|
7529
|
-
// in typography elements. See getTranslationSequence function
|
|
7530
|
-
const translSeqCodonStartGenomicPosArr = [];
|
|
7531
|
-
for (const el of sequenceElements) {
|
|
7532
|
-
translSeqCodonStartGenomicPosArr.push({
|
|
7533
|
-
codonGenomicPos: el.key,
|
|
7534
|
-
sequenceLetter: el.props.children,
|
|
7535
|
-
});
|
|
7536
|
-
}
|
|
7537
|
-
if (translSeqCodonStartGenomicPosArr.length === 0) {
|
|
7538
|
-
return;
|
|
7539
|
-
}
|
|
7540
|
-
// Trim any sequence before first start codon and after stop codon
|
|
7541
|
-
const startCodonIndex = translationSequence.indexOf('M');
|
|
7542
|
-
const stopCodonIndex = translationSequence.indexOf('*');
|
|
7543
|
-
const startCodonPos = translSeqCodonStartGenomicPosArr[startCodonIndex].codonGenomicPos;
|
|
7544
|
-
const stopCodonPos = translSeqCodonStartGenomicPosArr[stopCodonIndex].codonGenomicPos;
|
|
7545
|
-
if (!startCodonPos || !stopCodonPos) {
|
|
7546
|
-
return;
|
|
7547
|
-
}
|
|
7548
|
-
const startCodonGenomicLoc = getCodonGenomicLocation(startCodonPos);
|
|
7549
|
-
let stopCodonGenomicLoc = getCodonGenomicLocation(stopCodonPos);
|
|
7550
|
-
if (strand === 1) {
|
|
7551
|
-
if (startCodonGenomicLoc > stopCodonGenomicLoc) {
|
|
7552
|
-
notify('Start codon genomic location should be less than stop codon genomic location', 'error');
|
|
7553
|
-
return;
|
|
7554
|
-
}
|
|
7555
|
-
let promise;
|
|
7556
|
-
stopCodonGenomicLoc += 3; // move to end of stop codon
|
|
7557
|
-
if (startCodonGenomicLoc !== cdsMin) {
|
|
7558
|
-
promise = new Promise((resolve) => {
|
|
7559
|
-
updateCDSLocation(cdsMin, startCodonGenomicLoc, feature, true, () => {
|
|
7560
|
-
resolve(true);
|
|
7561
|
-
});
|
|
7562
|
-
});
|
|
7563
|
-
}
|
|
7564
|
-
if (stopCodonGenomicLoc !== cdsMax) {
|
|
7565
|
-
if (promise) {
|
|
7566
|
-
void promise.then(() => {
|
|
7567
|
-
updateCDSLocation(cdsMax, stopCodonGenomicLoc, feature, false);
|
|
7568
|
-
});
|
|
7569
|
-
}
|
|
7570
|
-
else {
|
|
7571
|
-
updateCDSLocation(cdsMax, stopCodonGenomicLoc, feature, false);
|
|
7572
|
-
}
|
|
7573
|
-
}
|
|
7574
|
-
}
|
|
7575
|
-
if (strand === -1) {
|
|
7576
|
-
// reverse strand
|
|
7577
|
-
if (startCodonGenomicLoc < stopCodonGenomicLoc) {
|
|
7578
|
-
notify('Start codon genomic location should be less than stop codon genomic location', 'error');
|
|
7579
|
-
return;
|
|
7580
|
-
}
|
|
7581
|
-
let promise;
|
|
7582
|
-
stopCodonGenomicLoc -= 3; // move to end of stop codon
|
|
7583
|
-
if (startCodonGenomicLoc !== cdsMax) {
|
|
7584
|
-
promise = new Promise((resolve) => {
|
|
7585
|
-
updateCDSLocation(cdsMax, startCodonGenomicLoc, feature, false, () => {
|
|
7586
|
-
resolve(true);
|
|
7587
|
-
});
|
|
7588
|
-
});
|
|
7589
|
-
}
|
|
7590
|
-
if (stopCodonGenomicLoc !== cdsMin) {
|
|
7591
|
-
if (promise) {
|
|
7592
|
-
void promise.then(() => {
|
|
7593
|
-
updateCDSLocation(cdsMin, stopCodonGenomicLoc, feature, true);
|
|
7594
|
-
});
|
|
7595
|
-
}
|
|
7596
|
-
else {
|
|
7597
|
-
updateCDSLocation(cdsMin, stopCodonGenomicLoc, feature, true);
|
|
7598
|
-
}
|
|
7599
|
-
}
|
|
7600
|
-
}
|
|
7601
|
-
notify('Translation sequence trimmed to start and stop codons', 'success');
|
|
7602
|
-
};
|
|
7603
|
-
const onCopyClick = () => {
|
|
7604
|
-
const seqDiv = seqRef.current;
|
|
7605
|
-
if (!seqDiv) {
|
|
7606
|
-
return;
|
|
7607
|
-
}
|
|
7608
|
-
void copyToClipboard(seqDiv);
|
|
7609
|
-
};
|
|
7610
7810
|
return (jsxs("div", { children: [cdsPresent && (jsx("div", { children: jsxs(Grid, { container: true, justifyContent: "center", alignItems: "center", style: { textAlign: 'center' }, children: [jsx(Grid, { size: 1 }), strand === 1 ? (jsx(Grid, { size: 4, children: jsx(StyledTextField, { margin: "dense", variant: "outlined", value: cdsMin + 1, onChangeCommitted: (newLocation) => {
|
|
7611
7811
|
return updateCDSLocation(cdsMin, newLocation - 1, feature, true);
|
|
7612
7812
|
}, style: { border: '1px solid black', borderRadius: 5 }, disabled: changeInProgress }) })) : (jsx(Grid, { size: 4, children: jsx(StyledTextField, { margin: "dense", variant: "outlined", value: cdsMax, onChangeCommitted: (newLocation) => {
|
|
@@ -7616,8 +7816,12 @@ const TranscriptWidgetEditLocation = observer(function TranscriptWidgetEditLocat
|
|
|
7616
7816
|
}, style: { border: '1px solid black', borderRadius: 5 }, disabled: changeInProgress }) })) : (jsx(Grid, { size: 4, children: jsx(StyledTextField, { margin: "dense", variant: "outlined", value: cdsMin + 1, onChangeCommitted: (newLocation) => {
|
|
7617
7817
|
return updateCDSLocation(cdsMin, newLocation - 1, feature, true);
|
|
7618
7818
|
}, style: { border: '1px solid black', borderRadius: 5 }, disabled: changeInProgress }) })), jsx(Grid, { size: 1 })] }) })), jsxs("div", { style: { marginTop: 5, marginBottom: 10 }, children: [jsx("div", { style: { textAlign: 'center' }, children: jsx(Typography, { children: "Exons" }) }), transcriptExonParts.map((loc, index) => {
|
|
7619
|
-
return (
|
|
7620
|
-
|
|
7819
|
+
return (
|
|
7820
|
+
// eslint-disable-next-line @eslint-react/no-array-index-key
|
|
7821
|
+
jsx("div", { children: loc.type === 'exon' && (jsxs(Grid, { container: true, justifyContent: "center", alignItems: "center", style: { textAlign: 'center' }, children: [jsx(Grid, { size: 1, children: index !== 0 &&
|
|
7822
|
+
getFivePrimeSpliceSite(loc, index).map((site, idx) => (jsx(Typography
|
|
7823
|
+
// eslint-disable-next-line @eslint-react/no-array-index-key
|
|
7824
|
+
, { component: 'span', color: site.color, children: site.spliceSite }, idx))) }), strand === 1 ? (jsx(Grid, { size: 4, style: { padding: 0 }, children: jsx(StyledTextField, { margin: "dense", variant: "outlined", value: loc.min + 1, onChangeCommitted: (newLocation) => {
|
|
7621
7825
|
return handleExonLocationChange(loc.min, newLocation - 1, feature, true);
|
|
7622
7826
|
}, disabled: changeInProgress }) })) : (jsx(Grid, { size: 4, style: { padding: 0 }, children: jsx(StyledTextField, { margin: "dense", variant: "outlined", value: loc.max, onChangeCommitted: (newLocation) => {
|
|
7623
7827
|
return handleExonLocationChange(loc.max, newLocation, feature, false);
|
|
@@ -7626,14 +7830,10 @@ const TranscriptWidgetEditLocation = observer(function TranscriptWidgetEditLocat
|
|
|
7626
7830
|
}, disabled: changeInProgress }) })) : (jsx(Grid, { size: 4, style: { padding: 0 }, children: jsx(StyledTextField, { margin: "dense", variant: "outlined", value: loc.min + 1, onChangeCommitted: (newLocation) => {
|
|
7627
7831
|
return handleExonLocationChange(loc.min, newLocation - 1, feature, true);
|
|
7628
7832
|
}, disabled: changeInProgress }) })), jsx(Grid, { size: 1, children: index !== transcriptExonParts.length - 1 &&
|
|
7629
|
-
getThreePrimeSpliceSite(loc, index).map((site, idx) => (jsx(Typography
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
7633
|
-
flexDirection: 'row',
|
|
7634
|
-
alignItems: 'center',
|
|
7635
|
-
gap: 10,
|
|
7636
|
-
}, children: [jsx(Tooltip$1, { title: "Copy", children: jsx("button", { onClick: onCopyClick, style: { border: 'none', background: 'none', padding: 0 }, disabled: changeInProgress, children: jsx(ContentCopyIcon, { style: { fontSize: 15 } }) }) }), jsx(Tooltip$1, { title: "Trim", children: jsx("button", { onClick: trimTranslationSequence, style: { border: 'none', background: 'none', padding: 0 }, disabled: changeInProgress, children: jsx(ContentCutIcon, { style: { fontSize: 15 } }) }) })] })] })] }) }))] }));
|
|
7833
|
+
getThreePrimeSpliceSite(loc, index).map((site, idx) => (jsx(Typography
|
|
7834
|
+
// eslint-disable-next-line @eslint-react/no-array-index-key
|
|
7835
|
+
, { component: 'span', color: site.color, children: site.spliceSite }, idx))) })] })) }, index));
|
|
7836
|
+
})] }), cdsPresent && (jsx(Translation, { changeInProgress: changeInProgress, cdsLocations: cdsLocations, refData: refData, strand: strand, updateCDSLocation: updateCDSLocation, cdsMin: cdsMin, cdsMax: cdsMax, feature: feature, session: session }))] }));
|
|
7637
7837
|
});
|
|
7638
7838
|
|
|
7639
7839
|
const HeaderTableCell = styled(TableCell)(() => ({
|
|
@@ -7670,7 +7870,7 @@ const ApolloTranscriptDetailsWidget = observer(function ApolloTranscriptDetails(
|
|
|
7670
7870
|
const { assembly, feature, refName } = model;
|
|
7671
7871
|
useEffect(() => {
|
|
7672
7872
|
setPanelState(DEFAULT_PANELS);
|
|
7673
|
-
// eslint-disable-next-line react
|
|
7873
|
+
// eslint-disable-next-line @eslint-react/exhaustive-deps
|
|
7674
7874
|
}, [feature]);
|
|
7675
7875
|
const session = getSession(model);
|
|
7676
7876
|
const { pluginManager } = getEnv(session);
|
|
@@ -7729,8 +7929,8 @@ function handleFeatureTypeChange(changeManager, feature, oldType, newType) {
|
|
|
7729
7929
|
typeName: 'TypeChange',
|
|
7730
7930
|
changedIds: [featureId],
|
|
7731
7931
|
featureId,
|
|
7732
|
-
oldType
|
|
7733
|
-
newType
|
|
7932
|
+
oldType,
|
|
7933
|
+
newType,
|
|
7734
7934
|
assembly: feature.assemblyId,
|
|
7735
7935
|
});
|
|
7736
7936
|
return changeManager.submit(change);
|
|
@@ -7826,7 +8026,7 @@ const NumberCell = observer(function NumberCell({ initialValue, notifyError, onC
|
|
|
7826
8026
|
if (initialValue !== value) {
|
|
7827
8027
|
setValue(initialValue);
|
|
7828
8028
|
}
|
|
7829
|
-
// eslint-disable-next-line react
|
|
8029
|
+
// eslint-disable-next-line @eslint-react/exhaustive-deps
|
|
7830
8030
|
}, [initialValue]);
|
|
7831
8031
|
useEffect(() => {
|
|
7832
8032
|
if (blur) {
|
|
@@ -8667,8 +8867,9 @@ function draw$3(display, ctx, gene, row, rowInFeature, block) {
|
|
|
8667
8867
|
const [top, left, width] = getFeatureBox(display, gene, row, block);
|
|
8668
8868
|
const height = getRowCount$2(display, gene) * apolloRowHeight;
|
|
8669
8869
|
if (width > 2) {
|
|
8670
|
-
|
|
8671
|
-
|
|
8870
|
+
const selectedColor = readConfObject(session.getPluginConfiguration(), 'geneBackgroundColor', {
|
|
8871
|
+
featureType: gene.type,
|
|
8872
|
+
}) ?? alpha(theme.palette.background.paper, 0.6);
|
|
8672
8873
|
ctx.fillStyle = selectedColor;
|
|
8673
8874
|
ctx.fillRect(left, top, width, height);
|
|
8674
8875
|
}
|
|
@@ -8941,7 +9142,7 @@ function getLayout(display, feature) {
|
|
|
8941
9142
|
}
|
|
8942
9143
|
return layout;
|
|
8943
9144
|
}
|
|
8944
|
-
function getContextMenuItems$1(display,
|
|
9145
|
+
function getContextMenuItems$1(display, transcript) {
|
|
8945
9146
|
const { changeManager, regions, selectedFeature, session } = display;
|
|
8946
9147
|
const [region] = regions;
|
|
8947
9148
|
const currentAssemblyId = display.getAssemblyId(region.assemblyName);
|
|
@@ -8951,7 +9152,7 @@ function getContextMenuItems$1(display, feature) {
|
|
|
8951
9152
|
label: 'Open transcript editor',
|
|
8952
9153
|
onClick: () => {
|
|
8953
9154
|
const apolloTranscriptWidget = session.addWidget('ApolloTranscriptDetails', 'apolloTranscriptDetails', {
|
|
8954
|
-
feature,
|
|
9155
|
+
feature: transcript,
|
|
8955
9156
|
assembly: currentAssemblyId,
|
|
8956
9157
|
changeManager,
|
|
8957
9158
|
refName: region.refName,
|
|
@@ -8971,7 +9172,7 @@ function getContextMenuItems$1(display, feature) {
|
|
|
8971
9172
|
doneCallback();
|
|
8972
9173
|
},
|
|
8973
9174
|
changeManager,
|
|
8974
|
-
sourceFeature:
|
|
9175
|
+
sourceFeature: transcript,
|
|
8975
9176
|
sourceAssemblyId: currentAssemblyId,
|
|
8976
9177
|
selectedFeature,
|
|
8977
9178
|
setSelectedFeature: (feature) => {
|
|
@@ -8991,7 +9192,7 @@ function getContextMenuItems$1(display, feature) {
|
|
|
8991
9192
|
doneCallback();
|
|
8992
9193
|
},
|
|
8993
9194
|
changeManager,
|
|
8994
|
-
sourceFeature:
|
|
9195
|
+
sourceFeature: transcript,
|
|
8995
9196
|
sourceAssemblyId: currentAssemblyId,
|
|
8996
9197
|
selectedFeature,
|
|
8997
9198
|
setSelectedFeature: (feature) => {
|
|
@@ -9001,6 +9202,31 @@ function getContextMenuItems$1(display, feature) {
|
|
|
9001
9202
|
]);
|
|
9002
9203
|
},
|
|
9003
9204
|
});
|
|
9205
|
+
const { children } = transcript;
|
|
9206
|
+
if (!children) {
|
|
9207
|
+
return [];
|
|
9208
|
+
}
|
|
9209
|
+
const cdsChildren = [...children.values()].filter((child) => isCDSFeature(child, session));
|
|
9210
|
+
if (cdsChildren.length === 0) {
|
|
9211
|
+
menuItems.push({
|
|
9212
|
+
label: 'Add coding sequence',
|
|
9213
|
+
onClick: () => {
|
|
9214
|
+
session.queueDialog((doneCallback) => [
|
|
9215
|
+
AddCodingSequence,
|
|
9216
|
+
{
|
|
9217
|
+
session,
|
|
9218
|
+
handleClose: () => {
|
|
9219
|
+
doneCallback();
|
|
9220
|
+
},
|
|
9221
|
+
changeManager,
|
|
9222
|
+
sourceFeature: transcript,
|
|
9223
|
+
sourceAssemblyId: currentAssemblyId,
|
|
9224
|
+
refName: region.refName,
|
|
9225
|
+
},
|
|
9226
|
+
]);
|
|
9227
|
+
},
|
|
9228
|
+
});
|
|
9229
|
+
}
|
|
9004
9230
|
return menuItems;
|
|
9005
9231
|
}
|
|
9006
9232
|
// False positive here, none of these functions use "this"
|
|
@@ -9178,7 +9404,7 @@ function colorCode(letter, theme) {
|
|
|
9178
9404
|
letterUpper === 'C' ||
|
|
9179
9405
|
letterUpper === 'G' ||
|
|
9180
9406
|
letterUpper === 'T') {
|
|
9181
|
-
return theme.palette.bases[letterUpper].main
|
|
9407
|
+
return theme.palette.bases[letterUpper].main;
|
|
9182
9408
|
}
|
|
9183
9409
|
return 'lightgray';
|
|
9184
9410
|
}
|
|
@@ -9275,8 +9501,7 @@ function baseModelFactory$2(_pluginManager, configSchema) {
|
|
|
9275
9501
|
return internetAccounts.find((ia) => getConf(ia, 'internetAccountId') === internetAccountConfigId);
|
|
9276
9502
|
},
|
|
9277
9503
|
get changeManager() {
|
|
9278
|
-
return self.session.apolloDataStore
|
|
9279
|
-
.changeManager;
|
|
9504
|
+
return self.session.apolloDataStore.changeManager;
|
|
9280
9505
|
},
|
|
9281
9506
|
getAssemblyId(assemblyName) {
|
|
9282
9507
|
const { assemblyManager } = self.session;
|
|
@@ -9287,12 +9512,10 @@ function baseModelFactory$2(_pluginManager, configSchema) {
|
|
|
9287
9512
|
return assembly.name;
|
|
9288
9513
|
},
|
|
9289
9514
|
get selectedFeature() {
|
|
9290
|
-
return self.session
|
|
9291
|
-
.apolloSelectedFeature;
|
|
9515
|
+
return self.session.apolloSelectedFeature;
|
|
9292
9516
|
},
|
|
9293
9517
|
get hoveredFeature() {
|
|
9294
|
-
return self.session
|
|
9295
|
-
.apolloHoveredFeature;
|
|
9518
|
+
return self.session.apolloHoveredFeature;
|
|
9296
9519
|
},
|
|
9297
9520
|
}))
|
|
9298
9521
|
.actions((self) => ({
|
|
@@ -9337,8 +9560,11 @@ function baseModelFactory$2(_pluginManager, configSchema) {
|
|
|
9337
9560
|
const { filteredFeatureTypes, trackMenuItems: superTrackMenuItems } = self;
|
|
9338
9561
|
return {
|
|
9339
9562
|
trackMenuItems() {
|
|
9340
|
-
const { graphical, table, showCheckResults } = self;
|
|
9341
|
-
|
|
9563
|
+
const { graphical, table, showCheckResults, session } = self;
|
|
9564
|
+
const [region] = self.regions;
|
|
9565
|
+
const { assemblyName } = region;
|
|
9566
|
+
const backendDriver = session.apolloDataStore.getBackendDriver(assemblyName);
|
|
9567
|
+
const items = [
|
|
9342
9568
|
...superTrackMenuItems(),
|
|
9343
9569
|
{
|
|
9344
9570
|
type: 'subMenu',
|
|
@@ -9390,7 +9616,7 @@ function baseModelFactory$2(_pluginManager, configSchema) {
|
|
|
9390
9616
|
{
|
|
9391
9617
|
label: 'Filter features by type',
|
|
9392
9618
|
onClick: () => {
|
|
9393
|
-
const session = self
|
|
9619
|
+
const { session } = self;
|
|
9394
9620
|
self.session.queueDialog((doneCallback) => [
|
|
9395
9621
|
FilterFeatures,
|
|
9396
9622
|
{
|
|
@@ -9406,76 +9632,93 @@ function baseModelFactory$2(_pluginManager, configSchema) {
|
|
|
9406
9632
|
]);
|
|
9407
9633
|
},
|
|
9408
9634
|
},
|
|
9409
|
-
|
|
9410
|
-
|
|
9411
|
-
|
|
9635
|
+
];
|
|
9636
|
+
if (backendDriver instanceof LocalDriver) {
|
|
9637
|
+
items.push({
|
|
9638
|
+
label: 'Import annotations',
|
|
9639
|
+
icon: InputIcon,
|
|
9412
9640
|
onClick: () => {
|
|
9413
|
-
const [region] = self.regions;
|
|
9414
|
-
const { assemblyName } = region;
|
|
9415
|
-
const assembly = self.getAssemblyId(assemblyName);
|
|
9416
|
-
if (!assembly) {
|
|
9417
|
-
return;
|
|
9418
|
-
}
|
|
9419
|
-
const session = self.session;
|
|
9420
9641
|
session.queueDialog((doneCallback) => [
|
|
9421
|
-
|
|
9642
|
+
ImportAnnotations,
|
|
9422
9643
|
{
|
|
9423
9644
|
session,
|
|
9645
|
+
assemblyName,
|
|
9424
9646
|
handleClose: () => {
|
|
9425
9647
|
doneCallback();
|
|
9426
9648
|
},
|
|
9427
|
-
assembly,
|
|
9428
9649
|
},
|
|
9429
9650
|
]);
|
|
9430
9651
|
},
|
|
9652
|
+
});
|
|
9653
|
+
}
|
|
9654
|
+
items.push({
|
|
9655
|
+
label: 'Export annotations',
|
|
9656
|
+
icon: Export,
|
|
9657
|
+
onClick: () => {
|
|
9658
|
+
const [region] = self.regions;
|
|
9659
|
+
const { assemblyName } = region;
|
|
9660
|
+
const assembly = self.getAssemblyId(assemblyName);
|
|
9661
|
+
if (!assembly) {
|
|
9662
|
+
return;
|
|
9663
|
+
}
|
|
9664
|
+
const { session } = self;
|
|
9665
|
+
session.queueDialog((doneCallback) => [
|
|
9666
|
+
DownloadGFF3,
|
|
9667
|
+
{
|
|
9668
|
+
session,
|
|
9669
|
+
handleClose: () => {
|
|
9670
|
+
doneCallback();
|
|
9671
|
+
},
|
|
9672
|
+
assembly,
|
|
9673
|
+
},
|
|
9674
|
+
]);
|
|
9431
9675
|
},
|
|
9432
|
-
|
|
9433
|
-
|
|
9434
|
-
|
|
9435
|
-
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
},
|
|
9450
|
-
assembly,
|
|
9676
|
+
}, {
|
|
9677
|
+
label: 'View Change Log',
|
|
9678
|
+
icon: TrackChangesIcon,
|
|
9679
|
+
onClick: () => {
|
|
9680
|
+
const [region] = self.regions;
|
|
9681
|
+
const { assemblyName } = region;
|
|
9682
|
+
const assembly = self.getAssemblyId(assemblyName);
|
|
9683
|
+
if (!assembly) {
|
|
9684
|
+
return;
|
|
9685
|
+
}
|
|
9686
|
+
const { session } = self;
|
|
9687
|
+
session.queueDialog((doneCallback) => [
|
|
9688
|
+
ViewChangeLog,
|
|
9689
|
+
{
|
|
9690
|
+
session,
|
|
9691
|
+
handleClose: () => {
|
|
9692
|
+
doneCallback();
|
|
9451
9693
|
},
|
|
9452
|
-
|
|
9453
|
-
|
|
9694
|
+
assembly,
|
|
9695
|
+
},
|
|
9696
|
+
]);
|
|
9454
9697
|
},
|
|
9455
|
-
|
|
9456
|
-
|
|
9457
|
-
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
|
|
9464
|
-
|
|
9465
|
-
|
|
9466
|
-
|
|
9467
|
-
|
|
9468
|
-
|
|
9469
|
-
|
|
9470
|
-
|
|
9471
|
-
|
|
9472
|
-
},
|
|
9473
|
-
assembly,
|
|
9698
|
+
}, {
|
|
9699
|
+
label: 'View Check Results',
|
|
9700
|
+
icon: FactCheckIcon,
|
|
9701
|
+
onClick: () => {
|
|
9702
|
+
const [region] = self.regions;
|
|
9703
|
+
const { assemblyName } = region;
|
|
9704
|
+
const assembly = self.getAssemblyId(assemblyName);
|
|
9705
|
+
if (!assembly) {
|
|
9706
|
+
return;
|
|
9707
|
+
}
|
|
9708
|
+
const { session } = self;
|
|
9709
|
+
session.queueDialog((doneCallback) => [
|
|
9710
|
+
ViewCheckResults,
|
|
9711
|
+
{
|
|
9712
|
+
session,
|
|
9713
|
+
handleClose: () => {
|
|
9714
|
+
doneCallback();
|
|
9474
9715
|
},
|
|
9475
|
-
|
|
9476
|
-
|
|
9716
|
+
assembly,
|
|
9717
|
+
},
|
|
9718
|
+
]);
|
|
9477
9719
|
},
|
|
9478
|
-
|
|
9720
|
+
});
|
|
9721
|
+
return items;
|
|
9479
9722
|
},
|
|
9480
9723
|
};
|
|
9481
9724
|
})
|
|
@@ -10541,8 +10784,7 @@ function baseModelFactory$1(_pluginManager, configSchema) {
|
|
|
10541
10784
|
return internetAccounts.find((ia) => getConf(ia, 'internetAccountId') === internetAccountConfigId);
|
|
10542
10785
|
},
|
|
10543
10786
|
get changeManager() {
|
|
10544
|
-
return self.session.apolloDataStore
|
|
10545
|
-
.changeManager;
|
|
10787
|
+
return self.session.apolloDataStore.changeManager;
|
|
10546
10788
|
},
|
|
10547
10789
|
getAssemblyId(assemblyName) {
|
|
10548
10790
|
const { assemblyManager } = self.session;
|
|
@@ -10553,12 +10795,10 @@ function baseModelFactory$1(_pluginManager, configSchema) {
|
|
|
10553
10795
|
return assembly.name;
|
|
10554
10796
|
},
|
|
10555
10797
|
get selectedFeature() {
|
|
10556
|
-
return self.session
|
|
10557
|
-
.apolloSelectedFeature;
|
|
10798
|
+
return self.session.apolloSelectedFeature;
|
|
10558
10799
|
},
|
|
10559
10800
|
get hoveredFeature() {
|
|
10560
|
-
return self.session
|
|
10561
|
-
.apolloHoveredFeature;
|
|
10801
|
+
return self.session.apolloHoveredFeature;
|
|
10562
10802
|
},
|
|
10563
10803
|
get height() {
|
|
10564
10804
|
const { sequenceRowHeight } = self;
|
|
@@ -10770,11 +11010,9 @@ const FilterTranscripts = observer(function FilterTranscripts({ sourceFeature, f
|
|
|
10770
11010
|
onUpdate(newForms);
|
|
10771
11011
|
setExcludedTranscripts(newForms);
|
|
10772
11012
|
};
|
|
10773
|
-
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) => (
|
|
10774
|
-
// eslint-disable-next-line react/jsx-key
|
|
10775
|
-
jsx(FormControlLabel, { control: jsx(Checkbox, { checked: !excludedTranscripts.includes(item), onChange: () => {
|
|
11013
|
+
return (jsx(Dialog, { open: true, maxWidth: false, "data-testid": "filter-transcripts-dialog", title: "Filter transcripts by ID", handleClose: handleClose, children: jsxs(DialogContent, { children: [jsx(DialogContentText, { children: "Select the alternate transcripts you want to display in the apollo track" }), jsx(Grid, { container: true, spacing: 2, children: jsx(Grid, { size: 8, children: jsx(FormGroup, { children: allTranscripts.map((item) => (jsx(FormControlLabel, { control: jsx(Checkbox, { checked: !excludedTranscripts.includes(item), onChange: () => {
|
|
10776
11014
|
handleChange(item);
|
|
10777
|
-
}, slotProps: { input: { 'aria-label': 'controlled' } } }), label: item }))) }) }) })] }) }));
|
|
11015
|
+
}, slotProps: { input: { 'aria-label': 'controlled' } } }), label: item }, item))) }) }) })] }) }));
|
|
10778
11016
|
});
|
|
10779
11017
|
|
|
10780
11018
|
let forwardFillLight = null;
|
|
@@ -11620,8 +11858,7 @@ function baseModelFactory(_pluginManager, configSchema) {
|
|
|
11620
11858
|
return internetAccounts.find((ia) => getConf(ia, 'internetAccountId') === internetAccountConfigId);
|
|
11621
11859
|
},
|
|
11622
11860
|
get changeManager() {
|
|
11623
|
-
return self.session.apolloDataStore
|
|
11624
|
-
.changeManager;
|
|
11861
|
+
return self.session.apolloDataStore.changeManager;
|
|
11625
11862
|
},
|
|
11626
11863
|
getAssemblyId(assemblyName) {
|
|
11627
11864
|
const { assemblyManager } = self.session;
|
|
@@ -11632,12 +11869,10 @@ function baseModelFactory(_pluginManager, configSchema) {
|
|
|
11632
11869
|
return assembly.name;
|
|
11633
11870
|
},
|
|
11634
11871
|
get selectedFeature() {
|
|
11635
|
-
return self.session
|
|
11636
|
-
.apolloSelectedFeature;
|
|
11872
|
+
return self.session.apolloSelectedFeature;
|
|
11637
11873
|
},
|
|
11638
11874
|
get hoveredFeature() {
|
|
11639
|
-
return self.session
|
|
11640
|
-
.apolloHoveredFeature;
|
|
11875
|
+
return self.session.apolloHoveredFeature;
|
|
11641
11876
|
},
|
|
11642
11877
|
}))
|
|
11643
11878
|
.actions((self) => ({
|
|
@@ -11765,7 +12000,7 @@ function baseModelFactory(_pluginManager, configSchema) {
|
|
|
11765
12000
|
{
|
|
11766
12001
|
label: 'Filter features by type',
|
|
11767
12002
|
onClick: () => {
|
|
11768
|
-
const session = self
|
|
12003
|
+
const { session } = self;
|
|
11769
12004
|
self.session.queueDialog((doneCallback) => [
|
|
11770
12005
|
FilterFeatures,
|
|
11771
12006
|
{
|
|
@@ -12203,17 +12438,12 @@ function mouseEventsModelIntermediateFactory(pluginManager, configSchema) {
|
|
|
12203
12438
|
if (!featureTypeOntology) {
|
|
12204
12439
|
throw new Error('featureTypeOntology is undefined');
|
|
12205
12440
|
}
|
|
12206
|
-
|
|
12207
|
-
|
|
12208
|
-
foundFeature = layoutRow.find((f) => f.feature.type == 'exon' &&
|
|
12441
|
+
const foundFeature = self.geneTrackRowNums.includes(row)
|
|
12442
|
+
? layoutRow.find((f) => f.feature.type == 'exon' &&
|
|
12209
12443
|
bp >= f.feature.min &&
|
|
12210
|
-
bp <= f.feature.max)
|
|
12211
|
-
|
|
12212
|
-
|
|
12213
|
-
}
|
|
12214
|
-
}
|
|
12215
|
-
else {
|
|
12216
|
-
foundFeature = layoutRow.find((f) => {
|
|
12444
|
+
bp <= f.feature.max) ??
|
|
12445
|
+
layoutRow.find((f) => bp >= f.feature.min && bp <= f.feature.max)
|
|
12446
|
+
: layoutRow.find((f) => {
|
|
12217
12447
|
const { feature } = f;
|
|
12218
12448
|
const featureID = feature.attributes.get('gff_id')?.toString();
|
|
12219
12449
|
const isTranscript = featureTypeOntology.isTypeOf(feature.type, 'transcript');
|
|
@@ -12235,7 +12465,6 @@ function mouseEventsModelIntermediateFactory(pluginManager, configSchema) {
|
|
|
12235
12465
|
}
|
|
12236
12466
|
return false;
|
|
12237
12467
|
});
|
|
12238
|
-
}
|
|
12239
12468
|
if (!foundFeature) {
|
|
12240
12469
|
return mousePosition;
|
|
12241
12470
|
}
|
|
@@ -12559,26 +12788,32 @@ function CreateApolloAnnotation({ annotationFeature, assembly, handleClose, refS
|
|
|
12559
12788
|
const apolloAssembly = apolloSessionModel.apolloDataStore.assemblies.get(assembly.name);
|
|
12560
12789
|
const refSeq = apolloAssembly?.refSeqs.get(refSeqId);
|
|
12561
12790
|
const features = refSeq?.getFeatures(region.start, region.end);
|
|
12562
|
-
const getDestinationFeatures = () => {
|
|
12563
|
-
const filteredFeatures = [];
|
|
12564
|
-
for (const f of features ?? []) {
|
|
12565
|
-
if (f.min > region.end || f.max < region.start) {
|
|
12566
|
-
continue;
|
|
12567
|
-
}
|
|
12568
|
-
// Destination feature should be of type gene
|
|
12569
|
-
if (featureTypeOntology?.isTypeOf(f.type, 'gene')) {
|
|
12570
|
-
const featureSnapshot = getSnapshot(f);
|
|
12571
|
-
filteredFeatures.push(featureSnapshot);
|
|
12572
|
-
}
|
|
12573
|
-
}
|
|
12574
|
-
return filteredFeatures;
|
|
12575
|
-
};
|
|
12576
12791
|
useEffect(() => {
|
|
12792
|
+
const getDestinationFeatures = () => {
|
|
12793
|
+
const filteredFeatures = [];
|
|
12794
|
+
for (const f of features ?? []) {
|
|
12795
|
+
if (f.min > region.end || f.max < region.start) {
|
|
12796
|
+
continue;
|
|
12797
|
+
}
|
|
12798
|
+
// Destination feature should be of type gene
|
|
12799
|
+
if (featureTypeOntology?.isTypeOf(f.type, 'gene')) {
|
|
12800
|
+
const featureSnapshot = getSnapshot(f);
|
|
12801
|
+
filteredFeatures.push(featureSnapshot);
|
|
12802
|
+
}
|
|
12803
|
+
}
|
|
12804
|
+
return filteredFeatures;
|
|
12805
|
+
};
|
|
12577
12806
|
setErrorMessage('');
|
|
12578
|
-
const
|
|
12579
|
-
setDestinationFeatures(
|
|
12580
|
-
setSelectedDestinationFeature(
|
|
12581
|
-
}, [
|
|
12807
|
+
const destinationFeatures = getDestinationFeatures();
|
|
12808
|
+
setDestinationFeatures(destinationFeatures);
|
|
12809
|
+
setSelectedDestinationFeature(destinationFeatures[0]);
|
|
12810
|
+
}, [
|
|
12811
|
+
checkedChildrens,
|
|
12812
|
+
featureTypeOntology,
|
|
12813
|
+
features,
|
|
12814
|
+
parentFeatureChecked,
|
|
12815
|
+
region,
|
|
12816
|
+
]);
|
|
12582
12817
|
const handleParentFeatureCheck = (event) => {
|
|
12583
12818
|
const isChecked = event.target.checked;
|
|
12584
12819
|
setParentFeatureChecked(isChecked);
|
|
@@ -12858,8 +13093,21 @@ function annotationFromPileup(pluggableElement) {
|
|
|
12858
13093
|
getAnnotationFeature(jbrowseFeature, refSeqId) {
|
|
12859
13094
|
const start = jbrowseFeature.get('start');
|
|
12860
13095
|
const end = jbrowseFeature.get('end');
|
|
12861
|
-
const strand = jbrowseFeature.get('strand');
|
|
12862
13096
|
const name = jbrowseFeature.get('name');
|
|
13097
|
+
let strand;
|
|
13098
|
+
const tags = jbrowseFeature.get('tags');
|
|
13099
|
+
const { ts, TS, XS } = tags;
|
|
13100
|
+
for (const tag of [ts, TS, XS]) {
|
|
13101
|
+
if (!strand && tag) {
|
|
13102
|
+
if ([1, '1', '+'].includes(tag)) {
|
|
13103
|
+
strand = 1;
|
|
13104
|
+
}
|
|
13105
|
+
else if ([-1, '-1', '-'].includes(tag)) {
|
|
13106
|
+
strand = -1;
|
|
13107
|
+
}
|
|
13108
|
+
}
|
|
13109
|
+
}
|
|
13110
|
+
strand ??= jbrowseFeature.get('strand');
|
|
12863
13111
|
const cigarData = jbrowseFeature.get('CIGAR');
|
|
12864
13112
|
const ops = parseCigar(cigarData);
|
|
12865
13113
|
let position = start;
|
|
@@ -12875,9 +13123,7 @@ function annotationFromPileup(pluggableElement) {
|
|
|
12875
13123
|
case 'M':
|
|
12876
13124
|
case '=':
|
|
12877
13125
|
case 'X': {
|
|
12878
|
-
|
|
12879
|
-
currentExonStart = position;
|
|
12880
|
-
}
|
|
13126
|
+
currentExonStart ??= position;
|
|
12881
13127
|
position += len;
|
|
12882
13128
|
break;
|
|
12883
13129
|
}
|
|
@@ -13291,7 +13537,7 @@ const LinearApolloDisplay = observer(function LinearApolloDisplay(props) {
|
|
|
13291
13537
|
: undefined, menuItems: contextMenuItems })] }))] }));
|
|
13292
13538
|
});
|
|
13293
13539
|
|
|
13294
|
-
const TrackLines = observer(function TrackLines({ model, hrStyle = { margin: 0, top: 0, color: 'black' }, idx
|
|
13540
|
+
const TrackLines = observer(function TrackLines({ model, hrStyle = { margin: 0, top: 0, color: 'black' }, idx, }) {
|
|
13295
13541
|
const { apolloRowHeight, highestRow, showFeatureLabels } = model;
|
|
13296
13542
|
const featureLabelSpacer = showFeatureLabels ? 2 : 1;
|
|
13297
13543
|
return (jsx("div", { style: {
|
|
@@ -13363,8 +13609,7 @@ const LinearApolloSixFrameDisplay = observer(function LinearApolloSixFrameDispla
|
|
|
13363
13609
|
regionNumber: idx,
|
|
13364
13610
|
})?.offsetPx ?? 0) - lgv.offsetPx;
|
|
13365
13611
|
const [feature] = checkResult.featureIds;
|
|
13366
|
-
if (!feature ||
|
|
13367
|
-
!feature.parent ||
|
|
13612
|
+
if (!feature?.parent ||
|
|
13368
13613
|
!looksLikeGene(feature.parent, session)) {
|
|
13369
13614
|
return null;
|
|
13370
13615
|
}
|
|
@@ -13616,18 +13861,15 @@ function addTopLevelMenus(rootModel) {
|
|
|
13616
13861
|
}
|
|
13617
13862
|
}
|
|
13618
13863
|
|
|
13619
|
-
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
13620
13864
|
/* eslint-disable @typescript-eslint/no-confusing-void-expression */
|
|
13621
13865
|
const ApolloJobModel = types
|
|
13622
13866
|
.model('JobsManager', {})
|
|
13623
13867
|
.views((self) => ({
|
|
13624
13868
|
get jobStatusWidget() {
|
|
13625
13869
|
const { widgets } = getSession(self);
|
|
13626
|
-
|
|
13627
|
-
if (!jobStatusWidget) {
|
|
13870
|
+
const jobStatusWidget = widgets.get('JobsList') ??
|
|
13628
13871
|
// @ts-expect-error: addWidget function not detected on the session
|
|
13629
|
-
|
|
13630
|
-
}
|
|
13872
|
+
getSession(self).addWidget('JobsListWidget', 'JobsList');
|
|
13631
13873
|
return jobStatusWidget;
|
|
13632
13874
|
},
|
|
13633
13875
|
}))
|
|
@@ -13906,19 +14148,15 @@ function clientDataStoreFactory(AnnotationFeatureExtended) {
|
|
|
13906
14148
|
continue;
|
|
13907
14149
|
}
|
|
13908
14150
|
const { assemblyName, refName } = region;
|
|
13909
|
-
|
|
13910
|
-
|
|
13911
|
-
assembly = self.assemblies.put({ _id: assemblyName, refSeqs: {} });
|
|
13912
|
-
}
|
|
14151
|
+
const assembly = self.assemblies.get(assemblyName) ??
|
|
14152
|
+
self.assemblies.put({ _id: assemblyName, refSeqs: {} });
|
|
13913
14153
|
const [firstFeature] = features;
|
|
13914
|
-
|
|
13915
|
-
|
|
13916
|
-
ref = assembly.refSeqs.put({
|
|
14154
|
+
const ref = assembly.refSeqs.get(firstFeature.refSeq) ??
|
|
14155
|
+
assembly.refSeqs.put({
|
|
13917
14156
|
_id: firstFeature.refSeq,
|
|
13918
14157
|
name: refName,
|
|
13919
14158
|
features: {},
|
|
13920
14159
|
});
|
|
13921
|
-
}
|
|
13922
14160
|
for (const feature of features) {
|
|
13923
14161
|
if (!ref.features.has(feature._id)) {
|
|
13924
14162
|
ref.features.put(feature);
|
|
@@ -13935,24 +14173,153 @@ function clientDataStoreFactory(AnnotationFeatureExtended) {
|
|
|
13935
14173
|
}
|
|
13936
14174
|
const { refSeq, seq } = yield backendDriver.getSequence(region);
|
|
13937
14175
|
const { assemblyName, end, refName, start } = region;
|
|
13938
|
-
|
|
13939
|
-
|
|
13940
|
-
|
|
13941
|
-
|
|
13942
|
-
let ref = assembly.refSeqs.get(refSeq);
|
|
13943
|
-
if (!ref) {
|
|
13944
|
-
ref = assembly.refSeqs.put({
|
|
13945
|
-
_id: refSeq,
|
|
13946
|
-
name: refName,
|
|
13947
|
-
sequence: [],
|
|
13948
|
-
});
|
|
13949
|
-
}
|
|
14176
|
+
const assembly = self.assemblies.get(assemblyName) ??
|
|
14177
|
+
self.assemblies.put({ _id: assemblyName, refSeqs: {} });
|
|
14178
|
+
const ref = assembly.refSeqs.get(refSeq) ??
|
|
14179
|
+
assembly.refSeqs.put({ _id: refSeq, name: refName, sequence: [] });
|
|
13950
14180
|
ref.addSequence({ start, stop: end, sequence: seq });
|
|
13951
14181
|
}
|
|
13952
14182
|
}),
|
|
13953
14183
|
}));
|
|
13954
14184
|
}
|
|
13955
14185
|
|
|
14186
|
+
function reassignIds(feature) {
|
|
14187
|
+
const newChildren = {};
|
|
14188
|
+
if (feature.children) {
|
|
14189
|
+
for (const child of Object.values(feature.children)) {
|
|
14190
|
+
const newChild = reassignIds(child);
|
|
14191
|
+
newChildren[newChild._id] = newChild;
|
|
14192
|
+
}
|
|
14193
|
+
}
|
|
14194
|
+
return {
|
|
14195
|
+
...feature,
|
|
14196
|
+
_id: new ObjectID().toHexString(),
|
|
14197
|
+
children: feature.children ? newChildren : undefined,
|
|
14198
|
+
};
|
|
14199
|
+
}
|
|
14200
|
+
function DuplicateFeatureDialog({ assemblyName, changeManager, existingFeature, featureSnapshot, handleClose, }) {
|
|
14201
|
+
const [choice, setChoice] = useState('keepExisting');
|
|
14202
|
+
const [errorMessage, setErrorMessage] = useState('');
|
|
14203
|
+
const [submitted, setSubmitted] = useState(false);
|
|
14204
|
+
const handleSubmit = async () => {
|
|
14205
|
+
setErrorMessage('');
|
|
14206
|
+
setSubmitted(true);
|
|
14207
|
+
try {
|
|
14208
|
+
if (choice === 'useNew') {
|
|
14209
|
+
const deleteChange = new DeleteFeatureChange({
|
|
14210
|
+
typeName: 'DeleteFeatureChange',
|
|
14211
|
+
assembly: assemblyName,
|
|
14212
|
+
changedIds: [existingFeature._id],
|
|
14213
|
+
deletedFeature: existingFeature,
|
|
14214
|
+
});
|
|
14215
|
+
await changeManager.submit(deleteChange);
|
|
14216
|
+
const addChange = new AddFeatureChange({
|
|
14217
|
+
typeName: 'AddFeatureChange',
|
|
14218
|
+
assembly: assemblyName,
|
|
14219
|
+
changedIds: [featureSnapshot._id],
|
|
14220
|
+
addedFeature: featureSnapshot,
|
|
14221
|
+
});
|
|
14222
|
+
await changeManager.submit(addChange);
|
|
14223
|
+
}
|
|
14224
|
+
else if (choice === 'keepBoth') {
|
|
14225
|
+
const newSnapshot = reassignIds(featureSnapshot);
|
|
14226
|
+
const addChange = new AddFeatureChange({
|
|
14227
|
+
typeName: 'AddFeatureChange',
|
|
14228
|
+
assembly: assemblyName,
|
|
14229
|
+
changedIds: [newSnapshot._id],
|
|
14230
|
+
addedFeature: newSnapshot,
|
|
14231
|
+
});
|
|
14232
|
+
await changeManager.submit(addChange);
|
|
14233
|
+
}
|
|
14234
|
+
handleClose();
|
|
14235
|
+
}
|
|
14236
|
+
catch (error) {
|
|
14237
|
+
setErrorMessage(String(error));
|
|
14238
|
+
setSubmitted(false);
|
|
14239
|
+
}
|
|
14240
|
+
};
|
|
14241
|
+
return (jsxs(Dialog, { open: true, title: "Duplicate Feature Detected", handleClose: handleClose, maxWidth: "sm", fullWidth: true, children: [jsxs(DialogContent, { children: [jsxs(DialogContentText, { children: ["A feature with ID \u201C", featureSnapshot._id, "\u201D already exists. How would you like to resolve this conflict?"] }), jsxs(FormControl, { component: "fieldset", sx: { mt: 2 }, children: [jsx(FormLabel, { component: "legend", children: "Resolution" }), jsxs(RadioGroup, { value: choice, onChange: (e) => {
|
|
14242
|
+
setChoice(e.target.value);
|
|
14243
|
+
}, children: [jsx(FormControlLabel, { value: "keepExisting", control: jsx(Radio, {}), label: "Keep the existing feature" }), jsx(FormControlLabel, { value: "useNew", control: jsx(Radio, {}), label: "Replace with the new feature" }), jsx(FormControlLabel, { value: "keepBoth", control: jsx(Radio, {}), label: "Keep both (assign new IDs to the incoming feature)" })] })] }), errorMessage ? (jsx(DialogContentText, { color: "error", sx: { mt: 1 }, children: errorMessage })) : null] }), jsxs(DialogActions, { children: [jsx(Button, { variant: "contained", onClick: handleSubmit, disabled: submitted, children: "Confirm" }), jsx(Button, { onClick: handleClose, disabled: submitted, children: "Cancel" })] })] }));
|
|
14244
|
+
}
|
|
14245
|
+
|
|
14246
|
+
function fromUrlSafeBase64(urlSafeBase64) {
|
|
14247
|
+
let base64 = urlSafeBase64.replaceAll('-', '+').replaceAll('_', '/');
|
|
14248
|
+
const pad = base64.length % 4;
|
|
14249
|
+
if (pad) {
|
|
14250
|
+
base64 += '='.repeat(4 - pad);
|
|
14251
|
+
}
|
|
14252
|
+
return base64;
|
|
14253
|
+
}
|
|
14254
|
+
// Base64 decode + decompress
|
|
14255
|
+
async function decompress(encoded) {
|
|
14256
|
+
const binaryString = atob(encoded);
|
|
14257
|
+
// eslint-disable-next-line unicorn/prefer-code-point
|
|
14258
|
+
const bytes = Uint8Array.from(binaryString, (char) => char.charCodeAt(0));
|
|
14259
|
+
const stream = new Blob([bytes]).stream();
|
|
14260
|
+
const decompressionStream = new DecompressionStream('gzip');
|
|
14261
|
+
const decompressedStream = stream.pipeThrough(decompressionStream);
|
|
14262
|
+
const response = new Response(decompressedStream);
|
|
14263
|
+
const decompressed = await response.arrayBuffer();
|
|
14264
|
+
return JSON.parse(new TextDecoder().decode(decompressed));
|
|
14265
|
+
}
|
|
14266
|
+
async function handleApolloFeaturesUrlParam(encodedFeatures, apolloDataStore, assemblyManager, session) {
|
|
14267
|
+
const base64 = fromUrlSafeBase64(encodedFeatures);
|
|
14268
|
+
const decompressed = await decompress(base64);
|
|
14269
|
+
const featuresData = decompressed;
|
|
14270
|
+
for (const [assemblyName, featureSnapshots] of Object.entries(featuresData)) {
|
|
14271
|
+
const backendDriver = apolloDataStore.getBackendDriver(assemblyName);
|
|
14272
|
+
if (!(backendDriver instanceof LocalDriver)) {
|
|
14273
|
+
continue;
|
|
14274
|
+
}
|
|
14275
|
+
const assembly = (await assemblyManager.waitForAssembly(assemblyName));
|
|
14276
|
+
if (!assembly) {
|
|
14277
|
+
throw new Error(`Assembly not found: "${assemblyName}"`);
|
|
14278
|
+
}
|
|
14279
|
+
const { regions } = assembly;
|
|
14280
|
+
if (!regions) {
|
|
14281
|
+
throw new Error(`Assembly not found: "${assemblyName}"`);
|
|
14282
|
+
}
|
|
14283
|
+
const refNames = regions.map((r) => r.refName);
|
|
14284
|
+
const db = await openDb(assemblyName, refNames);
|
|
14285
|
+
for (const featureSnapshot of featureSnapshots) {
|
|
14286
|
+
const storeName = `features-${featureSnapshot.refSeq}`;
|
|
14287
|
+
const existing = (await db.get(storeName, featureSnapshot._id));
|
|
14288
|
+
if (existing === undefined) {
|
|
14289
|
+
const change = new AddFeatureChange({
|
|
14290
|
+
typeName: 'AddFeatureChange',
|
|
14291
|
+
assembly: assemblyName,
|
|
14292
|
+
changedIds: [featureSnapshot._id],
|
|
14293
|
+
addedFeature: featureSnapshot,
|
|
14294
|
+
});
|
|
14295
|
+
await apolloDataStore.changeManager.submit(change);
|
|
14296
|
+
}
|
|
14297
|
+
else {
|
|
14298
|
+
// get rid of undefined values in JSON
|
|
14299
|
+
// eslint-disable-next-line unicorn/prefer-structured-clone
|
|
14300
|
+
const existingFeature = JSON.parse(JSON.stringify(existing));
|
|
14301
|
+
if (!equal(featureSnapshot, existingFeature)) {
|
|
14302
|
+
await new Promise((resolve) => {
|
|
14303
|
+
session.queueDialog((doneCallback) => [
|
|
14304
|
+
DuplicateFeatureDialog,
|
|
14305
|
+
{
|
|
14306
|
+
featureSnapshot,
|
|
14307
|
+
existingFeature,
|
|
14308
|
+
assemblyName,
|
|
14309
|
+
changeManager: apolloDataStore.changeManager,
|
|
14310
|
+
handleClose: () => {
|
|
14311
|
+
doneCallback();
|
|
14312
|
+
resolve();
|
|
14313
|
+
},
|
|
14314
|
+
},
|
|
14315
|
+
]);
|
|
14316
|
+
});
|
|
14317
|
+
}
|
|
14318
|
+
}
|
|
14319
|
+
}
|
|
14320
|
+
}
|
|
14321
|
+
}
|
|
14322
|
+
|
|
13956
14323
|
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
13957
14324
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
13958
14325
|
function extendSession(pluginManager, sessionModel) {
|
|
@@ -14038,8 +14405,7 @@ function extendSession(pluginManager, sessionModel) {
|
|
|
14038
14405
|
for (const block of dynamicBlocks.contentBlocks) {
|
|
14039
14406
|
const { assemblyName, end, refName, start } = block;
|
|
14040
14407
|
const assembly = self.apolloDataStore.assemblies.get(assemblyName);
|
|
14041
|
-
if (assembly
|
|
14042
|
-
assembly.backendDriverType === 'CollaborationServerDriver') {
|
|
14408
|
+
if (assembly?.backendDriverType === 'CollaborationServerDriver') {
|
|
14043
14409
|
locations.push({ assemblyName, refName, start, end });
|
|
14044
14410
|
}
|
|
14045
14411
|
}
|
|
@@ -14098,8 +14464,8 @@ function extendSession(pluginManager, sessionModel) {
|
|
|
14098
14464
|
for (const block of dynamicBlocks.contentBlocks) {
|
|
14099
14465
|
const { assemblyName, end, refName, start } = block;
|
|
14100
14466
|
const assembly = self.apolloDataStore.assemblies.get(assemblyName);
|
|
14101
|
-
if (assembly
|
|
14102
|
-
|
|
14467
|
+
if (assembly?.backendDriverType ===
|
|
14468
|
+
'CollaborationServerDriver') {
|
|
14103
14469
|
locations.push({ assemblyName, refName, start, end });
|
|
14104
14470
|
}
|
|
14105
14471
|
}
|
|
@@ -14219,6 +14585,37 @@ function extendSession(pluginManager, sessionModel) {
|
|
|
14219
14585
|
beforeDestroy() {
|
|
14220
14586
|
self.abortController.abort(new DOMException('Clean up Apollo session', 'AbortError'));
|
|
14221
14587
|
},
|
|
14588
|
+
}))
|
|
14589
|
+
.actions((self) => ({
|
|
14590
|
+
async afterCreate() {
|
|
14591
|
+
if (isElectron) {
|
|
14592
|
+
return;
|
|
14593
|
+
}
|
|
14594
|
+
const url = new URL(globalThis.location.href);
|
|
14595
|
+
const apolloFeatures = url.searchParams.get('apolloFeatures');
|
|
14596
|
+
if (!apolloFeatures) {
|
|
14597
|
+
return;
|
|
14598
|
+
}
|
|
14599
|
+
const { assemblyManager } = self;
|
|
14600
|
+
await handleApolloFeaturesUrlParam(apolloFeatures, self.apolloDataStore, assemblyManager, self);
|
|
14601
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
14602
|
+
const updatedURL = new URL(globalThis.location.href);
|
|
14603
|
+
updatedURL.searchParams.delete('apolloFeatures');
|
|
14604
|
+
globalThis.history.replaceState(null, '', updatedURL.toString());
|
|
14605
|
+
},
|
|
14606
|
+
afterAttach() {
|
|
14607
|
+
addDisposer(self, autorun((reaction) => {
|
|
14608
|
+
const { focusedViewId, activeWidgets } = self;
|
|
14609
|
+
if (!(focusedViewId && activeWidgets)) {
|
|
14610
|
+
return;
|
|
14611
|
+
}
|
|
14612
|
+
const trackSelector = activeWidgets.get('hierarchicalTrackSelector');
|
|
14613
|
+
// @ts-expect-error Don't have type for track selector
|
|
14614
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
14615
|
+
trackSelector?.setView(focusedViewId);
|
|
14616
|
+
reaction.dispose();
|
|
14617
|
+
}));
|
|
14618
|
+
},
|
|
14222
14619
|
}))
|
|
14223
14620
|
.views((self) => {
|
|
14224
14621
|
const superTrackActions = self
|
|
@@ -14350,7 +14747,6 @@ function extendSession(pluginManager, sessionModel) {
|
|
|
14350
14747
|
}
|
|
14351
14748
|
|
|
14352
14749
|
/* eslint-disable @typescript-eslint/unbound-method */
|
|
14353
|
-
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
14354
14750
|
/* eslint-disable @typescript-eslint/no-misused-promises */
|
|
14355
14751
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
14356
14752
|
function isApolloMessageData(data) {
|
|
@@ -14538,10 +14934,10 @@ class ApolloPlugin extends Plugin {
|
|
|
14538
14934
|
});
|
|
14539
14935
|
if (!inWebWorker) {
|
|
14540
14936
|
pluginManager.addToExtensionPoint('Core-extendWorker', (handle) => {
|
|
14541
|
-
if (!('on' in handle && handle.on)) {
|
|
14937
|
+
if (!('on' in handle.client && handle.client.on)) {
|
|
14542
14938
|
return handle;
|
|
14543
14939
|
}
|
|
14544
|
-
handle.on('apollo', async (event) => {
|
|
14940
|
+
handle.client.on('apollo', async (event) => {
|
|
14545
14941
|
if (!isApolloMessageData(event)) {
|
|
14546
14942
|
return;
|
|
14547
14943
|
}
|
|
@@ -14559,7 +14955,7 @@ class ApolloPlugin extends Plugin {
|
|
|
14559
14955
|
break;
|
|
14560
14956
|
}
|
|
14561
14957
|
const { seq: sequence } = await backendDriver.getSequence(region);
|
|
14562
|
-
handle.
|
|
14958
|
+
handle.worker.postMessage({
|
|
14563
14959
|
apollo,
|
|
14564
14960
|
messageId,
|
|
14565
14961
|
sequence,
|
|
@@ -14577,7 +14973,7 @@ class ApolloPlugin extends Plugin {
|
|
|
14577
14973
|
break;
|
|
14578
14974
|
}
|
|
14579
14975
|
const regions = await backendDriver.getRegions(assembly);
|
|
14580
|
-
handle.
|
|
14976
|
+
handle.worker.postMessage({
|
|
14581
14977
|
apollo,
|
|
14582
14978
|
messageId,
|
|
14583
14979
|
regions,
|
|
@@ -14595,7 +14991,7 @@ class ApolloPlugin extends Plugin {
|
|
|
14595
14991
|
break;
|
|
14596
14992
|
}
|
|
14597
14993
|
const refNameAliases = await backendDriver.getRefNameAliases(assembly);
|
|
14598
|
-
handle.
|
|
14994
|
+
handle.worker.postMessage({
|
|
14599
14995
|
apollo,
|
|
14600
14996
|
messageId,
|
|
14601
14997
|
refNameAliases,
|