@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
@@ -1615,7 +1615,6 @@
1615
1615
  });
1616
1616
  AssemblySpecificChange$3.AssemblySpecificChange = void 0;
1617
1617
  AssemblySpecificChange$3.isAssemblySpecificChange = isAssemblySpecificChange$1;
1618
- /* eslint-disable @typescript-eslint/no-unnecessary-condition */
1619
1618
  var Change_1$1 = Change$3;
1620
1619
  function isAssemblySpecificChange$1(
1621
1620
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -1632,7 +1631,37 @@
1632
1631
  _this.assembly = json.assembly;
1633
1632
  return _this;
1634
1633
  }
1635
- return _createClass(AssemblySpecificChange);
1634
+ _createClass(AssemblySpecificChange, [{
1635
+ key: "getIndexedIds",
1636
+ value: function getIndexedIds(feature, idsToIndex) {
1637
+ var _a;
1638
+ var indexedIds = [];
1639
+ var _iterator = _createForOfIteratorHelper(idsToIndex !== null && idsToIndex !== void 0 ? idsToIndex : []),
1640
+ _step;
1641
+ try {
1642
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
1643
+ var additionalId = _step.value;
1644
+ var idValue = (_a = feature.attributes) === null || _a === void 0 ? void 0 : _a[additionalId];
1645
+ if (idValue) {
1646
+ indexedIds.push(idValue[0]);
1647
+ }
1648
+ }
1649
+ } catch (err) {
1650
+ _iterator.e(err);
1651
+ } finally {
1652
+ _iterator.f();
1653
+ }
1654
+ if (feature.children) {
1655
+ for (var _i = 0, _Object$values = Object.values(feature.children); _i < _Object$values.length; _i++) {
1656
+ var child = _Object$values[_i];
1657
+ var childIndexedIds = this.getIndexedIds(child, idsToIndex);
1658
+ indexedIds.push.apply(indexedIds, _toConsumableArray(childIndexedIds));
1659
+ }
1660
+ }
1661
+ return indexedIds;
1662
+ }
1663
+ }]);
1664
+ return AssemblySpecificChange;
1636
1665
  }(Change_1$1.Change);
1637
1666
  AssemblySpecificChange$3.AssemblySpecificChange = AssemblySpecificChange$2;
1638
1667
 
@@ -4706,7 +4735,6 @@
4706
4735
  });
4707
4736
  AssemblySpecificChange$1.AssemblySpecificChange = void 0;
4708
4737
  AssemblySpecificChange$1.isAssemblySpecificChange = isAssemblySpecificChange;
4709
- /* eslint-disable @typescript-eslint/no-unnecessary-condition */
4710
4738
  var Change_1 = Change$1;
4711
4739
  function isAssemblySpecificChange(
4712
4740
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -4723,7 +4751,37 @@
4723
4751
  _this.assembly = json.assembly;
4724
4752
  return _this;
4725
4753
  }
4726
- return _createClass(AssemblySpecificChange);
4754
+ _createClass(AssemblySpecificChange, [{
4755
+ key: "getIndexedIds",
4756
+ value: function getIndexedIds(feature, idsToIndex) {
4757
+ var _a;
4758
+ var indexedIds = [];
4759
+ var _iterator = _createForOfIteratorHelper(idsToIndex !== null && idsToIndex !== void 0 ? idsToIndex : []),
4760
+ _step;
4761
+ try {
4762
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
4763
+ var additionalId = _step.value;
4764
+ var idValue = (_a = feature.attributes) === null || _a === void 0 ? void 0 : _a[additionalId];
4765
+ if (idValue) {
4766
+ indexedIds.push(idValue[0]);
4767
+ }
4768
+ }
4769
+ } catch (err) {
4770
+ _iterator.e(err);
4771
+ } finally {
4772
+ _iterator.f();
4773
+ }
4774
+ if (feature.children) {
4775
+ for (var _i = 0, _Object$values = Object.values(feature.children); _i < _Object$values.length; _i++) {
4776
+ var child = _Object$values[_i];
4777
+ var childIndexedIds = this.getIndexedIds(child, idsToIndex);
4778
+ indexedIds.push.apply(indexedIds, _toConsumableArray(childIndexedIds));
4779
+ }
4780
+ }
4781
+ return indexedIds;
4782
+ }
4783
+ }]);
4784
+ return AssemblySpecificChange;
4727
4785
  }(Change_1.Change);
4728
4786
  AssemblySpecificChange$1.AssemblySpecificChange = AssemblySpecificChange;
4729
4787
 
@@ -5626,7 +5684,7 @@
5626
5684
  var util_1$9 = require$$1__default$1["default"];
5627
5685
  var bson_objectid_1$2 = /*#__PURE__*/tslib_1$4.__importDefault(objectid);
5628
5686
  var gffReservedKeys_1 = gffReservedKeys;
5629
- function gff3ToAnnotationFeature(gff3Feature, refSeq, featureIds) {
5687
+ function gff3ToAnnotationFeature(gff3Feature, refSeq) {
5630
5688
  var _gff3Feature = _slicedToArray(gff3Feature, 1),
5631
5689
  firstFeature = _gff3Feature[0];
5632
5690
  var end = firstFeature.end,
@@ -5650,7 +5708,7 @@
5650
5708
  _getFeatureMinMax2 = _slicedToArray(_getFeatureMinMax, 2),
5651
5709
  min = _getFeatureMinMax2[0],
5652
5710
  max = _getFeatureMinMax2[1];
5653
- var convertedChildren = convertChildren(gff3Feature, refSeq, featureIds);
5711
+ var convertedChildren = convertChildren(gff3Feature, refSeq);
5654
5712
  var convertedAttributes = convertFeatureAttributes$1(gff3Feature);
5655
5713
  var feature = {
5656
5714
  _id: new bson_objectid_1$2["default"]().toHexString(),
@@ -5674,9 +5732,6 @@
5674
5732
  if (convertedAttributes) {
5675
5733
  feature.attributes = convertedAttributes;
5676
5734
  }
5677
- if (featureIds) {
5678
- featureIds.push(feature._id);
5679
- }
5680
5735
  return feature;
5681
5736
  }
5682
5737
  function getFeatureMinMax(gff3Feature) {
@@ -5810,7 +5865,7 @@
5810
5865
  if (firstChildFeatureLocation.type === 'CDS') {
5811
5866
  cdsFeatures.push(childFeature);
5812
5867
  } else {
5813
- var child = gff3ToAnnotationFeature(childFeature, refSeq, featureIds);
5868
+ var child = gff3ToAnnotationFeature(childFeature, refSeq);
5814
5869
  convertedChildren[child._id] = child;
5815
5870
  }
5816
5871
  }
@@ -5820,7 +5875,7 @@
5820
5875
  _iterator2.f();
5821
5876
  }
5822
5877
  if (cdsFeatures.length > 0) {
5823
- var processedCDS = processCDS(cdsFeatures, refSeq, featureIds);
5878
+ var processedCDS = processCDS(cdsFeatures, refSeq);
5824
5879
  var _iterator3 = _createForOfIteratorHelper(processedCDS),
5825
5880
  _step3;
5826
5881
  try {
@@ -6052,7 +6107,7 @@
6052
6107
  * should be represented in our internal structure
6053
6108
  * @param cdsFeatures -
6054
6109
  */
6055
- function processCDS(cdsFeatures, refSeq, featureIds) {
6110
+ function processCDS(cdsFeatures, refSeq) {
6056
6111
  var locationCounts = cdsFeatures.map(function (cds) {
6057
6112
  return cds.length;
6058
6113
  });
@@ -6063,7 +6118,7 @@
6063
6118
  return count > 1;
6064
6119
  })) {
6065
6120
  return cdsFeatures.map(function (cds) {
6066
- return gff3ToAnnotationFeature(cds, refSeq, featureIds);
6121
+ return gff3ToAnnotationFeature(cds, refSeq);
6067
6122
  });
6068
6123
  }
6069
6124
  // If all CDS have a single location, we guess that this GFF3 represented CDS
@@ -6089,7 +6144,7 @@
6089
6144
  });
6090
6145
  // If no overlaps, assume it's a single CDS feature
6091
6146
  if (!overlapping) {
6092
- return [gff3ToAnnotationFeature(sortedCDSLocations, refSeq, featureIds)];
6147
+ return [gff3ToAnnotationFeature(sortedCDSLocations, refSeq)];
6093
6148
  }
6094
6149
  // Some CDS locations overlap, the best we can do is use the original order to
6095
6150
  // guess how to group the locations into features
