@apollo-annotation/jbrowse-plugin-apollo 0.3.9 → 0.3.11
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 +235 -120
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +233 -118
- 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 +562 -298
- 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 -4
- package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +1 -1
- package/src/ApolloInternetAccount/model.ts +6 -2
- package/src/BackendDrivers/CollaborationServerDriver.ts +11 -5
- package/src/ChangeManager.ts +19 -4
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +29 -9
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +2 -2
- package/src/LinearApolloDisplay/glyphs/util.ts +17 -0
- package/src/LinearApolloReferenceSequenceDisplay/drawSequenceOverlay.ts +18 -25
- package/src/LinearApolloReferenceSequenceDisplay/drawSequenceTrack.ts +41 -59
- package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +33 -2
- package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +101 -3
- package/src/components/AddAssembly.tsx +1 -1
- package/src/components/ImportFeatures.tsx +1 -1
- package/src/components/OntologyTermAutocomplete.tsx +2 -2
- package/src/components/OntologyTermMultiSelect.tsx +2 -2
- package/src/makeDisplayComponent.tsx +1 -1
- package/src/session/ClientDataStore.ts +1 -1
- package/src/session/session.ts +4 -0
- package/src/util/displayUtils.ts +28 -0
- package/src/util/glyphUtils.ts +18 -0
package/dist/index.esm.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { checkRegistry, isAssemblySpecificChange, Change, isFeatureChange, changeRegistry } from '@apollo-annotation/common';
|
|
2
|
-
import { gff3ToAnnotationFeature, AddAssemblyFromExternalChange, AddAssemblyAndFeaturesFromFileChange, AddAssemblyFromFileChange, AddAssemblyAliasesChange, AddFeatureChange, validationRegistry, ValidationResultSet, annotationFeatureToGFF3, splitStringIntoChunks, DeleteAssemblyChange, DeleteFeatureChange, LocationStartChange, LocationEndChange, AddFeaturesFromFileChange, UserChange, DeleteUserChange, MergeExonsChange, MergeTranscriptsChange, AddRefSeqAliasesChange, SplitExonChange, getDecodedToken,
|
|
2
|
+
import { gff3ToAnnotationFeature, AddAssemblyFromExternalChange, AddAssemblyAndFeaturesFromFileChange, AddAssemblyFromFileChange, AddAssemblyAliasesChange, AddFeatureChange, validationRegistry, makeUserSessionId, ValidationResultSet, annotationFeatureToGFF3, splitStringIntoChunks, DeleteAssemblyChange, DeleteFeatureChange, LocationStartChange, LocationEndChange, AddFeaturesFromFileChange, UserChange, DeleteUserChange, MergeExonsChange, MergeTranscriptsChange, AddRefSeqAliasesChange, SplitExonChange, getDecodedToken, isGFFInternalAttribute, isGFFColumnInternal, internalToGFF, gffInternalToColumn, gffToInternal, gffColumnToInternal, FeatureAttributeChange, TypeChange, StrandChange, 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 { getContainingView, isUriLocation, isLocalPathLocation, getSession, isElectron, isAbstractMenuManager, getEnv, revcom, defaultCodonTable,
|
|
6
|
+
import { isSessionModelWithWidgets, getContainingView, isUriLocation, isLocalPathLocation, getSession, isElectron, isAbstractMenuManager, getEnv, revcom, defaultCodonTable, getFrame, intersection2, doesIntersect2, measureText } from '@jbrowse/core/util';
|
|
7
7
|
import AddIcon from '@mui/icons-material/Add';
|
|
8
8
|
import { DialogTitle, IconButton, DialogContent, LinearProgress, TextField, Accordion, AccordionSummary, Typography, AccordionDetails, FormGroup, FormControlLabel, Checkbox, Box, Tooltip, Table, TableBody, TableRow, TableCell, InputAdornment, DialogActions, Button, DialogContentText, Autocomplete, FormControl, InputLabel, Select, MenuItem, RadioGroup, Radio, TableContainer, Paper, TableHead, useTheme, FormHelperText, Grid, SvgIcon, Divider, Chip, List, ListItem, ListItemText, Menu, ListItemIcon, alpha, createTheme, Alert, Badge, Avatar, CircularProgress } from '@mui/material';
|
|
9
9
|
import { autorun, entries, observable } from 'mobx';
|
|
@@ -69,7 +69,7 @@ import TrackChangesIcon from '@mui/icons-material/TrackChanges';
|
|
|
69
69
|
import UndoIcon from '@mui/icons-material/Undo';
|
|
70
70
|
import SaveIcon from '@mui/icons-material/Save';
|
|
71
71
|
|
|
72
|
-
var version = "0.3.
|
|
72
|
+
var version = "0.3.11";
|
|
73
73
|
|
|
74
74
|
const ApolloConfigSchema = ConfigurationSchema('ApolloInternetAccount', {
|
|
75
75
|
baseURL: {
|
|
@@ -504,6 +504,19 @@ function getContextMenuItemsForFeature$2(display, sourceFeature) {
|
|
|
504
504
|
]);
|
|
505
505
|
},
|
|
506
506
|
});
|
|
507
|
+
if (isSessionModelWithWidgets(session)) {
|
|
508
|
+
menuItems.push({
|
|
509
|
+
label: 'Open feature details',
|
|
510
|
+
onClick: () => {
|
|
511
|
+
const apolloGeneWidget = session.addWidget('ApolloFeatureDetailsWidget', 'apolloFeatureDetailsWidget', {
|
|
512
|
+
feature: sourceFeature,
|
|
513
|
+
assembly: currentAssemblyId,
|
|
514
|
+
refName: region.refName,
|
|
515
|
+
});
|
|
516
|
+
session.showWidget(apolloGeneWidget);
|
|
517
|
+
},
|
|
518
|
+
});
|
|
519
|
+
}
|
|
507
520
|
return menuItems;
|
|
508
521
|
}
|
|
509
522
|
function navToFeatureCenter(feature, paddingPct, refSeqLength) {
|
|
@@ -752,7 +765,7 @@ function AddAssembly({ changeManager, handleClose, session, }) {
|
|
|
752
765
|
statusMessage: 'Pre-validating',
|
|
753
766
|
progressPct: 0,
|
|
754
767
|
cancelCallback: () => {
|
|
755
|
-
controller.abort();
|
|
768
|
+
controller.abort('AddAssembly');
|
|
756
769
|
jobsManager.abortJob(job.name);
|
|
757
770
|
},
|
|
758
771
|
};
|
|
@@ -2150,7 +2163,7 @@ function OntologyTermAutocomplete({ fetchValidTerms, filterTerms: filterTermsPro
|
|
|
2150
2163
|
});
|
|
2151
2164
|
}
|
|
2152
2165
|
return () => {
|
|
2153
|
-
controller.abort();
|
|
2166
|
+
controller.abort('OntologyTermAutocomplete matcher');
|
|
2154
2167
|
};
|
|
2155
2168
|
}, [session, valueString, filterTerms, ontologyStore, needToLoadCurrentTerm]);
|
|
2156
2169
|
// effect for loading term autocompletions
|
|
@@ -2169,7 +2182,7 @@ function OntologyTermAutocomplete({ fetchValidTerms, filterTerms: filterTermsPro
|
|
|
2169
2182
|
});
|
|
2170
2183
|
}
|
|
2171
2184
|
return () => {
|
|
2172
|
-
controller.abort();
|
|
2185
|
+
controller.abort('OntologyTermAutocomplete loader');
|
|
2173
2186
|
};
|
|
2174
2187
|
}, [
|
|
2175
2188
|
needToLoadTermChoices,
|
|
@@ -2321,17 +2334,24 @@ class ChangeManager {
|
|
|
2321
2334
|
// pre-validate
|
|
2322
2335
|
const session = getSession(this.dataStore);
|
|
2323
2336
|
const controller = new AbortController();
|
|
2324
|
-
|
|
2337
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
2338
|
+
const { jobsManager, isLocked, changeInProgress, setChangeInProgress } = getSession(this.dataStore);
|
|
2325
2339
|
if (isLocked) {
|
|
2326
2340
|
session.notify('Cannot submit changes in locked mode');
|
|
2341
|
+
setChangeInProgress(false);
|
|
2327
2342
|
return;
|
|
2328
2343
|
}
|
|
2344
|
+
if (changeInProgress) {
|
|
2345
|
+
session.notify('Could not submit change, there is another change still in progress');
|
|
2346
|
+
return;
|
|
2347
|
+
}
|
|
2348
|
+
setChangeInProgress(true);
|
|
2329
2349
|
const job = {
|
|
2330
2350
|
name: change.typeName,
|
|
2331
2351
|
statusMessage: 'Pre-validating',
|
|
2332
2352
|
progressPct: 0,
|
|
2333
2353
|
cancelCallback: () => {
|
|
2334
|
-
controller.abort();
|
|
2354
|
+
controller.abort('ChangeManager');
|
|
2335
2355
|
},
|
|
2336
2356
|
};
|
|
2337
2357
|
if (updateJobsManager) {
|
|
@@ -2344,6 +2364,7 @@ class ChangeManager {
|
|
|
2344
2364
|
jobsManager.abortJob(job.name, msg);
|
|
2345
2365
|
}
|
|
2346
2366
|
session.notify(msg, 'error');
|
|
2367
|
+
setChangeInProgress(false);
|
|
2347
2368
|
return;
|
|
2348
2369
|
}
|
|
2349
2370
|
try {
|
|
@@ -2356,6 +2377,7 @@ class ChangeManager {
|
|
|
2356
2377
|
}
|
|
2357
2378
|
console.error(error);
|
|
2358
2379
|
session.notify(`Error encountered in client: ${String(error)}. Data may be out of sync, please refresh the page`, 'error');
|
|
2380
|
+
setChangeInProgress(false);
|
|
2359
2381
|
return;
|
|
2360
2382
|
}
|
|
2361
2383
|
// post-validate
|
|
@@ -2386,6 +2408,7 @@ class ChangeManager {
|
|
|
2386
2408
|
console.error(error);
|
|
2387
2409
|
session.notify(String(error), 'error');
|
|
2388
2410
|
await this.undo(change, false);
|
|
2411
|
+
setChangeInProgress(false);
|
|
2389
2412
|
return;
|
|
2390
2413
|
}
|
|
2391
2414
|
if (!backendResult.ok) {
|
|
@@ -2395,6 +2418,7 @@ class ChangeManager {
|
|
|
2395
2418
|
}
|
|
2396
2419
|
session.notify(msg, 'error');
|
|
2397
2420
|
await this.undo(change, false);
|
|
2421
|
+
setChangeInProgress(false);
|
|
2398
2422
|
return;
|
|
2399
2423
|
}
|
|
2400
2424
|
if (change.notification) {
|
|
@@ -2408,6 +2432,7 @@ class ChangeManager {
|
|
|
2408
2432
|
if (updateJobsManager) {
|
|
2409
2433
|
jobsManager.done(job);
|
|
2410
2434
|
}
|
|
2435
|
+
setChangeInProgress(false);
|
|
2411
2436
|
}
|
|
2412
2437
|
async undo(change, submitToBackend = true) {
|
|
2413
2438
|
const inverseChange = change.getInverse();
|
|
@@ -2513,17 +2538,22 @@ class CollaborationServerDriver extends BackendDriver {
|
|
|
2513
2538
|
checkSocket(assembly, refSeq, internetAccount) {
|
|
2514
2539
|
const { socket } = internetAccount;
|
|
2515
2540
|
const token = internetAccount.retrieveToken();
|
|
2541
|
+
if (!token) {
|
|
2542
|
+
return;
|
|
2543
|
+
}
|
|
2544
|
+
const localSessionId = makeUserSessionId(token);
|
|
2516
2545
|
const channel = `${assembly}-${refSeq}`;
|
|
2517
2546
|
const changeManager = new ChangeManager(this.clientStore);
|
|
2518
2547
|
if (!socket.hasListeners(channel)) {
|
|
2519
2548
|
socket.on(channel, async (message) => {
|
|
2520
2549
|
// Save server last change sequence into session storage
|
|
2521
2550
|
internetAccount.setLastChangeSequenceNumber(Number(message.changeSequence));
|
|
2522
|
-
if (message.userSessionId
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2551
|
+
if (message.userSessionId === localSessionId) {
|
|
2552
|
+
return; // we did this change, no need to apply it again
|
|
2553
|
+
}
|
|
2554
|
+
const change = Change.fromJSON(message.changeInfo);
|
|
2555
|
+
if (isFeatureChange(change) && this.haveDataForChange(change)) {
|
|
2556
|
+
await changeManager.submit(change, { submitToBackend: false });
|
|
2527
2557
|
}
|
|
2528
2558
|
});
|
|
2529
2559
|
}
|
|
@@ -3936,7 +3966,7 @@ function ImportFeatures({ changeManager, handleClose, session, }) {
|
|
|
3936
3966
|
statusMessage: 'Uploading file, this may take awhile',
|
|
3937
3967
|
progressPct: 0,
|
|
3938
3968
|
cancelCallback: () => {
|
|
3939
|
-
controller.abort();
|
|
3969
|
+
controller.abort('ImportFeatures');
|
|
3940
3970
|
jobsManager.abortJob(job.name);
|
|
3941
3971
|
},
|
|
3942
3972
|
};
|
|
@@ -5166,7 +5196,7 @@ const AuthTypeSelector = ({ baseURL, handleClose, name, }) => {
|
|
|
5166
5196
|
}
|
|
5167
5197
|
});
|
|
5168
5198
|
return () => {
|
|
5169
|
-
controller.abort();
|
|
5199
|
+
controller.abort('AuthTypeSelector');
|
|
5170
5200
|
};
|
|
5171
5201
|
}, [baseURL]);
|
|
5172
5202
|
function handleClick(authType) {
|
|
@@ -5603,8 +5633,13 @@ const stateModelFactory$3 = (configSchema) => {
|
|
|
5603
5633
|
return;
|
|
5604
5634
|
}
|
|
5605
5635
|
if (self.role) {
|
|
5606
|
-
|
|
5607
|
-
|
|
5636
|
+
try {
|
|
5637
|
+
await self.initialize(self.role);
|
|
5638
|
+
reaction.dispose();
|
|
5639
|
+
}
|
|
5640
|
+
catch {
|
|
5641
|
+
// if initialize fails, do nothing so the autorun runs again
|
|
5642
|
+
}
|
|
5608
5643
|
}
|
|
5609
5644
|
}, { name: 'ApolloInternetAccount' });
|
|
5610
5645
|
},
|
|
@@ -6879,6 +6914,7 @@ const TranscriptWidgetEditLocation = observer(function TranscriptWidgetEditLocat
|
|
|
6879
6914
|
const refData = currentAssembly?.getByRefName(refName);
|
|
6880
6915
|
const { changeManager } = session.apolloDataStore;
|
|
6881
6916
|
const seqRef = useRef(null);
|
|
6917
|
+
const { changeInProgress } = session;
|
|
6882
6918
|
if (!refData) {
|
|
6883
6919
|
return null;
|
|
6884
6920
|
}
|
|
@@ -7326,10 +7362,13 @@ const TranscriptWidgetEditLocation = observer(function TranscriptWidgetEditLocat
|
|
|
7326
7362
|
// highlight start codon and stop codons
|
|
7327
7363
|
if (codonSeq === 'ATG') {
|
|
7328
7364
|
elements.push(React.createElement(Typography, { component: 'span', style: {
|
|
7329
|
-
backgroundColor: 'yellow',
|
|
7365
|
+
backgroundColor: changeInProgress ? 'lightgray' : 'yellow',
|
|
7330
7366
|
cursor: 'pointer',
|
|
7331
7367
|
border: '1px solid black',
|
|
7332
7368
|
}, key: codonGenomicPos, onClick: () => {
|
|
7369
|
+
if (changeInProgress) {
|
|
7370
|
+
return;
|
|
7371
|
+
}
|
|
7333
7372
|
// NOTE: codonGenomicPos is important here for calculating the genomic location
|
|
7334
7373
|
// of the start codon. We are using the codonGenomicPos as the key in the typography
|
|
7335
7374
|
// elements to maintain the genomic postion of the codon start
|
|
@@ -7413,20 +7452,21 @@ const TranscriptWidgetEditLocation = observer(function TranscriptWidgetEditLocat
|
|
|
7413
7452
|
}
|
|
7414
7453
|
// Trim any sequence before first start codon and after stop codon
|
|
7415
7454
|
const startCodonIndex = translationSequence.indexOf('M');
|
|
7416
|
-
const stopCodonIndex = translationSequence.indexOf('*')
|
|
7455
|
+
const stopCodonIndex = translationSequence.indexOf('*');
|
|
7417
7456
|
const startCodonPos = translSeqCodonStartGenomicPosArr[startCodonIndex].codonGenomicPos;
|
|
7418
7457
|
const stopCodonPos = translSeqCodonStartGenomicPosArr[stopCodonIndex].codonGenomicPos;
|
|
7419
7458
|
if (!startCodonPos || !stopCodonPos) {
|
|
7420
7459
|
return;
|
|
7421
7460
|
}
|
|
7422
7461
|
const startCodonGenomicLoc = getCodonGenomicLocation(startCodonPos);
|
|
7423
|
-
|
|
7462
|
+
let stopCodonGenomicLoc = getCodonGenomicLocation(stopCodonPos);
|
|
7424
7463
|
if (strand === 1) {
|
|
7425
7464
|
if (startCodonGenomicLoc > stopCodonGenomicLoc) {
|
|
7426
7465
|
notify('Start codon genomic location should be less than stop codon genomic location', 'error');
|
|
7427
7466
|
return;
|
|
7428
7467
|
}
|
|
7429
7468
|
let promise;
|
|
7469
|
+
stopCodonGenomicLoc += 3; // move to end of stop codon
|
|
7430
7470
|
if (startCodonGenomicLoc !== cdsMin) {
|
|
7431
7471
|
promise = new Promise((resolve) => {
|
|
7432
7472
|
updateCDSLocation(cdsMin, startCodonGenomicLoc, feature, true, () => {
|
|
@@ -7452,6 +7492,7 @@ const TranscriptWidgetEditLocation = observer(function TranscriptWidgetEditLocat
|
|
|
7452
7492
|
return;
|
|
7453
7493
|
}
|
|
7454
7494
|
let promise;
|
|
7495
|
+
stopCodonGenomicLoc -= 3; // move to end of stop codon
|
|
7455
7496
|
if (startCodonGenomicLoc !== cdsMax) {
|
|
7456
7497
|
promise = new Promise((resolve) => {
|
|
7457
7498
|
updateCDSLocation(cdsMax, startCodonGenomicLoc, feature, false, () => {
|
|
@@ -7495,27 +7536,29 @@ const TranscriptWidgetEditLocation = observer(function TranscriptWidgetEditLocat
|
|
|
7495
7536
|
gap: 10,
|
|
7496
7537
|
} },
|
|
7497
7538
|
React.createElement(Tooltip, { title: "Copy" },
|
|
7498
|
-
React.createElement(
|
|
7539
|
+
React.createElement("button", { onClick: onCopyClick, style: { border: 'none', background: 'none', padding: 0 }, disabled: changeInProgress },
|
|
7540
|
+
React.createElement(ContentCopyIcon, { style: { fontSize: 15 } }))),
|
|
7499
7541
|
React.createElement(Tooltip, { title: "Trim" },
|
|
7500
|
-
React.createElement(
|
|
7542
|
+
React.createElement("button", { onClick: trimTranslationSequence, style: { border: 'none', background: 'none', padding: 0 }, disabled: changeInProgress },
|
|
7543
|
+
React.createElement(ContentCutIcon, { style: { fontSize: 15 } })))))),
|
|
7501
7544
|
React.createElement(Grid, { container: true, justifyContent: "center", alignItems: "center", style: { textAlign: 'center', marginTop: 10 } },
|
|
7502
7545
|
React.createElement(Grid, { size: 1 }),
|
|
7503
7546
|
strand === 1 ? (React.createElement(Grid, { size: 4 },
|
|
7504
7547
|
React.createElement(StyledTextField, { margin: "dense", variant: "outlined", value: cdsMin + 1, onChangeCommitted: (newLocation) => {
|
|
7505
7548
|
return updateCDSLocation(cdsMin, newLocation - 1, feature, true);
|
|
7506
|
-
}, style: { border: '1px solid black', borderRadius: 5 } }))) : (React.createElement(Grid, { size: 4 },
|
|
7549
|
+
}, style: { border: '1px solid black', borderRadius: 5 }, disabled: changeInProgress }))) : (React.createElement(Grid, { size: 4 },
|
|
7507
7550
|
React.createElement(StyledTextField, { margin: "dense", variant: "outlined", value: cdsMax, onChangeCommitted: (newLocation) => {
|
|
7508
7551
|
return updateCDSLocation(cdsMax, newLocation, feature, false);
|
|
7509
|
-
}, style: { border: '1px solid black', borderRadius: 5 } }))),
|
|
7552
|
+
}, style: { border: '1px solid black', borderRadius: 5 }, disabled: changeInProgress }))),
|
|
7510
7553
|
React.createElement(Grid, { size: 2 },
|
|
7511
7554
|
React.createElement(Typography, { component: 'span' }, "CDS")),
|
|
7512
7555
|
strand === 1 ? (React.createElement(Grid, { size: 4 },
|
|
7513
7556
|
React.createElement(StyledTextField, { margin: "dense", variant: "outlined", value: cdsMax, onChangeCommitted: (newLocation) => {
|
|
7514
7557
|
return updateCDSLocation(cdsMax, newLocation, feature, false);
|
|
7515
|
-
}, style: { border: '1px solid black', borderRadius: 5 } }))) : (React.createElement(Grid, { size: 4 },
|
|
7558
|
+
}, style: { border: '1px solid black', borderRadius: 5 }, disabled: changeInProgress }))) : (React.createElement(Grid, { size: 4 },
|
|
7516
7559
|
React.createElement(StyledTextField, { margin: "dense", variant: "outlined", value: cdsMin + 1, onChangeCommitted: (newLocation) => {
|
|
7517
7560
|
return updateCDSLocation(cdsMin, newLocation - 1, feature, true);
|
|
7518
|
-
}, style: { border: '1px solid black', borderRadius: 5 } }))),
|
|
7561
|
+
}, style: { border: '1px solid black', borderRadius: 5 }, disabled: changeInProgress }))),
|
|
7519
7562
|
React.createElement(Grid, { size: 1 })))),
|
|
7520
7563
|
React.createElement("div", { style: { marginTop: 5 } }, transcriptExonParts.map((loc, index) => {
|
|
7521
7564
|
return (React.createElement("div", { key: index }, loc.type === 'exon' && (React.createElement(Grid, { container: true, justifyContent: "center", alignItems: "center", style: { textAlign: 'center' } },
|
|
@@ -7524,19 +7567,19 @@ const TranscriptWidgetEditLocation = observer(function TranscriptWidgetEditLocat
|
|
|
7524
7567
|
strand === 1 ? (React.createElement(Grid, { size: 4, style: { padding: 0 } },
|
|
7525
7568
|
React.createElement(StyledTextField, { margin: "dense", variant: "outlined", value: loc.min + 1, onChangeCommitted: (newLocation) => {
|
|
7526
7569
|
return handleExonLocationChange(loc.min, newLocation - 1, feature, true);
|
|
7527
|
-
} }))) : (React.createElement(Grid, { size: 4, style: { padding: 0 } },
|
|
7570
|
+
}, disabled: changeInProgress }))) : (React.createElement(Grid, { size: 4, style: { padding: 0 } },
|
|
7528
7571
|
React.createElement(StyledTextField, { margin: "dense", variant: "outlined", value: loc.max, onChangeCommitted: (newLocation) => {
|
|
7529
7572
|
return handleExonLocationChange(loc.max, newLocation, feature, false);
|
|
7530
|
-
} }))),
|
|
7573
|
+
}, disabled: changeInProgress }))),
|
|
7531
7574
|
React.createElement(Grid, { size: 2 },
|
|
7532
7575
|
React.createElement(Strand, { strand: feature.strand })),
|
|
7533
7576
|
strand === 1 ? (React.createElement(Grid, { size: 4, style: { padding: 0 } },
|
|
7534
7577
|
React.createElement(StyledTextField, { margin: "dense", variant: "outlined", value: loc.max, onChangeCommitted: (newLocation) => {
|
|
7535
7578
|
return handleExonLocationChange(loc.max, newLocation, feature, false);
|
|
7536
|
-
} }))) : (React.createElement(Grid, { size: 4, style: { padding: 0 } },
|
|
7579
|
+
}, disabled: changeInProgress }))) : (React.createElement(Grid, { size: 4, style: { padding: 0 } },
|
|
7537
7580
|
React.createElement(StyledTextField, { margin: "dense", variant: "outlined", value: loc.min + 1, onChangeCommitted: (newLocation) => {
|
|
7538
7581
|
return handleExonLocationChange(loc.min, newLocation - 1, feature, true);
|
|
7539
|
-
} }))),
|
|
7582
|
+
}, disabled: changeInProgress }))),
|
|
7540
7583
|
React.createElement(Grid, { size: 1 }, index !== transcriptExonParts.length - 1 &&
|
|
7541
7584
|
getThreePrimeSpliceSite(loc, index).map((site, idx) => (React.createElement(Typography, { key: idx, component: 'span', color: site.color }, site.spliceSite))))))));
|
|
7542
7585
|
}))));
|
|
@@ -9205,8 +9248,8 @@ function getContextMenuItems$2(display, mousePosition) {
|
|
|
9205
9248
|
},
|
|
9206
9249
|
});
|
|
9207
9250
|
if (isSessionModelWithWidgets(session)) {
|
|
9208
|
-
contextMenuItemsForFeature.
|
|
9209
|
-
label: 'Open transcript
|
|
9251
|
+
contextMenuItemsForFeature.splice(1, 0, {
|
|
9252
|
+
label: 'Open transcript editor',
|
|
9210
9253
|
onClick: () => {
|
|
9211
9254
|
const apolloTranscriptWidget = session.addWidget('ApolloTranscriptDetails', 'apolloTranscriptDetails', {
|
|
9212
9255
|
feature,
|
|
@@ -9554,6 +9597,25 @@ function clusterResultByMessage(items, width, touchesAsOverlap) {
|
|
|
9554
9597
|
clusters.sort((a, b) => a.message.localeCompare(b.message) || a.start - b.start);
|
|
9555
9598
|
return clusters;
|
|
9556
9599
|
}
|
|
9600
|
+
function codonColorCode(letter, theme, highContrast) {
|
|
9601
|
+
if (letter === 'M') {
|
|
9602
|
+
return theme.palette.startCodon;
|
|
9603
|
+
}
|
|
9604
|
+
if (letter === '*') {
|
|
9605
|
+
return highContrast ? theme.palette.text.primary : theme.palette.stopCodon;
|
|
9606
|
+
}
|
|
9607
|
+
return;
|
|
9608
|
+
}
|
|
9609
|
+
function colorCode(letter, theme) {
|
|
9610
|
+
const letterUpper = letter.toUpperCase();
|
|
9611
|
+
if (letterUpper === 'A' ||
|
|
9612
|
+
letterUpper === 'C' ||
|
|
9613
|
+
letterUpper === 'G' ||
|
|
9614
|
+
letterUpper === 'T') {
|
|
9615
|
+
return theme.palette.bases[letterUpper].main.toString();
|
|
9616
|
+
}
|
|
9617
|
+
return 'lightgray';
|
|
9618
|
+
}
|
|
9557
9619
|
|
|
9558
9620
|
const minDisplayHeight$2 = 20;
|
|
9559
9621
|
function baseModelFactory$2(_pluginManager, configSchema) {
|
|
@@ -10379,34 +10441,26 @@ function stateModelFactory$2(pluginManager, configSchema) {
|
|
|
10379
10441
|
|
|
10380
10442
|
const configSchema$1 = ConfigurationSchema('LinearApolloReferenceSequenceDisplay', {}, { explicitIdentifier: 'displayId', explicitlyTyped: true });
|
|
10381
10443
|
|
|
10382
|
-
function getSeqRow(strand, bpPerPx) {
|
|
10444
|
+
function getSeqRow(strand, bpPerPx, reversed) {
|
|
10383
10445
|
if (bpPerPx > 1 || strand === undefined) {
|
|
10384
10446
|
return;
|
|
10385
10447
|
}
|
|
10448
|
+
if (reversed) {
|
|
10449
|
+
return strand === 1 ? 4 : 3;
|
|
10450
|
+
}
|
|
10386
10451
|
return strand === 1 ? 3 : 4;
|
|
10387
10452
|
}
|
|
10388
|
-
function getTranslationRow(frame, bpPerPx) {
|
|
10389
|
-
const
|
|
10390
|
-
|
|
10391
|
-
|
|
10392
|
-
return 0;
|
|
10393
|
-
}
|
|
10394
|
-
case 2: {
|
|
10395
|
-
return 1;
|
|
10396
|
-
}
|
|
10397
|
-
case 1: {
|
|
10398
|
-
return 2;
|
|
10399
|
-
}
|
|
10400
|
-
case -1: {
|
|
10401
|
-
return 3 + offset;
|
|
10402
|
-
}
|
|
10403
|
-
case -2: {
|
|
10404
|
-
return 4 + offset;
|
|
10405
|
-
}
|
|
10406
|
-
case -3: {
|
|
10407
|
-
return 5 + offset;
|
|
10408
|
-
}
|
|
10453
|
+
function getTranslationRow(frame, bpPerPx, reversed) {
|
|
10454
|
+
const frameRows = bpPerPx <= 1 ? [2, 1, 0, 7, 6, 5] : [2, 1, 0, 5, 4, 3];
|
|
10455
|
+
if (reversed) {
|
|
10456
|
+
frameRows.reverse();
|
|
10409
10457
|
}
|
|
10458
|
+
frameRows.unshift(0);
|
|
10459
|
+
const row = frameRows.at(frame);
|
|
10460
|
+
if (row === undefined) {
|
|
10461
|
+
throw new Error('could not find row');
|
|
10462
|
+
}
|
|
10463
|
+
return row;
|
|
10410
10464
|
}
|
|
10411
10465
|
function getLeftPx$1(feature, bpPerPx, offsetPx, block) {
|
|
10412
10466
|
const blockLeftPx = block.offsetPx - offsetPx;
|
|
@@ -10428,7 +10482,7 @@ function fillAndStrokeRect(ctx, left, top, width, height, theme, selected = fals
|
|
|
10428
10482
|
ctx.strokeRect(left, top, width, height);
|
|
10429
10483
|
}
|
|
10430
10484
|
function drawHighlight(ctx, feature, bpPerPx, offsetPx, rowHeight, block, theme, selected = false) {
|
|
10431
|
-
const row = getSeqRow(feature.strand, bpPerPx);
|
|
10485
|
+
const row = getSeqRow(feature.strand, bpPerPx, block.reversed);
|
|
10432
10486
|
if (!row) {
|
|
10433
10487
|
return;
|
|
10434
10488
|
}
|
|
@@ -10452,7 +10506,7 @@ function drawCDSHighlight(ctx, feature, bpPerPx, offsetPx, rowHeight, block, the
|
|
|
10452
10506
|
}
|
|
10453
10507
|
for (const loc of cdsLocs) {
|
|
10454
10508
|
const frame = getFrame(loc.min, loc.max, feature.strand ?? 1, loc.phase);
|
|
10455
|
-
const row = getTranslationRow(frame, bpPerPx);
|
|
10509
|
+
const row = getTranslationRow(frame, bpPerPx, block.reversed);
|
|
10456
10510
|
const left = getLeftPx$1(loc, bpPerPx, offsetPx, block);
|
|
10457
10511
|
const top = row * rowHeight;
|
|
10458
10512
|
const width = (loc.max - loc.min) / bpPerPx;
|
|
@@ -10475,76 +10529,71 @@ function drawSequenceOverlay(canvas, ctx, hoveredFeature, selectedFeature, rowHe
|
|
|
10475
10529
|
hoveredFeature?.feature,
|
|
10476
10530
|
].filter((f) => f !== undefined)) {
|
|
10477
10531
|
if (featureTypeOntology.isTypeOf(feature.type, 'CDS')) {
|
|
10478
|
-
drawCDSHighlight(ctx, feature, bpPerPx, offsetPx, rowHeight, block, theme,
|
|
10532
|
+
drawCDSHighlight(ctx, feature, bpPerPx, offsetPx, rowHeight, block, theme, feature._id === selectedFeature?._id);
|
|
10479
10533
|
}
|
|
10480
10534
|
else {
|
|
10481
|
-
drawHighlight(ctx, feature, bpPerPx, offsetPx, rowHeight, block, theme,
|
|
10535
|
+
drawHighlight(ctx, feature, bpPerPx, offsetPx, rowHeight, block, theme, feature._id === selectedFeature?._id);
|
|
10482
10536
|
}
|
|
10483
10537
|
}
|
|
10484
10538
|
ctx.restore();
|
|
10485
10539
|
}
|
|
10486
10540
|
}
|
|
10487
10541
|
|
|
10488
|
-
function
|
|
10489
|
-
const
|
|
10490
|
-
|
|
10491
|
-
|
|
10492
|
-
|
|
10493
|
-
|
|
10494
|
-
|
|
10495
|
-
|
|
10496
|
-
return
|
|
10542
|
+
function getLeftPx(display, feature, block) {
|
|
10543
|
+
const { lgv } = display;
|
|
10544
|
+
const { bpPerPx, offsetPx } = lgv;
|
|
10545
|
+
const blockLeftPx = block.offsetPx - offsetPx;
|
|
10546
|
+
const featureLeftBpDistanceFromBlockLeftBp = block.reversed
|
|
10547
|
+
? block.end - feature.max
|
|
10548
|
+
: feature.min - block.start;
|
|
10549
|
+
const featureLeftPxDistanceFromBlockLeftPx = featureLeftBpDistanceFromBlockLeftBp / bpPerPx;
|
|
10550
|
+
return blockLeftPx + featureLeftPxDistanceFromBlockLeftPx;
|
|
10497
10551
|
}
|
|
10498
|
-
|
|
10499
|
-
|
|
10500
|
-
|
|
10501
|
-
|
|
10502
|
-
|
|
10503
|
-
|
|
10504
|
-
|
|
10505
|
-
|
|
10552
|
+
/**
|
|
10553
|
+
* Perform a canvas strokeRect, but have the stroke be contained within the
|
|
10554
|
+
* given rect instead of centered on it.
|
|
10555
|
+
*/
|
|
10556
|
+
function strokeRectInner(ctx, left, top, width, height, color) {
|
|
10557
|
+
ctx.strokeStyle = color;
|
|
10558
|
+
ctx.lineWidth = 1;
|
|
10559
|
+
ctx.strokeRect(left + 0.5, top + 0.5, width - 1, height - 1);
|
|
10506
10560
|
}
|
|
10561
|
+
|
|
10507
10562
|
function drawLetter(seqTrackctx, left, top, width, letter) {
|
|
10508
10563
|
const fontSize = Math.min(width, 10);
|
|
10509
10564
|
seqTrackctx.fillStyle = '#000';
|
|
10510
10565
|
seqTrackctx.font = `${fontSize}px`;
|
|
10511
10566
|
const textWidth = seqTrackctx.measureText(letter).width;
|
|
10512
|
-
const textX = left + (width - textWidth) / 2;
|
|
10567
|
+
const textX = Math.round(left + (width - textWidth) / 2);
|
|
10513
10568
|
seqTrackctx.fillText(letter, textX, top + 10);
|
|
10514
10569
|
}
|
|
10515
|
-
function drawTranslationFrameBackgrounds(
|
|
10570
|
+
function drawTranslationFrameBackgrounds(ctx, bpPerPx, theme, highContrast, left, width, sequenceRowHeight, reversed) {
|
|
10516
10571
|
const frames = bpPerPx <= 1 ? [3, 2, 1, 0, 0, -1, -2, -3] : [3, 2, 1, -1, -2, -3];
|
|
10572
|
+
if (reversed) {
|
|
10573
|
+
frames.reverse();
|
|
10574
|
+
}
|
|
10517
10575
|
for (const [idx, frame] of frames.entries()) {
|
|
10518
10576
|
const frameColor = theme.palette.framesCDS.at(frame)?.main;
|
|
10519
10577
|
if (!frameColor) {
|
|
10520
10578
|
continue;
|
|
10521
10579
|
}
|
|
10522
10580
|
const top = idx * sequenceRowHeight;
|
|
10523
|
-
const { offsetPx } = dynamicBlocks;
|
|
10524
|
-
const left = Math.max(0, -offsetPx);
|
|
10525
|
-
const width = dynamicBlocks.totalWidthPx;
|
|
10526
10581
|
ctx.fillStyle = highContrast ? theme.palette.background.default : frameColor;
|
|
10527
10582
|
ctx.fillRect(left, top, width, sequenceRowHeight);
|
|
10528
10583
|
if (highContrast) {
|
|
10529
10584
|
// eslint-disable-next-line prefer-destructuring
|
|
10530
|
-
|
|
10531
|
-
ctx
|
|
10532
|
-
}
|
|
10533
|
-
}
|
|
10534
|
-
// allows inter-region padding lines to show through
|
|
10535
|
-
for (const block of dynamicBlocks.getBlocks()) {
|
|
10536
|
-
if (block.type === 'InterRegionPaddingBlock') {
|
|
10537
|
-
const left = block.offsetPx - dynamicBlocks.offsetPx;
|
|
10538
|
-
ctx.clearRect(left, 0, block.widthPx, canvas.height);
|
|
10585
|
+
const strokeStyle = theme.palette.grey[200];
|
|
10586
|
+
strokeRectInner(ctx, left, top, width, sequenceRowHeight, strokeStyle);
|
|
10539
10587
|
}
|
|
10540
10588
|
}
|
|
10541
10589
|
}
|
|
10542
10590
|
function drawBase(ctx, base, index, leftPx, bpPerPx, rowHeight, theme) {
|
|
10543
|
-
|
|
10544
|
-
if (width < 1) {
|
|
10591
|
+
if (1 / bpPerPx < 1) {
|
|
10545
10592
|
return;
|
|
10546
10593
|
}
|
|
10547
|
-
const left = leftPx + index / bpPerPx;
|
|
10594
|
+
const left = Math.round(leftPx + index / bpPerPx);
|
|
10595
|
+
const nextLeft = Math.round(leftPx + (index + 1) / bpPerPx);
|
|
10596
|
+
const width = nextLeft - left;
|
|
10548
10597
|
const strands = [-1, 1];
|
|
10549
10598
|
for (const strand of strands) {
|
|
10550
10599
|
const top = (strand === 1 ? 3 : 4) * rowHeight;
|
|
@@ -10552,13 +10601,13 @@ function drawBase(ctx, base, index, leftPx, bpPerPx, rowHeight, theme) {
|
|
|
10552
10601
|
ctx.fillStyle = colorCode(baseCode, theme);
|
|
10553
10602
|
ctx.fillRect(left, top, width, rowHeight);
|
|
10554
10603
|
if (1 / bpPerPx >= 12) {
|
|
10555
|
-
|
|
10556
|
-
ctx
|
|
10604
|
+
const strokeStyle = theme.palette.text.disabled;
|
|
10605
|
+
strokeRectInner(ctx, left, top, width, rowHeight, strokeStyle);
|
|
10557
10606
|
drawLetter(ctx, left, top, width, baseCode);
|
|
10558
10607
|
}
|
|
10559
10608
|
}
|
|
10560
10609
|
}
|
|
10561
|
-
function drawCodon(ctx, codon, leftPx, index, theme, highContrast, bpPerPx, bp, rowHeight, showStartCodons, showStopCodons) {
|
|
10610
|
+
function drawCodon$1(ctx, codon, leftPx, index, theme, highContrast, bpPerPx, bp, rowHeight, showStartCodons, showStopCodons) {
|
|
10562
10611
|
const frameOffsets = (bpPerPx <= 1 ? [0, 2, 1, 0, 7, 6, 5] : [0, 2, 1, 0, 5, 4, 3]).map((b) => b * rowHeight);
|
|
10563
10612
|
const strands = [-1, 1];
|
|
10564
10613
|
for (const strand of strands) {
|
|
@@ -10568,7 +10617,8 @@ function drawCodon(ctx, codon, leftPx, index, theme, highContrast, bpPerPx, bp,
|
|
|
10568
10617
|
continue;
|
|
10569
10618
|
}
|
|
10570
10619
|
const left = Math.round(leftPx + index / bpPerPx);
|
|
10571
|
-
const
|
|
10620
|
+
const nextLeft = Math.round(leftPx + (index + 3) / bpPerPx);
|
|
10621
|
+
const width = nextLeft - left;
|
|
10572
10622
|
const codonCode = strand === 1 ? codon : revcom(codon);
|
|
10573
10623
|
const aminoAcidCode = defaultCodonTable[codonCode];
|
|
10574
10624
|
const fillColor = codonColorCode(aminoAcidCode, theme, highContrast);
|
|
@@ -10579,8 +10629,8 @@ function drawCodon(ctx, codon, leftPx, index, theme, highContrast, bpPerPx, bp,
|
|
|
10579
10629
|
ctx.fillRect(left, top, width, rowHeight);
|
|
10580
10630
|
}
|
|
10581
10631
|
if (1 / bpPerPx >= 4) {
|
|
10582
|
-
|
|
10583
|
-
ctx
|
|
10632
|
+
const strokeStyle = theme.palette.text.disabled;
|
|
10633
|
+
strokeRectInner(ctx, left, top, width, rowHeight, strokeStyle);
|
|
10584
10634
|
drawLetter(ctx, left, top, width, aminoAcidCode);
|
|
10585
10635
|
}
|
|
10586
10636
|
}
|
|
@@ -10591,9 +10641,10 @@ function drawSequenceTrack(canvas, theme, bpPerPx, offsetPx, dynamicBlocks, high
|
|
|
10591
10641
|
return;
|
|
10592
10642
|
}
|
|
10593
10643
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
10594
|
-
drawTranslationFrameBackgrounds(canvas, ctx, bpPerPx, theme, dynamicBlocks, highContrast, sequenceRowHeight);
|
|
10595
10644
|
const { apolloDataStore } = session;
|
|
10596
10645
|
for (const block of dynamicBlocks.contentBlocks) {
|
|
10646
|
+
const totalOffsetPx = block.offsetPx - offsetPx;
|
|
10647
|
+
drawTranslationFrameBackgrounds(ctx, bpPerPx, theme, highContrast, totalOffsetPx, block.widthPx, sequenceRowHeight, block.reversed);
|
|
10597
10648
|
const assembly = apolloDataStore.assemblies.get(block.assemblyName);
|
|
10598
10649
|
const ref = assembly?.getByRefName(block.refName);
|
|
10599
10650
|
const roundedStart = Math.floor(block.start);
|
|
@@ -10603,16 +10654,20 @@ function drawSequenceTrack(canvas, theme, bpPerPx, offsetPx, dynamicBlocks, high
|
|
|
10603
10654
|
return;
|
|
10604
10655
|
}
|
|
10605
10656
|
seq = seq.toUpperCase();
|
|
10606
|
-
|
|
10607
|
-
|
|
10657
|
+
if (block.reversed) {
|
|
10658
|
+
seq = revcom(seq);
|
|
10659
|
+
}
|
|
10660
|
+
const baseOffsetPx = (block.reversed ? roundedEnd - block.end : block.start - roundedStart) /
|
|
10661
|
+
bpPerPx;
|
|
10662
|
+
const seqLeftPx = totalOffsetPx - baseOffsetPx;
|
|
10608
10663
|
for (let i = 0; i < seq.length; i++) {
|
|
10609
|
-
const bp = roundedStart + i;
|
|
10664
|
+
const bp = block.reversed ? roundedEnd - i : roundedStart + i;
|
|
10610
10665
|
const codon = seq.slice(i, i + 3);
|
|
10611
10666
|
drawBase(ctx, seq[i], i, seqLeftPx, bpPerPx, sequenceRowHeight, theme);
|
|
10612
10667
|
if (codon.length !== 3) {
|
|
10613
10668
|
continue;
|
|
10614
10669
|
}
|
|
10615
|
-
drawCodon(ctx, codon, seqLeftPx, i, theme, highContrast, bpPerPx, bp, sequenceRowHeight, showStartCodons, showStopCodons);
|
|
10670
|
+
drawCodon$1(ctx, codon, seqLeftPx, i, theme, highContrast, bpPerPx, bp, sequenceRowHeight, showStartCodons, showStopCodons);
|
|
10616
10671
|
}
|
|
10617
10672
|
}
|
|
10618
10673
|
}
|
|
@@ -11676,6 +11731,8 @@ function baseModelFactory(_pluginManager, configSchema) {
|
|
|
11676
11731
|
graphical: true,
|
|
11677
11732
|
table: false,
|
|
11678
11733
|
showFeatureLabels: true,
|
|
11734
|
+
showStartCodons: false,
|
|
11735
|
+
showStopCodons: true,
|
|
11679
11736
|
showCheckResults: true,
|
|
11680
11737
|
zoomThreshold: 200,
|
|
11681
11738
|
heightPreConfig: types.maybe(types.refinement('displayHeight', types.number, (n) => n >= minDisplayHeight)),
|
|
@@ -11804,6 +11861,12 @@ function baseModelFactory(_pluginManager, configSchema) {
|
|
|
11804
11861
|
toggleShowFeatureLabels() {
|
|
11805
11862
|
self.showFeatureLabels = !self.showFeatureLabels;
|
|
11806
11863
|
},
|
|
11864
|
+
toggleShowStartCodons() {
|
|
11865
|
+
self.showStartCodons = !self.showStartCodons;
|
|
11866
|
+
},
|
|
11867
|
+
toggleShowStopCodons() {
|
|
11868
|
+
self.showStopCodons = !self.showStopCodons;
|
|
11869
|
+
},
|
|
11807
11870
|
toggleShowCheckResults() {
|
|
11808
11871
|
self.showCheckResults = !self.showCheckResults;
|
|
11809
11872
|
},
|
|
@@ -11818,7 +11881,7 @@ function baseModelFactory(_pluginManager, configSchema) {
|
|
|
11818
11881
|
const { filteredFeatureTypes, trackMenuItems: superTrackMenuItems } = self;
|
|
11819
11882
|
return {
|
|
11820
11883
|
trackMenuItems() {
|
|
11821
|
-
const { graphical, table, showFeatureLabels, showCheckResults } = self;
|
|
11884
|
+
const { graphical, table, showFeatureLabels, showStartCodons, showStopCodons, showCheckResults, } = self;
|
|
11822
11885
|
return [
|
|
11823
11886
|
...superTrackMenuItems(),
|
|
11824
11887
|
{
|
|
@@ -11857,6 +11920,22 @@ function baseModelFactory(_pluginManager, configSchema) {
|
|
|
11857
11920
|
self.toggleShowFeatureLabels();
|
|
11858
11921
|
},
|
|
11859
11922
|
},
|
|
11923
|
+
{
|
|
11924
|
+
label: 'Show start codons',
|
|
11925
|
+
type: 'checkbox',
|
|
11926
|
+
checked: showStartCodons,
|
|
11927
|
+
onClick: () => {
|
|
11928
|
+
self.toggleShowStartCodons();
|
|
11929
|
+
},
|
|
11930
|
+
},
|
|
11931
|
+
{
|
|
11932
|
+
label: 'Show stop codons',
|
|
11933
|
+
type: 'checkbox',
|
|
11934
|
+
checked: showStopCodons,
|
|
11935
|
+
onClick: () => {
|
|
11936
|
+
self.toggleShowStopCodons();
|
|
11937
|
+
},
|
|
11938
|
+
},
|
|
11860
11939
|
{
|
|
11861
11940
|
label: 'Check Results',
|
|
11862
11941
|
type: 'checkbox',
|
|
@@ -11933,7 +12012,7 @@ function baseModelFactory(_pluginManager, configSchema) {
|
|
|
11933
12012
|
return;
|
|
11934
12013
|
}
|
|
11935
12014
|
void self.session.apolloDataStore.loadFeatures(self.regions);
|
|
11936
|
-
if (self.lgv.bpPerPx <=
|
|
12015
|
+
if (self.lgv.bpPerPx <= self.zoomThreshold) {
|
|
11937
12016
|
void self.session.apolloDataStore.loadRefSeq(self.regions);
|
|
11938
12017
|
}
|
|
11939
12018
|
}, { name: 'LinearApolloSixFrameDisplayLoadFeatures', delay: 1000 }));
|
|
@@ -12125,6 +12204,28 @@ function layoutsModelFactory(pluginManager, configSchema) {
|
|
|
12125
12204
|
}));
|
|
12126
12205
|
}
|
|
12127
12206
|
|
|
12207
|
+
function drawCodon(ctx, codon, leftPx, index, theme, highContrast, bpPerPx, bp, rowHeight, showFeatureLabels, showStartCodons, showStopCodons) {
|
|
12208
|
+
const frameOffsets = (showFeatureLabels ? [0, 4, 2, 0, 14, 12, 10] : [0, 2, 1, 0, 7, 6, 5]).map((b) => b * rowHeight);
|
|
12209
|
+
const strands = [-1, 1];
|
|
12210
|
+
for (const strand of strands) {
|
|
12211
|
+
const frame = getFrame(bp, bp + 3, strand, 0);
|
|
12212
|
+
const top = frameOffsets.at(frame);
|
|
12213
|
+
if (top === undefined) {
|
|
12214
|
+
continue;
|
|
12215
|
+
}
|
|
12216
|
+
const left = Math.round(leftPx + index / bpPerPx);
|
|
12217
|
+
const width = Math.round(3 / bpPerPx) === 0 ? 1 : Math.round(3 / bpPerPx);
|
|
12218
|
+
const codonCode = strand === 1 ? codon : revcom(codon);
|
|
12219
|
+
const aminoAcidCode = defaultCodonTable[codonCode];
|
|
12220
|
+
const fillColor = codonColorCode(aminoAcidCode, theme, highContrast);
|
|
12221
|
+
if (fillColor &&
|
|
12222
|
+
((showStopCodons && aminoAcidCode == '*') ||
|
|
12223
|
+
(showStartCodons && aminoAcidCode != '*'))) {
|
|
12224
|
+
ctx.fillStyle = fillColor;
|
|
12225
|
+
ctx.fillRect(left, top, width, rowHeight);
|
|
12226
|
+
}
|
|
12227
|
+
}
|
|
12228
|
+
}
|
|
12128
12229
|
function renderingModelFactory(pluginManager, configSchema) {
|
|
12129
12230
|
const LinearApolloSixFrameDisplayLayouts = layoutsModelFactory(pluginManager, configSchema);
|
|
12130
12231
|
return LinearApolloSixFrameDisplayLayouts.named('LinearApolloSixFrameDisplayRendering')
|
|
@@ -12214,11 +12315,11 @@ function renderingModelFactory(pluginManager, configSchema) {
|
|
|
12214
12315
|
}
|
|
12215
12316
|
}, { name: 'LinearApolloSixFrameDisplayRenderCollaborators' }));
|
|
12216
12317
|
addDisposer(self, autorun(() => {
|
|
12217
|
-
const { canvas, featureLayouts, featuresHeight, lgv } = self;
|
|
12318
|
+
const { apolloRowHeight, canvas, featureLayouts, featuresHeight, lgv, session, theme, showFeatureLabels, showStartCodons, showStopCodons, } = self;
|
|
12218
12319
|
if (!lgv.initialized || self.regionCannotBeRendered()) {
|
|
12219
12320
|
return;
|
|
12220
12321
|
}
|
|
12221
|
-
const { displayedRegions, dynamicBlocks } = lgv;
|
|
12322
|
+
const { bpPerPx, offsetPx, displayedRegions, dynamicBlocks } = lgv;
|
|
12222
12323
|
const ctx = canvas?.getContext('2d');
|
|
12223
12324
|
if (!ctx) {
|
|
12224
12325
|
return;
|
|
@@ -12242,6 +12343,27 @@ function renderingModelFactory(pluginManager, configSchema) {
|
|
|
12242
12343
|
}
|
|
12243
12344
|
}
|
|
12244
12345
|
}
|
|
12346
|
+
if (showStartCodons || showStopCodons) {
|
|
12347
|
+
const { apolloDataStore } = session;
|
|
12348
|
+
for (const block of dynamicBlocks.contentBlocks) {
|
|
12349
|
+
const assembly = apolloDataStore.assemblies.get(block.assemblyName);
|
|
12350
|
+
const ref = assembly?.getByRefName(block.refName);
|
|
12351
|
+
const roundedStart = Math.floor(block.start);
|
|
12352
|
+
const roundedEnd = Math.ceil(block.end);
|
|
12353
|
+
let seq = ref?.getSequence(roundedStart, roundedEnd);
|
|
12354
|
+
if (!seq) {
|
|
12355
|
+
break;
|
|
12356
|
+
}
|
|
12357
|
+
seq = seq.toUpperCase();
|
|
12358
|
+
const baseOffsetPx = (block.start - roundedStart) / bpPerPx;
|
|
12359
|
+
const seqLeftPx = Math.round(block.offsetPx - offsetPx - baseOffsetPx);
|
|
12360
|
+
for (let i = 0; i < seq.length; i++) {
|
|
12361
|
+
const bp = roundedStart + i;
|
|
12362
|
+
const codon = seq.slice(i, i + 3);
|
|
12363
|
+
drawCodon(ctx, codon, seqLeftPx, i, theme, true, bpPerPx, bp, apolloRowHeight, showFeatureLabels, showStartCodons, showStopCodons);
|
|
12364
|
+
}
|
|
12365
|
+
}
|
|
12366
|
+
}
|
|
12245
12367
|
}, { name: 'LinearApolloSixFrameDisplayRenderFeatures' }));
|
|
12246
12368
|
},
|
|
12247
12369
|
}));
|
|
@@ -13192,17 +13314,6 @@ function annotationFromJBrowseFeature(pluggableElement) {
|
|
|
13192
13314
|
return pluggableElement;
|
|
13193
13315
|
}
|
|
13194
13316
|
|
|
13195
|
-
function getLeftPx(display, feature, block) {
|
|
13196
|
-
const { lgv } = display;
|
|
13197
|
-
const { bpPerPx, offsetPx } = lgv;
|
|
13198
|
-
const blockLeftPx = block.offsetPx - offsetPx;
|
|
13199
|
-
const featureLeftBpDistanceFromBlockLeftBp = block.reversed
|
|
13200
|
-
? block.end - feature.max
|
|
13201
|
-
: feature.min - block.start;
|
|
13202
|
-
const featureLeftPxDistanceFromBlockLeftPx = featureLeftBpDistanceFromBlockLeftBp / bpPerPx;
|
|
13203
|
-
return blockLeftPx + featureLeftPxDistanceFromBlockLeftPx;
|
|
13204
|
-
}
|
|
13205
|
-
|
|
13206
13317
|
const CheckResultWarnings = observer(function CheckResultWarnings({ display, }) {
|
|
13207
13318
|
const { classes } = useStyles$1();
|
|
13208
13319
|
const { apolloDragging, apolloRowHeight, lgv, session, showCheckResults } = display;
|
|
@@ -13534,7 +13645,7 @@ const ResizeHandle = ({ onResize, }) => {
|
|
|
13534
13645
|
const controller = new AbortController();
|
|
13535
13646
|
const { signal } = controller;
|
|
13536
13647
|
function abortDrag() {
|
|
13537
|
-
controller.abort();
|
|
13648
|
+
controller.abort('makeDisplayComponent');
|
|
13538
13649
|
}
|
|
13539
13650
|
globalThis.addEventListener('mousemove', mouseMove, { signal });
|
|
13540
13651
|
globalThis.addEventListener('mouseup', abortDrag, { signal });
|
|
@@ -13938,7 +14049,7 @@ function clientDataStoreFactory(AnnotationFeatureExtended) {
|
|
|
13938
14049
|
statusMessage: `Loading ontology "${name}", version "${version}", this may take a while`,
|
|
13939
14050
|
progressPct: 0,
|
|
13940
14051
|
cancelCallback: () => {
|
|
13941
|
-
controller.abort();
|
|
14052
|
+
controller.abort('ClientDataStore');
|
|
13942
14053
|
jobsManager.abortJob(job.name);
|
|
13943
14054
|
},
|
|
13944
14055
|
};
|
|
@@ -14077,6 +14188,7 @@ function extendSession(pluginManager, sessionModel) {
|
|
|
14077
14188
|
apolloSelectedFeature: types.safeReference(AnnotationFeatureExtended),
|
|
14078
14189
|
jobsManager: types.optional(ApolloJobModel, {}),
|
|
14079
14190
|
isLocked: types.optional(types.boolean, false),
|
|
14191
|
+
changeInProgress: types.optional(types.boolean, false),
|
|
14080
14192
|
})
|
|
14081
14193
|
.volatile(() => ({
|
|
14082
14194
|
apolloHoveredFeature: undefined,
|
|
@@ -14139,6 +14251,9 @@ function extendSession(pluginManager, sessionModel) {
|
|
|
14139
14251
|
toggleLocked() {
|
|
14140
14252
|
self.isLocked = !self.isLocked;
|
|
14141
14253
|
},
|
|
14254
|
+
setChangeInProgress(changeInProgress) {
|
|
14255
|
+
self.changeInProgress = changeInProgress;
|
|
14256
|
+
},
|
|
14142
14257
|
getPluginConfiguration() {
|
|
14143
14258
|
const { jbrowse } = getRoot(self);
|
|
14144
14259
|
const pluginConfiguration =
|