@apollo-annotation/jbrowse-plugin-apollo 0.3.9 → 0.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/index.esm.js +235 -120
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/jbrowse-plugin-apollo.cjs.development.js +233 -118
  4. package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
  5. package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
  6. package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
  7. package/dist/jbrowse-plugin-apollo.umd.development.js +391 -195
  8. package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
  9. package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
  10. package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
  11. package/package.json +4 -4
  12. package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +1 -1
  13. package/src/ApolloInternetAccount/model.ts +6 -2
  14. package/src/BackendDrivers/CollaborationServerDriver.ts +11 -5
  15. package/src/ChangeManager.ts +19 -4
  16. package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +29 -9
  17. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +2 -2
  18. package/src/LinearApolloDisplay/glyphs/util.ts +17 -0
  19. package/src/LinearApolloReferenceSequenceDisplay/drawSequenceOverlay.ts +18 -25
  20. package/src/LinearApolloReferenceSequenceDisplay/drawSequenceTrack.ts +41 -59
  21. package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +33 -2
  22. package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +101 -3
  23. package/src/components/AddAssembly.tsx +1 -1
  24. package/src/components/ImportFeatures.tsx +1 -1
  25. package/src/components/OntologyTermAutocomplete.tsx +2 -2
  26. package/src/components/OntologyTermMultiSelect.tsx +2 -2
  27. package/src/makeDisplayComponent.tsx +1 -1
  28. package/src/session/ClientDataStore.ts +1 -1
  29. package/src/session/session.ts +4 -0
  30. package/src/util/displayUtils.ts +28 -0
  31. 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, makeUserSessionId, isGFFInternalAttribute, isGFFColumnInternal, internalToGFF, gffInternalToColumn, gffToInternal, gffColumnToInternal, FeatureAttributeChange, TypeChange, StrandChange, filterJBrowseConfig, ImportJBrowseConfigChange, changes, CDSCheck, TranscriptCheck, CoreValidation, ParentChildValidation } from '@apollo-annotation/shared';
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, isSessionModelWithWidgets, getFrame, intersection2, doesIntersect2, measureText } from '@jbrowse/core/util';
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.9";
72
+ var version = "0.3.10";
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
- const { jobsManager, isLocked } = getSession(this.dataStore);
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 !== token && message.channel === channel) {
2523
- const change = Change.fromJSON(message.changeInfo);
2524
- if (isFeatureChange(change) && this.haveDataForChange(change)) {
2525
- await changeManager.submit(change, { submitToBackend: false });
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
- await self.initialize(self.role);
5607
- reaction.dispose();
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('*') + 1;
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
- const stopCodonGenomicLoc = getCodonGenomicLocation(stopCodonPos);
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(ContentCopyIcon, { style: { fontSize: 15, cursor: 'pointer' }, onClick: onCopyClick })),
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(ContentCutIcon, { style: { fontSize: 15, cursor: 'pointer' }, onClick: trimTranslationSequence }))))),
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.push({
9209
- label: 'Open transcript details',
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 offset = bpPerPx <= 1 ? 2 : 0;
10390
- switch (frame) {
10391
- case 3: {
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, true);
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, true);
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 colorCode(letter, theme) {
10489
- const letterUpper = letter.toUpperCase();
10490
- if (letterUpper === 'A' ||
10491
- letterUpper === 'C' ||
10492
- letterUpper === 'G' ||
10493
- letterUpper === 'T') {
10494
- return theme.palette.bases[letterUpper].main.toString();
10495
- }
10496
- return 'lightgray';
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
- function codonColorCode(letter, theme, highContrast) {
10499
- if (letter === 'M') {
10500
- return theme.palette.startCodon;
10501
- }
10502
- if (letter === '*') {
10503
- return highContrast ? theme.palette.text.primary : theme.palette.stopCodon;
10504
- }
10505
- return;
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(canvas, ctx, bpPerPx, theme, dynamicBlocks, highContrast, sequenceRowHeight) {
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
- ctx.strokeStyle = theme.palette.grey[200];
10531
- ctx.strokeRect(left, top, width, sequenceRowHeight);
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
- const width = 1 / bpPerPx;
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
- ctx.strokeStyle = theme.palette.text.disabled;
10556
- ctx.strokeRect(left, top, width, rowHeight);
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 width = Math.round(3 / bpPerPx);
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
- ctx.strokeStyle = theme.palette.text.disabled;
10583
- ctx.strokeRect(left, top, width, rowHeight);
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
- const baseOffsetPx = (block.start - roundedStart) / bpPerPx;
10607
- const seqLeftPx = Math.round(block.offsetPx - offsetPx - baseOffsetPx);
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 <= 3) {
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 =