@@ -6126,7 +6181,7 @@
6126
6181
  _iterator12.f();
6127
6182
  }
6128
6183
  return groupedLocations.map(function (group) {
6129
- return gff3ToAnnotationFeature(group, refSeq, featureIds);
6184
+ return gff3ToAnnotationFeature(group, refSeq);
6130
6185
  });
6131
6186
  }
6132
6187
 
@@ -6486,71 +6541,76 @@
6486
6541
  key: "addFeatureIntoDb",
6487
6542
  value: function () {
6488
6543
  var _addFeatureIntoDb = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(gff3Feature, backend) {
6489
- var featureModel, refSeqModel, user, assembly, refSeqCache, _gff3Feature, refName, refSeqDoc, _yield$refSeqModel$fi, featureIds, newFeature;
6544
+ var INDEXED_IDS, idsToIndex, assembly, refSeqCache, featureModel, refSeqModel, user, _gff3Feature, refName, refSeqDoc, _yield$refSeqModel$fi, newFeature, allIds, indexedIds;
6490
6545
  return _regeneratorRuntime().wrap(function _callee3$(_context3) {
6491
6546
  while (1) switch (_context3.prev = _context3.next) {
6492
6547
  case 0:
6493
- featureModel = backend.featureModel, refSeqModel = backend.refSeqModel, user = backend.user;
6548
+ INDEXED_IDS = browser$1.env.INDEXED_IDS;
6549
+ if (INDEXED_IDS) {
6550
+ idsToIndex = INDEXED_IDS.split(',');
6551
+ }
6494
6552
  assembly = this.assembly, refSeqCache = this.refSeqCache;
6553
+ featureModel = backend.featureModel, refSeqModel = backend.refSeqModel, user = backend.user;
6495
6554
  _gff3Feature = _slicedToArray(gff3Feature, 1), refName = _gff3Feature[0].seq_id;
6496
6555
  if (refName) {
6497
- _context3.next = 5;
6556
+ _context3.next = 7;
6498
6557
  break;
6499
6558
  }
6500
6559
  throw new Error("Valid seq_id not found in feature ".concat(JSON.stringify(gff3Feature)));
6501
- case 5:
6560
+ case 7:
6502
6561
  refSeqDoc = refSeqCache.get(refName);
6503
6562
  if (refSeqDoc) {
6504
- _context3.next = 20;
6563
+ _context3.next = 22;
6505
6564
  break;
6506
6565
  }
6507
- _context3.next = 9;
6566
+ _context3.next = 11;
6508
6567
  return refSeqModel.findOne({
6509
6568
  assembly: assembly,
6510
6569
  name: refName
6511
6570
  }).exec();
6512
- case 9:
6571
+ case 11:
6513
6572
  _context3.t1 = _yield$refSeqModel$fi = _context3.sent;
6514
6573
  _context3.t0 = _context3.t1 !== null;
6515
6574
  if (!_context3.t0) {
6516
- _context3.next = 13;
6575
+ _context3.next = 15;
6517
6576
  break;
6518
6577
  }
6519
6578
  _context3.t0 = _yield$refSeqModel$fi !== void 0;
6520
- case 13:
6579
+ case 15:
6521
6580
  if (!_context3.t0) {
6522
- _context3.next = 17;
6581
+ _context3.next = 19;
6523
6582
  break;
6524
6583
  }
6525
6584
  _context3.t2 = _yield$refSeqModel$fi;
6526
- _context3.next = 18;
6585
+ _context3.next = 20;
6527
6586
  break;
6528
- case 17:
6587
+ case 19:
6529
6588
  _context3.t2 = undefined;
6530
- case 18:
6589
+ case 20:
6531
6590
  refSeqDoc = _context3.t2;
6532
6591
  if (refSeqDoc) {
6533
6592
  refSeqCache.set(refName, refSeqDoc);
6534
6593
  }
6535
- case 20:
6594
+ case 22:
6536
6595
  if (refSeqDoc) {
6537
- _context3.next = 22;
6596
+ _context3.next = 24;
6538
6597
  break;
6539
6598
  }
6540
6599
  throw new Error("RefSeq was not found by assembly \"".concat(assembly, "\" and seq_id \"").concat(refName, "\" not found"));
6541
- case 22:
6542
- // Let's add featureId to parent feature
6543
- featureIds = [];
6544
- newFeature = (0, GFF3_1.gff3ToAnnotationFeature)(gff3Feature, refSeqDoc._id, featureIds); // Add into Mongo
6600
+ case 24:
6601
+ newFeature = (0, GFF3_1.gff3ToAnnotationFeature)(gff3Feature, refSeqDoc._id);
6602
+ allIds = this.getAllIds(newFeature);
6603
+ indexedIds = this.getIndexedIds(newFeature, idsToIndex); // Add into Mongo
6545
6604
  // We cannot use Mongo 'session' / transaction here because Mongo has 16 MB limit for transaction
6546
- _context3.next = 26;
6605
+ _context3.next = 29;
6547
6606
  return featureModel.create([_objectSpread2(_objectSpread2({
6548
- allIds: featureIds
6607
+ allIds: allIds,
6608
+ indexedIds: indexedIds
6549
6609
  }, newFeature), {}, {
6550
6610
  user: user,
6551
6611
  status: -1
6552
6612
  })]);
6553
- case 26:
6613
+ case 29:
6554
6614
  case "end":
6555
6615
  return _context3.stop();
6556
6616
  }
@@ -6561,6 +6621,19 @@
6561
6621
  }
6562
6622
  return addFeatureIntoDb;
6563
6623
  }()
6624
+ }, {
6625
+ key: "getAllIds",
6626
+ value: function getAllIds(feature) {
6627
+ var allIds = [feature._id];
6628
+ if (feature.children) {
6629
+ for (var _i = 0, _Object$values = Object.values(feature.children); _i < _Object$values.length; _i++) {
6630
+ var child = _Object$values[_i];
6631
+ var childIds = this.getAllIds(child);
6632
+ allIds.push.apply(allIds, _toConsumableArray(childIds));
6633
+ }
6634
+ }
6635
+ return allIds;
6636
+ }
6564
6637
  }]);
6565
6638
  return FromFileBaseChange;
6566
6639
  }(common_1$n.AssemblySpecificChange);
