@apollo-annotation/jbrowse-plugin-apollo 0.1.21 → 0.2.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/index.esm.js +431 -570
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +439 -578
- 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 +11064 -1091
- 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/package.json +4 -5
- package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +4 -2
- package/src/ApolloInternetAccount/configSchema.ts +1 -1
- package/src/ApolloInternetAccount/model.ts +5 -10
- package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +1 -1
- package/src/ApolloSixFrameRenderer/components/ApolloRendering.tsx +4 -5
- package/src/BackendDrivers/DesktopFileDriver.ts +3 -2
- package/src/FeatureDetailsWidget/Attributes.tsx +1 -6
- package/src/FeatureDetailsWidget/NumberTextField.tsx +1 -0
- package/src/FeatureDetailsWidget/StringTextField.tsx +1 -0
- package/src/FeatureDetailsWidget/TranscriptBasic.tsx +131 -382
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +209 -284
- package/src/FeatureDetailsWidget/model.ts +4 -4
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +1 -0
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +25 -3
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +95 -32
- package/src/LinearApolloDisplay/stateModel/base.ts +5 -3
- package/src/LinearApolloDisplay/stateModel/index.ts +1 -1
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +1 -1
- package/src/LinearApolloDisplay/stateModel/rendering.ts +1 -1
- package/src/OntologyManager/OntologyStore/fulltext.ts +5 -2
- package/src/OntologyManager/OntologyStore/index.ts +25 -22
- package/src/OntologyManager/OntologyStore/indexeddb-storage.ts +8 -3
- package/src/OntologyManager/index.ts +31 -8
- package/src/SixFrameFeatureDisplay/stateModel.ts +1 -1
- package/src/TabularEditor/HybridGrid/HybridGrid.tsx +1 -0
- package/src/TabularEditor/HybridGrid/NumberCell.tsx +1 -0
- package/src/TabularEditor/model.ts +1 -1
- package/src/components/AddChildFeature.tsx +1 -0
- package/src/components/AddFeature.tsx +1 -1
- package/src/components/AddRefSeqAliases.tsx +1 -0
- package/src/components/CopyFeature.tsx +1 -0
- package/src/components/DeleteAssembly.tsx +1 -0
- package/src/components/DeleteFeature.tsx +1 -0
- package/src/components/LogOut.tsx +2 -1
- package/src/components/ManageChecks.tsx +1 -1
- package/src/components/ManageUsers.tsx +2 -1
- package/src/components/OntologyTermAutocomplete.tsx +7 -9
- package/src/components/OntologyTermMultiSelect.tsx +2 -1
- package/src/components/OpenLocalFile.tsx +3 -1
- package/src/components/ViewChangeLog.tsx +1 -0
- package/src/components/ViewCheckResults.tsx +1 -0
- package/src/config.ts +5 -0
- package/src/extensions/annotationFromPileup.ts +1 -1
- package/src/makeDisplayComponent.tsx +28 -7
- package/src/session/ClientDataStore.ts +1 -1
- package/src/session/session.ts +2 -1
package/dist/index.esm.js
CHANGED
|
@@ -3,7 +3,7 @@ import { gff3ToAnnotationFeature, AddAssemblyFromExternalChange, AddAssemblyAndF
|
|
|
3
3
|
import { ConfigurationSchema, readConfObject, getConf, ConfigurationReference } from '@jbrowse/core/configuration';
|
|
4
4
|
import { BaseInternetAccountConfig, InternetAccount, RendererType, TextSearchAdapterType, BaseDisplay, WidgetType, createBaseTrackConfig, TrackType, createBaseTrackModel, InternetAccountType, DisplayType } from '@jbrowse/core/pluggableElementTypes';
|
|
5
5
|
import Plugin from '@jbrowse/core/Plugin';
|
|
6
|
-
import {
|
|
6
|
+
import { isUriLocation, isLocalPathLocation, isElectron, isAbstractMenuManager, getSession, getContainingView, getFrame, revcom, isSessionModelWithWidgets, doesIntersect2, defaultCodonTable, intersection2, reverse, defaultStarts, defaultStops } from '@jbrowse/core/util';
|
|
7
7
|
import AddIcon from '@mui/icons-material/Add';
|
|
8
8
|
import { autorun, toJS, observable } from 'mobx';
|
|
9
9
|
import { getSnapshot, getParent, getRoot, types, addDisposer, flow, isAlive, resolveIdentifier, getParentOfType, applySnapshot } from 'mobx-state-tree';
|
|
@@ -21,10 +21,11 @@ import CloseIcon from '@mui/icons-material/Close';
|
|
|
21
21
|
import { observer } from 'mobx-react';
|
|
22
22
|
import { makeStyles } from 'tss-react/mui';
|
|
23
23
|
import { LocalPathLocation, UriLocation, BlobLocation, ElementId } from '@jbrowse/core/util/types/mst';
|
|
24
|
+
import { openDB, deleteDB } from 'idb/with-async-ittr';
|
|
25
|
+
import { checkAbortSignal, isAbortException } from '@jbrowse/core/util/aborting';
|
|
24
26
|
import jsonpath from 'jsonpath';
|
|
25
27
|
import { openLocation } from '@jbrowse/core/util/io';
|
|
26
28
|
import equal from 'fast-deep-equal/es6';
|
|
27
|
-
import { openDB } from 'idb/with-async-ittr';
|
|
28
29
|
import { saveAs } from 'file-saver';
|
|
29
30
|
import Checkbox$1 from '@mui/material/Checkbox';
|
|
30
31
|
import FormControlLabel$1 from '@mui/material/FormControlLabel';
|
|
@@ -49,7 +50,7 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
|
|
49
50
|
import ErrorIcon from '@mui/icons-material/Error';
|
|
50
51
|
import SaveIcon from '@mui/icons-material/Save';
|
|
51
52
|
|
|
52
|
-
var version = "0.
|
|
53
|
+
var version = "0.2.0";
|
|
53
54
|
|
|
54
55
|
const ApolloConfigSchema = ConfigurationSchema('ApolloInternetAccount', {
|
|
55
56
|
baseURL: {
|
|
@@ -685,7 +686,7 @@ function elaborateMatch(textIndexPaths, term, queryWordIndexes, queryWords, pref
|
|
|
685
686
|
const sortedWordIndexes = [...queryWordIndexes].sort();
|
|
686
687
|
const matchedQueryWords = sortedWordIndexes.map((i) => queryWords[i]);
|
|
687
688
|
const queryWordRegexps = matchedQueryWords.map((queryWord) => {
|
|
688
|
-
const escaped = queryWord.replaceAll(/[$()*+./?[\\\]^{|}-]/g,
|
|
689
|
+
const escaped = queryWord.replaceAll(/[$()*+./?[\\\]^{|}-]/g, String.raw `\$&`);
|
|
689
690
|
return new RegExp(`\\b${escaped}`, 'gi');
|
|
690
691
|
});
|
|
691
692
|
// const needle = matchedQueryWords.join(' ')
|
|
@@ -832,7 +833,13 @@ async function loadOboGraphJson(db) {
|
|
|
832
833
|
// TODO: using file streaming along with an event-based json parser
|
|
833
834
|
// instead of JSON.parse and .readFile could probably make this faster
|
|
834
835
|
// and less memory intensive
|
|
835
|
-
|
|
836
|
+
let oboGraph;
|
|
837
|
+
try {
|
|
838
|
+
oboGraph = JSON.parse(await openLocation(this.sourceLocation).readFile('utf8'));
|
|
839
|
+
}
|
|
840
|
+
catch {
|
|
841
|
+
throw new Error('Error in loading ontology');
|
|
842
|
+
}
|
|
836
843
|
const parseTime = Date.now();
|
|
837
844
|
const [graph, ...additionalGraphs] = oboGraph.graphs ?? [];
|
|
838
845
|
if (!graph) {
|
|
@@ -911,12 +918,6 @@ async function isDatabaseCurrent(db) {
|
|
|
911
918
|
}
|
|
912
919
|
|
|
913
920
|
/* eslint-disable @typescript-eslint/only-throw-error */
|
|
914
|
-
/**
|
|
915
|
-
* @deprecated use the one from jbrowse core when it is published
|
|
916
|
-
**/
|
|
917
|
-
function isLocalPathLocation(location) {
|
|
918
|
-
return (typeof location === 'object' && location !== null && 'localPath' in location);
|
|
919
|
-
}
|
|
920
921
|
async function arrayFromAsync(iter) {
|
|
921
922
|
const a = [];
|
|
922
923
|
for await (const i of iter) {
|
|
@@ -991,14 +992,21 @@ class OntologyStore {
|
|
|
991
992
|
if (await this.isDatabaseCurrent(db)) {
|
|
992
993
|
return db;
|
|
993
994
|
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
995
|
+
try {
|
|
996
|
+
const { sourceLocation, sourceType } = this;
|
|
997
|
+
if (sourceType === 'obo-graph-json') {
|
|
998
|
+
await this.loadOboGraphJson(db);
|
|
999
|
+
}
|
|
1000
|
+
else {
|
|
1001
|
+
throw new Error(`ontology source file ${JSON.stringify(sourceLocation)} has type ${sourceType}, which is not yet supported`);
|
|
1002
|
+
}
|
|
1003
|
+
return db;
|
|
997
1004
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1005
|
+
catch (error) {
|
|
1006
|
+
db.close();
|
|
1007
|
+
await deleteDB(this.dbName);
|
|
1008
|
+
throw error;
|
|
1000
1009
|
}
|
|
1001
|
-
return db;
|
|
1002
1010
|
}
|
|
1003
1011
|
async termCount(tx) {
|
|
1004
1012
|
const db = await this.db;
|
|
@@ -1176,7 +1184,7 @@ class OntologyStore {
|
|
|
1176
1184
|
}
|
|
1177
1185
|
// fetch the full nodes and filter out deprecated ones
|
|
1178
1186
|
const terms = [];
|
|
1179
|
-
for
|
|
1187
|
+
for (const termId of termIds) {
|
|
1180
1188
|
const node = await myTx.objectStore('nodes').get(termId);
|
|
1181
1189
|
if (node && isOntologyClass(node) && !isDeprecated(node)) {
|
|
1182
1190
|
terms.push(node);
|
|
@@ -1234,15 +1242,22 @@ const OntologyManagerType = types
|
|
|
1234
1242
|
'SO:': 'http://purl.obolibrary.org/obo/SO_',
|
|
1235
1243
|
}),
|
|
1236
1244
|
})
|
|
1245
|
+
.views((self) => ({
|
|
1246
|
+
get featureTypeOntologyName() {
|
|
1247
|
+
const jbConfig = getRoot(self).jbrowse
|
|
1248
|
+
.configuration;
|
|
1249
|
+
const pluginConfiguration = jbConfig.ApolloPlugin;
|
|
1250
|
+
const featureTypeOntologyName = readConfObject(pluginConfiguration, 'featureTypeOntologyName');
|
|
1251
|
+
return featureTypeOntologyName;
|
|
1252
|
+
},
|
|
1253
|
+
}))
|
|
1237
1254
|
.views((self) => ({
|
|
1238
1255
|
/**
|
|
1239
1256
|
* gets the OntologyRecord for the ontology we should be
|
|
1240
1257
|
* using for feature types (e.g. SO or maybe biotypes)
|
|
1241
1258
|
**/
|
|
1242
1259
|
get featureTypeOntology() {
|
|
1243
|
-
|
|
1244
|
-
// we should be using. currently hardcoded to use SO.
|
|
1245
|
-
return this.findOntology('Sequence Ontology');
|
|
1260
|
+
return this.findOntology(self.featureTypeOntologyName);
|
|
1246
1261
|
},
|
|
1247
1262
|
findOntology(name, version) {
|
|
1248
1263
|
return self.ontologies.find((record) => {
|
|
@@ -1354,7 +1369,6 @@ function OntologyTermAutocomplete({ fetchValidTerms, filterTerms: filterTermsPro
|
|
|
1354
1369
|
// effect for clearing choices when not open
|
|
1355
1370
|
useEffect(() => {
|
|
1356
1371
|
if (!open) {
|
|
1357
|
-
// eslint-disable-next-line unicorn/no-useless-undefined
|
|
1358
1372
|
setTermChoices(undefined);
|
|
1359
1373
|
}
|
|
1360
1374
|
}, [open]);
|
|
@@ -1389,7 +1403,7 @@ function OntologyTermAutocomplete({ fetchValidTerms, filterTerms: filterTermsPro
|
|
|
1389
1403
|
}
|
|
1390
1404
|
}, (error) => {
|
|
1391
1405
|
if (!signal.aborted && !isAbortException(error)) {
|
|
1392
|
-
session.notify(error.message, 'error');
|
|
1406
|
+
session.notify(error instanceof Error ? error.message : String(error), 'error');
|
|
1393
1407
|
}
|
|
1394
1408
|
});
|
|
1395
1409
|
}
|
|
@@ -1408,7 +1422,6 @@ function OntologyTermAutocomplete({ fetchValidTerms, filterTerms: filterTermsPro
|
|
|
1408
1422
|
return;
|
|
1409
1423
|
}
|
|
1410
1424
|
if (typeof newValue === 'string') {
|
|
1411
|
-
// eslint-disable-next-line unicorn/no-useless-undefined
|
|
1412
1425
|
setCurrentOntologyTerm(undefined);
|
|
1413
1426
|
onChange(valueString, newValue);
|
|
1414
1427
|
}
|
|
@@ -1443,7 +1456,7 @@ async function getCurrentTerm(ontologyStore, currentTermLabel, filterTerms, _sig
|
|
|
1443
1456
|
}
|
|
1444
1457
|
// TODO: support prefixed IDs as ontology terms here (e.g. SO:001234)
|
|
1445
1458
|
const terms = await ontologyStore.getTermsWithLabelOrSynonym(currentTermLabel, { includeSubclasses: false });
|
|
1446
|
-
const term = terms.find(filterTerms ?? (() => true));
|
|
1459
|
+
const term = terms.find((term) => (filterTerms ?? (() => true))(term));
|
|
1447
1460
|
if (!term) {
|
|
1448
1461
|
throw new Error(`not a valid ${ontologyStore.ontologyName} term`);
|
|
1449
1462
|
}
|
|
@@ -1525,7 +1538,7 @@ function AddChildFeature({ changeManager, handleClose, session, sourceAssemblyId
|
|
|
1525
1538
|
React__default.createElement(DialogContentText, { color: "error" }, errorMessage))) : null));
|
|
1526
1539
|
}
|
|
1527
1540
|
|
|
1528
|
-
/* eslint-disable @typescript-eslint/
|
|
1541
|
+
/* eslint-disable @typescript-eslint/unbound-method */
|
|
1529
1542
|
function AddFeature({ changeManager, handleClose, region, session, }) {
|
|
1530
1543
|
const { notify } = session;
|
|
1531
1544
|
const [end, setEnd] = useState(String(region.end));
|
|
@@ -1585,7 +1598,6 @@ function AddFeature({ changeManager, handleClose, region, session, }) {
|
|
|
1585
1598
|
break;
|
|
1586
1599
|
}
|
|
1587
1600
|
default: {
|
|
1588
|
-
// eslint-disable-next-line unicorn/no-useless-undefined
|
|
1589
1601
|
setStrand(undefined);
|
|
1590
1602
|
}
|
|
1591
1603
|
}
|
|
@@ -1800,7 +1812,7 @@ function CopyFeature({ changeManager, handleClose, session, sourceAssemblyId, so
|
|
|
1800
1812
|
React__default.createElement(DialogContentText, { color: "error" }, errorMessage))) : null));
|
|
1801
1813
|
}
|
|
1802
1814
|
|
|
1803
|
-
/* eslint-disable @typescript-eslint/
|
|
1815
|
+
/* eslint-disable @typescript-eslint/unbound-method */
|
|
1804
1816
|
function DeleteAssembly({ changeManager, handleClose, session, }) {
|
|
1805
1817
|
const { internetAccounts } = getRoot(session);
|
|
1806
1818
|
const [selectedAssembly, setSelectedAssembly] = useState();
|
|
@@ -2183,6 +2195,7 @@ function ImportFeatures({ changeManager, handleClose, session, }) {
|
|
|
2183
2195
|
React__default.createElement(DialogContentText, { color: "error" }, errorMessage))) : null));
|
|
2184
2196
|
}
|
|
2185
2197
|
|
|
2198
|
+
/* eslint-disable @typescript-eslint/unbound-method */
|
|
2186
2199
|
function LogOut({ handleClose, session }) {
|
|
2187
2200
|
const { internetAccounts } = getRoot(session);
|
|
2188
2201
|
const [errorMessage, setErrorMessage] = useState('');
|
|
@@ -2202,7 +2215,7 @@ function LogOut({ handleClose, session }) {
|
|
|
2202
2215
|
event.preventDefault();
|
|
2203
2216
|
setErrorMessage('');
|
|
2204
2217
|
selectedInternetAccount.removeToken();
|
|
2205
|
-
|
|
2218
|
+
globalThis.location.reload();
|
|
2206
2219
|
}
|
|
2207
2220
|
return (React__default.createElement(Dialog, { open: true, title: "Log out", handleClose: handleClose, maxWidth: false, "data-testid": "log-out" },
|
|
2208
2221
|
React__default.createElement("form", { onSubmit: onSubmit },
|
|
@@ -2323,7 +2336,7 @@ function ManageChecks({ handleClose, session }) {
|
|
|
2323
2336
|
}
|
|
2324
2337
|
else {
|
|
2325
2338
|
const index = checks.indexOf(_id, 0);
|
|
2326
|
-
if (index
|
|
2339
|
+
if (index !== -1) {
|
|
2327
2340
|
checks.splice(index, 1);
|
|
2328
2341
|
}
|
|
2329
2342
|
setSelectedChecks(checks);
|
|
@@ -2364,7 +2377,7 @@ function ManageChecks({ handleClose, session }) {
|
|
|
2364
2377
|
React__default.createElement(DialogContentText, { color: "error" }, errorMessage))) : null));
|
|
2365
2378
|
}
|
|
2366
2379
|
|
|
2367
|
-
/* eslint-disable @typescript-eslint/
|
|
2380
|
+
/* eslint-disable @typescript-eslint/unbound-method */
|
|
2368
2381
|
function ManageUsers({ changeManager, handleClose, session, }) {
|
|
2369
2382
|
const { internetAccounts } = getRoot(session);
|
|
2370
2383
|
const apolloInternetAccounts = internetAccounts.filter((ia) => ia.type === 'ApolloInternetAccount' && ia.role?.includes('admin'));
|
|
@@ -2448,7 +2461,7 @@ function ManageUsers({ changeManager, handleClose, session, }) {
|
|
|
2448
2461
|
type: 'actions',
|
|
2449
2462
|
getActions: (params) => [
|
|
2450
2463
|
React__default.createElement(GridActionsCellItem, { key: `delete-${params.id}`, icon: React__default.createElement(DeleteIcon, null), onClick: async () => {
|
|
2451
|
-
if (
|
|
2464
|
+
if (globalThis.confirm('Delete this user?')) {
|
|
2452
2465
|
await deleteUser(params.id);
|
|
2453
2466
|
}
|
|
2454
2467
|
}, disabled: isCurrentUser(params.id), label: "Delete" }),
|
|
@@ -2488,7 +2501,7 @@ function ManageUsers({ changeManager, handleClose, session, }) {
|
|
|
2488
2501
|
React__default.createElement(DialogContentText, { color: "error" }, errorMessage))) : null));
|
|
2489
2502
|
}
|
|
2490
2503
|
|
|
2491
|
-
/* eslint-disable @typescript-eslint/
|
|
2504
|
+
/* eslint-disable @typescript-eslint/unbound-method */
|
|
2492
2505
|
// interface TermAutocompleteResult extends TermValue {
|
|
2493
2506
|
// label: string[]
|
|
2494
2507
|
// match: string
|
|
@@ -2900,7 +2913,7 @@ function ModifyFeatureAttribute({ changeManager, handleClose, session, sourceAss
|
|
|
2900
2913
|
React__default.createElement(Button, { variant: "outlined", type: "submit", disabled: showAddNewForm, onClick: handleClose }, "Cancel")))));
|
|
2901
2914
|
}
|
|
2902
2915
|
|
|
2903
|
-
/* eslint-disable @typescript-eslint/no-
|
|
2916
|
+
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
2904
2917
|
function OpenLocalFile({ handleClose, session }) {
|
|
2905
2918
|
const { apolloDataStore } = session;
|
|
2906
2919
|
const { addAssembly, addSessionAssembly, assemblyManager, notify } = session;
|
|
@@ -2996,7 +3009,7 @@ function OpenLocalFile({ handleClose, session }) {
|
|
|
2996
3009
|
React__default.createElement(DialogContentText, { color: "error" }, errorMessage))) : null));
|
|
2997
3010
|
}
|
|
2998
3011
|
|
|
2999
|
-
/* eslint-disable @typescript-eslint/
|
|
3012
|
+
/* eslint-disable @typescript-eslint/unbound-method */
|
|
3000
3013
|
const useStyles$d = makeStyles()((theme) => ({
|
|
3001
3014
|
changeTextarea: {
|
|
3002
3015
|
fontFamily: 'monospace',
|
|
@@ -3520,7 +3533,9 @@ const AuthTypeSelector = ({ baseURL, handleClose, name, }) => {
|
|
|
3520
3533
|
setLoginTypes(data);
|
|
3521
3534
|
}
|
|
3522
3535
|
getAuthTypes().catch((error) => {
|
|
3523
|
-
|
|
3536
|
+
if (!isAbortException(error)) {
|
|
3537
|
+
setErrorMessage(String(error));
|
|
3538
|
+
}
|
|
3524
3539
|
});
|
|
3525
3540
|
return () => {
|
|
3526
3541
|
controller.abort();
|
|
@@ -3643,7 +3658,7 @@ const stateModelFactory$2 = (configSchema) => {
|
|
|
3643
3658
|
async openAuthWindow(type, resolve, reject) {
|
|
3644
3659
|
const redirectUri = isElectron
|
|
3645
3660
|
? 'http://localhost/auth'
|
|
3646
|
-
:
|
|
3661
|
+
: globalThis.location.origin + globalThis.location.pathname;
|
|
3647
3662
|
const url = new URL('auth/login', self.baseURL);
|
|
3648
3663
|
const params = new URLSearchParams({
|
|
3649
3664
|
type,
|
|
@@ -3652,7 +3667,7 @@ const stateModelFactory$2 = (configSchema) => {
|
|
|
3652
3667
|
url.search = params.toString();
|
|
3653
3668
|
const eventName = `JBrowseAuthWindow-${self.internetAccountId}`;
|
|
3654
3669
|
if (isElectron) {
|
|
3655
|
-
const { ipcRenderer } =
|
|
3670
|
+
const { ipcRenderer } = globalThis.require('electron');
|
|
3656
3671
|
const redirectUriFromElectron = await ipcRenderer.invoke('openAuthWindow', {
|
|
3657
3672
|
internetAccountId: self.internetAccountId,
|
|
3658
3673
|
data: { redirect_uri: redirectUri },
|
|
@@ -3826,14 +3841,9 @@ const stateModelFactory$2 = (configSchema) => {
|
|
|
3826
3841
|
}
|
|
3827
3842
|
});
|
|
3828
3843
|
socket.on('REQUEST_INFORMATION', (message) => {
|
|
3829
|
-
const { channel,
|
|
3844
|
+
const { channel, userSessionId } = message;
|
|
3830
3845
|
if (channel === 'REQUEST_INFORMATION' && userSessionId !== token) {
|
|
3831
|
-
|
|
3832
|
-
case 'CURRENT_LOCATION': {
|
|
3833
|
-
session.broadcastLocations();
|
|
3834
|
-
break;
|
|
3835
|
-
}
|
|
3836
|
-
}
|
|
3846
|
+
session.broadcastLocations();
|
|
3837
3847
|
}
|
|
3838
3848
|
});
|
|
3839
3849
|
},
|
|
@@ -4505,7 +4515,6 @@ function ApolloRendering(props) {
|
|
|
4505
4515
|
}
|
|
4506
4516
|
await changeManager.submit(change);
|
|
4507
4517
|
}
|
|
4508
|
-
// eslint-disable-next-line unicorn/no-useless-undefined
|
|
4509
4518
|
setDragging(undefined);
|
|
4510
4519
|
setMovedDuringLastMouseDown(false);
|
|
4511
4520
|
}
|
|
@@ -4518,7 +4527,6 @@ function ApolloRendering(props) {
|
|
|
4518
4527
|
React__default.createElement(Menu, { open: Boolean(contextMenuFeature), anchorReference: "anchorPosition", anchorPosition: contextCoord
|
|
4519
4528
|
? { left: contextCoord[0], top: contextCoord[1] }
|
|
4520
4529
|
: undefined, "data-testid": "base_linear_display_context_menu", onClose: () => {
|
|
4521
|
-
// eslint-disable-next-line unicorn/no-useless-undefined
|
|
4522
4530
|
setContextMenuFeature(undefined);
|
|
4523
4531
|
} },
|
|
4524
4532
|
React__default.createElement(MenuItem, { disabled: isReadOnly, key: 1, value: 1, onClick: () => {
|
|
@@ -4532,7 +4540,6 @@ function ApolloRendering(props) {
|
|
|
4532
4540
|
session,
|
|
4533
4541
|
handleClose: () => {
|
|
4534
4542
|
doneCallback();
|
|
4535
|
-
// eslint-disable-next-line unicorn/no-useless-undefined
|
|
4536
4543
|
setContextMenuFeature(undefined);
|
|
4537
4544
|
},
|
|
4538
4545
|
changeManager,
|
|
@@ -4553,7 +4560,6 @@ function ApolloRendering(props) {
|
|
|
4553
4560
|
session,
|
|
4554
4561
|
handleClose: () => {
|
|
4555
4562
|
doneCallback();
|
|
4556
|
-
// eslint-disable-next-line unicorn/no-useless-undefined
|
|
4557
4563
|
setContextMenuFeature(undefined);
|
|
4558
4564
|
},
|
|
4559
4565
|
changeManager,
|
|
@@ -4573,7 +4579,6 @@ function ApolloRendering(props) {
|
|
|
4573
4579
|
session,
|
|
4574
4580
|
handleClose: () => {
|
|
4575
4581
|
doneCallback();
|
|
4576
|
-
// eslint-disable-next-line unicorn/no-useless-undefined
|
|
4577
4582
|
setContextMenuFeature(undefined);
|
|
4578
4583
|
},
|
|
4579
4584
|
changeManager,
|
|
@@ -4691,11 +4696,16 @@ function installApolloTextSearchAdapter(pluginManager) {
|
|
|
4691
4696
|
|
|
4692
4697
|
const ApolloPluginConfigurationSchema = ConfigurationSchema('ApolloPlugin', {
|
|
4693
4698
|
ontologies: types.array(OntologyRecordConfiguration),
|
|
4699
|
+
featureTypeOntologyName: {
|
|
4700
|
+
description: 'Name of the feature type ontology',
|
|
4701
|
+
type: 'string',
|
|
4702
|
+
defaultValue: 'Sequence Ontology',
|
|
4703
|
+
},
|
|
4694
4704
|
});
|
|
4695
4705
|
|
|
4696
4706
|
function parseCigar(cigar) {
|
|
4697
4707
|
return (cigar.toUpperCase().match(/\d+\D/g) ?? []).map((op) => {
|
|
4698
|
-
return [(
|
|
4708
|
+
return [(/\D/.exec(op) ?? [])[0], Number.parseInt(op, 10)];
|
|
4699
4709
|
});
|
|
4700
4710
|
}
|
|
4701
4711
|
function annotationFromPileup(pluggableElement) {
|
|
@@ -4867,7 +4877,7 @@ function annotationFromPileup(pluggableElement) {
|
|
|
4867
4877
|
return pluggableElement;
|
|
4868
4878
|
}
|
|
4869
4879
|
|
|
4870
|
-
/* eslint-disable @typescript-eslint/
|
|
4880
|
+
/* eslint-disable @typescript-eslint/unbound-method */
|
|
4871
4881
|
const StringTextField = observer(function StringTextField({ onChangeCommitted, value: initialValue, ...props }) {
|
|
4872
4882
|
const [value, setValue] = useState(String(initialValue));
|
|
4873
4883
|
const [blur, setBlur] = useState(false);
|
|
@@ -5072,7 +5082,7 @@ const Attributes = observer(function Attributes({ assembly, editable, feature, s
|
|
|
5072
5082
|
}
|
|
5073
5083
|
}
|
|
5074
5084
|
return (React__default.createElement(React__default.Fragment, null,
|
|
5075
|
-
React__default.createElement(Typography, {
|
|
5085
|
+
React__default.createElement(Typography, { variant: "h5" }, "Attributes"),
|
|
5076
5086
|
React__default.createElement(Grid, { container: true, direction: "column", spacing: 1 },
|
|
5077
5087
|
Object.entries(attributes).map(([key, value]) => {
|
|
5078
5088
|
if (key === '') {
|
|
@@ -5121,7 +5131,7 @@ const Attributes = observer(function Attributes({ assembly, editable, feature, s
|
|
|
5121
5131
|
errorMessage ? (React__default.createElement(Typography, { color: "error" }, errorMessage)) : null));
|
|
5122
5132
|
});
|
|
5123
5133
|
|
|
5124
|
-
/* eslint-disable @typescript-eslint/
|
|
5134
|
+
/* eslint-disable @typescript-eslint/unbound-method */
|
|
5125
5135
|
const NumberTextField = observer(function NumberTextField({ onChangeCommitted, value: initialValue, ...props }) {
|
|
5126
5136
|
const [value, setValue] = useState(String(initialValue));
|
|
5127
5137
|
const [blur, setBlur] = useState(false);
|
|
@@ -5248,7 +5258,7 @@ const BasicInformation = observer(function BasicInformation({ assembly, feature,
|
|
|
5248
5258
|
errorMessage ? (React__default.createElement(Typography, { color: "error" }, errorMessage)) : null));
|
|
5249
5259
|
});
|
|
5250
5260
|
|
|
5251
|
-
function formatSequence
|
|
5261
|
+
function formatSequence(seq, refName, start, end, wrap) {
|
|
5252
5262
|
const header = `>${refName}:${start + 1}–${end}\n`;
|
|
5253
5263
|
const body = wrap === undefined ? seq : splitStringIntoChunks(seq, wrap).join('\n');
|
|
5254
5264
|
return `${header}${body}`;
|
|
@@ -5278,7 +5288,7 @@ const Sequence = observer(function Sequence({ assembly, feature, refName, sessio
|
|
|
5278
5288
|
if (showSequence) {
|
|
5279
5289
|
sequence = refSeq.getSequence(min, max);
|
|
5280
5290
|
if (sequence) {
|
|
5281
|
-
sequence = formatSequence
|
|
5291
|
+
sequence = formatSequence(sequence, refName, min, max);
|
|
5282
5292
|
}
|
|
5283
5293
|
else {
|
|
5284
5294
|
void session.apolloDataStore.loadRefSeq([
|
|
@@ -5418,383 +5428,219 @@ const ApolloTranscriptDetailsModel = types
|
|
|
5418
5428
|
},
|
|
5419
5429
|
}));
|
|
5420
5430
|
|
|
5421
|
-
/**
|
|
5422
|
-
* Get single feature by featureId
|
|
5423
|
-
* @param feature -
|
|
5424
|
-
* @param featureId -
|
|
5425
|
-
* @returns
|
|
5426
|
-
*/
|
|
5427
|
-
function getFeatureFromId(feature, featureId) {
|
|
5428
|
-
if (feature._id === featureId) {
|
|
5429
|
-
return feature;
|
|
5430
|
-
}
|
|
5431
|
-
// Check if there is also childFeatures in parent feature and it's not empty
|
|
5432
|
-
// Let's get featureId from recursive method
|
|
5433
|
-
if (!feature.children) {
|
|
5434
|
-
return;
|
|
5435
|
-
}
|
|
5436
|
-
for (const [, childFeature] of feature.children) {
|
|
5437
|
-
const subFeature = getFeatureFromId(childFeature, featureId);
|
|
5438
|
-
if (subFeature) {
|
|
5439
|
-
return subFeature;
|
|
5440
|
-
}
|
|
5441
|
-
}
|
|
5442
|
-
return;
|
|
5443
|
-
}
|
|
5444
|
-
function findExonInRange(exons, pairStart, pairEnd) {
|
|
5445
|
-
for (const exon of exons) {
|
|
5446
|
-
if (Number(exon.min) <= pairStart && Number(exon.max) >= pairEnd) {
|
|
5447
|
-
return exon;
|
|
5448
|
-
}
|
|
5449
|
-
}
|
|
5450
|
-
return null;
|
|
5451
|
-
}
|
|
5452
|
-
function removeMatchingExon(exons, matchStart, matchEnd) {
|
|
5453
|
-
// Filter the array to remove elements matching the specified start and end
|
|
5454
|
-
return exons.filter((exon) => !(exon.min === matchStart && exon.max === matchEnd));
|
|
5455
|
-
}
|
|
5456
5431
|
const TranscriptBasicInformation = observer(function TranscriptBasicInformation({ assembly, feature, refName, session, }) {
|
|
5457
5432
|
const { notify } = session;
|
|
5458
5433
|
const currentAssembly = session.apolloDataStore.assemblies.get(assembly);
|
|
5459
5434
|
const refData = currentAssembly?.getByRefName(refName);
|
|
5460
5435
|
const { changeManager } = session.apolloDataStore;
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
notify('Feature start cannot be less than parent starts', 'error');
|
|
5466
|
-
return;
|
|
5467
|
-
}
|
|
5468
|
-
const subFeature = getFeatureFromId(feature, featureId);
|
|
5469
|
-
if (!subFeature?.children) {
|
|
5470
|
-
return;
|
|
5436
|
+
const theme = useTheme();
|
|
5437
|
+
function handleLocationChange(oldLocation, newLocation, feature, isMin) {
|
|
5438
|
+
if (!feature.children) {
|
|
5439
|
+
throw new Error('Transcript should have child features');
|
|
5471
5440
|
}
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
if ((child[1].type === 'CDS' || child[1].type === 'exon') &&
|
|
5475
|
-
child[1].min === oldStart) {
|
|
5441
|
+
for (const [, child] of feature.children) {
|
|
5442
|
+
if (isMin && oldLocation - 1 === child.min) {
|
|
5476
5443
|
const change = new LocationStartChange({
|
|
5477
5444
|
typeName: 'LocationStartChange',
|
|
5478
|
-
changedIds: [child
|
|
5479
|
-
featureId,
|
|
5480
|
-
oldStart,
|
|
5481
|
-
newStart,
|
|
5445
|
+
changedIds: [child._id],
|
|
5446
|
+
featureId: feature._id,
|
|
5447
|
+
oldStart: oldLocation - 1,
|
|
5448
|
+
newStart: newLocation - 1,
|
|
5482
5449
|
assembly,
|
|
5483
5450
|
});
|
|
5484
5451
|
changeManager.submit(change).catch(() => {
|
|
5485
5452
|
notify('Error updating feature start position', 'error');
|
|
5486
5453
|
});
|
|
5454
|
+
return;
|
|
5487
5455
|
}
|
|
5488
|
-
|
|
5489
|
-
return;
|
|
5490
|
-
}
|
|
5491
|
-
function handleEndChange(newEnd, featureId, oldEnd) {
|
|
5492
|
-
const subFeature = getFeatureFromId(feature, featureId);
|
|
5493
|
-
if (newEnd > feature.max) {
|
|
5494
|
-
notify('Feature start cannot be greater than parent end', 'error');
|
|
5495
|
-
return;
|
|
5496
|
-
}
|
|
5497
|
-
if (!subFeature?.children) {
|
|
5498
|
-
return;
|
|
5499
|
-
}
|
|
5500
|
-
// Let's check CDS start and end values. And possibly update those too
|
|
5501
|
-
for (const child of subFeature.children) {
|
|
5502
|
-
if ((child[1].type === 'CDS' || child[1].type === 'exon') &&
|
|
5503
|
-
child[1].max === oldEnd) {
|
|
5456
|
+
if (!isMin && newLocation === child.max) {
|
|
5504
5457
|
const change = new LocationEndChange({
|
|
5505
5458
|
typeName: 'LocationEndChange',
|
|
5506
|
-
changedIds: [child
|
|
5507
|
-
featureId,
|
|
5508
|
-
oldEnd,
|
|
5509
|
-
newEnd,
|
|
5459
|
+
changedIds: [child._id],
|
|
5460
|
+
featureId: feature._id,
|
|
5461
|
+
oldEnd: child.max,
|
|
5462
|
+
newEnd: newLocation,
|
|
5510
5463
|
assembly,
|
|
5511
5464
|
});
|
|
5512
5465
|
changeManager.submit(change).catch(() => {
|
|
5513
|
-
notify('Error updating feature
|
|
5466
|
+
notify('Error updating feature start position', 'error');
|
|
5514
5467
|
});
|
|
5468
|
+
return;
|
|
5515
5469
|
}
|
|
5516
5470
|
}
|
|
5517
|
-
return;
|
|
5518
5471
|
}
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
const traverse = (currentFeature) => {
|
|
5522
|
-
if (currentFeature.type === 'exon') {
|
|
5523
|
-
exonsArray.push({
|
|
5524
|
-
min: currentFeature.min + 1,
|
|
5525
|
-
max: currentFeature.max,
|
|
5526
|
-
});
|
|
5527
|
-
}
|
|
5528
|
-
if (currentFeature.children) {
|
|
5529
|
-
for (const child of currentFeature.children) {
|
|
5530
|
-
traverse(child[1]);
|
|
5531
|
-
}
|
|
5532
|
-
}
|
|
5533
|
-
};
|
|
5534
|
-
traverse(featureNew);
|
|
5535
|
-
const CDSresult = [];
|
|
5536
|
-
const CDSData = featureNew.cdsLocations;
|
|
5537
|
-
if (refData) {
|
|
5538
|
-
for (const CDSDatum of CDSData) {
|
|
5539
|
-
for (const dataPoint of CDSDatum) {
|
|
5540
|
-
let startSeq = refData.getSequence(Number(dataPoint.min) - 2, Number(dataPoint.min));
|
|
5541
|
-
let endSeq = refData.getSequence(Number(dataPoint.max), Number(dataPoint.max) + 2);
|
|
5542
|
-
if (featureNew.strand === -1 && startSeq && endSeq) {
|
|
5543
|
-
startSeq = revcom(startSeq);
|
|
5544
|
-
endSeq = revcom(endSeq);
|
|
5545
|
-
}
|
|
5546
|
-
const oneCDS = {
|
|
5547
|
-
id: featureNew._id,
|
|
5548
|
-
type: 'CDS',
|
|
5549
|
-
strand: Number(featureNew.strand),
|
|
5550
|
-
min: dataPoint.min + 1,
|
|
5551
|
-
max: dataPoint.max,
|
|
5552
|
-
oldMin: dataPoint.min + 1,
|
|
5553
|
-
oldMax: dataPoint.max,
|
|
5554
|
-
startSeq,
|
|
5555
|
-
endSeq,
|
|
5556
|
-
};
|
|
5557
|
-
// CDSresult.push(oneCDS)
|
|
5558
|
-
// Check if there is already an object with the same start and end
|
|
5559
|
-
const exists = CDSresult.some((obj) => obj.min === oneCDS.min &&
|
|
5560
|
-
obj.max === oneCDS.max &&
|
|
5561
|
-
obj.type === oneCDS.type);
|
|
5562
|
-
// If no such object exists, add the new object to the array
|
|
5563
|
-
if (!exists) {
|
|
5564
|
-
CDSresult.push(oneCDS);
|
|
5565
|
-
}
|
|
5566
|
-
// Add possible UTRs
|
|
5567
|
-
const foundExon = findExonInRange(exonsArray, dataPoint.min + 1, dataPoint.max);
|
|
5568
|
-
if (foundExon && Number(foundExon.min) < dataPoint.min) {
|
|
5569
|
-
if (feature.strand === 1) {
|
|
5570
|
-
const oneCDS = {
|
|
5571
|
-
id: feature._id,
|
|
5572
|
-
type: 'five_prime_UTR',
|
|
5573
|
-
strand: Number(feature.strand),
|
|
5574
|
-
min: foundExon.min,
|
|
5575
|
-
max: dataPoint.min,
|
|
5576
|
-
oldMin: foundExon.min,
|
|
5577
|
-
oldMax: dataPoint.min,
|
|
5578
|
-
startSeq: '',
|
|
5579
|
-
endSeq: '',
|
|
5580
|
-
};
|
|
5581
|
-
CDSresult.push(oneCDS);
|
|
5582
|
-
}
|
|
5583
|
-
else {
|
|
5584
|
-
const oneCDS = {
|
|
5585
|
-
id: feature._id,
|
|
5586
|
-
type: 'three_prime_UTR',
|
|
5587
|
-
strand: Number(feature.strand),
|
|
5588
|
-
min: dataPoint.min + 1,
|
|
5589
|
-
max: foundExon.min + 1,
|
|
5590
|
-
oldMin: dataPoint.min + 1,
|
|
5591
|
-
oldMax: foundExon.min + 1,
|
|
5592
|
-
startSeq: '',
|
|
5593
|
-
endSeq: '',
|
|
5594
|
-
};
|
|
5595
|
-
CDSresult.push(oneCDS);
|
|
5596
|
-
}
|
|
5597
|
-
exonsArray = removeMatchingExon(exonsArray, foundExon.min, foundExon.max);
|
|
5598
|
-
}
|
|
5599
|
-
if (foundExon && Number(foundExon.max) > dataPoint.max) {
|
|
5600
|
-
if (feature.strand === 1) {
|
|
5601
|
-
const oneCDS = {
|
|
5602
|
-
id: feature._id,
|
|
5603
|
-
type: 'three_prime_UTR',
|
|
5604
|
-
strand: Number(feature.strand),
|
|
5605
|
-
min: dataPoint.max + 1,
|
|
5606
|
-
max: foundExon.max,
|
|
5607
|
-
oldMin: dataPoint.max + 1,
|
|
5608
|
-
oldMax: foundExon.max,
|
|
5609
|
-
startSeq: '',
|
|
5610
|
-
endSeq: '',
|
|
5611
|
-
};
|
|
5612
|
-
CDSresult.push(oneCDS);
|
|
5613
|
-
}
|
|
5614
|
-
else {
|
|
5615
|
-
const oneCDS = {
|
|
5616
|
-
id: feature._id,
|
|
5617
|
-
type: 'five_prime_UTR',
|
|
5618
|
-
strand: Number(feature.strand),
|
|
5619
|
-
min: dataPoint.min + 1,
|
|
5620
|
-
max: foundExon.max,
|
|
5621
|
-
oldMin: dataPoint.min + 1,
|
|
5622
|
-
oldMax: foundExon.max,
|
|
5623
|
-
startSeq: '',
|
|
5624
|
-
endSeq: '',
|
|
5625
|
-
};
|
|
5626
|
-
CDSresult.push(oneCDS);
|
|
5627
|
-
}
|
|
5628
|
-
exonsArray = removeMatchingExon(exonsArray, foundExon.min, foundExon.max);
|
|
5629
|
-
}
|
|
5630
|
-
if (dataPoint.min + 1 === foundExon?.min &&
|
|
5631
|
-
dataPoint.max === foundExon.max) {
|
|
5632
|
-
exonsArray = removeMatchingExon(exonsArray, foundExon.min, foundExon.max);
|
|
5633
|
-
}
|
|
5634
|
-
}
|
|
5635
|
-
}
|
|
5636
|
-
}
|
|
5637
|
-
// Add remaining UTRs if any
|
|
5638
|
-
if (exonsArray.length > 0) {
|
|
5639
|
-
// eslint-disable-next-line unicorn/no-array-for-each
|
|
5640
|
-
exonsArray.forEach((element) => {
|
|
5641
|
-
if (featureNew.strand === 1) {
|
|
5642
|
-
const oneCDS = {
|
|
5643
|
-
id: featureNew._id,
|
|
5644
|
-
type: 'five_prime_UTR',
|
|
5645
|
-
strand: Number(featureNew.strand),
|
|
5646
|
-
min: element.min + 1,
|
|
5647
|
-
max: element.max,
|
|
5648
|
-
oldMin: element.min + 1,
|
|
5649
|
-
oldMax: element.max,
|
|
5650
|
-
startSeq: '',
|
|
5651
|
-
endSeq: '',
|
|
5652
|
-
};
|
|
5653
|
-
CDSresult.push(oneCDS);
|
|
5654
|
-
}
|
|
5655
|
-
else {
|
|
5656
|
-
const oneCDS = {
|
|
5657
|
-
id: featureNew._id,
|
|
5658
|
-
type: 'three_prime_UTR',
|
|
5659
|
-
strand: Number(featureNew.strand),
|
|
5660
|
-
min: element.min + 1,
|
|
5661
|
-
max: element.max + 1,
|
|
5662
|
-
oldMin: element.min + 1,
|
|
5663
|
-
oldMax: element.max + 1,
|
|
5664
|
-
startSeq: '',
|
|
5665
|
-
endSeq: '',
|
|
5666
|
-
};
|
|
5667
|
-
CDSresult.push(oneCDS);
|
|
5668
|
-
}
|
|
5669
|
-
exonsArray = removeMatchingExon(exonsArray, element.min, element.max);
|
|
5670
|
-
});
|
|
5472
|
+
if (!refData) {
|
|
5473
|
+
return null;
|
|
5671
5474
|
}
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5475
|
+
const { strand, transcriptParts } = feature;
|
|
5476
|
+
const [firstLocation] = transcriptParts;
|
|
5477
|
+
const locationData = firstLocation
|
|
5478
|
+
.map((loc, idx) => {
|
|
5479
|
+
const { max, min, type } = loc;
|
|
5480
|
+
let label = type;
|
|
5481
|
+
if (label === 'threePrimeUTR') {
|
|
5482
|
+
label = '3` UTR';
|
|
5483
|
+
}
|
|
5484
|
+
else if (label === 'fivePrimeUTR') {
|
|
5485
|
+
label = '5` UTR';
|
|
5486
|
+
}
|
|
5487
|
+
let fivePrimeSpliceSite;
|
|
5488
|
+
let threePrimeSpliceSite;
|
|
5489
|
+
let frameColor;
|
|
5490
|
+
if (type === 'CDS') {
|
|
5491
|
+
const { phase } = loc;
|
|
5492
|
+
const frame = getFrame(min, max, strand ?? 1, phase);
|
|
5493
|
+
frameColor = theme.palette.framesCDS.at(frame)?.main;
|
|
5494
|
+
const previousLoc = firstLocation.at(idx - 1);
|
|
5495
|
+
const nextLoc = firstLocation.at(idx + 1);
|
|
5496
|
+
if (strand === 1) {
|
|
5497
|
+
if (previousLoc?.type === 'intron') {
|
|
5498
|
+
fivePrimeSpliceSite = refData.getSequence(min - 2, min);
|
|
5499
|
+
}
|
|
5500
|
+
if (nextLoc?.type === 'intron') {
|
|
5501
|
+
threePrimeSpliceSite = refData.getSequence(max, max + 2);
|
|
5502
|
+
}
|
|
5689
5503
|
}
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5504
|
+
else {
|
|
5505
|
+
if (previousLoc?.type === 'intron') {
|
|
5506
|
+
fivePrimeSpliceSite = revcom(refData.getSequence(max, max + 2));
|
|
5507
|
+
}
|
|
5508
|
+
if (nextLoc?.type === 'intron') {
|
|
5509
|
+
threePrimeSpliceSite = revcom(refData.getSequence(min - 2, min));
|
|
5510
|
+
}
|
|
5694
5511
|
}
|
|
5695
5512
|
}
|
|
5696
|
-
|
|
5697
|
-
|
|
5513
|
+
return {
|
|
5514
|
+
min,
|
|
5515
|
+
max,
|
|
5516
|
+
label,
|
|
5517
|
+
fivePrimeSpliceSite,
|
|
5518
|
+
threePrimeSpliceSite,
|
|
5519
|
+
frameColor,
|
|
5520
|
+
};
|
|
5521
|
+
})
|
|
5522
|
+
.filter((loc) => loc.label !== 'intron');
|
|
5698
5523
|
return (React__default.createElement(React__default.Fragment, null,
|
|
5699
|
-
React__default.createElement(Typography, { variant: "h5"
|
|
5700
|
-
React__default.createElement(
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
:
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
width: '150px',
|
|
5720
|
-
backgroundColor: item.endSeq.trim() === '' &&
|
|
5721
|
-
index + 1 !== transcriptItems.length
|
|
5722
|
-
? 'lightblue'
|
|
5723
|
-
: 'inherit',
|
|
5724
|
-
}, variant: "outlined", value: item.max, onChangeCommitted: (newEnd) => {
|
|
5725
|
-
handleEndChange(newEnd, item.id, Number(item.oldMax));
|
|
5726
|
-
} }),
|
|
5727
|
-
React__default.createElement("span", { style: { marginLeft: '8px', fontWeight: 'bold' } }, item.endSeq)))))));
|
|
5524
|
+
React__default.createElement(Typography, { variant: "h5" }, "Structure"),
|
|
5525
|
+
React__default.createElement(Typography, { variant: "h6" },
|
|
5526
|
+
strand === 1 ? 'Forward' : 'Reverse',
|
|
5527
|
+
" strand"),
|
|
5528
|
+
React__default.createElement(TableContainer, { component: Paper },
|
|
5529
|
+
React__default.createElement(Table, { size: "small" },
|
|
5530
|
+
React__default.createElement(TableBody, null, locationData.map((loc) => (React__default.createElement(TableRow, { key: `${loc.label}:${loc.min}-${loc.max}` },
|
|
5531
|
+
React__default.createElement(TableCell, { component: "th", scope: "row", style: { background: loc.frameColor } }, loc.label),
|
|
5532
|
+
React__default.createElement(TableCell, null, loc.fivePrimeSpliceSite ?? ''),
|
|
5533
|
+
React__default.createElement(TableCell, { padding: "none" },
|
|
5534
|
+
React__default.createElement(NumberTextField, { margin: "dense", variant: "outlined", value: strand === 1 ? loc.min + 1 : loc.max, onChangeCommitted: (newLocation) => {
|
|
5535
|
+
handleLocationChange(strand === 1 ? loc.min + 1 : loc.max, newLocation, feature, strand === 1);
|
|
5536
|
+
} })),
|
|
5537
|
+
React__default.createElement(TableCell, { padding: "none" },
|
|
5538
|
+
React__default.createElement(NumberTextField, { margin: "dense",
|
|
5539
|
+
// disabled={item.type !== 'CDS'}
|
|
5540
|
+
variant: "outlined", value: strand === 1 ? loc.max : loc.min + 1, onChangeCommitted: (newLocation) => {
|
|
5541
|
+
handleLocationChange(strand === 1 ? loc.max : loc.min + 1, newLocation, feature, strand !== 1);
|
|
5542
|
+
} })),
|
|
5543
|
+
React__default.createElement(TableCell, null, loc.threePrimeSpliceSite ?? '')))))))));
|
|
5728
5544
|
});
|
|
5729
5545
|
|
|
5730
|
-
const
|
|
5731
|
-
|
|
5732
|
-
const
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5546
|
+
const SEQUENCE_WRAP_LENGTH = 60;
|
|
5547
|
+
function getSequenceSegments(segmentType, feature, getSequence) {
|
|
5548
|
+
const segments = [];
|
|
5549
|
+
const { cdsLocations, strand, transcriptParts } = feature;
|
|
5550
|
+
switch (segmentType) {
|
|
5551
|
+
case 'genomic':
|
|
5552
|
+
case 'cDNA': {
|
|
5553
|
+
const [firstLocation] = transcriptParts;
|
|
5554
|
+
for (const loc of firstLocation) {
|
|
5555
|
+
if (segmentType === 'cDNA' && loc.type === 'intron') {
|
|
5556
|
+
continue;
|
|
5557
|
+
}
|
|
5558
|
+
let sequence = getSequence(loc.min, loc.max);
|
|
5559
|
+
if (strand === -1) {
|
|
5560
|
+
sequence = revcom(sequence);
|
|
5561
|
+
}
|
|
5562
|
+
const type = loc.type === 'fivePrimeUTR' || loc.type === 'threePrimeUTR'
|
|
5563
|
+
? 'UTR'
|
|
5564
|
+
: loc.type;
|
|
5565
|
+
const previousSegment = segments.at(-1);
|
|
5566
|
+
if (!previousSegment) {
|
|
5567
|
+
const sequenceLines = splitStringIntoChunks(sequence, SEQUENCE_WRAP_LENGTH);
|
|
5568
|
+
segments.push({
|
|
5569
|
+
type,
|
|
5570
|
+
sequenceLines,
|
|
5571
|
+
locs: [{ min: loc.min, max: loc.max }],
|
|
5572
|
+
});
|
|
5573
|
+
continue;
|
|
5574
|
+
}
|
|
5575
|
+
if (previousSegment.type === type) {
|
|
5576
|
+
const [previousSegmentFirstLine, ...previousSegmentFollowingLines] = previousSegment.sequenceLines;
|
|
5577
|
+
const newSequence = previousSegmentFollowingLines.join('') + sequence;
|
|
5578
|
+
previousSegment.sequenceLines = [
|
|
5579
|
+
previousSegmentFirstLine,
|
|
5580
|
+
...splitStringIntoChunks(newSequence, SEQUENCE_WRAP_LENGTH),
|
|
5581
|
+
];
|
|
5582
|
+
previousSegment.locs.push({ min: loc.min, max: loc.max });
|
|
5583
|
+
}
|
|
5584
|
+
else {
|
|
5585
|
+
const count = segments.reduce((accumulator, currentSegment) => accumulator +
|
|
5586
|
+
currentSegment.sequenceLines.reduce((subAccumulator, currentLine) => subAccumulator + currentLine.length, 0), 0);
|
|
5587
|
+
const previousLineLength = count % SEQUENCE_WRAP_LENGTH;
|
|
5588
|
+
const newSegmentFirstLineLength = SEQUENCE_WRAP_LENGTH - previousLineLength;
|
|
5589
|
+
const newSegmentFirstLine = sequence.slice(0, newSegmentFirstLineLength);
|
|
5590
|
+
const newSegmentRemainderLines = splitStringIntoChunks(sequence.slice(newSegmentFirstLineLength), SEQUENCE_WRAP_LENGTH);
|
|
5591
|
+
segments.push({
|
|
5592
|
+
type,
|
|
5593
|
+
sequenceLines: [newSegmentFirstLine, ...newSegmentRemainderLines],
|
|
5594
|
+
locs: [{ min: loc.min, max: loc.max }],
|
|
5595
|
+
});
|
|
5596
|
+
}
|
|
5759
5597
|
}
|
|
5598
|
+
return segments;
|
|
5760
5599
|
}
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
if (i > 0 && CDSresult[i].min === CDSresult[i - 1].max) {
|
|
5773
|
-
// Clear "startSeq" if the current item's "start" is equal to the previous item's "end"
|
|
5774
|
-
CDSresult[i].startSeq = '';
|
|
5775
|
-
}
|
|
5776
|
-
if (i < CDSresult.length - 1 &&
|
|
5777
|
-
CDSresult[i].max === CDSresult[i + 1].min) {
|
|
5778
|
-
// Clear "endSeq" if the next item's "start" is equal to the current item's "end"
|
|
5779
|
-
CDSresult[i].endSeq = '';
|
|
5600
|
+
case 'CDS': {
|
|
5601
|
+
let wholeSequence = '';
|
|
5602
|
+
const [firstLocation] = cdsLocations;
|
|
5603
|
+
const locs = [];
|
|
5604
|
+
for (const loc of firstLocation) {
|
|
5605
|
+
let sequence = getSequence(loc.min, loc.max);
|
|
5606
|
+
if (strand === -1) {
|
|
5607
|
+
sequence = revcom(sequence);
|
|
5608
|
+
}
|
|
5609
|
+
wholeSequence += sequence;
|
|
5610
|
+
locs.push({ min: loc.min, max: loc.max });
|
|
5780
5611
|
}
|
|
5612
|
+
const sequenceLines = splitStringIntoChunks(wholeSequence, SEQUENCE_WRAP_LENGTH);
|
|
5613
|
+
segments.push({ type: 'CDS', sequenceLines, locs });
|
|
5614
|
+
return segments;
|
|
5615
|
+
}
|
|
5616
|
+
}
|
|
5617
|
+
}
|
|
5618
|
+
function getSegmentColor(type) {
|
|
5619
|
+
switch (type) {
|
|
5620
|
+
case 'upOrDownstream': {
|
|
5621
|
+
return 'rgb(255,255,255)';
|
|
5622
|
+
}
|
|
5623
|
+
case 'UTR': {
|
|
5624
|
+
return 'rgb(194,106,119)';
|
|
5625
|
+
}
|
|
5626
|
+
case 'CDS': {
|
|
5627
|
+
return 'rgb(93,168,153)';
|
|
5628
|
+
}
|
|
5629
|
+
case 'intron': {
|
|
5630
|
+
return 'rgb(187,187,187)';
|
|
5631
|
+
}
|
|
5632
|
+
case 'protein': {
|
|
5633
|
+
return 'rgb(148,203,236)';
|
|
5781
5634
|
}
|
|
5782
5635
|
}
|
|
5783
|
-
return CDSresult;
|
|
5784
|
-
};
|
|
5785
|
-
function formatSequence(seq, refName, start, end, wrap) {
|
|
5786
|
-
const header = `>${refName}:${start + 1}–${end}\n`;
|
|
5787
|
-
const body = wrap === undefined ? seq : splitStringIntoChunks(seq, wrap).join('\n');
|
|
5788
|
-
return `${header}${body}`;
|
|
5789
5636
|
}
|
|
5790
|
-
const utrColor = 'rgb(20,200,200)'; // Slightly brighter cyan
|
|
5791
|
-
const cdsColor = 'rgb(240,200,20)'; // Slightly brighter yellow
|
|
5792
|
-
let textSegments = [{ text: '', color: '' }];
|
|
5793
5637
|
const TranscriptSequence = observer(function TranscriptSequence({ assembly, feature, refName, session, }) {
|
|
5794
5638
|
const currentAssembly = session.apolloDataStore.assemblies.get(assembly);
|
|
5795
5639
|
const refData = currentAssembly?.getByRefName(refName);
|
|
5796
5640
|
const [showSequence, setShowSequence] = useState(false);
|
|
5797
|
-
const [selectedOption, setSelectedOption] = useState('
|
|
5641
|
+
const [selectedOption, setSelectedOption] = useState('CDS');
|
|
5642
|
+
const theme = useTheme();
|
|
5643
|
+
const seqRef = useRef(null);
|
|
5798
5644
|
if (!(currentAssembly && refData)) {
|
|
5799
5645
|
return null;
|
|
5800
5646
|
}
|
|
@@ -5802,150 +5648,85 @@ const TranscriptSequence = observer(function TranscriptSequence({ assembly, feat
|
|
|
5802
5648
|
if (!refSeq) {
|
|
5803
5649
|
return null;
|
|
5804
5650
|
}
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
let sequence = '';
|
|
5808
|
-
if (showSequence) {
|
|
5809
|
-
getSequenceAsString(min, max);
|
|
5810
|
-
}
|
|
5811
|
-
function getSequenceAsString(start, end) {
|
|
5812
|
-
sequence = refSeq?.getSequence(start, end) ?? '';
|
|
5813
|
-
if (sequence === '') {
|
|
5814
|
-
void session.apolloDataStore.loadRefSeq([
|
|
5815
|
-
{ assemblyName: assembly, refName, start, end },
|
|
5816
|
-
]);
|
|
5817
|
-
}
|
|
5818
|
-
else {
|
|
5819
|
-
sequence = formatSequence(sequence, refName, start, end);
|
|
5820
|
-
}
|
|
5821
|
-
getSequenceAsTextSegment(selectedOption); // For color coded sequence
|
|
5822
|
-
return sequence;
|
|
5651
|
+
if (feature.type !== 'mRNA') {
|
|
5652
|
+
return null;
|
|
5823
5653
|
}
|
|
5824
5654
|
const handleSeqButtonClick = () => {
|
|
5825
5655
|
setShowSequence(!showSequence);
|
|
5826
5656
|
};
|
|
5827
|
-
function getSequenceAsTextSegment(option) {
|
|
5828
|
-
let seqData = '';
|
|
5829
|
-
textSegments = [];
|
|
5830
|
-
if (!refData) {
|
|
5831
|
-
return;
|
|
5832
|
-
}
|
|
5833
|
-
switch (option) {
|
|
5834
|
-
case 'CDS': {
|
|
5835
|
-
textSegments.push({ text: `>${refName} : CDS\n`, color: 'black' });
|
|
5836
|
-
for (const item of transcriptItems) {
|
|
5837
|
-
if (item.type === 'CDS') {
|
|
5838
|
-
const refSeq = refData.getSequence(Number(item.min + 1), Number(item.max));
|
|
5839
|
-
seqData += item.strand === -1 && refSeq ? revcom(refSeq) : refSeq;
|
|
5840
|
-
textSegments.push({ text: seqData, color: cdsColor });
|
|
5841
|
-
}
|
|
5842
|
-
}
|
|
5843
|
-
break;
|
|
5844
|
-
}
|
|
5845
|
-
case 'cDNA': {
|
|
5846
|
-
textSegments.push({ text: `>${refName} : cDNA\n`, color: 'black' });
|
|
5847
|
-
for (const item of transcriptItems) {
|
|
5848
|
-
if (item.type === 'CDS' ||
|
|
5849
|
-
item.type === 'three_prime_UTR' ||
|
|
5850
|
-
item.type === 'five_prime_UTR') {
|
|
5851
|
-
const refSeq = refData.getSequence(Number(item.min + 1), Number(item.max));
|
|
5852
|
-
seqData += item.strand === -1 && refSeq ? revcom(refSeq) : refSeq;
|
|
5853
|
-
if (item.type === 'CDS') {
|
|
5854
|
-
textSegments.push({ text: seqData, color: cdsColor });
|
|
5855
|
-
}
|
|
5856
|
-
else {
|
|
5857
|
-
textSegments.push({ text: seqData, color: utrColor });
|
|
5858
|
-
}
|
|
5859
|
-
}
|
|
5860
|
-
}
|
|
5861
|
-
break;
|
|
5862
|
-
}
|
|
5863
|
-
case 'Full': {
|
|
5864
|
-
textSegments.push({
|
|
5865
|
-
text: `>${refName} : Full genomic\n`,
|
|
5866
|
-
color: 'black',
|
|
5867
|
-
});
|
|
5868
|
-
let lastEnd = 0;
|
|
5869
|
-
let count = 0;
|
|
5870
|
-
for (const item of transcriptItems) {
|
|
5871
|
-
count++;
|
|
5872
|
-
if (lastEnd != 0 &&
|
|
5873
|
-
lastEnd != Number(item.min) &&
|
|
5874
|
-
count != transcriptItems.length) {
|
|
5875
|
-
// Intron etc. between CDS/UTRs. No need to check this on very last item
|
|
5876
|
-
const refSeq = refData.getSequence(lastEnd + 1, Number(item.min) - 1);
|
|
5877
|
-
seqData += item.strand === -1 && refSeq ? revcom(refSeq) : refSeq;
|
|
5878
|
-
textSegments.push({ text: seqData, color: 'black' });
|
|
5879
|
-
}
|
|
5880
|
-
if (item.type === 'CDS' ||
|
|
5881
|
-
item.type === 'three_prime_UTR' ||
|
|
5882
|
-
item.type === 'five_prime_UTR') {
|
|
5883
|
-
const refSeq = refData.getSequence(Number(item.min + 1), Number(item.max));
|
|
5884
|
-
seqData += item.strand === -1 && refSeq ? revcom(refSeq) : refSeq;
|
|
5885
|
-
switch (item.type) {
|
|
5886
|
-
case 'CDS': {
|
|
5887
|
-
textSegments.push({ text: seqData, color: cdsColor });
|
|
5888
|
-
break;
|
|
5889
|
-
}
|
|
5890
|
-
case 'three_prime_UTR': {
|
|
5891
|
-
textSegments.push({ text: seqData, color: utrColor });
|
|
5892
|
-
break;
|
|
5893
|
-
}
|
|
5894
|
-
case 'five_prime_UTR': {
|
|
5895
|
-
textSegments.push({ text: seqData, color: utrColor });
|
|
5896
|
-
break;
|
|
5897
|
-
}
|
|
5898
|
-
default: {
|
|
5899
|
-
textSegments.push({ text: seqData, color: 'black' });
|
|
5900
|
-
break;
|
|
5901
|
-
}
|
|
5902
|
-
}
|
|
5903
|
-
}
|
|
5904
|
-
lastEnd = Number(item.max);
|
|
5905
|
-
}
|
|
5906
|
-
break;
|
|
5907
|
-
}
|
|
5908
|
-
}
|
|
5909
|
-
}
|
|
5910
5657
|
function handleChangeSeqOption(e) {
|
|
5911
5658
|
const option = e.target.value;
|
|
5912
5659
|
setSelectedOption(option);
|
|
5913
|
-
getSequenceAsTextSegment(option);
|
|
5914
5660
|
}
|
|
5915
5661
|
// Function to copy text to clipboard
|
|
5916
5662
|
const copyToClipboard = () => {
|
|
5917
|
-
const
|
|
5918
|
-
if (
|
|
5919
|
-
|
|
5920
|
-
.writeText(textToCopy)
|
|
5921
|
-
.then(() => {
|
|
5922
|
-
// console.log('Text copied to clipboard!')
|
|
5923
|
-
})
|
|
5924
|
-
.catch((error) => {
|
|
5925
|
-
console.error('Failed to copy text to clipboard', error);
|
|
5926
|
-
});
|
|
5663
|
+
const seqDiv = seqRef.current;
|
|
5664
|
+
if (!seqDiv) {
|
|
5665
|
+
return;
|
|
5927
5666
|
}
|
|
5667
|
+
const textBlob = new Blob([seqDiv.outerText], { type: 'text/plain' });
|
|
5668
|
+
const htmlBlob = new Blob([seqDiv.outerHTML], { type: 'text/html' });
|
|
5669
|
+
const clipboardItem = new ClipboardItem({
|
|
5670
|
+
[textBlob.type]: textBlob,
|
|
5671
|
+
[htmlBlob.type]: htmlBlob,
|
|
5672
|
+
});
|
|
5673
|
+
void navigator.clipboard.write([clipboardItem]);
|
|
5928
5674
|
};
|
|
5929
|
-
const
|
|
5930
|
-
|
|
5931
|
-
|
|
5675
|
+
const sequenceSegments = showSequence
|
|
5676
|
+
? getSequenceSegments(selectedOption, feature, (min, max) => refData.getSequence(min, max))
|
|
5677
|
+
: [];
|
|
5678
|
+
const locationIntervals = [];
|
|
5679
|
+
if (showSequence) {
|
|
5680
|
+
const allLocs = sequenceSegments.flatMap((segment) => segment.locs);
|
|
5681
|
+
let [previous] = allLocs;
|
|
5682
|
+
for (let i = 1; i < allLocs.length; i++) {
|
|
5683
|
+
if (previous.min === allLocs[i].max || previous.max === allLocs[i].min) {
|
|
5684
|
+
previous = {
|
|
5685
|
+
min: Math.min(previous.min, allLocs[i].min),
|
|
5686
|
+
max: Math.max(previous.max, allLocs[i].max),
|
|
5687
|
+
};
|
|
5688
|
+
}
|
|
5689
|
+
else {
|
|
5690
|
+
locationIntervals.push(previous);
|
|
5691
|
+
previous = allLocs[i];
|
|
5692
|
+
}
|
|
5693
|
+
}
|
|
5694
|
+
locationIntervals.push(previous);
|
|
5695
|
+
}
|
|
5932
5696
|
return (React__default.createElement(React__default.Fragment, null,
|
|
5933
|
-
React__default.createElement(Typography, {
|
|
5697
|
+
React__default.createElement(Typography, { variant: "h5" }, "Sequence"),
|
|
5934
5698
|
React__default.createElement("div", null,
|
|
5935
|
-
React__default.createElement(Button, { variant: "contained",
|
|
5936
|
-
|
|
5937
|
-
React__default.createElement(
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5699
|
+
React__default.createElement(Button, { variant: "contained", onClick: handleSeqButtonClick }, showSequence ? 'Hide sequence' : 'Show sequence')),
|
|
5700
|
+
showSequence && (React__default.createElement(React__default.Fragment, null,
|
|
5701
|
+
React__default.createElement(Select, { defaultValue: "CDS", value: selectedOption, onChange: handleChangeSeqOption },
|
|
5702
|
+
React__default.createElement(MenuItem, { value: "CDS" }, "CDS"),
|
|
5703
|
+
React__default.createElement(MenuItem, { value: "cDNA" }, "cDNA"),
|
|
5704
|
+
React__default.createElement(MenuItem, { value: "genomic" }, "Genomic")),
|
|
5705
|
+
React__default.createElement(Paper, { style: {
|
|
5706
|
+
fontFamily: 'monospace',
|
|
5707
|
+
padding: theme.spacing(),
|
|
5708
|
+
overflowX: 'auto',
|
|
5709
|
+
}, ref: seqRef },
|
|
5710
|
+
">",
|
|
5711
|
+
refSeq.name,
|
|
5712
|
+
":",
|
|
5713
|
+
locationIntervals
|
|
5714
|
+
.map((interval) => feature.strand === 1
|
|
5715
|
+
? `${interval.min + 1}-${interval.max}`
|
|
5716
|
+
: `${interval.max}-${interval.min + 1}`)
|
|
5717
|
+
.join(';'),
|
|
5718
|
+
"(",
|
|
5719
|
+
feature.strand === 1 ? '+' : '-',
|
|
5720
|
+
")",
|
|
5721
|
+
React__default.createElement("br", null),
|
|
5722
|
+
sequenceSegments.map((segment, index) => (React__default.createElement("span", { key: `${segment.type}-${index}`, style: {
|
|
5723
|
+
background: getSegmentColor(segment.type),
|
|
5724
|
+
color: theme.palette.getContrastText(getSegmentColor(segment.type)),
|
|
5725
|
+
} }, segment.sequenceLines.map((sequenceLine, idx) => (React__default.createElement(React__default.Fragment, { key: `${sequenceLine.slice(0, 5)}-${idx}` },
|
|
5726
|
+
sequenceLine,
|
|
5727
|
+
idx === segment.sequenceLines.length - 1 &&
|
|
5728
|
+
sequenceLine.length !== SEQUENCE_WRAP_LENGTH ? null : (React__default.createElement("br", null))))))))),
|
|
5729
|
+
React__default.createElement(Button, { variant: "contained", onClick: copyToClipboard }, "Copy sequence")))));
|
|
5949
5730
|
});
|
|
5950
5731
|
|
|
5951
5732
|
const useStyles$7 = makeStyles()((theme) => ({
|
|
@@ -6176,7 +5957,7 @@ function featureContextMenuItems(feature, region, getAssemblyId, selectedFeature
|
|
|
6176
5957
|
return menuItems;
|
|
6177
5958
|
}
|
|
6178
5959
|
|
|
6179
|
-
/* eslint-disable @typescript-eslint/
|
|
5960
|
+
/* eslint-disable @typescript-eslint/unbound-method */
|
|
6180
5961
|
const useStyles$5 = makeStyles()((theme) => ({
|
|
6181
5962
|
inputWrapper: {
|
|
6182
5963
|
position: 'relative',
|
|
@@ -6405,7 +6186,9 @@ function baseModelFactory(_pluginManager, configSchema) {
|
|
|
6405
6186
|
return;
|
|
6406
6187
|
}
|
|
6407
6188
|
void self.session.apolloDataStore.loadFeatures(self.regions);
|
|
6408
|
-
|
|
6189
|
+
if (self.lgv.bpPerPx <= 3) {
|
|
6190
|
+
void self.session.apolloDataStore.loadRefSeq(self.regions);
|
|
6191
|
+
}
|
|
6409
6192
|
}, { name: 'LinearApolloDisplayLoadFeatures', delay: 1000 }));
|
|
6410
6193
|
},
|
|
6411
6194
|
}));
|
|
@@ -7290,7 +7073,7 @@ function drawTooltip$2(display, context) {
|
|
|
7290
7073
|
if (!position) {
|
|
7291
7074
|
return;
|
|
7292
7075
|
}
|
|
7293
|
-
const { layoutIndex, layoutRow } = position;
|
|
7076
|
+
const { featureRow, layoutIndex, layoutRow } = position;
|
|
7294
7077
|
const { bpPerPx, displayedRegions, offsetPx } = lgv;
|
|
7295
7078
|
const displayedRegion = displayedRegions[layoutIndex];
|
|
7296
7079
|
const { refName, reversed } = displayedRegion;
|
|
@@ -7302,7 +7085,7 @@ function drawTooltip$2(display, context) {
|
|
|
7302
7085
|
coord: reversed ? max : min,
|
|
7303
7086
|
regionNumber: layoutIndex,
|
|
7304
7087
|
})?.offsetPx ?? 0) - offsetPx;
|
|
7305
|
-
const top = layoutRow * apolloRowHeight;
|
|
7088
|
+
const top = (layoutRow + featureRow) * apolloRowHeight;
|
|
7306
7089
|
const widthPx = length / bpPerPx;
|
|
7307
7090
|
const featureType = `Type: ${feature.type}`;
|
|
7308
7091
|
const { attributes } = feature;
|
|
@@ -7448,6 +7231,20 @@ function getContextMenuItems$2(display) {
|
|
|
7448
7231
|
session.showWidget(apolloFeatureWidget);
|
|
7449
7232
|
},
|
|
7450
7233
|
});
|
|
7234
|
+
if (sourceFeature.type === 'mRNA' && isSessionModelWithWidgets(session)) {
|
|
7235
|
+
menuItems.push({
|
|
7236
|
+
label: 'Edit transcript details',
|
|
7237
|
+
onClick: () => {
|
|
7238
|
+
const apolloTranscriptWidget = session.addWidget('ApolloTranscriptDetails', 'apolloTranscriptDetails', {
|
|
7239
|
+
feature: sourceFeature,
|
|
7240
|
+
assembly: currentAssemblyId,
|
|
7241
|
+
changeManager,
|
|
7242
|
+
refName: region.refName,
|
|
7243
|
+
});
|
|
7244
|
+
session.showWidget(apolloTranscriptWidget);
|
|
7245
|
+
},
|
|
7246
|
+
});
|
|
7247
|
+
}
|
|
7451
7248
|
return menuItems;
|
|
7452
7249
|
}
|
|
7453
7250
|
function getFeatureFromLayout$2(feature, _bp, _row) {
|
|
@@ -7532,35 +7329,49 @@ const boxGlyph = {
|
|
|
7532
7329
|
onMouseUp: onMouseUp$2,
|
|
7533
7330
|
};
|
|
7534
7331
|
|
|
7535
|
-
let
|
|
7536
|
-
let
|
|
7537
|
-
|
|
7332
|
+
let forwardFillLight = null;
|
|
7333
|
+
let backwardFillLight = null;
|
|
7334
|
+
let forwardFillDark = null;
|
|
7335
|
+
let backwardFillDark = null;
|
|
7336
|
+
if ('document' in globalThis) {
|
|
7538
7337
|
for (const direction of ['forward', 'backward']) {
|
|
7539
|
-
const
|
|
7540
|
-
|
|
7541
|
-
|
|
7542
|
-
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7338
|
+
for (const themeMode of ['light', 'dark']) {
|
|
7339
|
+
const canvas = document.createElement('canvas');
|
|
7340
|
+
const canvasSize = 10;
|
|
7341
|
+
canvas.width = canvas.height = canvasSize;
|
|
7342
|
+
const ctx = canvas.getContext('2d');
|
|
7343
|
+
if (ctx) {
|
|
7344
|
+
const stripeColor1 = themeMode === 'light' ? 'rgba(0,0,0,0)' : 'rgba(0,0,0,0.75)';
|
|
7345
|
+
const stripeColor2 = themeMode === 'light' ? 'rgba(255,255,255,0.25)' : 'rgba(0,0,0,0.50)';
|
|
7346
|
+
const gradient = direction === 'forward'
|
|
7347
|
+
? ctx.createLinearGradient(0, canvasSize, canvasSize, 0)
|
|
7348
|
+
: ctx.createLinearGradient(0, 0, canvasSize, canvasSize);
|
|
7349
|
+
gradient.addColorStop(0, stripeColor1);
|
|
7350
|
+
gradient.addColorStop(0.25, stripeColor1);
|
|
7351
|
+
gradient.addColorStop(0.25, stripeColor2);
|
|
7352
|
+
gradient.addColorStop(0.5, stripeColor2);
|
|
7353
|
+
gradient.addColorStop(0.5, stripeColor1);
|
|
7354
|
+
gradient.addColorStop(0.75, stripeColor1);
|
|
7355
|
+
gradient.addColorStop(0.75, stripeColor2);
|
|
7356
|
+
gradient.addColorStop(1, stripeColor2);
|
|
7357
|
+
ctx.fillStyle = gradient;
|
|
7358
|
+
ctx.fillRect(0, 0, 10, 10);
|
|
7359
|
+
if (direction === 'forward') {
|
|
7360
|
+
if (themeMode === 'light') {
|
|
7361
|
+
forwardFillLight = ctx.createPattern(canvas, 'repeat');
|
|
7362
|
+
}
|
|
7363
|
+
else {
|
|
7364
|
+
forwardFillDark = ctx.createPattern(canvas, 'repeat');
|
|
7365
|
+
}
|
|
7366
|
+
}
|
|
7367
|
+
else {
|
|
7368
|
+
if (themeMode === 'light') {
|
|
7369
|
+
backwardFillLight = ctx.createPattern(canvas, 'repeat');
|
|
7370
|
+
}
|
|
7371
|
+
else {
|
|
7372
|
+
backwardFillDark = ctx.createPattern(canvas, 'repeat');
|
|
7373
|
+
}
|
|
7374
|
+
}
|
|
7564
7375
|
}
|
|
7565
7376
|
}
|
|
7566
7377
|
}
|
|
@@ -7573,12 +7384,25 @@ function draw$1(ctx, feature, row, stateModel, displayedRegionIndex) {
|
|
|
7573
7384
|
const rowHeight = apolloRowHeight;
|
|
7574
7385
|
const exonHeight = Math.round(0.6 * rowHeight);
|
|
7575
7386
|
const cdsHeight = Math.round(0.9 * rowHeight);
|
|
7576
|
-
const { strand } = feature;
|
|
7577
|
-
const { children } = feature;
|
|
7387
|
+
const { children, min, strand } = feature;
|
|
7578
7388
|
if (!children) {
|
|
7579
7389
|
return;
|
|
7580
7390
|
}
|
|
7581
7391
|
const { apolloSelectedFeature } = session;
|
|
7392
|
+
// Draw background for gene
|
|
7393
|
+
const topLevelFeatureMinX = (lgv.bpToPx({
|
|
7394
|
+
refName,
|
|
7395
|
+
coord: min,
|
|
7396
|
+
regionNumber: displayedRegionIndex,
|
|
7397
|
+
})?.offsetPx ?? 0) - offsetPx;
|
|
7398
|
+
const topLevelFeatureWidthPx = feature.length / bpPerPx;
|
|
7399
|
+
const topLevelFeatureStartPx = reversed
|
|
7400
|
+
? topLevelFeatureMinX - topLevelFeatureWidthPx
|
|
7401
|
+
: topLevelFeatureMinX;
|
|
7402
|
+
const topLevelFeatureTop = row * rowHeight;
|
|
7403
|
+
const topLevelFeatureHeight = getRowCount$1(feature) * rowHeight;
|
|
7404
|
+
ctx.fillStyle = alpha(theme?.palette.background.paper ?? '#ffffff', 0.6);
|
|
7405
|
+
ctx.fillRect(topLevelFeatureStartPx, topLevelFeatureTop, topLevelFeatureWidthPx, topLevelFeatureHeight);
|
|
7582
7406
|
// Draw lines on different rows for each mRNA
|
|
7583
7407
|
let currentRow = 0;
|
|
7584
7408
|
for (const [, mrna] of children) {
|
|
@@ -7610,6 +7434,8 @@ function draw$1(ctx, feature, row, stateModel, displayedRegionIndex) {
|
|
|
7610
7434
|
currentRow += 1;
|
|
7611
7435
|
}
|
|
7612
7436
|
}
|
|
7437
|
+
const forwardFill = theme?.palette.mode === 'dark' ? forwardFillDark : forwardFillLight;
|
|
7438
|
+
const backwardFill = theme?.palette.mode === 'dark' ? backwardFillDark : backwardFillLight;
|
|
7613
7439
|
// Draw exon and CDS for each mRNA
|
|
7614
7440
|
currentRow = 0;
|
|
7615
7441
|
for (const [, child] of children) {
|
|
@@ -7746,11 +7572,31 @@ function drawHover$1(stateModel, ctx) {
|
|
|
7746
7572
|
ctx.fillRect(startPx, top, widthPx, apolloRowHeight * getRowCount$1(feature));
|
|
7747
7573
|
}
|
|
7748
7574
|
function getFeatureFromLayout$1(feature, bp, row) {
|
|
7749
|
-
const featureInThisRow = featuresForRow$1(feature)[row];
|
|
7575
|
+
const featureInThisRow = featuresForRow$1(feature)[row] || [];
|
|
7750
7576
|
for (const f of featureInThisRow) {
|
|
7577
|
+
let featureObj;
|
|
7751
7578
|
if (bp >= f.min && bp <= f.max && f.parent) {
|
|
7752
|
-
|
|
7579
|
+
featureObj = f;
|
|
7580
|
+
}
|
|
7581
|
+
if (!featureObj) {
|
|
7582
|
+
continue;
|
|
7583
|
+
}
|
|
7584
|
+
if (featureObj.type === 'CDS' &&
|
|
7585
|
+
featureObj.parent &&
|
|
7586
|
+
featureObj.parent.type === 'mRNA') {
|
|
7587
|
+
const { cdsLocations } = featureObj.parent;
|
|
7588
|
+
for (const cdsLoc of cdsLocations) {
|
|
7589
|
+
for (const loc of cdsLoc) {
|
|
7590
|
+
if (bp >= loc.min && bp <= loc.max) {
|
|
7591
|
+
return featureObj;
|
|
7592
|
+
}
|
|
7593
|
+
}
|
|
7594
|
+
}
|
|
7595
|
+
// If mouse position is in the intron region, return the mRNA
|
|
7596
|
+
return featureObj.parent;
|
|
7753
7597
|
}
|
|
7598
|
+
// If mouse position is in a feature that is not a CDS, return the feature
|
|
7599
|
+
return featureObj;
|
|
7754
7600
|
}
|
|
7755
7601
|
return feature;
|
|
7756
7602
|
}
|
|
@@ -8213,6 +8059,7 @@ async function fetchValidTypeTerms(feature, ontologyStore, _signal) {
|
|
|
8213
8059
|
return;
|
|
8214
8060
|
}
|
|
8215
8061
|
|
|
8062
|
+
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
8216
8063
|
const useStyles$3 = makeStyles()((theme) => ({
|
|
8217
8064
|
scrollableTable: {
|
|
8218
8065
|
width: '100%',
|
|
@@ -8385,7 +8232,7 @@ function stateModelFactory$1(pluginManager, configSchema) {
|
|
|
8385
8232
|
.named('LinearApolloDisplay');
|
|
8386
8233
|
}
|
|
8387
8234
|
|
|
8388
|
-
/* eslint-disable @typescript-eslint/
|
|
8235
|
+
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
8389
8236
|
const useStyles$1 = makeStyles()((theme) => ({
|
|
8390
8237
|
canvasContainer: {
|
|
8391
8238
|
position: 'relative',
|
|
@@ -8552,6 +8399,11 @@ const useStyles = makeStyles()((theme) => ({
|
|
|
8552
8399
|
// position: 'relative',
|
|
8553
8400
|
userSelect: 'none',
|
|
8554
8401
|
},
|
|
8402
|
+
alertContainer: {
|
|
8403
|
+
display: 'flex',
|
|
8404
|
+
alignItems: 'center',
|
|
8405
|
+
justifyContent: 'center',
|
|
8406
|
+
},
|
|
8555
8407
|
}));
|
|
8556
8408
|
function scrollSelectedFeatureIntoView(model, scrollContainerRef) {
|
|
8557
8409
|
const { apolloRowHeight, selectedFeature } = model;
|
|
@@ -8574,18 +8426,18 @@ const ResizeHandle = ({ onResize, }) => {
|
|
|
8574
8426
|
const cancelDrag = useCallback((event) => {
|
|
8575
8427
|
event.stopPropagation();
|
|
8576
8428
|
event.preventDefault();
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
8429
|
+
globalThis.removeEventListener('mousemove', mouseMove);
|
|
8430
|
+
globalThis.removeEventListener('mouseup', cancelDrag);
|
|
8431
|
+
globalThis.removeEventListener('mouseleave', cancelDrag);
|
|
8580
8432
|
}, [mouseMove]);
|
|
8581
8433
|
return (
|
|
8582
8434
|
// TODO: a11y
|
|
8583
8435
|
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
|
8584
8436
|
React__default.createElement("div", { onMouseDown: (event) => {
|
|
8585
8437
|
event.stopPropagation();
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8438
|
+
globalThis.addEventListener('mousemove', mouseMove);
|
|
8439
|
+
globalThis.addEventListener('mouseup', cancelDrag);
|
|
8440
|
+
globalThis.addEventListener('mouseleave', cancelDrag);
|
|
8589
8441
|
}, onClick: (e) => {
|
|
8590
8442
|
e.stopPropagation();
|
|
8591
8443
|
e.preventDefault();
|
|
@@ -8600,6 +8452,10 @@ const AccordionControl = observer(function AccordionControl({ onClick, onResize,
|
|
|
8600
8452
|
title ? (React__default.createElement(Typography, { className: classes.title, variant: "caption", component: "span" }, title)) : null)));
|
|
8601
8453
|
});
|
|
8602
8454
|
const DisplayComponent = observer(function DisplayComponent({ model, ...other }) {
|
|
8455
|
+
const session = getSession(model);
|
|
8456
|
+
const { ontologyManager } = session.apolloDataStore;
|
|
8457
|
+
const { featureTypeOntology } = ontologyManager;
|
|
8458
|
+
const ontologyStore = featureTypeOntology?.dataStore;
|
|
8603
8459
|
const { classes } = useStyles();
|
|
8604
8460
|
const { detailsHeight, graphical, height: overallHeight, isShown, selectedFeature, table, tabularEditor, toggleShown, } = model;
|
|
8605
8461
|
const canvasScrollContainerRef = useRef(null);
|
|
@@ -8609,6 +8465,10 @@ const DisplayComponent = observer(function DisplayComponent({ model, ...other })
|
|
|
8609
8465
|
const onDetailsResize = (delta) => {
|
|
8610
8466
|
model.setDetailsHeight(detailsHeight - delta);
|
|
8611
8467
|
};
|
|
8468
|
+
if (!ontologyStore) {
|
|
8469
|
+
return (React__default.createElement("div", { className: classes.alertContainer },
|
|
8470
|
+
React__default.createElement(Alert, { severity: "error" }, "Could not load feature type ontology.")));
|
|
8471
|
+
}
|
|
8612
8472
|
if (graphical && table) {
|
|
8613
8473
|
const tabularHeight = tabularEditor.isShown ? detailsHeight : 0;
|
|
8614
8474
|
const featureAreaHeight = isShown
|
|
@@ -9178,7 +9038,7 @@ class DesktopFileDriver extends BackendDriver {
|
|
|
9178
9038
|
throw new Error(`Assembly ${assemblyName} not found`);
|
|
9179
9039
|
}
|
|
9180
9040
|
const { file } = getConf(assembly, ['sequence', 'metadata']);
|
|
9181
|
-
// eslint-disable-next-line @typescript-eslint/no-
|
|
9041
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
9182
9042
|
const fs = require('node:fs');
|
|
9183
9043
|
const fileContents = await fs.promises.readFile(file, 'utf8');
|
|
9184
9044
|
return loadAssemblyIntoClient(assemblyName, fileContents, this.clientStore);
|
|
@@ -9288,7 +9148,7 @@ class DesktopFileDriver extends BackendDriver {
|
|
|
9288
9148
|
});
|
|
9289
9149
|
}
|
|
9290
9150
|
const gff3Contents = gff.formatSync(gff3Items);
|
|
9291
|
-
// eslint-disable-next-line @typescript-eslint/no-
|
|
9151
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
9292
9152
|
const fs = require('node:fs');
|
|
9293
9153
|
await fs.promises.writeFile(file, gff3Contents, 'utf8');
|
|
9294
9154
|
const results = new ValidationResultSet();
|
|
@@ -9305,6 +9165,7 @@ function clientDataStoreFactory(AnnotationFeatureExtended) {
|
|
|
9305
9165
|
typeName: types.optional(types.literal('Client'), 'Client'),
|
|
9306
9166
|
assemblies: types.map(ApolloAssembly),
|
|
9307
9167
|
checkResults: types.map(CheckResult),
|
|
9168
|
+
ontologyManager: types.optional(OntologyManagerType, {}),
|
|
9308
9169
|
})
|
|
9309
9170
|
.views((self) => ({
|
|
9310
9171
|
get internetAccounts() {
|
|
@@ -9378,7 +9239,6 @@ function clientDataStoreFactory(AnnotationFeatureExtended) {
|
|
|
9378
9239
|
desktopFileDriver: isElectron
|
|
9379
9240
|
? new DesktopFileDriver(self)
|
|
9380
9241
|
: undefined,
|
|
9381
|
-
ontologyManager: OntologyManagerType.create(),
|
|
9382
9242
|
}))
|
|
9383
9243
|
.actions((self) => ({
|
|
9384
9244
|
afterCreate() {
|
|
@@ -9831,6 +9691,7 @@ function extendSession(pluginManager, sessionModel) {
|
|
|
9831
9691
|
postProcessor(snap) {
|
|
9832
9692
|
snap.apolloSelectedFeature = undefined;
|
|
9833
9693
|
const assemblies = Object.fromEntries(Object.entries(snap.apolloDataStore.assemblies).filter(([, assembly]) => assembly.backendDriverType === 'InMemoryFileDriver'));
|
|
9694
|
+
// @ts-expect-error ontologyManager isn't actually required
|
|
9834
9695
|
snap.apolloDataStore = {
|
|
9835
9696
|
typeName: 'Client',
|
|
9836
9697
|
assemblies,
|
|
@@ -10265,7 +10126,7 @@ class RefNameAliasAdapter extends BaseAdapter {
|
|
|
10265
10126
|
this.refNameAliases = refNameAliases;
|
|
10266
10127
|
return refNameAliases;
|
|
10267
10128
|
}
|
|
10268
|
-
|
|
10129
|
+
freeResources() {
|
|
10269
10130
|
// no resources to free
|
|
10270
10131
|
}
|
|
10271
10132
|
}
|