@@ -17288,7 +17361,7 @@
17288
17361
  value: function () {
17289
17362
  var _executeOnServer = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(backend) {
17290
17363
  var _logger$debug, _logger$debug2;
17291
- var assemblyModel, featureModel, refSeqModel, session, user, assembly, changes, logger, assemblyDoc, errMsg, featureCnt, _iterator, _step, _logger$debug3, change, addedFeature, allIds, copyFeature, parentFeatureId, _id, refSeq, refSeqDoc, _logger$debug4, _yield$featureModel$c, _yield$featureModel$c2, newFeatureDoc, _topLevelFeature$allI, topLevelFeature, parentFeature, childIds, _logger$verbose, _childIds, allIdsV2, _yield$featureModel$c3, _yield$featureModel$c4, _newFeatureDoc;
17364
+ var assemblyModel, featureModel, refSeqModel, session, user, assembly, changes, logger, assemblyDoc, errMsg, featureCnt, INDEXED_IDS, idsToIndex, _iterator, _step, _logger$debug3, change, addedFeature, allIds, copyFeature, parentFeatureId, _id, refSeq, refSeqDoc, _logger$debug4, indexedIds, _yield$featureModel$c, _yield$featureModel$c2, newFeatureDoc, _topLevelFeature$allI, topLevelFeature, parentFeature, childIds, _logger$verbose, _childIds, allIdsV2, _indexedIds, _yield$featureModel$c3, _yield$featureModel$c4, _newFeatureDoc;
17292
17365
  return _regeneratorRuntime().wrap(function _callee$(_context) {
17293
17366
  while (1) switch (_context.prev = _context.next) {
17294
17367
  case 0:
@@ -17308,119 +17381,127 @@
17308
17381
  case 9:
17309
17382
  featureCnt = 0;
17310
17383
  (_logger$debug = logger.debug) === null || _logger$debug === void 0 || _logger$debug.call(logger, "changes: ".concat(JSON.stringify(changes)));
17384
+ INDEXED_IDS = browser$1.env.INDEXED_IDS;
17385
+ if (INDEXED_IDS) {
17386
+ idsToIndex = INDEXED_IDS.split(',');
17387
+ }
17311
17388
  // Loop the changes
17312
17389
  _iterator = _createForOfIteratorHelper(changes);
17313
- _context.prev = 12;
17390
+ _context.prev = 14;
17314
17391
  _iterator.s();
17315
- case 14:
17392
+ case 16:
17316
17393
  if ((_step = _iterator.n()).done) {
17317
- _context.next = 61;
17394
+ _context.next = 65;
17318
17395
  break;
17319
17396
  }
17320
17397
  change = _step.value;
17321
17398
  (_logger$debug3 = logger.debug) === null || _logger$debug3 === void 0 || _logger$debug3.call(logger, "change: ".concat(JSON.stringify(change)));
17322
17399
  addedFeature = change.addedFeature, allIds = change.allIds, copyFeature = change.copyFeature, parentFeatureId = change.parentFeatureId;
17323
17400
  _id = addedFeature._id, refSeq = addedFeature.refSeq;
17324
- _context.next = 21;
17401
+ _context.next = 23;
17325
17402
  return refSeqModel.findById(refSeq).session(session).exec();
17326
- case 21:
17403
+ case 23:
17327
17404
  refSeqDoc = _context.sent;
17328
17405
  if (refSeqDoc) {
17329
- _context.next = 24;
17406
+ _context.next = 26;
17330
17407
  break;
17331
17408
  }
17332
17409
  throw new Error("RefSeq was not found by assembly \"".concat(assembly, "\" and seq_id \"").concat(refSeq, "\" not found"));
17333
- case 24:
17410
+ case 26:
17334
17411
  if (!copyFeature) {
17335
- _context.next = 34;
17412
+ _context.next = 37;
17336
17413
  break;
17337
17414
  }
17338
- _context.next = 27;
17415
+ indexedIds = this.getIndexedIds(addedFeature, idsToIndex); // Add into Mongo
17416
+ _context.next = 30;
17339
17417
  return featureModel.create([_objectSpread2(_objectSpread2({}, addedFeature), {}, {
17340
17418
  allIds: allIds,
17419
+ indexedIds: indexedIds,
17341
17420
  status: -1,
17342
17421
  user: user
17343
17422
  })], {
17344
17423
  session: session
17345
17424
  });
17346
- case 27:
17425
+ case 30:
17347
17426
  _yield$featureModel$c = _context.sent;
17348
17427
  _yield$featureModel$c2 = _slicedToArray(_yield$featureModel$c, 1);
17349
17428
  newFeatureDoc = _yield$featureModel$c2[0];
17350
17429
  (_logger$debug4 = logger.debug) === null || _logger$debug4 === void 0 || _logger$debug4.call(logger, "Copied feature, docId \"".concat(newFeatureDoc._id, "\" to assembly \"").concat(assembly, "\""));
17351
17430
  featureCnt++;
17352
- _context.next = 58;
17431
+ _context.next = 62;
17353
17432
  break;
17354
- case 34:
17433
+ case 37:
17355
17434
  if (!parentFeatureId) {
17356
- _context.next = 50;
17435
+ _context.next = 53;
17357
17436
  break;
17358
17437
  }
17359
- _context.next = 37;
17438
+ _context.next = 40;
17360
17439
  return featureModel.findOne({
17361
17440
  allIds: parentFeatureId
17362
17441
  }).session(session).exec();
17363
- case 37:
17442
+ case 40:
17364
17443
  topLevelFeature = _context.sent;
17365
17444
  if (topLevelFeature) {
17366
- _context.next = 40;
17445
+ _context.next = 43;
17367
17446
  break;
17368
17447
  }
17369
17448
  throw new Error("Could not find feature with ID \"".concat(parentFeatureId, "\""));
17370
- case 40:
17449
+ case 43:
17371
17450
  parentFeature = this.getFeatureFromId(topLevelFeature, parentFeatureId);
17372
17451
  if (parentFeature) {
17373
- _context.next = 43;
17452
+ _context.next = 46;
17374
17453
  break;
17375
17454
  }
17376
17455
  throw new Error("Could not find feature with ID \"".concat(parentFeatureId, "\" in feature \"").concat(topLevelFeature._id, "\""));
17377
- case 43:
17456
+ case 46:
17378
17457
  this.addChild(parentFeature, addedFeature);
17379
17458
  childIds = this.getChildFeatureIds(addedFeature);
17380
17459
  (_topLevelFeature$allI = topLevelFeature.allIds).push.apply(_topLevelFeature$allI, [_id].concat(_toConsumableArray(childIds)));
17381
- _context.next = 48;
17460
+ _context.next = 51;
17382
17461
  return topLevelFeature.save();
17383
- case 48:
17384
- _context.next = 58;
17462
+ case 51:
17463
+ _context.next = 62;
17385
17464
  break;
17386
- case 50:
17465
+ case 53:
17387
17466
  _childIds = this.getChildFeatureIds(addedFeature);
17388
17467
  allIdsV2 = [_id].concat(_toConsumableArray(_childIds));
17389
- _context.next = 54;
17468
+ _indexedIds = this.getIndexedIds(addedFeature, idsToIndex);
17469
+ _context.next = 58;
17390
17470
  return featureModel.create([_objectSpread2({
17391
17471
  allIds: allIdsV2,
17472
+ indexedIds: _indexedIds,
17392
17473
  status: 0
17393
17474
  }, addedFeature)], {
17394
17475
  session: session
17395
17476
  });
17396
- case 54:
17477
+ case 58:
17397
17478
  _yield$featureModel$c3 = _context.sent;
17398
17479
  _yield$featureModel$c4 = _slicedToArray(_yield$featureModel$c3, 1);
17399
17480
  _newFeatureDoc = _yield$featureModel$c4[0];
17400
17481
  (_logger$verbose = logger.verbose) === null || _logger$verbose === void 0 || _logger$verbose.call(logger, "Added docId \"".concat(_newFeatureDoc._id, "\""));
17401
- case 58:
17482
+ case 62:
17402
17483
  featureCnt++;
17403
- case 59:
17404
- _context.next = 14;
17484
+ case 63:
17485
+ _context.next = 16;
17405
17486
  break;
17406
- case 61:
17407
- _context.next = 66;
17487
+ case 65:
17488
+ _context.next = 70;
17408
17489
  break;
17409
- case 63:
17410
- _context.prev = 63;
17411
- _context.t0 = _context["catch"](12);
17490
+ case 67:
17491
+ _context.prev = 67;
17492
+ _context.t0 = _context["catch"](14);
17412
17493
  _iterator.e(_context.t0);
17413
- case 66:
17414
- _context.prev = 66;
17494
+ case 70:
17495
+ _context.prev = 70;
17415
17496
  _iterator.f();
17416
- return _context.finish(66);
17417
- case 69:
17497
+ return _context.finish(70);
17498
+ case 73:
17418
17499
  (_logger$debug2 = logger.debug) === null || _logger$debug2 === void 0 || _logger$debug2.call(logger, "Added ".concat(featureCnt, " new feature(s) into database."));
17419
- case 70:
17500
+ case 74:
17420
17501
  case "end":
17421
17502
  return _context.stop();
17422
17503
  }
17423
- }, _callee, this, [[12, 63, 66, 69]]);
17504
+ }, _callee, this, [[14, 67, 70, 73]]);
17424
17505
  }));
17425
17506
  function executeOnServer(_x) {
17426
17507
  return _executeOnServer.apply(this, arguments);
@@ -23804,7 +23885,7 @@
23804
23885
  d: "M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6z"
23805
23886
  }), 'Add');
23806
23887
 
23807
- var version = "0.3.9";
23888
+ var version = "0.3.10";
23808
23889
 
23809
23890
  const ApolloConfigSchema = configuration.ConfigurationSchema('ApolloInternetAccount', {
23810
23891
  baseURL: {
@@ -29343,6 +29424,19 @@
29343
29424
  ]);
29344
29425
  },
29345
29426
  });
29427
+ if (require$$1$2.isSessionModelWithWidgets(session)) {
29428
+ menuItems.push({
29429
+ label: 'Open feature details',
29430
+ onClick: () => {
29431
+ const apolloGeneWidget = session.addWidget('ApolloFeatureDetailsWidget', 'apolloFeatureDetailsWidget', {
29432
+ feature: sourceFeature,
29433
+ assembly: currentAssemblyId,
29434
+ refName: region.refName,
29435
+ });
29436
+ session.showWidget(apolloGeneWidget);
29437
+ },
29438
+ });
29439
+ }
29346
29440
  return menuItems;
29347
29441
  }
29348
29442
  function navToFeatureCenter(feature, paddingPct, refSeqLength) {
@@ -29595,7 +29689,7 @@
29595
29689
  statusMessage: 'Pre-validating',
29596
29690
  progressPct: 0,
29597
29691
  cancelCallback: () => {
29598
- controller.abort();
29692
+ controller.abort('AddAssembly');
29599
29693
  jobsManager.abortJob(job.name);
29600
29694
  },
29601
29695
  };
@@ -47874,7 +47968,7 @@
47874
47968
  });
47875
47969
  }
47876
47970
  return () => {
47877
- controller.abort();
47971
+ controller.abort('OntologyTermAutocomplete matcher');
47878
47972
  };
47879
47973
  }, [session, valueString, filterTerms, ontologyStore, needToLoadCurrentTerm]);
47880
47974
  // effect for loading term autocompletions
@@ -47893,7 +47987,7 @@
47893
47987
  });
47894
47988
  }
47895
47989
  return () => {
47896
- controller.abort();
47990
+ controller.abort('OntologyTermAutocomplete loader');
47897
47991
  };
47898
47992
  }, [
47899
47993
  needToLoadTermChoices,
@@ -48045,17 +48139,24 @@
48045
48139
  // pre-validate
48046
48140
  const session = require$$1$2.getSession(this.dataStore);
48047
48141
  const controller = new AbortController();
48048
- const { jobsManager, isLocked } = require$$1$2.getSession(this.dataStore);
48142
+ // eslint-disable-next-line @typescript-eslint/unbound-method
48143
+ const { jobsManager, isLocked, changeInProgress, setChangeInProgress } = require$$1$2.getSession(this.dataStore);
48049
48144
  if (isLocked) {
48050
48145
  session.notify('Cannot submit changes in locked mode');
48146
+ setChangeInProgress(false);
48147
+ return;
48148
+ }
48149
+ if (changeInProgress) {
48150
+ session.notify('Could not submit change, there is another change still in progress');
48051
48151
  return;
48052
48152
  }
48153
+ setChangeInProgress(true);
48053
48154
  const job = {
48054
48155
  name: change.typeName,
48055
48156
  statusMessage: 'Pre-validating',
48056
48157
  progressPct: 0,
48057
48158
  cancelCallback: () => {
48058
- controller.abort();
48159
+ controller.abort('ChangeManager');
48059
48160
  },
48060
48161
  };
48061
48162
  if (updateJobsManager) {
@@ -48068,6 +48169,7 @@
48068
48169
  jobsManager.abortJob(job.name, msg);
48069
48170
  }
48070
48171
  session.notify(msg, 'error');
48172
+ setChangeInProgress(false);
48071
48173
  return;
48072
48174
  }
48073
48175
  try {
@@ -48080,6 +48182,7 @@
48080
48182
  }
48081
48183
  console.error(error);
48082
48184
  session.notify(`Error encountered in client: ${String(error)}. Data may be out of sync, please refresh the page`, 'error');
48185
+ setChangeInProgress(false);
48083
48186
  return;
48084
48187
  }
48085
48188
  // post-validate
@@ -48110,6 +48213,7 @@
48110
48213
  console.error(error);
48111
48214
  session.notify(String(error), 'error');
48112
48215
  await this.undo(change, false);
48216
+ setChangeInProgress(false);
48113
48217
  return;
48114
48218
  }
48115
48219
  if (!backendResult.ok) {
@@ -48119,6 +48223,7 @@
48119
48223
  }
48120
48224
  session.notify(msg, 'error');
48121
48225
  await this.undo(change, false);
48226
+ setChangeInProgress(false);
48122
48227
  return;
48123
48228
  }
48124
48229
  if (change.notification) {
@@ -48132,6 +48237,7 @@
48132
48237
  if (updateJobsManager) {
48133
48238
  jobsManager.done(job);
48134
48239
  }
48240
+ setChangeInProgress(false);
48135
48241
  }
48136
48242
  async undo(change, submitToBackend = true) {
48137
48243
  const inverseChange = change.getInverse();
@@ -48237,17 +48343,22 @@
48237
48343
  checkSocket(assembly, refSeq, internetAccount) {
48238
48344
  const { socket } = internetAccount;
48239
48345
  const token = internetAccount.retrieveToken();
48346
+ if (!token) {
48347
+ return;
48348
+ }
48349
+ const localSessionId = dist$2.makeUserSessionId(token);
48240
48350
  const channel = `${assembly}-${refSeq}`;
48241
48351
  const changeManager = new ChangeManager(this.clientStore);
48242
48352
  if (!socket.hasListeners(channel)) {
48243
48353
  socket.on(channel, async (message) => {
48244
48354
  // Save server last change sequence into session storage
48245
48355
  internetAccount.setLastChangeSequenceNumber(Number(message.changeSequence));
48246
- if (message.userSessionId !== token && message.channel === channel) {
48247
- const change = dist$3.Change.fromJSON(message.changeInfo);
48248
- if (dist$3.isFeatureChange(change) && this.haveDataForChange(change)) {
48249
- await changeManager.submit(change, { submitToBackend: false });
48250
- }
48356
+ if (message.userSessionId === localSessionId) {
48357
+ return; // we did this change, no need to apply it again
48358
+ }
48359
+ const change = dist$3.Change.fromJSON(message.changeInfo);
48360
+ if (dist$3.isFeatureChange(change) && this.haveDataForChange(change)) {
48361
+ await changeManager.submit(change, { submitToBackend: false });
48251
48362
  }
48252
48363
  });
48253
48364
  }
@@ -49740,7 +49851,7 @@
49740
49851
  statusMessage: 'Uploading file, this may take awhile',
49741
49852
  progressPct: 0,
49742
49853
  cancelCallback: () => {
49743
- controller.abort();
49854
+ controller.abort('ImportFeatures');
49744
49855
  jobsManager.abortJob(job.name);
49745
49856
  },
49746
49857
  };
@@ -50991,7 +51102,7 @@
50991
51102
  }
50992
51103
  });
50993
51104
  return () => {
50994
- controller.abort();
51105
+ controller.abort('AuthTypeSelector');
50995
51106
  };
50996
51107
  }, [baseURL]);
50997
51108
  function handleClick(authType) {
@@ -51428,8 +51539,13 @@
51428
51539
  return;
51429
51540
  }
51430
51541
  if (self.role) {
51431
- await self.initialize(self.role);
51432
- reaction.dispose();
51542
+ try {
51543
+ await self.initialize(self.role);
51544
+ reaction.dispose();
51545
+ }
51546
+ catch {
51547
+ // if initialize fails, do nothing so the autorun runs again
51548
+ }
51433
51549
  }
51434
51550
  }, { name: 'ApolloInternetAccount' });
51435
51551
  },
@@ -55579,6 +55695,7 @@
55579
55695
  const refData = currentAssembly?.getByRefName(refName);
55580
55696
  const { changeManager } = session.apolloDataStore;
55581
55697
  const seqRef = React.useRef(null);
55698
+ const { changeInProgress } = session;
55582
55699
  if (!refData) {
55583
55700
  return null;
55584
55701
  }
@@ -56026,10 +56143,13 @@
56026
56143
  // highlight start codon and stop codons
56027
56144
  if (codonSeq === 'ATG') {
56028
56145
  elements.push(React__default["default"].createElement(material.Typography, { component: 'span', style: {
56029
- backgroundColor: 'yellow',
56146
+ backgroundColor: changeInProgress ? 'lightgray' : 'yellow',
56030
56147
  cursor: 'pointer',
56031
56148
  border: '1px solid black',
56032
56149
  }, key: codonGenomicPos, onClick: () => {
56150
+ if (changeInProgress) {
56151
+ return;
56152
+ }
56033
56153
  // NOTE: codonGenomicPos is important here for calculating the genomic location
56034
56154
  // of the start codon. We are using the codonGenomicPos as the key in the typography
56035
56155
  // elements to maintain the genomic postion of the codon start
@@ -56113,20 +56233,21 @@
56113
56233
  }
56114
56234
  // Trim any sequence before first start codon and after stop codon
56115
56235
  const startCodonIndex = translationSequence.indexOf('M');
56116
- const stopCodonIndex = translationSequence.indexOf('*') + 1;
56236
+ const stopCodonIndex = translationSequence.indexOf('*');
56117
56237
  const startCodonPos = translSeqCodonStartGenomicPosArr[startCodonIndex].codonGenomicPos;
56118
56238
  const stopCodonPos = translSeqCodonStartGenomicPosArr[stopCodonIndex].codonGenomicPos;
56119
56239
  if (!startCodonPos || !stopCodonPos) {
56120
56240
  return;
56121
56241
  }
56122
56242
  const startCodonGenomicLoc = getCodonGenomicLocation(startCodonPos);
56123
- const stopCodonGenomicLoc = getCodonGenomicLocation(stopCodonPos);
56243
+ let stopCodonGenomicLoc = getCodonGenomicLocation(stopCodonPos);
56124
56244
  if (strand === 1) {
56125
56245
  if (startCodonGenomicLoc > stopCodonGenomicLoc) {
56126
56246
  notify('Start codon genomic location should be less than stop codon genomic location', 'error');
56127
56247
  return;
56128
56248
  }
56129
56249
  let promise;
56250
+ stopCodonGenomicLoc += 3; // move to end of stop codon
56130
56251
  if (startCodonGenomicLoc !== cdsMin) {
56131
56252
  promise = new Promise((resolve) => {
56132
56253
  updateCDSLocation(cdsMin, startCodonGenomicLoc, feature, true, () => {
@@ -56152,6 +56273,7 @@
56152
56273
  return;
56153
56274
  }
56154
56275
  let promise;
56276
+ stopCodonGenomicLoc -= 3; // move to end of stop codon
56155
56277
  if (startCodonGenomicLoc !== cdsMax) {
56156
56278
  promise = new Promise((resolve) => {
56157
56279
  updateCDSLocation(cdsMax, startCodonGenomicLoc, feature, false, () => {
@@ -56195,27 +56317,29 @@
56195
56317
  gap: 10,
56196
56318
  } },
56197
56319
  React__default["default"].createElement(material.Tooltip, { title: "Copy" },
56198
- React__default["default"].createElement(ContentCopyIcon, { style: { fontSize: 15, cursor: 'pointer' }, onClick: onCopyClick })),
56320
+ React__default["default"].createElement("button", { onClick: onCopyClick, style: { border: 'none', background: 'none', padding: 0 }, disabled: changeInProgress },
56321
+ React__default["default"].createElement(ContentCopyIcon, { style: { fontSize: 15 } }))),
56199
56322
  React__default["default"].createElement(material.Tooltip, { title: "Trim" },
56200
- React__default["default"].createElement(ContentCutIcon, { style: { fontSize: 15, cursor: 'pointer' }, onClick: trimTranslationSequence }))))),
56323
+ React__default["default"].createElement("button", { onClick: trimTranslationSequence, style: { border: 'none', background: 'none', padding: 0 }, disabled: changeInProgress },
56324
+ React__default["default"].createElement(ContentCutIcon, { style: { fontSize: 15 } })))))),
56201
56325
  React__default["default"].createElement(material.Grid, { container: true, justifyContent: "center", alignItems: "center", style: { textAlign: 'center', marginTop: 10 } },
56202
56326
  React__default["default"].createElement(material.Grid, { size: 1 }),
56203
56327
  strand === 1 ? (React__default["default"].createElement(material.Grid, { size: 4 },
56204
56328
  React__default["default"].createElement(StyledTextField, { margin: "dense", variant: "outlined", value: cdsMin + 1, onChangeCommitted: (newLocation) => {
56205
56329
  return updateCDSLocation(cdsMin, newLocation - 1, feature, true);
56206
- }, style: { border: '1px solid black', borderRadius: 5 } }))) : (React__default["default"].createElement(material.Grid, { size: 4 },
56330
+ }, style: { border: '1px solid black', borderRadius: 5 }, disabled: changeInProgress }))) : (React__default["default"].createElement(material.Grid, { size: 4 },
56207
56331
  React__default["default"].createElement(StyledTextField, { margin: "dense", variant: "outlined", value: cdsMax, onChangeCommitted: (newLocation) => {
56208
56332
  return updateCDSLocation(cdsMax, newLocation, feature, false);
56209
- }, style: { border: '1px solid black', borderRadius: 5 } }))),
56333
+ }, style: { border: '1px solid black', borderRadius: 5 }, disabled: changeInProgress }))),
56210
56334
  React__default["default"].createElement(material.Grid, { size: 2 },
56211
56335
  React__default["default"].createElement(material.Typography, { component: 'span' }, "CDS")),
56212
56336
  strand === 1 ? (React__default["default"].createElement(material.Grid, { size: 4 },
56213
56337
  React__default["default"].createElement(StyledTextField, { margin: "dense", variant: "outlined", value: cdsMax, onChangeCommitted: (newLocation) => {
56214
56338
  return updateCDSLocation(cdsMax, newLocation, feature, false);
56215
- }, style: { border: '1px solid black', borderRadius: 5 } }))) : (React__default["default"].createElement(material.Grid, { size: 4 },
56339
+ }, style: { border: '1px solid black', borderRadius: 5 }, disabled: changeInProgress }))) : (React__default["default"].createElement(material.Grid, { size: 4 },
56216
56340
  React__default["default"].createElement(StyledTextField, { margin: "dense", variant: "outlined", value: cdsMin + 1, onChangeCommitted: (newLocation) => {
56217
56341
  return updateCDSLocation(cdsMin, newLocation - 1, feature, true);
56218
- }, style: { border: '1px solid black', borderRadius: 5 } }))),
56342
+ }, style: { border: '1px solid black', borderRadius: 5 }, disabled: changeInProgress }))),
56219
56343
  React__default["default"].createElement(material.Grid, { size: 1 })))),
56220
56344
  React__default["default"].createElement("div", { style: { marginTop: 5 } }, transcriptExonParts.map((loc, index) => {
56221
56345
  return (React__default["default"].createElement("div", { key: index }, loc.type === 'exon' && (React__default["default"].createElement(material.Grid, { container: true, justifyContent: "center", alignItems: "center", style: { textAlign: 'center' } },
@@ -56224,19 +56348,19 @@
56224
56348
  strand === 1 ? (React__default["default"].createElement(material.Grid, { size: 4, style: { padding: 0 } },
56225
56349
  React__default["default"].createElement(StyledTextField, { margin: "dense", variant: "outlined", value: loc.min + 1, onChangeCommitted: (newLocation) => {
56226
56350
  return handleExonLocationChange(loc.min, newLocation - 1, feature, true);
56227
- } }))) : (React__default["default"].createElement(material.Grid, { size: 4, style: { padding: 0 } },
56351
+ }, disabled: changeInProgress }))) : (React__default["default"].createElement(material.Grid, { size: 4, style: { padding: 0 } },
56228
56352
  React__default["default"].createElement(StyledTextField, { margin: "dense", variant: "outlined", value: loc.max, onChangeCommitted: (newLocation) => {
56229
56353
  return handleExonLocationChange(loc.max, newLocation, feature, false);
56230
- } }))),
56354
+ }, disabled: changeInProgress }))),
56231
56355
  React__default["default"].createElement(material.Grid, { size: 2 },
56232
56356
  React__default["default"].createElement(Strand, { strand: feature.strand })),
56233
56357
  strand === 1 ? (React__default["default"].createElement(material.Grid, { size: 4, style: { padding: 0 } },
56234
56358
  React__default["default"].createElement(StyledTextField, { margin: "dense", variant: "outlined", value: loc.max, onChangeCommitted: (newLocation) => {
56235
56359
  return handleExonLocationChange(loc.max, newLocation, feature, false);
56236
- } }))) : (React__default["default"].createElement(material.Grid, { size: 4, style: { padding: 0 } },
56360
+ }, disabled: changeInProgress }))) : (React__default["default"].createElement(material.Grid, { size: 4, style: { padding: 0 } },
56237
56361
  React__default["default"].createElement(StyledTextField, { margin: "dense", variant: "outlined", value: loc.min + 1, onChangeCommitted: (newLocation) => {
56238
56362
  return handleExonLocationChange(loc.min, newLocation - 1, feature, true);
56239
- } }))),
56363
+ }, disabled: changeInProgress }))),
56240
56364
  React__default["default"].createElement(material.Grid, { size: 1 }, index !== transcriptExonParts.length - 1 &&
56241
56365
  getThreePrimeSpliceSite(loc, index).map((site, idx) => (React__default["default"].createElement(material.Typography, { key: idx, component: 'span', color: site.color }, site.spliceSite))))))));
56242
56366
  }))));
@@ -57913,8 +58037,8 @@
57913
58037
  },
57914
58038
  });
57915
58039
  if (require$$1$2.isSessionModelWithWidgets(session)) {
57916
- contextMenuItemsForFeature.push({
57917
- label: 'Open transcript details',
58040
+ contextMenuItemsForFeature.splice(1, 0, {
58041
+ label: 'Open transcript editor',
57918
58042
  onClick: () => {
57919
58043
  const apolloTranscriptWidget = session.addWidget('ApolloTranscriptDetails', 'apolloTranscriptDetails', {
57920
58044
  feature,
@@ -58262,6 +58386,25 @@
58262
58386
  clusters.sort((a, b) => a.message.localeCompare(b.message) || a.start - b.start);
58263
58387
  return clusters;
58264
58388
  }
58389
+ function codonColorCode(letter, theme, highContrast) {
58390
+ if (letter === 'M') {
58391
+ return theme.palette.startCodon;
58392
+ }
58393
+ if (letter === '*') {
58394
+ return highContrast ? theme.palette.text.primary : theme.palette.stopCodon;
58395
+ }
58396
+ return;
58397
+ }
58398
+ function colorCode(letter, theme) {
58399
+ const letterUpper = letter.toUpperCase();
58400
+ if (letterUpper === 'A' ||
58401
+ letterUpper === 'C' ||
58402
+ letterUpper === 'G' ||
58403
+ letterUpper === 'T') {
58404
+ return theme.palette.bases[letterUpper].main.toString();
58405
+ }
58406
+ return 'lightgray';
58407
+ }
58265
58408
 
58266
58409
  const minDisplayHeight$2 = 20;
58267
58410
  function baseModelFactory$2(_pluginManager, configSchema) {
@@ -59087,34 +59230,26 @@
59087
59230
 
59088
59231
  const configSchema$1 = configuration.ConfigurationSchema('LinearApolloReferenceSequenceDisplay', {}, { explicitIdentifier: 'displayId', explicitlyTyped: true });
59089
59232
 
59090
- function getSeqRow(strand, bpPerPx) {
59233
+ function getSeqRow(strand, bpPerPx, reversed) {
59091
59234
  if (bpPerPx > 1 || strand === undefined) {
59092
59235
  return;
59093
59236
  }
59237
+ if (reversed) {
59238
+ return strand === 1 ? 4 : 3;
59239
+ }
59094
59240
  return strand === 1 ? 3 : 4;
59095
59241
  }
59096
- function getTranslationRow(frame, bpPerPx) {
59097
- const offset = bpPerPx <= 1 ? 2 : 0;
59098
- switch (frame) {
59099
- case 3: {
59100
- return 0;
59101
- }
59102
- case 2: {
59103
- return 1;
59104
- }
59105
- case 1: {
59106
- return 2;
59107
- }
59108
- case -1: {
59109
- return 3 + offset;
59110
- }
59111
- case -2: {
59112
- return 4 + offset;
59113
- }
59114
- case -3: {
59115
- return 5 + offset;
59116
- }
59242
+ function getTranslationRow(frame, bpPerPx, reversed) {
59243
+ const frameRows = bpPerPx <= 1 ? [2, 1, 0, 7, 6, 5] : [2, 1, 0, 5, 4, 3];
59244
+ if (reversed) {
59245
+ frameRows.reverse();
59246
+ }
59247
+ frameRows.unshift(0);
59248
+ const row = frameRows.at(frame);
59249
+ if (row === undefined) {
59250
+ throw new Error('could not find row');
59117
59251
  }
59252
+ return row;
59118
59253
  }
59119
59254
  function getLeftPx$1(feature, bpPerPx, offsetPx, block) {
59120
59255
  const blockLeftPx = block.offsetPx - offsetPx;
@@ -59136,7 +59271,7 @@
59136
59271
  ctx.strokeRect(left, top, width, height);
59137
59272
  }
59138
59273
  function drawHighlight(ctx, feature, bpPerPx, offsetPx, rowHeight, block, theme, selected = false) {
59139
- const row = getSeqRow(feature.strand, bpPerPx);
59274
+ const row = getSeqRow(feature.strand, bpPerPx, block.reversed);
59140
59275
  if (!row) {
59141
59276
  return;
59142
59277
  }
@@ -59160,7 +59295,7 @@
59160
59295
  }
59161
59296
  for (const loc of cdsLocs) {
59162
59297
  const frame = require$$1$2.getFrame(loc.min, loc.max, feature.strand ?? 1, loc.phase);
59163
- const row = getTranslationRow(frame, bpPerPx);
59298
+ const row = getTranslationRow(frame, bpPerPx, block.reversed);
59164
59299
  const left = getLeftPx$1(loc, bpPerPx, offsetPx, block);
59165
59300
  const top = row * rowHeight;
59166
59301
  const width = (loc.max - loc.min) / bpPerPx;
@@ -59183,76 +59318,71 @@
59183
59318
  hoveredFeature?.feature,
59184
59319
  ].filter((f) => f !== undefined)) {
59185
59320
  if (featureTypeOntology.isTypeOf(feature.type, 'CDS')) {
59186
- drawCDSHighlight(ctx, feature, bpPerPx, offsetPx, rowHeight, block, theme, true);
59321
+ drawCDSHighlight(ctx, feature, bpPerPx, offsetPx, rowHeight, block, theme, feature._id === selectedFeature?._id);
59187
59322
  }
59188
59323
  else {
59189
- drawHighlight(ctx, feature, bpPerPx, offsetPx, rowHeight, block, theme, true);
59324
+ drawHighlight(ctx, feature, bpPerPx, offsetPx, rowHeight, block, theme, feature._id === selectedFeature?._id);
59190
59325
  }
59191
59326
  }
59192
59327
  ctx.restore();
59193
59328
  }
59194
59329
  }
59195
59330
 
59196
- function colorCode(letter, theme) {
59197
- const letterUpper = letter.toUpperCase();
59198
- if (letterUpper === 'A' ||
59199
- letterUpper === 'C' ||
59200
- letterUpper === 'G' ||
59201
- letterUpper === 'T') {
59202
- return theme.palette.bases[letterUpper].main.toString();
59203
- }
59204
- return 'lightgray';
59331
+ function getLeftPx(display, feature, block) {
59332
+ const { lgv } = display;
59333
+ const { bpPerPx, offsetPx } = lgv;
59334
+ const blockLeftPx = block.offsetPx - offsetPx;
59335
+ const featureLeftBpDistanceFromBlockLeftBp = block.reversed
59336
+ ? block.end - feature.max
59337
+ : feature.min - block.start;
59338
+ const featureLeftPxDistanceFromBlockLeftPx = featureLeftBpDistanceFromBlockLeftBp / bpPerPx;
59339
+ return blockLeftPx + featureLeftPxDistanceFromBlockLeftPx;
59205
59340
  }
59206
- function codonColorCode(letter, theme, highContrast) {
59207
- if (letter === 'M') {
59208
- return theme.palette.startCodon;
59209
- }
59210
- if (letter === '*') {
59211
- return highContrast ? theme.palette.text.primary : theme.palette.stopCodon;
59212
- }
59213
- return;
59341
+ /**
59342
+ * Perform a canvas strokeRect, but have the stroke be contained within the
59343
+ * given rect instead of centered on it.
59344
+ */
59345
+ function strokeRectInner(ctx, left, top, width, height, color) {
59346
+ ctx.strokeStyle = color;
59347
+ ctx.lineWidth = 1;
59348
+ ctx.strokeRect(left + 0.5, top + 0.5, width - 1, height - 1);
59214
59349
  }
59350
+
59215
59351
  function drawLetter(seqTrackctx, left, top, width, letter) {
59216
59352
  const fontSize = Math.min(width, 10);
59217
59353
  seqTrackctx.fillStyle = '#000';
59218
59354
  seqTrackctx.font = `${fontSize}px`;
59219
59355
  const textWidth = seqTrackctx.measureText(letter).width;
59220
- const textX = left + (width - textWidth) / 2;
59356
+ const textX = Math.round(left + (width - textWidth) / 2);
59221
59357
  seqTrackctx.fillText(letter, textX, top + 10);
59222
59358
  }
59223
- function drawTranslationFrameBackgrounds(canvas, ctx, bpPerPx, theme, dynamicBlocks, highContrast, sequenceRowHeight) {
59359
+ function drawTranslationFrameBackgrounds(ctx, bpPerPx, theme, highContrast, left, width, sequenceRowHeight, reversed) {
59224
59360
  const frames = bpPerPx <= 1 ? [3, 2, 1, 0, 0, -1, -2, -3] : [3, 2, 1, -1, -2, -3];
59361
+ if (reversed) {
59362
+ frames.reverse();
59363
+ }
59225
59364
  for (const [idx, frame] of frames.entries()) {
59226
59365
  const frameColor = theme.palette.framesCDS.at(frame)?.main;
59227
59366
  if (!frameColor) {
59228
59367
  continue;
59229
59368
  }
59230
59369
  const top = idx * sequenceRowHeight;
59231
- const { offsetPx } = dynamicBlocks;
59232
- const left = Math.max(0, -offsetPx);
59233
- const width = dynamicBlocks.totalWidthPx;
59234
59370
  ctx.fillStyle = highContrast ? theme.palette.background.default : frameColor;
59235
59371
  ctx.fillRect(left, top, width, sequenceRowHeight);
59236
59372
  if (highContrast) {
59237
59373
  // eslint-disable-next-line prefer-destructuring
59238
- ctx.strokeStyle = theme.palette.grey[200];
59239
- ctx.strokeRect(left, top, width, sequenceRowHeight);
59240
- }
59241
- }
59242
- // allows inter-region padding lines to show through
59243
- for (const block of dynamicBlocks.getBlocks()) {
59244
- if (block.type === 'InterRegionPaddingBlock') {
59245
- const left = block.offsetPx - dynamicBlocks.offsetPx;
59246
- ctx.clearRect(left, 0, block.widthPx, canvas.height);
59374
+ const strokeStyle = theme.palette.grey[200];
59375
+ strokeRectInner(ctx, left, top, width, sequenceRowHeight, strokeStyle);
59247
59376
  }
59248
59377
  }
59249
59378
  }
59250
59379
  function drawBase(ctx, base, index, leftPx, bpPerPx, rowHeight, theme) {
59251
- const width = 1 / bpPerPx;
59252
- if (width < 1) {
59380
+ if (1 / bpPerPx < 1) {
59253
59381
  return;
59254
59382
  }
59255
- const left = leftPx + index / bpPerPx;
59383
+ const left = Math.round(leftPx + index / bpPerPx);
59384
+ const nextLeft = Math.round(leftPx + (index + 1) / bpPerPx);
59385
+ const width = nextLeft - left;
59256
59386
  const strands = [-1, 1];
59257
59387
  for (const strand of strands) {
59258
59388
  const top = (strand === 1 ? 3 : 4) * rowHeight;
@@ -59260,13 +59390,13 @@
59260
59390
  ctx.fillStyle = colorCode(baseCode, theme);
59261
59391
  ctx.fillRect(left, top, width, rowHeight);
59262
59392
  if (1 / bpPerPx >= 12) {
59263
- ctx.strokeStyle = theme.palette.text.disabled;
59264
- ctx.strokeRect(left, top, width, rowHeight);
59393
+ const strokeStyle = theme.palette.text.disabled;
59394
+ strokeRectInner(ctx, left, top, width, rowHeight, strokeStyle);
59265
59395
  drawLetter(ctx, left, top, width, baseCode);
59266
59396
  }
59267
59397
  }
59268
59398
  }
59269
- function drawCodon(ctx, codon, leftPx, index, theme, highContrast, bpPerPx, bp, rowHeight, showStartCodons, showStopCodons) {
59399
+ function drawCodon$1(ctx, codon, leftPx, index, theme, highContrast, bpPerPx, bp, rowHeight, showStartCodons, showStopCodons) {
59270
59400
  const frameOffsets = (bpPerPx <= 1 ? [0, 2, 1, 0, 7, 6, 5] : [0, 2, 1, 0, 5, 4, 3]).map((b) => b * rowHeight);
59271
59401
  const strands = [-1, 1];
59272
59402
  for (const strand of strands) {
@@ -59276,7 +59406,8 @@
59276
59406
  continue;
59277
59407
  }
59278
59408
  const left = Math.round(leftPx + index / bpPerPx);
59279
- const width = Math.round(3 / bpPerPx);
59409
+ const nextLeft = Math.round(leftPx + (index + 3) / bpPerPx);
59410
+ const width = nextLeft - left;
59280
59411
  const codonCode = strand === 1 ? codon : require$$1$2.revcom(codon);
59281
59412
  const aminoAcidCode = require$$1$2.defaultCodonTable[codonCode];
59282
59413
  const fillColor = codonColorCode(aminoAcidCode, theme, highContrast);
@@ -59287,8 +59418,8 @@
59287
59418
  ctx.fillRect(left, top, width, rowHeight);
59288
59419
  }
59289
59420
  if (1 / bpPerPx >= 4) {
59290
- ctx.strokeStyle = theme.palette.text.disabled;
59291
- ctx.strokeRect(left, top, width, rowHeight);
59421
+ const strokeStyle = theme.palette.text.disabled;
59422
+ strokeRectInner(ctx, left, top, width, rowHeight, strokeStyle);
59292
59423
  drawLetter(ctx, left, top, width, aminoAcidCode);
59293
59424
  }
59294
59425
  }
@@ -59299,9 +59430,10 @@
59299
59430
  return;
59300
59431
  }
59301
59432
  ctx.clearRect(0, 0, canvas.width, canvas.height);
59302
- drawTranslationFrameBackgrounds(canvas, ctx, bpPerPx, theme, dynamicBlocks, highContrast, sequenceRowHeight);
59303
59433
  const { apolloDataStore } = session;
59304
59434
  for (const block of dynamicBlocks.contentBlocks) {
59435
+ const totalOffsetPx = block.offsetPx - offsetPx;
59436
+ drawTranslationFrameBackgrounds(ctx, bpPerPx, theme, highContrast, totalOffsetPx, block.widthPx, sequenceRowHeight, block.reversed);
59305
59437
  const assembly = apolloDataStore.assemblies.get(block.assemblyName);
59306
59438
  const ref = assembly?.getByRefName(block.refName);
59307
59439
  const roundedStart = Math.floor(block.start);
@@ -59311,16 +59443,20 @@
59311
59443
  return;
59312
59444
  }
59313
59445
  seq = seq.toUpperCase();
59314
- const baseOffsetPx = (block.start - roundedStart) / bpPerPx;
59315
- const seqLeftPx = Math.round(block.offsetPx - offsetPx - baseOffsetPx);
59446
+ if (block.reversed) {
59447
+ seq = require$$1$2.revcom(seq);
59448
+ }
59449
+ const baseOffsetPx = (block.reversed ? roundedEnd - block.end : block.start - roundedStart) /
59450
+ bpPerPx;
59451
+ const seqLeftPx = totalOffsetPx - baseOffsetPx;
59316
59452
  for (let i = 0; i < seq.length; i++) {
59317
- const bp = roundedStart + i;
59453
+ const bp = block.reversed ? roundedEnd - i : roundedStart + i;
59318
59454
  const codon = seq.slice(i, i + 3);
59319
59455
  drawBase(ctx, seq[i], i, seqLeftPx, bpPerPx, sequenceRowHeight, theme);
59320
59456
  if (codon.length !== 3) {
59321
59457
  continue;
59322
59458
  }
59323
- drawCodon(ctx, codon, seqLeftPx, i, theme, highContrast, bpPerPx, bp, sequenceRowHeight, showStartCodons, showStopCodons);
59459
+ drawCodon$1(ctx, codon, seqLeftPx, i, theme, highContrast, bpPerPx, bp, sequenceRowHeight, showStartCodons, showStopCodons);
59324
59460
  }
59325
59461
  }
59326
59462
  }
@@ -60384,6 +60520,8 @@
60384
60520
  graphical: true,
60385
60521
  table: false,
60386
60522
  showFeatureLabels: true,
60523
+ showStartCodons: false,
60524
+ showStopCodons: true,
60387
60525
  showCheckResults: true,
60388
60526
  zoomThreshold: 200,
60389
60527
  heightPreConfig: require$$1$3.types.maybe(require$$1$3.types.refinement('displayHeight', require$$1$3.types.number, (n) => n >= minDisplayHeight)),
@@ -60512,6 +60650,12 @@
60512
60650
  toggleShowFeatureLabels() {
60513
60651
  self.showFeatureLabels = !self.showFeatureLabels;
60514
60652
  },
60653
+ toggleShowStartCodons() {
60654
+ self.showStartCodons = !self.showStartCodons;
60655
+ },
60656
+ toggleShowStopCodons() {
60657
+ self.showStopCodons = !self.showStopCodons;
60658
+ },
60515
60659
  toggleShowCheckResults() {
60516
60660
  self.showCheckResults = !self.showCheckResults;
60517
60661
  },
@@ -60526,7 +60670,7 @@
60526
60670
  const { filteredFeatureTypes, trackMenuItems: superTrackMenuItems } = self;
60527
60671
  return {
60528
60672
  trackMenuItems() {
60529
- const { graphical, table, showFeatureLabels, showCheckResults } = self;
60673
+ const { graphical, table, showFeatureLabels, showStartCodons, showStopCodons, showCheckResults, } = self;
60530
60674
  return [
60531
60675
  ...superTrackMenuItems(),
60532
60676
  {
@@ -60565,6 +60709,22 @@
60565
60709
  self.toggleShowFeatureLabels();
60566
60710
  },
60567
60711
  },
60712
+ {
60713
+ label: 'Show start codons',
60714
+ type: 'checkbox',
60715
+ checked: showStartCodons,
60716
+ onClick: () => {
60717
+ self.toggleShowStartCodons();
60718
+ },
60719
+ },
60720
+ {
60721
+ label: 'Show stop codons',
60722
+ type: 'checkbox',
60723
+ checked: showStopCodons,
60724
+ onClick: () => {
60725
+ self.toggleShowStopCodons();
60726
+ },
60727
+ },
60568
60728
  {
60569
60729
  label: 'Check Results',
60570
60730
  type: 'checkbox',
@@ -60641,7 +60801,7 @@
60641
60801
  return;
60642
60802
  }
60643
60803
  void self.session.apolloDataStore.loadFeatures(self.regions);
60644
- if (self.lgv.bpPerPx <= 3) {
60804
+ if (self.lgv.bpPerPx <= self.zoomThreshold) {
60645
60805
  void self.session.apolloDataStore.loadRefSeq(self.regions);
60646
60806
  }
60647
60807
  }, { name: 'LinearApolloSixFrameDisplayLoadFeatures', delay: 1000 }));
@@ -60833,6 +60993,28 @@
60833
60993
  }));
60834
60994
  }
60835
60995
 
60996
+ function drawCodon(ctx, codon, leftPx, index, theme, highContrast, bpPerPx, bp, rowHeight, showFeatureLabels, showStartCodons, showStopCodons) {
60997
+ const frameOffsets = (showFeatureLabels ? [0, 4, 2, 0, 14, 12, 10] : [0, 2, 1, 0, 7, 6, 5]).map((b) => b * rowHeight);
60998
+ const strands = [-1, 1];
60999
+ for (const strand of strands) {
61000
+ const frame = require$$1$2.getFrame(bp, bp + 3, strand, 0);
61001
+ const top = frameOffsets.at(frame);
61002
+ if (top === undefined) {
61003
+ continue;
61004
+ }
61005
+ const left = Math.round(leftPx + index / bpPerPx);
61006
+ const width = Math.round(3 / bpPerPx) === 0 ? 1 : Math.round(3 / bpPerPx);
61007
+ const codonCode = strand === 1 ? codon : require$$1$2.revcom(codon);
61008
+ const aminoAcidCode = require$$1$2.defaultCodonTable[codonCode];
61009
+ const fillColor = codonColorCode(aminoAcidCode, theme, highContrast);
61010
+ if (fillColor &&
61011
+ ((showStopCodons && aminoAcidCode == '*') ||
61012
+ (showStartCodons && aminoAcidCode != '*'))) {
61013
+ ctx.fillStyle = fillColor;
61014
+ ctx.fillRect(left, top, width, rowHeight);
61015
+ }
61016
+ }
61017
+ }
60836
61018
  function renderingModelFactory(pluginManager, configSchema) {
60837
61019
  const LinearApolloSixFrameDisplayLayouts = layoutsModelFactory(pluginManager, configSchema);
60838
61020
  return LinearApolloSixFrameDisplayLayouts.named('LinearApolloSixFrameDisplayRendering')
@@ -60922,11 +61104,11 @@
60922
61104
  }
60923
61105
  }, { name: 'LinearApolloSixFrameDisplayRenderCollaborators' }));
60924
61106
  require$$1$3.addDisposer(self, mobx.autorun(() => {
60925
- const { canvas, featureLayouts, featuresHeight, lgv } = self;
61107
+ const { apolloRowHeight, canvas, featureLayouts, featuresHeight, lgv, session, theme, showFeatureLabels, showStartCodons, showStopCodons, } = self;
60926
61108
  if (!lgv.initialized || self.regionCannotBeRendered()) {
60927
61109
  return;
60928
61110
  }
60929
- const { displayedRegions, dynamicBlocks } = lgv;
61111
+ const { bpPerPx, offsetPx, displayedRegions, dynamicBlocks } = lgv;
60930
61112
  const ctx = canvas?.getContext('2d');
60931
61113
  if (!ctx) {
60932
61114
  return;
@@ -60950,6 +61132,27 @@
60950
61132
  }
60951
61133
  }
60952
61134
  }
61135
+ if (showStartCodons || showStopCodons) {
61136
+ const { apolloDataStore } = session;
61137
+ for (const block of dynamicBlocks.contentBlocks) {
61138
+ const assembly = apolloDataStore.assemblies.get(block.assemblyName);
61139
+ const ref = assembly?.getByRefName(block.refName);
61140
+ const roundedStart = Math.floor(block.start);
61141
+ const roundedEnd = Math.ceil(block.end);
61142
+ let seq = ref?.getSequence(roundedStart, roundedEnd);
61143
+ if (!seq) {
61144
+ break;
61145
+ }
61146
+ seq = seq.toUpperCase();
61147
+ const baseOffsetPx = (block.start - roundedStart) / bpPerPx;
61148
+ const seqLeftPx = Math.round(block.offsetPx - offsetPx - baseOffsetPx);
61149
+ for (let i = 0; i < seq.length; i++) {
61150
+ const bp = roundedStart + i;
61151
+ const codon = seq.slice(i, i + 3);
61152
+ drawCodon(ctx, codon, seqLeftPx, i, theme, true, bpPerPx, bp, apolloRowHeight, showFeatureLabels, showStartCodons, showStopCodons);
61153
+ }
61154
+ }
61155
+ }
60953
61156
  }, { name: 'LinearApolloSixFrameDisplayRenderFeatures' }));
60954
61157
  },
60955
61158
  }));
@@ -61912,17 +62115,6 @@
61912
62115
  d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m1 15h-2v-2h2zm0-4h-2V7h2z"
61913
62116
  }), 'Error');
61914
62117
 
61915
- function getLeftPx(display, feature, block) {
61916
- const { lgv } = display;
61917
- const { bpPerPx, offsetPx } = lgv;
61918
- const blockLeftPx = block.offsetPx - offsetPx;
61919
- const featureLeftBpDistanceFromBlockLeftBp = block.reversed
61920
- ? block.end - feature.max
61921
- : feature.min - block.start;
61922
- const featureLeftPxDistanceFromBlockLeftPx = featureLeftBpDistanceFromBlockLeftBp / bpPerPx;
61923
- return blockLeftPx + featureLeftPxDistanceFromBlockLeftPx;
61924
- }
61925
-
61926
62118
  const CheckResultWarnings = mobxReact.observer(function CheckResultWarnings({ display, }) {
61927
62119
  const { classes } = useStyles$1();
61928
62120
  const { apolloDragging, apolloRowHeight, lgv, session, showCheckResults } = display;
@@ -62254,7 +62446,7 @@
62254
62446
  const controller = new AbortController();
62255
62447
  const { signal } = controller;
62256
62448
  function abortDrag() {
62257
- controller.abort();
62449
+ controller.abort('makeDisplayComponent');
62258
62450
  }
62259
62451
  globalThis.addEventListener('mousemove', mouseMove, { signal });
62260
62452
  globalThis.addEventListener('mouseup', abortDrag, { signal });
@@ -62691,7 +62883,7 @@
62691
62883
  statusMessage: `Loading ontology "${name}", version "${version}", this may take a while`,
62692
62884
  progressPct: 0,
62693
62885
  cancelCallback: () => {
62694
- controller.abort();
62886
+ controller.abort('ClientDataStore');
62695
62887
  jobsManager.abortJob(job.name);
62696
62888
  },
62697
62889
  };
@@ -62830,6 +63022,7 @@
62830
63022
  apolloSelectedFeature: require$$1$3.types.safeReference(AnnotationFeatureExtended),
62831
63023
  jobsManager: require$$1$3.types.optional(ApolloJobModel, {}),
62832
63024
  isLocked: require$$1$3.types.optional(require$$1$3.types.boolean, false),
63025
+ changeInProgress: require$$1$3.types.optional(require$$1$3.types.boolean, false),
62833
63026
  })
62834
63027
  .volatile(() => ({
62835
63028
  apolloHoveredFeature: undefined,
@@ -62892,6 +63085,9 @@
62892
63085
  toggleLocked() {
62893
63086
  self.isLocked = !self.isLocked;
62894
63087
  },
63088
+ setChangeInProgress(changeInProgress) {
63089
+ self.changeInProgress = changeInProgress;
63090
+ },
62895
63091
  getPluginConfiguration() {
62896
63092
  const { jbrowse } = require$$1$3.getRoot(self);
62897
63093
  const pluginConfiguration =