@apollo-annotation/jbrowse-plugin-apollo 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.esm.js +2072 -1496
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +2069 -1493
- package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.development.js +2256 -1533
- package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
- package/package.json +13 -11
- package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +7 -10
- package/src/FeatureDetailsWidget/ApolloFeatureDetailsWidget.tsx +3 -0
- package/src/FeatureDetailsWidget/Attributes.tsx +27 -27
- package/src/FeatureDetailsWidget/FeatureDetailsNavigation.tsx +65 -0
- package/src/FeatureDetailsWidget/TranscriptBasic.tsx +6 -1
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +25 -2
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +0 -1
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +8 -1
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +88 -40
- package/src/LinearApolloDisplay/glyphs/Glyph.ts +8 -1
- package/src/LinearApolloDisplay/stateModel/base.ts +28 -2
- package/src/LinearApolloDisplay/stateModel/layouts.ts +65 -11
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +25 -6
- package/src/LinearApolloDisplay/stateModel/rendering.ts +1 -2
- package/src/OntologyManager/OntologyStore/index.ts +6 -2
- package/src/OntologyManager/OntologyStore/indexeddb-storage.ts +41 -13
- package/src/OntologyManager/index.ts +35 -0
- package/src/SixFrameFeatureDisplay/stateModel.ts +11 -2
- package/src/TabularEditor/HybridGrid/Feature.tsx +1 -2
- package/src/TabularEditor/HybridGrid/HybridGrid.tsx +0 -1
- package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +8 -1
- package/src/components/AddRefSeqAliases.tsx +7 -8
- package/src/components/CopyFeature.tsx +1 -1
- package/src/components/CreateApolloAnnotation.tsx +304 -0
- package/src/components/DownloadGFF3.tsx +5 -1
- package/src/components/FilterFeatures.tsx +120 -0
- package/src/components/ModifyFeatureAttribute.tsx +27 -27
- package/src/components/OntologyTermMultiSelect.tsx +5 -5
- package/src/extensions/annotationFromJBrowseFeature.test.ts +119 -0
- package/src/extensions/annotationFromJBrowseFeature.ts +171 -0
- package/src/extensions/annotationFromPileup.ts +1 -1
- package/src/extensions/index.ts +1 -0
- package/src/index.ts +8 -2
- package/src/session/ClientDataStore.ts +29 -0
- package/src/session/session.ts +2 -5
- package/src/LinearApolloDisplay/stateModel/getGlyph.ts +0 -40
|
@@ -4702,10 +4702,10 @@
|
|
|
4702
4702
|
annotationFeatureToGFF3$1.annotationFeatureToGFF3 = annotationFeatureToGFF3;
|
|
4703
4703
|
var util_1$4 = require$$1__default$1["default"];
|
|
4704
4704
|
function annotationFeatureToGFF3(feature, parentId, refSeqNames) {
|
|
4705
|
-
var _feature$attributes$g, _feature$
|
|
4706
|
-
var attributes = JSON.parse(JSON.stringify(feature.attributes));
|
|
4705
|
+
var _feature$attributes, _feature$attributes$g, _feature$attributes2, _feature$attributes3;
|
|
4706
|
+
var attributes = JSON.parse(JSON.stringify((_feature$attributes = feature.attributes) !== null && _feature$attributes !== void 0 ? _feature$attributes : {}));
|
|
4707
4707
|
var ontologyTerms = [];
|
|
4708
|
-
var source = (_feature$attributes$g = (_feature$
|
|
4708
|
+
var source = (_feature$attributes$g = (_feature$attributes2 = feature.attributes) === null || _feature$attributes2 === void 0 || (_feature$attributes2 = _feature$attributes2.gff_source) === null || _feature$attributes2 === void 0 ? void 0 : _feature$attributes2[0]) !== null && _feature$attributes$g !== void 0 ? _feature$attributes$g : null;
|
|
4709
4709
|
delete attributes.gff_source;
|
|
4710
4710
|
if (parentId) {
|
|
4711
4711
|
attributes.Parent = [parentId];
|
|
@@ -4763,7 +4763,7 @@
|
|
|
4763
4763
|
if (ontologyTerms.length > 0) {
|
|
4764
4764
|
attributes.Ontology_term = ontologyTerms;
|
|
4765
4765
|
}
|
|
4766
|
-
var gff_score = (_feature$
|
|
4766
|
+
var gff_score = (_feature$attributes3 = feature.attributes) === null || _feature$attributes3 === void 0 ? void 0 : _feature$attributes3.gff_score;
|
|
4767
4767
|
var score = null;
|
|
4768
4768
|
if (gff_score && gff_score.length > 0) {
|
|
4769
4769
|
if (gff_score[0]) {
|
|
@@ -5069,7 +5069,7 @@
|
|
|
5069
5069
|
min = _getFeatureMinMax2[0],
|
|
5070
5070
|
max = _getFeatureMinMax2[1];
|
|
5071
5071
|
var convertedChildren = convertChildren(gff3Feature, refSeq, featureIds);
|
|
5072
|
-
var convertedAttributes = convertFeatureAttributes(gff3Feature);
|
|
5072
|
+
var convertedAttributes = convertFeatureAttributes$1(gff3Feature);
|
|
5073
5073
|
var feature = {
|
|
5074
5074
|
_id: new bson_objectid_1$1["default"]().toHexString(),
|
|
5075
5075
|
refSeq: refSeq !== null && refSeq !== void 0 ? refSeq : refName,
|
|
@@ -5117,7 +5117,7 @@
|
|
|
5117
5117
|
var max = Math.max.apply(Math, _toConsumableArray(maxes));
|
|
5118
5118
|
return [min - 1, max];
|
|
5119
5119
|
}
|
|
5120
|
-
function convertFeatureAttributes(gff3Feature) {
|
|
5120
|
+
function convertFeatureAttributes$1(gff3Feature) {
|
|
5121
5121
|
var convertedAttributes = {};
|
|
5122
5122
|
var scores = gff3Feature.map(function (f) {
|
|
5123
5123
|
return f.score;
|
|
@@ -5171,6 +5171,9 @@
|
|
|
5171
5171
|
var newKey = (0, gffReservedKeys_1.isGFFReservedAttribute)(key) ? gffReservedKeys_1.gffToInternal[key] : key;
|
|
5172
5172
|
var existingVal = convertedAttributes[newKey];
|
|
5173
5173
|
if (existingVal) {
|
|
5174
|
+
// if (JSON.stringify(existingVal) === JSON.stringify(val)) {
|
|
5175
|
+
// continue
|
|
5176
|
+
// }
|
|
5174
5177
|
var valSet = new Set([].concat(_toConsumableArray(existingVal), _toConsumableArray(val)));
|
|
5175
5178
|
convertedAttributes[newKey] = _toConsumableArray(valSet);
|
|
5176
5179
|
} else {
|
|
@@ -5204,6 +5207,8 @@
|
|
|
5204
5207
|
firstFeature = _locationsWithChildre[0];
|
|
5205
5208
|
var childFeatures = firstFeature.child_features;
|
|
5206
5209
|
var cdsFeatures = [];
|
|
5210
|
+
var exonFeatures = [];
|
|
5211
|
+
var utrFeatures = [];
|
|
5207
5212
|
var _iterator2 = _createForOfIteratorHelper(childFeatures),
|
|
5208
5213
|
_step2;
|
|
5209
5214
|
try {
|
|
@@ -5211,6 +5216,12 @@
|
|
|
5211
5216
|
var childFeature = _step2.value;
|
|
5212
5217
|
var _childFeature = _slicedToArray(childFeature, 1),
|
|
5213
5218
|
firstChildFeatureLocation = _childFeature[0];
|
|
5219
|
+
if (firstChildFeatureLocation.type === 'exon') {
|
|
5220
|
+
exonFeatures.push(childFeature);
|
|
5221
|
+
}
|
|
5222
|
+
if (firstChildFeatureLocation.type === 'three_prime_UTR' || firstChildFeatureLocation.type === 'five_prime_UTR') {
|
|
5223
|
+
utrFeatures.push(childFeature);
|
|
5224
|
+
}
|
|
5214
5225
|
if (firstChildFeatureLocation.type === 'three_prime_UTR' || firstChildFeatureLocation.type === 'five_prime_UTR' || firstChildFeatureLocation.type === 'intron' || firstChildFeatureLocation.type === 'start_codon' || firstChildFeatureLocation.type === 'stop_codon') {
|
|
5215
5226
|
continue;
|
|
5216
5227
|
}
|
|
@@ -5226,24 +5237,217 @@
|
|
|
5226
5237
|
} finally {
|
|
5227
5238
|
_iterator2.f();
|
|
5228
5239
|
}
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5240
|
+
if (cdsFeatures.length > 0) {
|
|
5241
|
+
var processedCDS = processCDS(cdsFeatures, refSeq, featureIds);
|
|
5242
|
+
var _iterator3 = _createForOfIteratorHelper(processedCDS),
|
|
5243
|
+
_step3;
|
|
5244
|
+
try {
|
|
5245
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
5246
|
+
var cds = _step3.value;
|
|
5247
|
+
convertedChildren[cds._id] = cds;
|
|
5248
|
+
}
|
|
5249
|
+
} catch (err) {
|
|
5250
|
+
_iterator3.e(err);
|
|
5251
|
+
} finally {
|
|
5252
|
+
_iterator3.f();
|
|
5253
|
+
}
|
|
5254
|
+
var missingExons = inferMissingExons(cdsFeatures, exonFeatures, utrFeatures, processedCDS[0].refSeq);
|
|
5255
|
+
var _iterator4 = _createForOfIteratorHelper(missingExons),
|
|
5256
|
+
_step4;
|
|
5257
|
+
try {
|
|
5258
|
+
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
|
|
5259
|
+
var exon = _step4.value;
|
|
5260
|
+
convertedChildren[exon._id] = exon;
|
|
5261
|
+
}
|
|
5262
|
+
} catch (err) {
|
|
5263
|
+
_iterator4.e(err);
|
|
5264
|
+
} finally {
|
|
5265
|
+
_iterator4.f();
|
|
5236
5266
|
}
|
|
5237
|
-
} catch (err) {
|
|
5238
|
-
_iterator3.e(err);
|
|
5239
|
-
} finally {
|
|
5240
|
-
_iterator3.f();
|
|
5241
5267
|
}
|
|
5242
5268
|
if (Object.keys(convertedChildren).length > 0) {
|
|
5243
5269
|
return convertedChildren;
|
|
5244
5270
|
}
|
|
5245
5271
|
return;
|
|
5246
5272
|
}
|
|
5273
|
+
function inferMissingExons(cdsFeatures, existingExons, utrFeatures, refSeq) {
|
|
5274
|
+
// Convert utrFeatures from GFF3Feature to AnnotationFeatureSnapshot
|
|
5275
|
+
var utrExons = [];
|
|
5276
|
+
var _iterator5 = _createForOfIteratorHelper(utrFeatures),
|
|
5277
|
+
_step5;
|
|
5278
|
+
try {
|
|
5279
|
+
for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
|
|
5280
|
+
var utrs = _step5.value;
|
|
5281
|
+
var _iterator7 = _createForOfIteratorHelper(utrs),
|
|
5282
|
+
_step7;
|
|
5283
|
+
try {
|
|
5284
|
+
for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
|
|
5285
|
+
var utr = _step7.value;
|
|
5286
|
+
if (!utr.start || !utr.end) {
|
|
5287
|
+
throw new Error("UTR has undefined start and/or end\n: ".concat(JSON.stringify(utr, null, 2)));
|
|
5288
|
+
}
|
|
5289
|
+
var strand = undefined;
|
|
5290
|
+
if (utr.strand === '+') {
|
|
5291
|
+
strand = 1;
|
|
5292
|
+
} else if (utr.strand === '-') {
|
|
5293
|
+
strand = -1;
|
|
5294
|
+
}
|
|
5295
|
+
utrExons.push({
|
|
5296
|
+
_id: new bson_objectid_1$1["default"]().toHexString(),
|
|
5297
|
+
refSeq: refSeq,
|
|
5298
|
+
type: 'exon',
|
|
5299
|
+
min: utr.start - 1,
|
|
5300
|
+
max: utr.end,
|
|
5301
|
+
strand: strand
|
|
5302
|
+
});
|
|
5303
|
+
}
|
|
5304
|
+
} catch (err) {
|
|
5305
|
+
_iterator7.e(err);
|
|
5306
|
+
} finally {
|
|
5307
|
+
_iterator7.f();
|
|
5308
|
+
}
|
|
5309
|
+
}
|
|
5310
|
+
} catch (err) {
|
|
5311
|
+
_iterator5.e(err);
|
|
5312
|
+
} finally {
|
|
5313
|
+
_iterator5.f();
|
|
5314
|
+
}
|
|
5315
|
+
utrExons.sort(function (a, b) {
|
|
5316
|
+
return a.min - b.min;
|
|
5317
|
+
});
|
|
5318
|
+
var missingExons = [];
|
|
5319
|
+
var _iterator6 = _createForOfIteratorHelper(cdsFeatures),
|
|
5320
|
+
_step6;
|
|
5321
|
+
try {
|
|
5322
|
+
for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
|
|
5323
|
+
var protein = _step6.value;
|
|
5324
|
+
protein.sort(function (a, b) {
|
|
5325
|
+
if (!a.start || !b.start) {
|
|
5326
|
+
throw new Error('CDS has undefined start');
|
|
5327
|
+
}
|
|
5328
|
+
return a.start - b.start;
|
|
5329
|
+
});
|
|
5330
|
+
for (var cdsIdx = 0; cdsIdx < protein.length; cdsIdx++) {
|
|
5331
|
+
var cds = protein[cdsIdx];
|
|
5332
|
+
// For CDS check if there is an exon containing it. If not, create an exon with same coords as the CDS.
|
|
5333
|
+
var exonFound = false;
|
|
5334
|
+
var _iterator8 = _createForOfIteratorHelper(existingExons),
|
|
5335
|
+
_step8;
|
|
5336
|
+
try {
|
|
5337
|
+
for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
|
|
5338
|
+
var x = _step8.value;
|
|
5339
|
+
if (x.length != 1) {
|
|
5340
|
+
throw new Error('Unexpected number of exons');
|
|
5341
|
+
}
|
|
5342
|
+
var _x = _slicedToArray(x, 1),
|
|
5343
|
+
exon = _x[0];
|
|
5344
|
+
if (exon.start && exon.end && cds.start && cds.end && exon.start <= cds.start && exon.end >= cds.end) {
|
|
5345
|
+
exonFound = true;
|
|
5346
|
+
break;
|
|
5347
|
+
}
|
|
5348
|
+
}
|
|
5349
|
+
} catch (err) {
|
|
5350
|
+
_iterator8.e(err);
|
|
5351
|
+
} finally {
|
|
5352
|
+
_iterator8.f();
|
|
5353
|
+
}
|
|
5354
|
+
if (!exonFound) {
|
|
5355
|
+
if (!cds.start || !cds.end) {
|
|
5356
|
+
throw new Error("CDS has undefined start and/or end: ".concat(JSON.stringify(cds, null, 2)));
|
|
5357
|
+
}
|
|
5358
|
+
var _strand = undefined;
|
|
5359
|
+
if (cds.strand === '+') {
|
|
5360
|
+
_strand = 1;
|
|
5361
|
+
} else if (cds.strand === '-') {
|
|
5362
|
+
_strand = -1;
|
|
5363
|
+
}
|
|
5364
|
+
var newExon = {
|
|
5365
|
+
_id: new bson_objectid_1$1["default"]().toHexString(),
|
|
5366
|
+
refSeq: refSeq,
|
|
5367
|
+
type: 'exon',
|
|
5368
|
+
min: cds.start - 1,
|
|
5369
|
+
max: cds.end,
|
|
5370
|
+
strand: _strand
|
|
5371
|
+
};
|
|
5372
|
+
if (cdsIdx === 0) {
|
|
5373
|
+
// If this CDS is the leftmost (or the only CDS in this protein), check if we need to add UTRs before it
|
|
5374
|
+
var _iterator9 = _createForOfIteratorHelper(utrExons),
|
|
5375
|
+
_step9;
|
|
5376
|
+
try {
|
|
5377
|
+
for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
|
|
5378
|
+
var _utr = _step9.value;
|
|
5379
|
+
if (_utr.max > newExon.min) {
|
|
5380
|
+
break;
|
|
5381
|
+
}
|
|
5382
|
+
if (_utr.max === newExon.min) {
|
|
5383
|
+
// UTR ends where exon begins: Extend the exon to include this UTR
|
|
5384
|
+
newExon.min = _utr.min;
|
|
5385
|
+
} else {
|
|
5386
|
+
missingExons.push(_utr);
|
|
5387
|
+
}
|
|
5388
|
+
}
|
|
5389
|
+
} catch (err) {
|
|
5390
|
+
_iterator9.e(err);
|
|
5391
|
+
} finally {
|
|
5392
|
+
_iterator9.f();
|
|
5393
|
+
}
|
|
5394
|
+
}
|
|
5395
|
+
if (cdsIdx === protein.length - 1) {
|
|
5396
|
+
// If this CDS is the rightmost (or the only CDS in this protein), check if we need to add UTRs after it
|
|
5397
|
+
var _iterator10 = _createForOfIteratorHelper(utrExons),
|
|
5398
|
+
_step10;
|
|
5399
|
+
try {
|
|
5400
|
+
for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) {
|
|
5401
|
+
var _utr2 = _step10.value;
|
|
5402
|
+
if (_utr2.min < newExon.max) {
|
|
5403
|
+
continue;
|
|
5404
|
+
}
|
|
5405
|
+
if (_utr2.min === newExon.max) {
|
|
5406
|
+
// UTR begins where exon end: Extend the exon to include this UTR
|
|
5407
|
+
newExon.max = _utr2.max;
|
|
5408
|
+
} else {
|
|
5409
|
+
missingExons.push(_utr2);
|
|
5410
|
+
}
|
|
5411
|
+
}
|
|
5412
|
+
} catch (err) {
|
|
5413
|
+
_iterator10.e(err);
|
|
5414
|
+
} finally {
|
|
5415
|
+
_iterator10.f();
|
|
5416
|
+
}
|
|
5417
|
+
}
|
|
5418
|
+
missingExons.push(newExon);
|
|
5419
|
+
}
|
|
5420
|
+
}
|
|
5421
|
+
}
|
|
5422
|
+
} catch (err) {
|
|
5423
|
+
_iterator6.e(err);
|
|
5424
|
+
} finally {
|
|
5425
|
+
_iterator6.f();
|
|
5426
|
+
}
|
|
5427
|
+
var mergedExons = mergeAnnotationFeatures(missingExons);
|
|
5428
|
+
return mergedExons;
|
|
5429
|
+
}
|
|
5430
|
+
function mergeAnnotationFeatures(features) {
|
|
5431
|
+
if (features.length === 0) {
|
|
5432
|
+
return [];
|
|
5433
|
+
}
|
|
5434
|
+
features.sort(function (a, b) {
|
|
5435
|
+
return a.min - b.min;
|
|
5436
|
+
});
|
|
5437
|
+
var res = [];
|
|
5438
|
+
res.push(features[0]);
|
|
5439
|
+
for (var i = 1; i < features.length; i++) {
|
|
5440
|
+
var last = res.at(-1);
|
|
5441
|
+
var curr = features[i];
|
|
5442
|
+
// If current interval overlaps with the last merged interval, merge them
|
|
5443
|
+
if (last && curr.min <= last.max) {
|
|
5444
|
+
last.max = Math.max(last.max, curr.max);
|
|
5445
|
+
} else {
|
|
5446
|
+
res.push(curr);
|
|
5447
|
+
}
|
|
5448
|
+
}
|
|
5449
|
+
return res;
|
|
5450
|
+
}
|
|
5247
5451
|
/**
|
|
5248
5452
|
* If a GFF3 file has CDS features that either (1) don't have an ID or (2) have
|
|
5249
5453
|
* different IDs for each CDS, we have to do a bit of guessing about how they
|
|
@@ -5295,11 +5499,11 @@
|
|
|
5295
5499
|
return cds[0];
|
|
5296
5500
|
});
|
|
5297
5501
|
var groupedLocations = [];
|
|
5298
|
-
var
|
|
5299
|
-
|
|
5502
|
+
var _iterator11 = _createForOfIteratorHelper(cdsLocations),
|
|
5503
|
+
_step11;
|
|
5300
5504
|
try {
|
|
5301
5505
|
var _loop = function _loop() {
|
|
5302
|
-
var location =
|
|
5506
|
+
var location = _step11.value;
|
|
5303
5507
|
var lastGroup = groupedLocations.at(-1);
|
|
5304
5508
|
if (!lastGroup) {
|
|
5305
5509
|
groupedLocations.push([location]);
|
|
@@ -5315,13 +5519,13 @@
|
|
|
5315
5519
|
lastGroup.push(location);
|
|
5316
5520
|
}
|
|
5317
5521
|
};
|
|
5318
|
-
for (
|
|
5522
|
+
for (_iterator11.s(); !(_step11 = _iterator11.n()).done;) {
|
|
5319
5523
|
if (_loop()) continue;
|
|
5320
5524
|
}
|
|
5321
5525
|
} catch (err) {
|
|
5322
|
-
|
|
5526
|
+
_iterator11.e(err);
|
|
5323
5527
|
} finally {
|
|
5324
|
-
|
|
5528
|
+
_iterator11.f();
|
|
5325
5529
|
}
|
|
5326
5530
|
return groupedLocations.map(function (group) {
|
|
5327
5531
|
return gff3ToAnnotationFeature(group, refSeq, featureIds);
|
|
@@ -20864,7 +21068,7 @@
|
|
|
20864
21068
|
}), 'Add');
|
|
20865
21069
|
default_1$a = Add["default"] = _default$c;
|
|
20866
21070
|
|
|
20867
|
-
var version = "0.3.
|
|
21071
|
+
var version = "0.3.3";
|
|
20868
21072
|
|
|
20869
21073
|
const ApolloConfigSchema = configuration.ConfigurationSchema('ApolloInternetAccount', {
|
|
20870
21074
|
baseURL: {
|
|
@@ -39013,17 +39217,6 @@
|
|
|
39013
39217
|
}
|
|
39014
39218
|
return _createClass(AbortError);
|
|
39015
39219
|
}( /*#__PURE__*/_wrapNativeSuper(Error));
|
|
39016
|
-
/**
|
|
39017
|
-
* properly check if the given AbortSignal is aborted.
|
|
39018
|
-
* per the standard, if the signal reads as aborted,
|
|
39019
|
-
* this function throws either a DOMException AbortError, or a regular error
|
|
39020
|
-
* with a `code` attribute set to `ERR_ABORTED`.
|
|
39021
|
-
*
|
|
39022
|
-
* for convenience, passing `undefined` is a no-op
|
|
39023
|
-
*
|
|
39024
|
-
* @param signal -
|
|
39025
|
-
* @returns nothing
|
|
39026
|
-
*/
|
|
39027
39220
|
function checkAbortSignal(signal) {
|
|
39028
39221
|
if (!signal) {
|
|
39029
39222
|
return;
|
|
@@ -39040,11 +39233,6 @@
|
|
|
39040
39233
|
return setTimeout(resolve, ms);
|
|
39041
39234
|
});
|
|
39042
39235
|
}
|
|
39043
|
-
/**
|
|
39044
|
-
* Skips to the next tick, then runs `checkAbortSignal`.
|
|
39045
|
-
* Await this to inside an otherwise synchronous loop to
|
|
39046
|
-
* provide a place to break when an abort signal is received.
|
|
39047
|
-
*/
|
|
39048
39236
|
function abortBreakPoint(_x) {
|
|
39049
39237
|
return _abortBreakPoint.apply(this, arguments);
|
|
39050
39238
|
}
|
|
@@ -39075,26 +39263,12 @@
|
|
|
39075
39263
|
}
|
|
39076
39264
|
function observeAbortSignal(signal) {
|
|
39077
39265
|
if (!signal) {
|
|
39078
|
-
return rxjs_1.Observable
|
|
39266
|
+
return new rxjs_1.Observable();
|
|
39079
39267
|
}
|
|
39080
39268
|
return (0, rxjs_1.fromEvent)(signal, 'abort');
|
|
39081
39269
|
}
|
|
39082
|
-
/**
|
|
39083
|
-
* check if the given exception was caused by an operation being intentionally aborted
|
|
39084
|
-
* @param exception -
|
|
39085
|
-
*/
|
|
39086
39270
|
function isAbortException(exception) {
|
|
39087
|
-
return exception instanceof Error && (
|
|
39088
|
-
// DOMException
|
|
39089
|
-
exception.name === 'AbortError' ||
|
|
39090
|
-
// standard-ish non-DOM abort exception
|
|
39091
|
-
exception.code === 'ERR_ABORTED' ||
|
|
39092
|
-
// message contains aborted for bubbling through RPC
|
|
39093
|
-
// things we have seen that we want to catch here
|
|
39094
|
-
// Error: aborted
|
|
39095
|
-
// AbortError: aborted
|
|
39096
|
-
// AbortError: The user aborted a request.
|
|
39097
|
-
!!/\b(aborted|aborterror)\b/i.test(exception.message));
|
|
39271
|
+
return exception instanceof Error && (exception.name === 'AbortError' || exception.code === 'ERR_ABORTED' || !!/\b(aborted|aborterror)\b/i.test(exception.message));
|
|
39098
39272
|
}
|
|
39099
39273
|
|
|
39100
39274
|
var jsonpath$1 = {exports: {}};
|
|
@@ -46029,6 +46203,8 @@
|
|
|
46029
46203
|
/** load a OBO Graph JSON file into a database */
|
|
46030
46204
|
async function loadOboGraphJson(db) {
|
|
46031
46205
|
const startTime = Date.now();
|
|
46206
|
+
let percentProgress = 1;
|
|
46207
|
+
this.options.update?.('Parsing JSON', percentProgress);
|
|
46032
46208
|
// TODO: using file streaming along with an event-based json parser
|
|
46033
46209
|
// instead of JSON.parse and .readFile could probably make this faster
|
|
46034
46210
|
// and less memory intensive
|
|
@@ -46039,6 +46215,8 @@
|
|
|
46039
46215
|
catch {
|
|
46040
46216
|
throw new Error('Error in loading ontology');
|
|
46041
46217
|
}
|
|
46218
|
+
percentProgress += 5;
|
|
46219
|
+
this.options.update?.('Parsing JSON complete', percentProgress);
|
|
46042
46220
|
const parseTime = Date.now();
|
|
46043
46221
|
const [graph, ...additionalGraphs] = oboGraph.graphs ?? [];
|
|
46044
46222
|
if (!graph) {
|
|
@@ -46057,31 +46235,51 @@
|
|
|
46057
46235
|
const fullTextIndexPaths = getTextIndexFields
|
|
46058
46236
|
.call(this)
|
|
46059
46237
|
.map((def) => def.jsonPath);
|
|
46060
|
-
|
|
46061
|
-
|
|
46062
|
-
|
|
46063
|
-
|
|
46064
|
-
|
|
46065
|
-
|
|
46238
|
+
if (graph.nodes) {
|
|
46239
|
+
let lastProgress = Math.round(percentProgress);
|
|
46240
|
+
for (const [, node] of graph.nodes.entries()) {
|
|
46241
|
+
percentProgress += 64 * (1 / graph.nodes.length);
|
|
46242
|
+
if (Math.round(percentProgress) != lastProgress &&
|
|
46243
|
+
percentProgress < 100) {
|
|
46244
|
+
this.options.update?.('Processing nodes', percentProgress);
|
|
46245
|
+
lastProgress = Math.round(percentProgress);
|
|
46246
|
+
}
|
|
46247
|
+
if (isOntologyDBNode(node)) {
|
|
46248
|
+
await nodeStore.add({
|
|
46249
|
+
...node,
|
|
46250
|
+
fullTextWords: serializeWords(getWords(node, fullTextIndexPaths, this.prefixes)),
|
|
46251
|
+
});
|
|
46252
|
+
}
|
|
46066
46253
|
}
|
|
46067
46254
|
}
|
|
46068
46255
|
// load edges
|
|
46069
46256
|
const edgeStore = tx.objectStore('edges');
|
|
46070
|
-
|
|
46071
|
-
|
|
46072
|
-
|
|
46257
|
+
if (graph.edges) {
|
|
46258
|
+
let lastProgress = Math.round(percentProgress);
|
|
46259
|
+
for (const [, edge] of graph.edges.entries()) {
|
|
46260
|
+
percentProgress += 30 * (1 / graph.edges.length);
|
|
46261
|
+
if (Math.round(percentProgress) != lastProgress &&
|
|
46262
|
+
percentProgress < 100) {
|
|
46263
|
+
this.options.update?.('Processing edges', percentProgress);
|
|
46264
|
+
lastProgress = Math.round(percentProgress);
|
|
46265
|
+
}
|
|
46266
|
+
if (isOntologyDBEdge(edge)) {
|
|
46267
|
+
await edgeStore.add(edge);
|
|
46268
|
+
}
|
|
46073
46269
|
}
|
|
46074
46270
|
}
|
|
46075
46271
|
await tx.done;
|
|
46076
46272
|
// record some metadata about this ontology and load operation
|
|
46077
46273
|
const tx2 = db.transaction('meta', 'readwrite');
|
|
46274
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
46275
|
+
const { update, ...otherOptions } = this.options;
|
|
46078
46276
|
await tx2.objectStore('meta').add({
|
|
46079
46277
|
ontologyRecord: {
|
|
46080
46278
|
name: this.ontologyName,
|
|
46081
46279
|
version: this.ontologyVersion,
|
|
46082
46280
|
sourceLocation: this.sourceLocation,
|
|
46083
46281
|
},
|
|
46084
|
-
storeOptions:
|
|
46282
|
+
storeOptions: otherOptions,
|
|
46085
46283
|
graphMeta: graph.meta,
|
|
46086
46284
|
timestamp: String(new Date()),
|
|
46087
46285
|
schemaVersion,
|
|
@@ -46146,8 +46344,8 @@
|
|
|
46146
46344
|
this.ontologyName = name;
|
|
46147
46345
|
this.ontologyVersion = version;
|
|
46148
46346
|
this.sourceLocation = source;
|
|
46149
|
-
this.db = this.prepareDatabase();
|
|
46150
46347
|
this.options = options ?? {};
|
|
46348
|
+
this.db = this.prepareDatabase();
|
|
46151
46349
|
}
|
|
46152
46350
|
/**
|
|
46153
46351
|
* check that the configuration of this ontology appears valid. Does not
|
|
@@ -46192,9 +46390,12 @@
|
|
|
46192
46390
|
return db;
|
|
46193
46391
|
}
|
|
46194
46392
|
try {
|
|
46195
|
-
const { sourceLocation, sourceType } = this;
|
|
46393
|
+
const { options, sourceLocation, sourceType } = this;
|
|
46196
46394
|
if (sourceType === 'obo-graph-json') {
|
|
46395
|
+
options.update?.('', 0);
|
|
46396
|
+
// add more updates inside `loadOboGraphJson`
|
|
46197
46397
|
await this.loadOboGraphJson(db);
|
|
46398
|
+
options.update?.('', 100);
|
|
46198
46399
|
}
|
|
46199
46400
|
else {
|
|
46200
46401
|
throw new Error(`ontology source file ${JSON.stringify(sourceLocation)} has type ${sourceType}, which is not yet supported`);
|
|
@@ -46414,6 +46615,7 @@
|
|
|
46414
46615
|
version: 'unversioned',
|
|
46415
46616
|
source: require$$1$3.types.union(mst.LocalPathLocation, mst.UriLocation, mst.BlobLocation),
|
|
46416
46617
|
options: require$$1$3.types.frozen(),
|
|
46618
|
+
equivalentTypes: require$$1$3.types.map(require$$1$3.types.array(require$$1$3.types.string)),
|
|
46417
46619
|
})
|
|
46418
46620
|
.volatile((_self) => ({
|
|
46419
46621
|
dataStore: undefined,
|
|
@@ -46431,6 +46633,37 @@
|
|
|
46431
46633
|
this.initDataStore();
|
|
46432
46634
|
}));
|
|
46433
46635
|
},
|
|
46636
|
+
setEquivalentTypes(type, equivalentTypes) {
|
|
46637
|
+
self.equivalentTypes.set(type, equivalentTypes);
|
|
46638
|
+
},
|
|
46639
|
+
}))
|
|
46640
|
+
.actions((self) => ({
|
|
46641
|
+
loadEquivalentTypes: require$$1$3.flow(function* loadEquivalentTypes(type) {
|
|
46642
|
+
if (!self.dataStore) {
|
|
46643
|
+
return;
|
|
46644
|
+
}
|
|
46645
|
+
const terms = (yield self.dataStore.getTermsWithLabelOrSynonym(type));
|
|
46646
|
+
const equivalents = terms
|
|
46647
|
+
.map((term) => term.lbl)
|
|
46648
|
+
.filter((term) => term != undefined);
|
|
46649
|
+
self.setEquivalentTypes(type, equivalents);
|
|
46650
|
+
}),
|
|
46651
|
+
}))
|
|
46652
|
+
.views((self) => ({
|
|
46653
|
+
isTypeOf(queryType, typeOf) {
|
|
46654
|
+
if (queryType === typeOf) {
|
|
46655
|
+
return true;
|
|
46656
|
+
}
|
|
46657
|
+
if (!self.dataStore) {
|
|
46658
|
+
return false;
|
|
46659
|
+
}
|
|
46660
|
+
const equivalents = self.equivalentTypes.get(typeOf);
|
|
46661
|
+
if (!equivalents) {
|
|
46662
|
+
void self.loadEquivalentTypes(typeOf);
|
|
46663
|
+
return false;
|
|
46664
|
+
}
|
|
46665
|
+
return equivalents.includes(queryType);
|
|
46666
|
+
},
|
|
46434
46667
|
}));
|
|
46435
46668
|
const OntologyManagerType = require$$1$3.types
|
|
46436
46669
|
.model('OntologyManager', {
|
|
@@ -46884,7 +47117,7 @@
|
|
|
46884
47117
|
}
|
|
46885
47118
|
const newRefNames = [...Object.entries(refNameAliases)]
|
|
46886
47119
|
.filter(([id, refName]) => id !== refName)
|
|
46887
|
-
.map(([id, refName]) => ({ _id: id, name: refName
|
|
47120
|
+
.map(([id, refName]) => ({ _id: id, name: refName }));
|
|
46888
47121
|
setRefNames(newRefNames);
|
|
46889
47122
|
setSelectedRefSeqId(newRefNames[0]?._id || '');
|
|
46890
47123
|
}
|
|
@@ -47249,7 +47482,11 @@
|
|
|
47249
47482
|
}
|
|
47250
47483
|
const { exportID } = (await response.json());
|
|
47251
47484
|
const exportURL = new URL('export', internetAccount.baseURL);
|
|
47252
|
-
const
|
|
47485
|
+
const params = {
|
|
47486
|
+
exportID,
|
|
47487
|
+
includeFASTA: 'true',
|
|
47488
|
+
};
|
|
47489
|
+
const exportSearchParams = new URLSearchParams(params);
|
|
47253
47490
|
exportURL.search = exportSearchParams.toString();
|
|
47254
47491
|
const exportUri = exportURL.toString();
|
|
47255
47492
|
window.open(exportUri, '_blank');
|
|
@@ -48473,8 +48710,8 @@
|
|
|
48473
48710
|
// .map((m) => m.score)
|
|
48474
48711
|
// .join(', ')
|
|
48475
48712
|
return (React__namespace.createElement("li", { ...other },
|
|
48476
|
-
React__namespace.createElement(material.
|
|
48477
|
-
React__namespace.createElement(material.
|
|
48713
|
+
React__namespace.createElement(material.Grid2, { container: true },
|
|
48714
|
+
React__namespace.createElement(material.Grid2, null,
|
|
48478
48715
|
React__namespace.createElement(material.Typography, { component: "span" }, ontologyManager.applyPrefixes(option.term.id)),
|
|
48479
48716
|
' ',
|
|
48480
48717
|
React__namespace.createElement(HighlightedText, { str: option.term.lbl ?? '(no label)', search: inputValue }),
|
|
@@ -48675,43 +48912,43 @@
|
|
|
48675
48912
|
return (React__default["default"].createElement(Dialog, { open: true, title: "Feature attributes", handleClose: handleClose, maxWidth: false, "data-testid": "modify-feature-attribute" },
|
|
48676
48913
|
React__default["default"].createElement("form", { onSubmit: onSubmit },
|
|
48677
48914
|
React__default["default"].createElement(material.DialogContent, null,
|
|
48678
|
-
React__default["default"].createElement(material.
|
|
48915
|
+
React__default["default"].createElement(material.Grid2, { container: true, direction: "column", spacing: 1 },
|
|
48679
48916
|
Object.entries(attributes).map(([key, value]) => {
|
|
48680
48917
|
const EditorComponent = reservedKeys$1.get(key) ?? CustomAttributeValueEditor$1;
|
|
48681
|
-
return (React__default["default"].createElement(material.
|
|
48682
|
-
React__default["default"].createElement(material.
|
|
48918
|
+
return (React__default["default"].createElement(material.Grid2, { container: true, spacing: 3, alignItems: "center", key: key },
|
|
48919
|
+
React__default["default"].createElement(material.Grid2, null,
|
|
48683
48920
|
React__default["default"].createElement(material.Paper, { variant: "outlined", className: classes.attributeName },
|
|
48684
48921
|
React__default["default"].createElement(material.Typography, null, key))),
|
|
48685
|
-
React__default["default"].createElement(material.
|
|
48922
|
+
React__default["default"].createElement(material.Grid2, { flexGrow: 1 },
|
|
48686
48923
|
React__default["default"].createElement(EditorComponent, { session: session, value: value, onChange: makeOnChange(key) })),
|
|
48687
|
-
React__default["default"].createElement(material.
|
|
48924
|
+
React__default["default"].createElement(material.Grid2, null,
|
|
48688
48925
|
React__default["default"].createElement(material.IconButton, { "aria-label": "delete", size: "medium", disabled: !editable, onClick: () => {
|
|
48689
48926
|
deleteAttribute(key);
|
|
48690
48927
|
} },
|
|
48691
48928
|
React__default["default"].createElement(default_1$7, { fontSize: "medium", key: key })))));
|
|
48692
48929
|
}),
|
|
48693
|
-
React__default["default"].createElement(material.
|
|
48930
|
+
React__default["default"].createElement(material.Grid2, null,
|
|
48694
48931
|
React__default["default"].createElement(material.Button, { color: "primary", variant: "contained", disabled: showAddNewForm || !editable, onClick: () => {
|
|
48695
48932
|
setShowAddNewForm(true);
|
|
48696
48933
|
} }, "Add new")),
|
|
48697
|
-
showAddNewForm ? (React__default["default"].createElement(material.
|
|
48934
|
+
showAddNewForm ? (React__default["default"].createElement(material.Grid2, null,
|
|
48698
48935
|
React__default["default"].createElement(material.Paper, { elevation: 8, className: classes.newAttributePaper },
|
|
48699
|
-
React__default["default"].createElement(material.
|
|
48700
|
-
React__default["default"].createElement(material.
|
|
48936
|
+
React__default["default"].createElement(material.Grid2, { container: true, direction: "column" },
|
|
48937
|
+
React__default["default"].createElement(material.Grid2, null,
|
|
48701
48938
|
React__default["default"].createElement(material.FormControl, null,
|
|
48702
48939
|
React__default["default"].createElement(material.FormLabel, { id: "attribute-radio-button-group" }, "Select attribute type"),
|
|
48703
48940
|
React__default["default"].createElement(material.RadioGroup, { "aria-labelledby": "demo-radio-buttons-group-label", defaultValue: "custom", name: "radio-buttons-group", onChange: handleRadioButtonChange },
|
|
48704
|
-
React__default["default"].createElement(material.FormControlLabel, { value: "custom", control: React__default["default"].createElement(material.Radio, null), disableTypography: true, label: React__default["default"].createElement(material.
|
|
48705
|
-
React__default["default"].createElement(material.
|
|
48941
|
+
React__default["default"].createElement(material.FormControlLabel, { value: "custom", control: React__default["default"].createElement(material.Radio, null), disableTypography: true, label: React__default["default"].createElement(material.Grid2, { container: true, spacing: 1, alignItems: "center" },
|
|
48942
|
+
React__default["default"].createElement(material.Grid2, null,
|
|
48706
48943
|
React__default["default"].createElement(material.Typography, null, "Custom")),
|
|
48707
|
-
React__default["default"].createElement(material.
|
|
48944
|
+
React__default["default"].createElement(material.Grid2, null,
|
|
48708
48945
|
React__default["default"].createElement(material.TextField, { label: "Custom attribute key", variant: "outlined", value: reservedKeys$1.has(newAttributeKey)
|
|
48709
48946
|
? ''
|
|
48710
48947
|
: newAttributeKey, disabled: reservedKeys$1.has(newAttributeKey), onChange: (event) => {
|
|
48711
48948
|
setNewAttributeKey(event.target.value);
|
|
48712
48949
|
} }))) }),
|
|
48713
48950
|
[...reservedKeys$1.keys()].map((key) => (React__default["default"].createElement(material.FormControlLabel, { key: key, value: key, control: React__default["default"].createElement(material.Radio, null), label: key })))))),
|
|
48714
|
-
React__default["default"].createElement(material.
|
|
48951
|
+
React__default["default"].createElement(material.Grid2, null,
|
|
48715
48952
|
React__default["default"].createElement(material.DialogActions, null,
|
|
48716
48953
|
React__default["default"].createElement(material.Button, { key: "addButton", color: "primary", variant: "contained", style: { margin: 2 }, onClick: handleAddNewAttributeChange, disabled: !newAttributeKey }, "Add"),
|
|
48717
48954
|
React__default["default"].createElement(material.Button, { key: "cancelAddButton", variant: "outlined", type: "submit", onClick: () => {
|
|
@@ -49099,13 +49336,12 @@
|
|
|
49099
49336
|
};
|
|
49100
49337
|
return (React__default["default"].createElement(Dialog, { open: true, title: "Add reference sequence aliases", handleClose: handleClose, maxWidth: 'sm', "data-testid": "add-refseq-alias", fullWidth: true },
|
|
49101
49338
|
React__default["default"].createElement(material.DialogContent, { style: { display: 'flex', flexDirection: 'column' } },
|
|
49102
|
-
React__default["default"].createElement(material.
|
|
49103
|
-
React__default["default"].createElement(material.
|
|
49339
|
+
React__default["default"].createElement(material.Grid2, { container: true, spacing: 2 },
|
|
49340
|
+
React__default["default"].createElement(material.Grid2, null,
|
|
49104
49341
|
React__default["default"].createElement(material.FormControl, { disabled: enableSubmit && !errorMessage, fullWidth: true },
|
|
49105
49342
|
React__default["default"].createElement(material.InputLabel, { id: "demo-simple-select-label" }, "Assembly"),
|
|
49106
49343
|
React__default["default"].createElement(material.Select, { labelId: "demo-simple-select-label", id: "demo-simple-select", label: "Assembly", value: selectedAssembly?.name ?? '', onChange: handleChangeAssembly }, assemblies.map((option) => (React__default["default"].createElement(material.MenuItem, { key: option.name, value: option.name }, option.displayName ?? option.name)))))),
|
|
49107
|
-
React__default["default"].createElement(material.
|
|
49108
|
-
React__default["default"].createElement(material.Grid, { item: true, xs: 7 },
|
|
49344
|
+
React__default["default"].createElement(material.Grid2, null,
|
|
49109
49345
|
React__default["default"].createElement(material.InputLabel, null, "Load RefName alias"),
|
|
49110
49346
|
React__default["default"].createElement("input", { type: "file", onChange: handleChangeFileHandler, ref: fileRef, disabled: (enableSubmit && !errorMessage) || !selectedAssembly }))),
|
|
49111
49347
|
selectedAssembly && refNameAliasMap.size > 0 ? (React__default["default"].createElement("div", { style: { height: 200, width: '100%', marginTop: 20 } },
|
|
@@ -49796,16 +50032,7 @@
|
|
|
49796
50032
|
function isSimpleFeatureSerialized(args) {
|
|
49797
50033
|
return 'uniqueId' in args && _typeof(args.data) !== 'object';
|
|
49798
50034
|
}
|
|
49799
|
-
/**
|
|
49800
|
-
* Simple implementation of a feature object.
|
|
49801
|
-
*/
|
|
49802
50035
|
var SimpleFeature = /*#__PURE__*/function () {
|
|
49803
|
-
/**
|
|
49804
|
-
* @param args - SimpleFeature args
|
|
49805
|
-
*
|
|
49806
|
-
* Note: args.data.subfeatures can be an array of these same args,
|
|
49807
|
-
* which will be inflated to more instances of this class.
|
|
49808
|
-
*/
|
|
49809
50036
|
function SimpleFeature(args) {
|
|
49810
50037
|
var _this = this;
|
|
49811
50038
|
_classCallCheck(this, SimpleFeature);
|
|
@@ -49813,14 +50040,9 @@
|
|
|
49813
50040
|
if (isSimpleFeatureSerialized(args)) {
|
|
49814
50041
|
this.data = args;
|
|
49815
50042
|
} else {
|
|
49816
|
-
this.data = args.data
|
|
49817
|
-
// load handle from args.parent (not args.data.parent)
|
|
49818
|
-
// this reason is because if args is an object, it likely isn't properly loaded with
|
|
49819
|
-
// parent as a Feature reference (probably a raw parent ID or something instead)
|
|
50043
|
+
this.data = args.data;
|
|
49820
50044
|
this.parentHandle = args.parent;
|
|
49821
50045
|
}
|
|
49822
|
-
// the feature id comes from
|
|
49823
|
-
// args.id, args.data.uniqueId, or args.uniqueId due to this initialization
|
|
49824
50046
|
var id = isSimpleFeatureSerialized(args) ? args.uniqueId : args.id;
|
|
49825
50047
|
if (id === undefined || id === null) {
|
|
49826
50048
|
throw new Error('SimpleFeature requires a unique `id` or `data.uniqueId` attribute');
|
|
@@ -49830,9 +50052,7 @@
|
|
|
49830
50052
|
throw new Error("invalid feature data, end less than start. end: ".concat(this.data.end, " start: ").concat(this.data.start));
|
|
49831
50053
|
}
|
|
49832
50054
|
if (this.data.subfeatures) {
|
|
49833
|
-
this.subfeatures = (_a = this.data.subfeatures) === null || _a === void 0 ? void 0 : _a.map(
|
|
49834
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
49835
|
-
function (f, i) {
|
|
50055
|
+
this.subfeatures = (_a = this.data.subfeatures) === null || _a === void 0 ? void 0 : _a.map(function (f, i) {
|
|
49836
50056
|
return typeof f.get !== 'function' ? new SimpleFeature({
|
|
49837
50057
|
id: f.uniqueId || "".concat(id, "-").concat(i),
|
|
49838
50058
|
data: _objectSpread2({
|
|
@@ -49843,52 +50063,31 @@
|
|
|
49843
50063
|
});
|
|
49844
50064
|
}
|
|
49845
50065
|
}
|
|
49846
|
-
/**
|
|
49847
|
-
* Get a piece of data about the feature. All features must have
|
|
49848
|
-
* 'start' and 'end', but everything else is optional.
|
|
49849
|
-
*/
|
|
49850
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
49851
50066
|
_createClass(SimpleFeature, [{
|
|
49852
50067
|
key: "get",
|
|
49853
50068
|
value: function get(name) {
|
|
49854
50069
|
return name === 'subfeatures' ? this.subfeatures : name === 'parent' ? this.parent() : this.data[name];
|
|
49855
50070
|
}
|
|
49856
|
-
/**
|
|
49857
|
-
* Set an item of data.
|
|
49858
|
-
*/
|
|
49859
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
49860
50071
|
}, {
|
|
49861
50072
|
key: "set",
|
|
49862
50073
|
value: function set(name, val) {
|
|
49863
50074
|
this.data[name] = val;
|
|
49864
50075
|
}
|
|
49865
|
-
/**
|
|
49866
|
-
* Get an array listing which data keys are present in this feature.
|
|
49867
|
-
*/
|
|
49868
50076
|
}, {
|
|
49869
50077
|
key: "tags",
|
|
49870
50078
|
value: function tags() {
|
|
49871
50079
|
return Object.keys(this.data);
|
|
49872
50080
|
}
|
|
49873
|
-
/**
|
|
49874
|
-
* Get the unique ID of this feature.
|
|
49875
|
-
*/
|
|
49876
50081
|
}, {
|
|
49877
50082
|
key: "id",
|
|
49878
50083
|
value: function id() {
|
|
49879
50084
|
return this.uniqueId;
|
|
49880
50085
|
}
|
|
49881
|
-
/**
|
|
49882
|
-
* Get this feature's parent feature, or undefined if none.
|
|
49883
|
-
*/
|
|
49884
50086
|
}, {
|
|
49885
50087
|
key: "parent",
|
|
49886
50088
|
value: function parent() {
|
|
49887
50089
|
return this.parentHandle;
|
|
49888
50090
|
}
|
|
49889
|
-
/**
|
|
49890
|
-
* Get an array of child features, or undefined if none.
|
|
49891
|
-
*/
|
|
49892
50091
|
}, {
|
|
49893
50092
|
key: "children",
|
|
49894
50093
|
value: function children() {
|
|
@@ -49932,11 +50131,11 @@
|
|
|
49932
50131
|
const isInWebWorker$1 = typeof sessionStorage === 'undefined';
|
|
49933
50132
|
class ApolloSequenceAdapter extends BaseAdapter.BaseSequenceAdapter {
|
|
49934
50133
|
regions;
|
|
49935
|
-
async getRefNames(
|
|
49936
|
-
const regions = await this.getRegions(
|
|
50134
|
+
async getRefNames() {
|
|
50135
|
+
const regions = await this.getRegions();
|
|
49937
50136
|
return regions.map((regions) => regions.refName);
|
|
49938
50137
|
}
|
|
49939
|
-
async getRegions(
|
|
50138
|
+
async getRegions() {
|
|
49940
50139
|
if (this.regions) {
|
|
49941
50140
|
return this.regions;
|
|
49942
50141
|
}
|
|
@@ -49968,7 +50167,7 @@
|
|
|
49968
50167
|
removeEventListener('message', messageListener);
|
|
49969
50168
|
resolve(data.regions);
|
|
49970
50169
|
};
|
|
49971
|
-
addEventListener('message', messageListener
|
|
50170
|
+
addEventListener('message', messageListener);
|
|
49972
50171
|
// @ts-expect-error waiting for types to be published
|
|
49973
50172
|
globalThis.rpcServer.emit('apollo', {
|
|
49974
50173
|
apollo: true,
|
|
@@ -49985,7 +50184,7 @@
|
|
|
49985
50184
|
* @param param -
|
|
49986
50185
|
* @returns Observable of Feature objects in the region
|
|
49987
50186
|
*/
|
|
49988
|
-
getFeatures(region
|
|
50187
|
+
getFeatures(region) {
|
|
49989
50188
|
const { end, refName, start } = region;
|
|
49990
50189
|
const assemblyId = configuration.readConfObject(this.config, 'assemblyId');
|
|
49991
50190
|
const regionWithAssemblyName = { ...region, assemblyName: assemblyId };
|
|
@@ -50022,7 +50221,7 @@
|
|
|
50022
50221
|
removeEventListener('message', messageListener);
|
|
50023
50222
|
resolve(data.sequence);
|
|
50024
50223
|
};
|
|
50025
|
-
addEventListener('message', messageListener
|
|
50224
|
+
addEventListener('message', messageListener);
|
|
50026
50225
|
// @ts-expect-error waiting for types to be published
|
|
50027
50226
|
globalThis.rpcServer.emit('apollo', {
|
|
50028
50227
|
apollo: true,
|
|
@@ -50819,7 +51018,7 @@
|
|
|
50819
51018
|
.filter(([id, refName]) => id !== refName)
|
|
50820
51019
|
.map(([id, refName]) => ({
|
|
50821
51020
|
_id: id,
|
|
50822
|
-
name: refName
|
|
51021
|
+
name: refName,
|
|
50823
51022
|
}));
|
|
50824
51023
|
const refSeqId = newRefNames.find((item) => item.name === refName)?._id;
|
|
50825
51024
|
if (!refSeqId) {
|
|
@@ -50955,6 +51154,285 @@
|
|
|
50955
51154
|
return pluggableElement;
|
|
50956
51155
|
}
|
|
50957
51156
|
|
|
51157
|
+
/* eslint-disable react-hooks/exhaustive-deps */
|
|
51158
|
+
const isGeneOrTranscript = (annotationFeature, apolloSessionModel) => {
|
|
51159
|
+
const { featureTypeOntology } = apolloSessionModel.apolloDataStore.ontologyManager;
|
|
51160
|
+
if (!featureTypeOntology) {
|
|
51161
|
+
throw new Error('featureTypeOntology is undefined');
|
|
51162
|
+
}
|
|
51163
|
+
return (featureTypeOntology.isTypeOf(annotationFeature.type, 'gene') ||
|
|
51164
|
+
featureTypeOntology.isTypeOf(annotationFeature.type, 'mRNA') ||
|
|
51165
|
+
featureTypeOntology.isTypeOf(annotationFeature.type, 'transcript'));
|
|
51166
|
+
};
|
|
51167
|
+
const isTranscript = (annotationFeature, apolloSessionModel) => {
|
|
51168
|
+
const { featureTypeOntology } = apolloSessionModel.apolloDataStore.ontologyManager;
|
|
51169
|
+
if (!featureTypeOntology) {
|
|
51170
|
+
throw new Error('featureTypeOntology is undefined');
|
|
51171
|
+
}
|
|
51172
|
+
return (featureTypeOntology.isTypeOf(annotationFeature.type, 'mRNA') ||
|
|
51173
|
+
featureTypeOntology.isTypeOf(annotationFeature.type, 'transcript'));
|
|
51174
|
+
};
|
|
51175
|
+
function CreateApolloAnnotation({ annotationFeature, assembly, handleClose, refSeqId, session, }) {
|
|
51176
|
+
const apolloSessionModel = session;
|
|
51177
|
+
const childIds = React.useMemo(() => Object.keys(annotationFeature.children ?? {}), [annotationFeature]);
|
|
51178
|
+
const features = React.useMemo(() => {
|
|
51179
|
+
for (const [, asm] of apolloSessionModel.apolloDataStore.assemblies) {
|
|
51180
|
+
if (asm._id === assembly.name) {
|
|
51181
|
+
for (const [, refSeq] of asm.refSeqs) {
|
|
51182
|
+
if (refSeq._id === refSeqId) {
|
|
51183
|
+
return refSeq.features;
|
|
51184
|
+
}
|
|
51185
|
+
}
|
|
51186
|
+
}
|
|
51187
|
+
}
|
|
51188
|
+
return [];
|
|
51189
|
+
}, []);
|
|
51190
|
+
const [parentFeatureChecked, setParentFeatureChecked] = React.useState(true);
|
|
51191
|
+
const [checkedChildrens, setCheckedChildrens] = React.useState(childIds);
|
|
51192
|
+
const [errorMessage, setErrorMessage] = React.useState('');
|
|
51193
|
+
const [destinationFeatures, setDestinationFeatures] = React.useState([]);
|
|
51194
|
+
const [selectedDestinationFeature, setSelectedDestinationFeature] = React.useState();
|
|
51195
|
+
const getFeatures = (min, max) => {
|
|
51196
|
+
const filteredFeatures = [];
|
|
51197
|
+
for (const [, f] of features) {
|
|
51198
|
+
const featureSnapshot = require$$1$3.getSnapshot(f);
|
|
51199
|
+
if (min >= featureSnapshot.min && max <= featureSnapshot.max) {
|
|
51200
|
+
filteredFeatures.push(featureSnapshot);
|
|
51201
|
+
}
|
|
51202
|
+
}
|
|
51203
|
+
return filteredFeatures;
|
|
51204
|
+
};
|
|
51205
|
+
React.useEffect(() => {
|
|
51206
|
+
setErrorMessage('');
|
|
51207
|
+
if (checkedChildrens.length === 0) {
|
|
51208
|
+
setParentFeatureChecked(false);
|
|
51209
|
+
return;
|
|
51210
|
+
}
|
|
51211
|
+
if (annotationFeature.children) {
|
|
51212
|
+
const checkedAnnotationFeatureChildren = Object.values(annotationFeature.children)
|
|
51213
|
+
.filter((child) => isTranscript(child, apolloSessionModel))
|
|
51214
|
+
.filter((child) => checkedChildrens.includes(child._id));
|
|
51215
|
+
const mins = checkedAnnotationFeatureChildren.map((f) => f.min);
|
|
51216
|
+
const maxes = checkedAnnotationFeatureChildren.map((f) => f.max);
|
|
51217
|
+
const min = Math.min(...mins);
|
|
51218
|
+
const max = Math.max(...maxes);
|
|
51219
|
+
const filteredFeatures = getFeatures(min, max);
|
|
51220
|
+
setDestinationFeatures(filteredFeatures);
|
|
51221
|
+
if (filteredFeatures.length === 0 &&
|
|
51222
|
+
checkedChildrens.length > 0 &&
|
|
51223
|
+
!parentFeatureChecked) {
|
|
51224
|
+
setErrorMessage('No destination features found');
|
|
51225
|
+
}
|
|
51226
|
+
}
|
|
51227
|
+
}, [checkedChildrens]);
|
|
51228
|
+
const handleParentFeatureCheck = (event) => {
|
|
51229
|
+
const isChecked = event.target.checked;
|
|
51230
|
+
setParentFeatureChecked(isChecked);
|
|
51231
|
+
setCheckedChildrens(isChecked ? childIds : []);
|
|
51232
|
+
};
|
|
51233
|
+
const handleChildFeatureCheck = (event, child) => {
|
|
51234
|
+
setCheckedChildrens((prevChecked) => event.target.checked
|
|
51235
|
+
? [...prevChecked, child._id]
|
|
51236
|
+
: prevChecked.filter((childId) => childId !== child._id));
|
|
51237
|
+
};
|
|
51238
|
+
const handleDestinationFeatureChange = (e) => {
|
|
51239
|
+
const selectedFeature = destinationFeatures.find((f) => f._id === e.target.value);
|
|
51240
|
+
setSelectedDestinationFeature(selectedFeature);
|
|
51241
|
+
};
|
|
51242
|
+
const handleCreateApolloAnnotation = async () => {
|
|
51243
|
+
if (parentFeatureChecked) {
|
|
51244
|
+
const change = new dist$2.AddFeatureChange({
|
|
51245
|
+
changedIds: [annotationFeature._id],
|
|
51246
|
+
typeName: 'AddFeatureChange',
|
|
51247
|
+
assembly: assembly.name,
|
|
51248
|
+
addedFeature: annotationFeature,
|
|
51249
|
+
});
|
|
51250
|
+
await apolloSessionModel.apolloDataStore.changeManager.submit(change);
|
|
51251
|
+
session.notify('Annotation added successfully', 'success');
|
|
51252
|
+
handleClose();
|
|
51253
|
+
}
|
|
51254
|
+
else {
|
|
51255
|
+
if (!annotationFeature.children) {
|
|
51256
|
+
return;
|
|
51257
|
+
}
|
|
51258
|
+
if (!selectedDestinationFeature) {
|
|
51259
|
+
return;
|
|
51260
|
+
}
|
|
51261
|
+
for (const childId of checkedChildrens) {
|
|
51262
|
+
const child = annotationFeature.children[childId];
|
|
51263
|
+
const change = new dist$2.AddFeatureChange({
|
|
51264
|
+
parentFeatureId: selectedDestinationFeature._id,
|
|
51265
|
+
changedIds: [selectedDestinationFeature._id],
|
|
51266
|
+
typeName: 'AddFeatureChange',
|
|
51267
|
+
assembly: assembly.name,
|
|
51268
|
+
addedFeature: child,
|
|
51269
|
+
});
|
|
51270
|
+
await apolloSessionModel.apolloDataStore.changeManager.submit(change);
|
|
51271
|
+
session.notify('Annotation added successfully', 'success');
|
|
51272
|
+
handleClose();
|
|
51273
|
+
}
|
|
51274
|
+
}
|
|
51275
|
+
};
|
|
51276
|
+
return (React__default["default"].createElement(Dialog, { open: true, title: "Create Apollo Annotation", handleClose: handleClose, fullWidth: true, maxWidth: "sm" },
|
|
51277
|
+
React__default["default"].createElement(material.DialogTitle, { fontSize: 15 }, "Select the feature to be copied to apollo track"),
|
|
51278
|
+
React__default["default"].createElement(material.DialogContent, null,
|
|
51279
|
+
React__default["default"].createElement(material.Box, { sx: { ml: 3 } },
|
|
51280
|
+
isGeneOrTranscript(annotationFeature, apolloSessionModel) && (React__default["default"].createElement(material.FormControlLabel, { control: React__default["default"].createElement(material.Checkbox, { size: "small", checked: parentFeatureChecked, onChange: handleParentFeatureCheck }), label: `${annotationFeature.type}:${annotationFeature.min}..${annotationFeature.max}` })),
|
|
51281
|
+
annotationFeature.children && (React__default["default"].createElement(material.Box, { sx: { display: 'flex', flexDirection: 'column', ml: 3 } }, Object.values(annotationFeature.children)
|
|
51282
|
+
.filter((child) => isTranscript(child, apolloSessionModel))
|
|
51283
|
+
.map((child) => (React__default["default"].createElement(material.FormControlLabel, { key: child._id, control: React__default["default"].createElement(material.Checkbox, { size: "small", checked: checkedChildrens.includes(child._id), onChange: (e) => {
|
|
51284
|
+
handleChildFeatureCheck(e, child);
|
|
51285
|
+
} }), label: `${child.type}:${child.min}..${child.max}` })))))),
|
|
51286
|
+
!parentFeatureChecked &&
|
|
51287
|
+
checkedChildrens.length > 0 &&
|
|
51288
|
+
destinationFeatures.length > 0 && (React__default["default"].createElement(material.Box, { sx: { ml: 3 } },
|
|
51289
|
+
React__default["default"].createElement(material.Typography, { variant: "caption", fontSize: 12 }, "Select the destination feature to copy the selected features"),
|
|
51290
|
+
React__default["default"].createElement(material.Box, { sx: { mt: 1 } },
|
|
51291
|
+
React__default["default"].createElement(material.Select, { labelId: "label", style: { width: '100%' }, value: selectedDestinationFeature?._id ?? '', onChange: handleDestinationFeatureChange }, destinationFeatures.map((f) => (React__default["default"].createElement(material.MenuItem, { key: f._id, value: f._id }, `${f.type}:${f.min}..${f.max}`)))))))),
|
|
51292
|
+
React__default["default"].createElement(material.DialogActions, null,
|
|
51293
|
+
React__default["default"].createElement(material.Button, { variant: "contained", type: "submit", disabled: checkedChildrens.length === 0 ||
|
|
51294
|
+
(!parentFeatureChecked &&
|
|
51295
|
+
checkedChildrens.length > 0 &&
|
|
51296
|
+
!selectedDestinationFeature), onClick: handleCreateApolloAnnotation }, "Create"),
|
|
51297
|
+
React__default["default"].createElement(material.Button, { variant: "outlined", type: "submit", onClick: handleClose }, "Cancel")),
|
|
51298
|
+
errorMessage ? (React__default["default"].createElement(material.DialogContent, null,
|
|
51299
|
+
React__default["default"].createElement(material.DialogContentText, { color: "error" }, errorMessage))) : null));
|
|
51300
|
+
}
|
|
51301
|
+
|
|
51302
|
+
function simpleFeatureToGFF3Feature(feature, refSeqId) {
|
|
51303
|
+
const xfeature = JSON.parse(JSON.stringify(feature));
|
|
51304
|
+
const children = xfeature.subfeatures;
|
|
51305
|
+
const gff3Feature = [
|
|
51306
|
+
{
|
|
51307
|
+
start: xfeature.start + 1,
|
|
51308
|
+
end: xfeature.end,
|
|
51309
|
+
seq_id: refSeqId,
|
|
51310
|
+
source: xfeature.source ?? null,
|
|
51311
|
+
type: xfeature.type ?? null,
|
|
51312
|
+
score: xfeature.score ?? null,
|
|
51313
|
+
strand: xfeature.strand ? (xfeature.strand === 1 ? '+' : '-') : null,
|
|
51314
|
+
phase: xfeature.phase !== null || xfeature.phase !== undefined
|
|
51315
|
+
? xfeature.phase
|
|
51316
|
+
: null,
|
|
51317
|
+
attributes: convertFeatureAttributes(xfeature),
|
|
51318
|
+
derived_features: [],
|
|
51319
|
+
child_features: children
|
|
51320
|
+
? children.map((x) => simpleFeatureToGFF3Feature(x, refSeqId))
|
|
51321
|
+
: [],
|
|
51322
|
+
},
|
|
51323
|
+
];
|
|
51324
|
+
return gff3Feature;
|
|
51325
|
+
}
|
|
51326
|
+
function jbrowseFeatureToAnnotationFeature(feature, refSeqId) {
|
|
51327
|
+
return dist$2.gff3ToAnnotationFeature(simpleFeatureToGFF3Feature(feature, refSeqId));
|
|
51328
|
+
}
|
|
51329
|
+
function convertFeatureAttributes(feature) {
|
|
51330
|
+
const attributes = {};
|
|
51331
|
+
const defaultFields = new Set([
|
|
51332
|
+
'start',
|
|
51333
|
+
'end',
|
|
51334
|
+
'type',
|
|
51335
|
+
'strand',
|
|
51336
|
+
'refName',
|
|
51337
|
+
'subfeatures',
|
|
51338
|
+
'derived_features',
|
|
51339
|
+
'phase',
|
|
51340
|
+
'source',
|
|
51341
|
+
'score',
|
|
51342
|
+
]);
|
|
51343
|
+
for (const [key, value] of Object.entries(feature)) {
|
|
51344
|
+
if (defaultFields.has(key)) {
|
|
51345
|
+
continue;
|
|
51346
|
+
}
|
|
51347
|
+
attributes[key] = Array.isArray(value) ? value.map(String) : [String(value)];
|
|
51348
|
+
}
|
|
51349
|
+
return attributes;
|
|
51350
|
+
}
|
|
51351
|
+
function annotationFromJBrowseFeature(pluggableElement) {
|
|
51352
|
+
if (pluggableElement.name !== 'LinearBasicDisplay') {
|
|
51353
|
+
return pluggableElement;
|
|
51354
|
+
}
|
|
51355
|
+
const { stateModel } = pluggableElement;
|
|
51356
|
+
const newStateModel = stateModel
|
|
51357
|
+
.views((self) => ({
|
|
51358
|
+
getFirstRegion() {
|
|
51359
|
+
const lgv = require$$1$2.getContainingView(self);
|
|
51360
|
+
return lgv.dynamicBlocks.contentBlocks[0];
|
|
51361
|
+
},
|
|
51362
|
+
getAssembly() {
|
|
51363
|
+
const firstRegion = self.getFirstRegion();
|
|
51364
|
+
const session = require$$1$2.getSession(self);
|
|
51365
|
+
const { assemblyManager } = session;
|
|
51366
|
+
const { assemblyName } = firstRegion;
|
|
51367
|
+
const assembly = assemblyManager.get(assemblyName);
|
|
51368
|
+
if (!assembly) {
|
|
51369
|
+
throw new Error(`Could not find assembly named ${assemblyName}`);
|
|
51370
|
+
}
|
|
51371
|
+
return assembly;
|
|
51372
|
+
},
|
|
51373
|
+
getRefSeqId(assembly) {
|
|
51374
|
+
const firstRegion = self.getFirstRegion();
|
|
51375
|
+
const { refName } = firstRegion;
|
|
51376
|
+
const { refNameAliases } = assembly;
|
|
51377
|
+
if (!refNameAliases) {
|
|
51378
|
+
throw new Error(`Could not find aliases for ${assembly.name}`);
|
|
51379
|
+
}
|
|
51380
|
+
const newRefNames = [...Object.entries(refNameAliases)]
|
|
51381
|
+
.filter(([id, refName]) => id !== refName)
|
|
51382
|
+
.map(([id, refName]) => ({
|
|
51383
|
+
_id: id,
|
|
51384
|
+
name: refName,
|
|
51385
|
+
}));
|
|
51386
|
+
const refSeqId = newRefNames.find((item) => item.name === refName)?._id;
|
|
51387
|
+
if (!refSeqId) {
|
|
51388
|
+
throw new Error(`Could not find refSeqId named ${refName}`);
|
|
51389
|
+
}
|
|
51390
|
+
return refSeqId;
|
|
51391
|
+
},
|
|
51392
|
+
getAnnotationFeature(assembly) {
|
|
51393
|
+
const refSeqId = self.getRefSeqId(assembly);
|
|
51394
|
+
const sfeature = self.contextMenuFeature.data;
|
|
51395
|
+
return jbrowseFeatureToAnnotationFeature(sfeature, refSeqId);
|
|
51396
|
+
},
|
|
51397
|
+
}))
|
|
51398
|
+
.views((self) => {
|
|
51399
|
+
const superContextMenuItems = self.contextMenuItems;
|
|
51400
|
+
const session = require$$1$2.getSession(self);
|
|
51401
|
+
const assembly = self.getAssembly();
|
|
51402
|
+
return {
|
|
51403
|
+
contextMenuItems() {
|
|
51404
|
+
const feature = self.contextMenuFeature;
|
|
51405
|
+
if (!feature) {
|
|
51406
|
+
return superContextMenuItems();
|
|
51407
|
+
}
|
|
51408
|
+
return [
|
|
51409
|
+
...superContextMenuItems(),
|
|
51410
|
+
{
|
|
51411
|
+
label: 'Create Apollo annotation',
|
|
51412
|
+
icon: default_1$a,
|
|
51413
|
+
onClick: () => {
|
|
51414
|
+
session.queueDialog((doneCallback) => [
|
|
51415
|
+
CreateApolloAnnotation,
|
|
51416
|
+
{
|
|
51417
|
+
session,
|
|
51418
|
+
handleClose: () => {
|
|
51419
|
+
doneCallback();
|
|
51420
|
+
},
|
|
51421
|
+
annotationFeature: self.getAnnotationFeature(assembly),
|
|
51422
|
+
assembly,
|
|
51423
|
+
refSeqId: self.getRefSeqId(assembly),
|
|
51424
|
+
},
|
|
51425
|
+
]);
|
|
51426
|
+
},
|
|
51427
|
+
},
|
|
51428
|
+
];
|
|
51429
|
+
},
|
|
51430
|
+
};
|
|
51431
|
+
});
|
|
51432
|
+
pluggableElement.stateModel = newStateModel;
|
|
51433
|
+
return pluggableElement;
|
|
51434
|
+
}
|
|
51435
|
+
|
|
50958
51436
|
/* eslint-disable @typescript-eslint/unbound-method */
|
|
50959
51437
|
const StringTextField = mobxReact.observer(function StringTextField({ onChangeCommitted, value: initialValue, ...props }) {
|
|
50960
51438
|
const [value, setValue] = React.useState(String(initialValue));
|
|
@@ -51161,44 +51639,44 @@
|
|
|
51161
51639
|
}
|
|
51162
51640
|
return (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
51163
51641
|
React__default["default"].createElement(material.Typography, { variant: "h5" }, "Attributes"),
|
|
51164
|
-
React__default["default"].createElement(material.
|
|
51642
|
+
React__default["default"].createElement(material.Grid2, { container: true, direction: "column", spacing: 1 },
|
|
51165
51643
|
Object.entries(attributes).map(([key, value]) => {
|
|
51166
51644
|
if (key === '') {
|
|
51167
51645
|
return null;
|
|
51168
51646
|
}
|
|
51169
51647
|
const EditorComponent = reservedKeys.get(key) ?? CustomAttributeValueEditor;
|
|
51170
|
-
return (React__default["default"].createElement(material.
|
|
51171
|
-
React__default["default"].createElement(material.
|
|
51648
|
+
return (React__default["default"].createElement(material.Grid2, { container: true, spacing: 3, alignItems: "center", key: key },
|
|
51649
|
+
React__default["default"].createElement(material.Grid2, null,
|
|
51172
51650
|
React__default["default"].createElement(material.Paper, { variant: "outlined", className: classes.attributeName },
|
|
51173
51651
|
React__default["default"].createElement(material.Typography, null, key))),
|
|
51174
|
-
React__default["default"].createElement(material.
|
|
51652
|
+
React__default["default"].createElement(material.Grid2, { flexGrow: 1 },
|
|
51175
51653
|
React__default["default"].createElement(EditorComponent, { session: session, value: value, onChange: (newValue) => onChangeCommitted(key, newValue) })),
|
|
51176
|
-
React__default["default"].createElement(material.
|
|
51654
|
+
React__default["default"].createElement(material.Grid2, null,
|
|
51177
51655
|
React__default["default"].createElement(material.IconButton, { "aria-label": "delete", size: "medium", disabled: !editable, onClick: () => onChangeCommitted(key) },
|
|
51178
51656
|
React__default["default"].createElement(default_1$7, { fontSize: "medium", key: key })))));
|
|
51179
51657
|
}),
|
|
51180
|
-
React__default["default"].createElement(material.
|
|
51658
|
+
React__default["default"].createElement(material.Grid2, null,
|
|
51181
51659
|
React__default["default"].createElement(material.Button, { color: "primary", variant: "contained", disabled: showAddNewForm || !editable, onClick: () => {
|
|
51182
51660
|
setShowAddNewForm(true);
|
|
51183
51661
|
} }, "Add new")),
|
|
51184
|
-
showAddNewForm ? (React__default["default"].createElement(material.
|
|
51662
|
+
showAddNewForm ? (React__default["default"].createElement(material.Grid2, null,
|
|
51185
51663
|
React__default["default"].createElement(material.Paper, { elevation: 8, className: classes.newAttributePaper },
|
|
51186
|
-
React__default["default"].createElement(material.
|
|
51187
|
-
React__default["default"].createElement(material.
|
|
51664
|
+
React__default["default"].createElement(material.Grid2, { container: true, direction: "column" },
|
|
51665
|
+
React__default["default"].createElement(material.Grid2, null,
|
|
51188
51666
|
React__default["default"].createElement(material.FormControl, null,
|
|
51189
51667
|
React__default["default"].createElement(material.FormLabel, { id: "attribute-radio-button-group" }, "Select attribute type"),
|
|
51190
51668
|
React__default["default"].createElement(material.RadioGroup, { "aria-labelledby": "demo-radio-buttons-group-label", defaultValue: "custom", name: "radio-buttons-group", onChange: handleRadioButtonChange },
|
|
51191
|
-
React__default["default"].createElement(material.FormControlLabel, { value: "custom", control: React__default["default"].createElement(material.Radio, null), disableTypography: true, label: React__default["default"].createElement(material.
|
|
51192
|
-
React__default["default"].createElement(material.
|
|
51669
|
+
React__default["default"].createElement(material.FormControlLabel, { value: "custom", control: React__default["default"].createElement(material.Radio, null), disableTypography: true, label: React__default["default"].createElement(material.Grid2, { container: true, spacing: 1, alignItems: "center" },
|
|
51670
|
+
React__default["default"].createElement(material.Grid2, null,
|
|
51193
51671
|
React__default["default"].createElement(material.Typography, null, "Custom")),
|
|
51194
|
-
React__default["default"].createElement(material.
|
|
51672
|
+
React__default["default"].createElement(material.Grid2, null,
|
|
51195
51673
|
React__default["default"].createElement(material.TextField, { label: "Custom attribute key", variant: "outlined", value: reservedKeys.has(newAttributeKey)
|
|
51196
51674
|
? ''
|
|
51197
51675
|
: newAttributeKey, disabled: reservedKeys.has(newAttributeKey), onChange: (event) => {
|
|
51198
51676
|
setNewAttributeKey(event.target.value);
|
|
51199
51677
|
} }))) }),
|
|
51200
51678
|
[...reservedKeys.keys()].map((key) => (React__default["default"].createElement(material.FormControlLabel, { key: key, value: key, control: React__default["default"].createElement(material.Radio, null), label: key })))))),
|
|
51201
|
-
React__default["default"].createElement(material.
|
|
51679
|
+
React__default["default"].createElement(material.Grid2, null,
|
|
51202
51680
|
React__default["default"].createElement(material.DialogActions, null,
|
|
51203
51681
|
React__default["default"].createElement(material.Button, { key: "addButton", color: "primary", variant: "contained", onClick: handleAddNewAttributeChange, disabled: !newAttributeKey }, "Add"),
|
|
51204
51682
|
React__default["default"].createElement(material.Button, { key: "cancelAddButton", variant: "outlined", type: "submit", onClick: () => {
|
|
@@ -51380,6 +51858,47 @@
|
|
|
51380
51858
|
React__default["default"].createElement("div", null, showSequence && (React__default["default"].createElement("textarea", { readOnly: true, rows: 20, className: classes.sequence, value: sequence })))));
|
|
51381
51859
|
});
|
|
51382
51860
|
|
|
51861
|
+
const FeatureDetailsNavigation = mobxReact.observer(function FeatureDetailsNavigation(props) {
|
|
51862
|
+
const { feature, model } = props;
|
|
51863
|
+
const { children, parent } = feature;
|
|
51864
|
+
const childFeatures = [];
|
|
51865
|
+
if (children) {
|
|
51866
|
+
for (const [, child] of children) {
|
|
51867
|
+
childFeatures.push(child);
|
|
51868
|
+
}
|
|
51869
|
+
}
|
|
51870
|
+
if (!(parent ?? childFeatures.length > 0)) {
|
|
51871
|
+
return null;
|
|
51872
|
+
}
|
|
51873
|
+
return (React__default["default"].createElement("div", null,
|
|
51874
|
+
React__default["default"].createElement(material.Typography, { variant: "h5" }, "Go to related feature"),
|
|
51875
|
+
parent && (React__default["default"].createElement("div", null,
|
|
51876
|
+
React__default["default"].createElement(material.Typography, { variant: "h6" }, "Parent:"),
|
|
51877
|
+
React__default["default"].createElement(material.Button, { variant: "contained", onClick: () => {
|
|
51878
|
+
model.setFeature(parent);
|
|
51879
|
+
} },
|
|
51880
|
+
parent.type,
|
|
51881
|
+
" (",
|
|
51882
|
+
parent.min,
|
|
51883
|
+
"..",
|
|
51884
|
+
parent.max,
|
|
51885
|
+
")"))),
|
|
51886
|
+
childFeatures.length > 0 && (React__default["default"].createElement("div", null,
|
|
51887
|
+
React__default["default"].createElement(material.Typography, { variant: "h6" },
|
|
51888
|
+
childFeatures.length === 1 ? 'Child' : 'Children',
|
|
51889
|
+
":"),
|
|
51890
|
+
childFeatures.map((child) => (React__default["default"].createElement("div", { key: child._id, style: { marginBottom: 5 } },
|
|
51891
|
+
React__default["default"].createElement(material.Button, { variant: "contained", onClick: () => {
|
|
51892
|
+
model.setFeature(child);
|
|
51893
|
+
} },
|
|
51894
|
+
child.type,
|
|
51895
|
+
" (",
|
|
51896
|
+
child.min,
|
|
51897
|
+
"..",
|
|
51898
|
+
child.max,
|
|
51899
|
+
")"))))))));
|
|
51900
|
+
});
|
|
51901
|
+
|
|
51383
51902
|
const useStyles$8 = mui.makeStyles()((theme) => ({
|
|
51384
51903
|
root: {
|
|
51385
51904
|
padding: theme.spacing(2),
|
|
@@ -51410,7 +51929,9 @@
|
|
|
51410
51929
|
React__default["default"].createElement("hr", null),
|
|
51411
51930
|
React__default["default"].createElement(Attributes, { feature: feature, session: session, assembly: currentAssembly._id, editable: true }),
|
|
51412
51931
|
React__default["default"].createElement("hr", null),
|
|
51413
|
-
React__default["default"].createElement(Sequence, { feature: feature, session: session, assembly: currentAssembly._id, refName: refName })
|
|
51932
|
+
React__default["default"].createElement(Sequence, { feature: feature, session: session, assembly: currentAssembly._id, refName: refName }),
|
|
51933
|
+
React__default["default"].createElement("hr", null),
|
|
51934
|
+
React__default["default"].createElement(FeatureDetailsNavigation, { model: model, feature: feature })));
|
|
51414
51935
|
});
|
|
51415
51936
|
|
|
51416
51937
|
var dist = {};
|
|
@@ -51551,18 +52072,27 @@
|
|
|
51551
52072
|
return false;
|
|
51552
52073
|
},
|
|
51553
52074
|
get transcriptParts() {
|
|
51554
|
-
|
|
51555
|
-
|
|
52075
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
|
|
52076
|
+
var session = (0, util_1.getSession)(self);
|
|
52077
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
52078
|
+
var apolloDataStore = session.apolloDataStore;
|
|
52079
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
|
52080
|
+
var featureTypeOntology = apolloDataStore.ontologyManager.featureTypeOntology;
|
|
52081
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
52082
|
+
if (!featureTypeOntology.isTypeOf(self.type, 'transcript')) {
|
|
52083
|
+
throw new Error('Only features of type "transcript" or equivalent can calculate CDS locations');
|
|
51556
52084
|
}
|
|
51557
52085
|
var children = self.children;
|
|
51558
52086
|
if (!children) {
|
|
51559
|
-
throw new Error('no CDS or exons in
|
|
52087
|
+
throw new Error('no CDS or exons in transcript');
|
|
51560
52088
|
}
|
|
51561
|
-
var cdsChildren = _toConsumableArray(children.values()).filter(
|
|
51562
|
-
|
|
52089
|
+
var cdsChildren = _toConsumableArray(children.values()).filter(
|
|
52090
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
52091
|
+
function (child) {
|
|
52092
|
+
return featureTypeOntology.isTypeOf(child.type, 'CDS');
|
|
51563
52093
|
});
|
|
51564
52094
|
if (cdsChildren.length === 0) {
|
|
51565
|
-
throw new Error('no CDS in
|
|
52095
|
+
throw new Error('no CDS in transcript');
|
|
51566
52096
|
}
|
|
51567
52097
|
var transcriptParts = [];
|
|
51568
52098
|
var _iterator4 = _createForOfIteratorHelper(cdsChildren),
|
|
@@ -51581,7 +52111,8 @@
|
|
|
51581
52111
|
for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
|
|
51582
52112
|
var _step5$value = _slicedToArray(_step5.value, 2),
|
|
51583
52113
|
_child = _step5$value[1];
|
|
51584
|
-
|
|
52114
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
52115
|
+
if (featureTypeOntology.isTypeOf(_child.type, 'exon')) {
|
|
51585
52116
|
exonLocations.push({
|
|
51586
52117
|
min: _child.min,
|
|
51587
52118
|
max: _child.max
|
|
@@ -52162,7 +52693,14 @@
|
|
|
52162
52693
|
if (!refData) {
|
|
52163
52694
|
return null;
|
|
52164
52695
|
}
|
|
52165
|
-
|
|
52696
|
+
let strand, transcriptParts;
|
|
52697
|
+
try {
|
|
52698
|
+
;
|
|
52699
|
+
({ strand, transcriptParts } = feature);
|
|
52700
|
+
}
|
|
52701
|
+
catch {
|
|
52702
|
+
return null;
|
|
52703
|
+
}
|
|
52166
52704
|
const [firstLocation] = transcriptParts;
|
|
52167
52705
|
const locationData = firstLocation
|
|
52168
52706
|
.map((loc, idx) => {
|
|
@@ -52303,6 +52841,28 @@
|
|
|
52303
52841
|
segments.push({ type: 'CDS', sequenceLines, locs });
|
|
52304
52842
|
return segments;
|
|
52305
52843
|
}
|
|
52844
|
+
case 'protein': {
|
|
52845
|
+
let wholeSequence = '';
|
|
52846
|
+
const [firstLocation] = cdsLocations;
|
|
52847
|
+
const locs = [];
|
|
52848
|
+
for (const loc of firstLocation) {
|
|
52849
|
+
let sequence = getSequence(loc.min, loc.max);
|
|
52850
|
+
if (strand === -1) {
|
|
52851
|
+
sequence = require$$1$2.revcom(sequence);
|
|
52852
|
+
}
|
|
52853
|
+
wholeSequence += sequence;
|
|
52854
|
+
locs.push({ min: loc.min, max: loc.max });
|
|
52855
|
+
}
|
|
52856
|
+
let protein = '';
|
|
52857
|
+
for (let i = 0; i < wholeSequence.length; i += 3) {
|
|
52858
|
+
const codonSeq = wholeSequence.slice(i, i + 3).toUpperCase();
|
|
52859
|
+
protein +=
|
|
52860
|
+
require$$1$2.defaultCodonTable[codonSeq] || '&';
|
|
52861
|
+
}
|
|
52862
|
+
const sequenceLines = dist$2.splitStringIntoChunks(protein, SEQUENCE_WRAP_LENGTH);
|
|
52863
|
+
segments.push({ type: 'protein', sequenceLines, locs });
|
|
52864
|
+
return segments;
|
|
52865
|
+
}
|
|
52306
52866
|
}
|
|
52307
52867
|
}
|
|
52308
52868
|
function getSegmentColor(type) {
|
|
@@ -52391,7 +52951,8 @@
|
|
|
52391
52951
|
React__default["default"].createElement(material.Select, { defaultValue: "CDS", value: selectedOption, onChange: handleChangeSeqOption },
|
|
52392
52952
|
React__default["default"].createElement(material.MenuItem, { value: "CDS" }, "CDS"),
|
|
52393
52953
|
React__default["default"].createElement(material.MenuItem, { value: "cDNA" }, "cDNA"),
|
|
52394
|
-
React__default["default"].createElement(material.MenuItem, { value: "genomic" }, "Genomic")
|
|
52954
|
+
React__default["default"].createElement(material.MenuItem, { value: "genomic" }, "Genomic"),
|
|
52955
|
+
React__default["default"].createElement(material.MenuItem, { value: "protein" }, "Protein")),
|
|
52395
52956
|
React__default["default"].createElement(material.Paper, { style: {
|
|
52396
52957
|
fontFamily: 'monospace',
|
|
52397
52958
|
padding: theme.spacing(),
|
|
@@ -52629,7 +53190,12 @@
|
|
|
52629
53190
|
]);
|
|
52630
53191
|
},
|
|
52631
53192
|
});
|
|
52632
|
-
|
|
53193
|
+
const { featureTypeOntology } = session.apolloDataStore.ontologyManager;
|
|
53194
|
+
if (!featureTypeOntology) {
|
|
53195
|
+
throw new Error('featureTypeOntology is undefined');
|
|
53196
|
+
}
|
|
53197
|
+
if (featureTypeOntology.isTypeOf(feature.type, 'transcript') &&
|
|
53198
|
+
require$$1$2.isSessionModelWithWidgets(session)) {
|
|
52633
53199
|
menuItems.push({
|
|
52634
53200
|
label: 'Edit transcript details',
|
|
52635
53201
|
onClick: () => {
|
|
@@ -52701,6 +53267,381 @@
|
|
|
52701
53267
|
} })));
|
|
52702
53268
|
});
|
|
52703
53269
|
|
|
53270
|
+
/* eslint-disable @typescript-eslint/use-unknown-in-catch-callback-variable */
|
|
53271
|
+
const useStyles$4 = mui.makeStyles()((theme) => ({
|
|
53272
|
+
typeContent: {
|
|
53273
|
+
display: 'inline-block',
|
|
53274
|
+
width: '174px',
|
|
53275
|
+
height: '100%',
|
|
53276
|
+
cursor: 'text',
|
|
53277
|
+
},
|
|
53278
|
+
feature: {
|
|
53279
|
+
td: {
|
|
53280
|
+
position: 'relative',
|
|
53281
|
+
verticalAlign: 'top',
|
|
53282
|
+
paddingLeft: '0.5em',
|
|
53283
|
+
},
|
|
53284
|
+
},
|
|
53285
|
+
arrow: {
|
|
53286
|
+
display: 'inline-block',
|
|
53287
|
+
width: '1.6em',
|
|
53288
|
+
textAlign: 'center',
|
|
53289
|
+
cursor: 'pointer',
|
|
53290
|
+
},
|
|
53291
|
+
arrowExpanded: {
|
|
53292
|
+
transform: 'rotate(90deg)',
|
|
53293
|
+
},
|
|
53294
|
+
hoveredFeature: {
|
|
53295
|
+
backgroundColor: theme.palette.action.hover,
|
|
53296
|
+
},
|
|
53297
|
+
typeInputElement: {
|
|
53298
|
+
border: 'none',
|
|
53299
|
+
background: 'none',
|
|
53300
|
+
},
|
|
53301
|
+
typeErrorMessage: {
|
|
53302
|
+
color: 'red',
|
|
53303
|
+
},
|
|
53304
|
+
}));
|
|
53305
|
+
function makeContextMenuItems(display, feature) {
|
|
53306
|
+
const { changeManager, getAssemblyId, regions, selectedFeature, session, setSelectedFeature, } = display;
|
|
53307
|
+
return featureContextMenuItems(feature, regions[0], getAssemblyId, selectedFeature, setSelectedFeature, session, changeManager);
|
|
53308
|
+
}
|
|
53309
|
+
function getTopLevelFeature(feature) {
|
|
53310
|
+
let cur = feature;
|
|
53311
|
+
while (cur.parent) {
|
|
53312
|
+
cur = cur.parent;
|
|
53313
|
+
}
|
|
53314
|
+
return cur;
|
|
53315
|
+
}
|
|
53316
|
+
const Feature = mobxReact.observer(function Feature({ depth, feature, isHovered, isSelected, model: displayState, selectedFeatureClass, setContextMenu, }) {
|
|
53317
|
+
const { classes } = useStyles$4();
|
|
53318
|
+
const { apolloHover, changeManager, selectedFeature, session, tabularEditor: tabularEditorState, } = displayState;
|
|
53319
|
+
const { featureCollapsed, filterText } = tabularEditorState;
|
|
53320
|
+
const { _id, children, max, min, strand, type } = feature;
|
|
53321
|
+
const expanded = !featureCollapsed.get(_id);
|
|
53322
|
+
const toggleExpanded = (e) => {
|
|
53323
|
+
e.stopPropagation();
|
|
53324
|
+
tabularEditorState.setFeatureCollapsed(_id, expanded);
|
|
53325
|
+
};
|
|
53326
|
+
// pop up a snackbar in the session notifying user of an error
|
|
53327
|
+
const notifyError = (e) => {
|
|
53328
|
+
session.notify(e.message, 'error');
|
|
53329
|
+
};
|
|
53330
|
+
return (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
53331
|
+
React__default["default"].createElement("tr", { onMouseEnter: (_e) => {
|
|
53332
|
+
displayState.setApolloHover({
|
|
53333
|
+
feature,
|
|
53334
|
+
topLevelFeature: getTopLevelFeature(feature),
|
|
53335
|
+
glyph: displayState.getGlyph(getTopLevelFeature(feature)),
|
|
53336
|
+
});
|
|
53337
|
+
}, className: classes.feature +
|
|
53338
|
+
(isSelected
|
|
53339
|
+
? ` ${selectedFeatureClass}`
|
|
53340
|
+
: isHovered
|
|
53341
|
+
? ` ${classes.hoveredFeature}`
|
|
53342
|
+
: ''), onClick: (e) => {
|
|
53343
|
+
e.stopPropagation();
|
|
53344
|
+
displayState.setSelectedFeature(feature);
|
|
53345
|
+
}, onContextMenu: (e) => {
|
|
53346
|
+
e.preventDefault();
|
|
53347
|
+
setContextMenu({
|
|
53348
|
+
position: { left: e.clientX + 2, top: e.clientY - 6 },
|
|
53349
|
+
items: makeContextMenuItems(displayState, feature),
|
|
53350
|
+
});
|
|
53351
|
+
return false;
|
|
53352
|
+
} },
|
|
53353
|
+
React__default["default"].createElement("td", { style: {
|
|
53354
|
+
whiteSpace: 'nowrap',
|
|
53355
|
+
borderLeft: `${depth * 2}em solid transparent`,
|
|
53356
|
+
} },
|
|
53357
|
+
children?.size ? (
|
|
53358
|
+
// TODO: a11y
|
|
53359
|
+
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
|
53360
|
+
React__default["default"].createElement("div", { onClick: toggleExpanded, className: classes.arrow + (expanded ? ` ${classes.arrowExpanded}` : '') }, "\u276F")) : null,
|
|
53361
|
+
React__default["default"].createElement("div", { className: classes.typeContent },
|
|
53362
|
+
React__default["default"].createElement(OntologyTermAutocomplete, { session: session, ontologyName: "Sequence Ontology", style: { width: 170 }, value: type, filterTerms: isOntologyClass, fetchValidTerms: fetchValidTypeTerms.bind(null, feature), renderInput: (params) => {
|
|
53363
|
+
return (React__default["default"].createElement("div", { ref: params.InputProps.ref },
|
|
53364
|
+
React__default["default"].createElement("input", { type: "text", ...params.inputProps, className: classes.typeInputElement, style: { width: 170 } }),
|
|
53365
|
+
params.error ? (React__default["default"].createElement("div", { className: classes.typeErrorMessage }, params.errorMessage ?? 'unknown error')) : null));
|
|
53366
|
+
}, onChange: (oldValue, newValue) => {
|
|
53367
|
+
if (newValue) {
|
|
53368
|
+
handleFeatureTypeChange(changeManager, feature, oldValue, newValue).catch(notifyError);
|
|
53369
|
+
}
|
|
53370
|
+
} }))),
|
|
53371
|
+
React__default["default"].createElement("td", null,
|
|
53372
|
+
React__default["default"].createElement(NumberCell, { initialValue: min + 1, notifyError: notifyError, onChangeCommitted: (newStart) => handleFeatureStartChange(changeManager, feature, min, newStart - 1) })),
|
|
53373
|
+
React__default["default"].createElement("td", null,
|
|
53374
|
+
React__default["default"].createElement(NumberCell, { initialValue: max, notifyError: notifyError, onChangeCommitted: (newEnd) => handleFeatureEndChange(changeManager, feature, max, newEnd) })),
|
|
53375
|
+
React__default["default"].createElement("td", null, strand === 1 ? '+' : strand === -1 ? '-' : undefined),
|
|
53376
|
+
React__default["default"].createElement("td", null,
|
|
53377
|
+
React__default["default"].createElement(FeatureAttributes, { filterText: filterText, feature: feature }))),
|
|
53378
|
+
expanded && children
|
|
53379
|
+
? [...children.entries()]
|
|
53380
|
+
.filter((entry) => {
|
|
53381
|
+
if (!filterText) {
|
|
53382
|
+
return true;
|
|
53383
|
+
}
|
|
53384
|
+
const [, childFeature] = entry;
|
|
53385
|
+
// search feature and its subfeatures for the text
|
|
53386
|
+
const text = JSON.stringify(childFeature);
|
|
53387
|
+
return text.includes(filterText);
|
|
53388
|
+
})
|
|
53389
|
+
.map(([featureId, childFeature]) => {
|
|
53390
|
+
const childHovered = apolloHover?.feature._id === childFeature._id;
|
|
53391
|
+
const childSelected = selectedFeature?._id === childFeature._id;
|
|
53392
|
+
return (React__default["default"].createElement(Feature, { isHovered: childHovered, isSelected: childSelected, selectedFeatureClass: selectedFeatureClass, key: featureId, depth: (depth || 0) + 1, feature: childFeature, model: displayState, setContextMenu: setContextMenu }));
|
|
53393
|
+
})
|
|
53394
|
+
: null));
|
|
53395
|
+
});
|
|
53396
|
+
async function fetchValidTypeTerms(feature, ontologyStore, _signal) {
|
|
53397
|
+
const { parent: parentFeature } = feature;
|
|
53398
|
+
if (parentFeature) {
|
|
53399
|
+
// if this is a child of an existing feature, restrict the autocomplete choices to valid
|
|
53400
|
+
// parts of that feature
|
|
53401
|
+
const parentTypeTerms = await ontologyStore.getTermsWithLabelOrSynonym(parentFeature.type, { includeSubclasses: false });
|
|
53402
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
53403
|
+
const parentTypeClassTerms = parentTypeTerms.filter(isOntologyClass);
|
|
53404
|
+
if (parentTypeClassTerms.length > 0) {
|
|
53405
|
+
const subpartTerms = await ontologyStore.getClassesThat('part_of', parentTypeClassTerms);
|
|
53406
|
+
return subpartTerms;
|
|
53407
|
+
}
|
|
53408
|
+
}
|
|
53409
|
+
return;
|
|
53410
|
+
}
|
|
53411
|
+
|
|
53412
|
+
const useStyles$3 = mui.makeStyles()((theme) => ({
|
|
53413
|
+
scrollableTable: {
|
|
53414
|
+
width: '100%',
|
|
53415
|
+
height: '100%',
|
|
53416
|
+
th: {
|
|
53417
|
+
position: 'sticky',
|
|
53418
|
+
top: 0,
|
|
53419
|
+
zIndex: 2,
|
|
53420
|
+
textAlign: 'left',
|
|
53421
|
+
background: theme.palette.background.paper,
|
|
53422
|
+
paddingTop: '3.2em',
|
|
53423
|
+
},
|
|
53424
|
+
td: { whiteSpace: 'normal' },
|
|
53425
|
+
},
|
|
53426
|
+
selectedFeature: {
|
|
53427
|
+
backgroundColor: theme.palette.action.selected,
|
|
53428
|
+
},
|
|
53429
|
+
}));
|
|
53430
|
+
const HybridGrid = mobxReact.observer(function HybridGrid({ model, }) {
|
|
53431
|
+
const { apolloHover, seenFeatures, selectedFeature, tabularEditor } = model;
|
|
53432
|
+
const theme = material.useTheme();
|
|
53433
|
+
const { classes } = useStyles$3();
|
|
53434
|
+
const scrollContainerRef = React.useRef(null);
|
|
53435
|
+
const [contextMenu, setContextMenu] = React.useState(null);
|
|
53436
|
+
const { filterText } = tabularEditor;
|
|
53437
|
+
// scrolls to selected feature if one is selected and it's not already visible
|
|
53438
|
+
React.useEffect(() => {
|
|
53439
|
+
const scrollContainer = scrollContainerRef.current;
|
|
53440
|
+
if (scrollContainer && selectedFeature) {
|
|
53441
|
+
const selectedRow = scrollContainer.querySelector(`.${classes.selectedFeature}`);
|
|
53442
|
+
if (selectedRow) {
|
|
53443
|
+
const currScroll = scrollContainer.scrollTop;
|
|
53444
|
+
const newScrollTop = selectedRow.offsetTop - 25;
|
|
53445
|
+
const isVisible = newScrollTop > currScroll &&
|
|
53446
|
+
newScrollTop < currScroll + scrollContainer.offsetHeight;
|
|
53447
|
+
if (!isVisible) {
|
|
53448
|
+
scrollContainer.scroll({ top: newScrollTop - 40, behavior: 'smooth' });
|
|
53449
|
+
}
|
|
53450
|
+
}
|
|
53451
|
+
}
|
|
53452
|
+
}, [selectedFeature, seenFeatures, classes.selectedFeature]);
|
|
53453
|
+
return (React__default["default"].createElement("div", { ref: scrollContainerRef, style: { width: '100%', overflowY: 'auto', height: '100%' } },
|
|
53454
|
+
React__default["default"].createElement("table", { className: classes.scrollableTable },
|
|
53455
|
+
React__default["default"].createElement("thead", null,
|
|
53456
|
+
React__default["default"].createElement("tr", null,
|
|
53457
|
+
React__default["default"].createElement("th", null, "Type"),
|
|
53458
|
+
React__default["default"].createElement("th", null, "Start"),
|
|
53459
|
+
React__default["default"].createElement("th", null, "End"),
|
|
53460
|
+
React__default["default"].createElement("th", null, "Strand"),
|
|
53461
|
+
React__default["default"].createElement("th", null, "Attributes"))),
|
|
53462
|
+
React__default["default"].createElement("tbody", null, [...seenFeatures.entries()]
|
|
53463
|
+
.filter((entry) => {
|
|
53464
|
+
if (!filterText) {
|
|
53465
|
+
return true;
|
|
53466
|
+
}
|
|
53467
|
+
const [, feature] = entry;
|
|
53468
|
+
// search feature and its subfeatures for the text
|
|
53469
|
+
const text = JSON.stringify(feature);
|
|
53470
|
+
return text.includes(filterText);
|
|
53471
|
+
})
|
|
53472
|
+
.sort((a, b) => {
|
|
53473
|
+
return a[1].min - b[1].min;
|
|
53474
|
+
})
|
|
53475
|
+
.map(([featureId, feature]) => {
|
|
53476
|
+
const isSelected = selectedFeature?._id === featureId;
|
|
53477
|
+
const isHovered = apolloHover?.feature._id === featureId;
|
|
53478
|
+
return (React__default["default"].createElement(Feature, { key: featureId, isSelected: isSelected, isHovered: isHovered, selectedFeatureClass: classes.selectedFeature, feature: feature, model: model, depth: 0, setContextMenu: setContextMenu }));
|
|
53479
|
+
}))),
|
|
53480
|
+
React__default["default"].createElement(ui.Menu, { open: Boolean(contextMenu), onMenuItemClick: (_, callback) => {
|
|
53481
|
+
callback();
|
|
53482
|
+
setContextMenu(null);
|
|
53483
|
+
}, onClose: () => {
|
|
53484
|
+
setContextMenu(null);
|
|
53485
|
+
}, TransitionProps: {
|
|
53486
|
+
onExit: () => {
|
|
53487
|
+
setContextMenu(null);
|
|
53488
|
+
},
|
|
53489
|
+
}, style: { zIndex: theme.zIndex.tooltip }, menuItems: contextMenu?.items ?? [], anchorReference: "anchorPosition", anchorPosition: contextMenu?.position })));
|
|
53490
|
+
});
|
|
53491
|
+
|
|
53492
|
+
var Clear = {};
|
|
53493
|
+
|
|
53494
|
+
var _interopRequireDefault$5 = interopRequireDefault.exports;
|
|
53495
|
+
Object.defineProperty(Clear, "__esModule", {
|
|
53496
|
+
value: true
|
|
53497
|
+
});
|
|
53498
|
+
var default_1$5 = Clear["default"] = void 0;
|
|
53499
|
+
var _createSvgIcon$5 = /*#__PURE__*/_interopRequireDefault$5(createSvgIcon);
|
|
53500
|
+
var _jsxRuntime$5 = require$$2__default["default"];
|
|
53501
|
+
var _default$5 = /*#__PURE__*/(0, _createSvgIcon$5["default"])( /*#__PURE__*/(0, _jsxRuntime$5.jsx)("path", {
|
|
53502
|
+
d: "M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"
|
|
53503
|
+
}), 'Clear');
|
|
53504
|
+
default_1$5 = Clear["default"] = _default$5;
|
|
53505
|
+
|
|
53506
|
+
var UnfoldLess = {};
|
|
53507
|
+
|
|
53508
|
+
var _interopRequireDefault$4 = interopRequireDefault.exports;
|
|
53509
|
+
Object.defineProperty(UnfoldLess, "__esModule", {
|
|
53510
|
+
value: true
|
|
53511
|
+
});
|
|
53512
|
+
var default_1$4 = UnfoldLess["default"] = void 0;
|
|
53513
|
+
var _createSvgIcon$4 = /*#__PURE__*/_interopRequireDefault$4(createSvgIcon);
|
|
53514
|
+
var _jsxRuntime$4 = require$$2__default["default"];
|
|
53515
|
+
var _default$4 = /*#__PURE__*/(0, _createSvgIcon$4["default"])( /*#__PURE__*/(0, _jsxRuntime$4.jsx)("path", {
|
|
53516
|
+
d: "M7.41 18.59 8.83 20 12 16.83 15.17 20l1.41-1.41L12 14l-4.59 4.59zm9.18-13.18L15.17 4 12 7.17 8.83 4 7.41 5.41 12 10l4.59-4.59z"
|
|
53517
|
+
}), 'UnfoldLess');
|
|
53518
|
+
default_1$4 = UnfoldLess["default"] = _default$4;
|
|
53519
|
+
|
|
53520
|
+
/* eslint-disable @typescript-eslint/unbound-method */
|
|
53521
|
+
const useStyles$2 = mui.makeStyles()({
|
|
53522
|
+
toolbar: {
|
|
53523
|
+
width: '100%',
|
|
53524
|
+
display: 'flex',
|
|
53525
|
+
paddingRight: '2em',
|
|
53526
|
+
flexDirection: 'row',
|
|
53527
|
+
justifyContent: 'space-between',
|
|
53528
|
+
position: 'absolute',
|
|
53529
|
+
zIndex: 4,
|
|
53530
|
+
},
|
|
53531
|
+
filterText: {},
|
|
53532
|
+
});
|
|
53533
|
+
const ToolBar = mobxReact.observer(function ToolBar({ model: displayState, }) {
|
|
53534
|
+
const model = displayState.tabularEditor;
|
|
53535
|
+
const { classes } = useStyles$2();
|
|
53536
|
+
return (React__default["default"].createElement("div", { className: classes.toolbar },
|
|
53537
|
+
React__default["default"].createElement(material.Tooltip, { title: "Collapse all" },
|
|
53538
|
+
React__default["default"].createElement(material.IconButton, { "aria-label": "collapse", sx: { marginTop: 0 }, onClick: model.collapseAllFeatures },
|
|
53539
|
+
React__default["default"].createElement(default_1$4, null))),
|
|
53540
|
+
React__default["default"].createElement(material.TextField, { className: classes.filterText, label: "Filter features", value: model.filterText, sx: { marginTop: 0 }, variant: "outlined", onChange: (event) => {
|
|
53541
|
+
model.setFilterText(event.target.value);
|
|
53542
|
+
}, InputProps: {
|
|
53543
|
+
endAdornment: (React__default["default"].createElement(material.InputAdornment, { position: "end" },
|
|
53544
|
+
React__default["default"].createElement(material.IconButton, { onClick: () => {
|
|
53545
|
+
model.clearFilterText();
|
|
53546
|
+
} },
|
|
53547
|
+
React__default["default"].createElement(default_1$5, null)))),
|
|
53548
|
+
} })));
|
|
53549
|
+
});
|
|
53550
|
+
|
|
53551
|
+
function stopPropagation(e) {
|
|
53552
|
+
e.stopPropagation();
|
|
53553
|
+
}
|
|
53554
|
+
const TabularEditorPane = mobxReact.observer(function TabularEditorPane({ model: displayState, }) {
|
|
53555
|
+
const model = displayState.tabularEditor;
|
|
53556
|
+
if (!model.isShown) {
|
|
53557
|
+
return null;
|
|
53558
|
+
}
|
|
53559
|
+
return (
|
|
53560
|
+
// TODO: a11y
|
|
53561
|
+
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
|
53562
|
+
React__default["default"].createElement("div", { onMouseDown: stopPropagation, onClick: stopPropagation, style: { width: '100%', height: '100%', position: 'relative' } },
|
|
53563
|
+
React__default["default"].createElement(ToolBar, { model: displayState }),
|
|
53564
|
+
React__default["default"].createElement(HybridGrid, { model: displayState })));
|
|
53565
|
+
});
|
|
53566
|
+
|
|
53567
|
+
const TabularEditorStateModelType = require$$1$3.types
|
|
53568
|
+
.model('TabularEditor', {
|
|
53569
|
+
isShown: true,
|
|
53570
|
+
featureCollapsed: require$$1$3.types.map(require$$1$3.types.boolean),
|
|
53571
|
+
filterText: '',
|
|
53572
|
+
})
|
|
53573
|
+
.actions((self) => ({
|
|
53574
|
+
setFeatureCollapsed(id, state) {
|
|
53575
|
+
self.featureCollapsed.set(id, state);
|
|
53576
|
+
},
|
|
53577
|
+
setFilterText(text) {
|
|
53578
|
+
self.filterText = text;
|
|
53579
|
+
},
|
|
53580
|
+
clearFilterText() {
|
|
53581
|
+
self.filterText = '';
|
|
53582
|
+
},
|
|
53583
|
+
collapseAllFeatures() {
|
|
53584
|
+
// iterate over all seen features and set them to collapsed
|
|
53585
|
+
const display = require$$1$3.getParent(self);
|
|
53586
|
+
for (const [featureId] of display.seenFeatures.entries()) {
|
|
53587
|
+
self.featureCollapsed.set(featureId, true);
|
|
53588
|
+
}
|
|
53589
|
+
},
|
|
53590
|
+
togglePane() {
|
|
53591
|
+
self.isShown = !self.isShown;
|
|
53592
|
+
},
|
|
53593
|
+
hidePane() {
|
|
53594
|
+
self.isShown = false;
|
|
53595
|
+
},
|
|
53596
|
+
showPane() {
|
|
53597
|
+
self.isShown = true;
|
|
53598
|
+
},
|
|
53599
|
+
// onPatch(patch: any) {
|
|
53600
|
+
// console.log(patch)
|
|
53601
|
+
// },
|
|
53602
|
+
}));
|
|
53603
|
+
|
|
53604
|
+
const FilterFeatures = mobxReact.observer(function FilterFeatures({ featureTypes, handleClose, onUpdate, session, }) {
|
|
53605
|
+
const [type, setType] = React.useState('');
|
|
53606
|
+
const [selectedFeatureTypes, setSelectedFeatureTypes] = React.useState(featureTypes);
|
|
53607
|
+
const handleChange = (value) => {
|
|
53608
|
+
setType(value);
|
|
53609
|
+
};
|
|
53610
|
+
const handleAddFeatureType = () => {
|
|
53611
|
+
if (type) {
|
|
53612
|
+
if (selectedFeatureTypes.includes(type)) {
|
|
53613
|
+
return;
|
|
53614
|
+
}
|
|
53615
|
+
onUpdate([...selectedFeatureTypes, type]);
|
|
53616
|
+
setSelectedFeatureTypes([...selectedFeatureTypes, type]);
|
|
53617
|
+
}
|
|
53618
|
+
};
|
|
53619
|
+
const handleFeatureTypeDelete = (value) => {
|
|
53620
|
+
const newTypes = selectedFeatureTypes.filter((type) => type !== value);
|
|
53621
|
+
onUpdate(newTypes);
|
|
53622
|
+
setSelectedFeatureTypes(newTypes);
|
|
53623
|
+
};
|
|
53624
|
+
return (React__default["default"].createElement(Dialog, { open: true, maxWidth: false, "data-testid": "filter-features-dialog", title: "Filter features by type", handleClose: handleClose },
|
|
53625
|
+
React__default["default"].createElement(material.DialogContent, null,
|
|
53626
|
+
React__default["default"].createElement(material.DialogContentText, null, "Select the feature types you want to display in the apollo track"),
|
|
53627
|
+
React__default["default"].createElement(material.Grid2, { container: true, spacing: 2 },
|
|
53628
|
+
React__default["default"].createElement(material.Grid2, { size: 8 },
|
|
53629
|
+
React__default["default"].createElement(OntologyTermAutocomplete, { session: session, ontologyName: "Sequence Ontology", style: { width: '100%' }, value: type, filterTerms: isOntologyClass, renderInput: (params) => (React__default["default"].createElement(material.TextField, { ...params, label: "Feature type", variant: "outlined", fullWidth: true })), onChange: (oldValue, newValue) => {
|
|
53630
|
+
if (newValue) {
|
|
53631
|
+
handleChange(newValue);
|
|
53632
|
+
}
|
|
53633
|
+
} })),
|
|
53634
|
+
React__default["default"].createElement(material.Grid2, { size: 4 },
|
|
53635
|
+
React__default["default"].createElement(material.Button, { variant: "contained", onClick: handleAddFeatureType, disabled: !type, style: { marginTop: 9 }, size: "medium" }, "Add"))),
|
|
53636
|
+
selectedFeatureTypes.length > 0 && (React__default["default"].createElement("div", null,
|
|
53637
|
+
React__default["default"].createElement("hr", null),
|
|
53638
|
+
React__default["default"].createElement("div", { style: { width: 300 } },
|
|
53639
|
+
React__default["default"].createElement(material.DialogContentText, null, "Selected feature types:"),
|
|
53640
|
+
React__default["default"].createElement(material.Box, { sx: { display: 'flex', flexWrap: 'wrap', gap: 0.5 } }, selectedFeatureTypes.map((value) => (React__default["default"].createElement(material.Chip, { key: value, label: value, onDelete: () => {
|
|
53641
|
+
handleFeatureTypeDelete(value);
|
|
53642
|
+
} }))))))))));
|
|
53643
|
+
});
|
|
53644
|
+
|
|
52704
53645
|
const minDisplayHeight = 20;
|
|
52705
53646
|
function baseModelFactory(_pluginManager, configSchema) {
|
|
52706
53647
|
return pluggableElementTypes.BaseDisplay.named('BaseLinearApolloDisplay')
|
|
@@ -52710,6 +53651,7 @@
|
|
|
52710
53651
|
graphical: true,
|
|
52711
53652
|
table: false,
|
|
52712
53653
|
heightPreConfig: require$$1$3.types.maybe(require$$1$3.types.refinement('displayHeight', require$$1$3.types.number, (n) => n >= minDisplayHeight)),
|
|
53654
|
+
filteredFeatureTypes: require$$1$3.types.array(require$$1$3.types.string),
|
|
52713
53655
|
})
|
|
52714
53656
|
.views((self) => {
|
|
52715
53657
|
const { configuration, renderProps: superRenderProps } = self;
|
|
@@ -52824,9 +53766,12 @@
|
|
|
52824
53766
|
self.graphical = true;
|
|
52825
53767
|
self.table = true;
|
|
52826
53768
|
},
|
|
53769
|
+
updateFilteredFeatureTypes(types) {
|
|
53770
|
+
self.filteredFeatureTypes = require$$1$3.cast(types);
|
|
53771
|
+
},
|
|
52827
53772
|
}))
|
|
52828
53773
|
.views((self) => {
|
|
52829
|
-
const { trackMenuItems: superTrackMenuItems } = self;
|
|
53774
|
+
const { filteredFeatureTypes, trackMenuItems: superTrackMenuItems } = self;
|
|
52830
53775
|
return {
|
|
52831
53776
|
trackMenuItems() {
|
|
52832
53777
|
const { graphical, table } = self;
|
|
@@ -52862,6 +53807,25 @@
|
|
|
52862
53807
|
},
|
|
52863
53808
|
],
|
|
52864
53809
|
},
|
|
53810
|
+
{
|
|
53811
|
+
label: 'Filter features by type',
|
|
53812
|
+
onClick: () => {
|
|
53813
|
+
const session = self.session;
|
|
53814
|
+
self.session.queueDialog((doneCallback) => [
|
|
53815
|
+
FilterFeatures,
|
|
53816
|
+
{
|
|
53817
|
+
session,
|
|
53818
|
+
handleClose: () => {
|
|
53819
|
+
doneCallback();
|
|
53820
|
+
},
|
|
53821
|
+
featureTypes: require$$1$3.getSnapshot(filteredFeatureTypes),
|
|
53822
|
+
onUpdate: (types) => {
|
|
53823
|
+
self.updateFilteredFeatureTypes(types);
|
|
53824
|
+
},
|
|
53825
|
+
},
|
|
53826
|
+
]);
|
|
53827
|
+
},
|
|
53828
|
+
},
|
|
52865
53829
|
];
|
|
52866
53830
|
},
|
|
52867
53831
|
};
|
|
@@ -52884,779 +53848,6 @@
|
|
|
52884
53848
|
}));
|
|
52885
53849
|
}
|
|
52886
53850
|
|
|
52887
|
-
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
52888
|
-
function layoutsModelFactory(pluginManager, configSchema) {
|
|
52889
|
-
const BaseLinearApolloDisplay = baseModelFactory(pluginManager, configSchema);
|
|
52890
|
-
return BaseLinearApolloDisplay.named('LinearApolloDisplayLayouts')
|
|
52891
|
-
.props({
|
|
52892
|
-
featuresMinMaxLimit: 500_000,
|
|
52893
|
-
})
|
|
52894
|
-
.volatile(() => ({
|
|
52895
|
-
seenFeatures: mobx.observable.map(),
|
|
52896
|
-
}))
|
|
52897
|
-
.views((self) => ({
|
|
52898
|
-
get featuresMinMax() {
|
|
52899
|
-
const { assemblyManager } = self.session;
|
|
52900
|
-
return self.lgv.displayedRegions.map((region) => {
|
|
52901
|
-
const assembly = assemblyManager.get(region.assemblyName);
|
|
52902
|
-
let min;
|
|
52903
|
-
let max;
|
|
52904
|
-
const { end, refName, start } = region;
|
|
52905
|
-
for (const [, feature] of self.seenFeatures) {
|
|
52906
|
-
if (refName !== assembly?.getCanonicalRefName(feature.refSeq) ||
|
|
52907
|
-
!require$$1$2.doesIntersect2(start, end, feature.min, feature.max) ||
|
|
52908
|
-
feature.length > self.featuresMinMaxLimit) {
|
|
52909
|
-
continue;
|
|
52910
|
-
}
|
|
52911
|
-
if (min === undefined) {
|
|
52912
|
-
({ min } = feature);
|
|
52913
|
-
}
|
|
52914
|
-
if (max === undefined) {
|
|
52915
|
-
({ max } = feature);
|
|
52916
|
-
}
|
|
52917
|
-
if (feature.minWithChildren < min) {
|
|
52918
|
-
({ min } = feature);
|
|
52919
|
-
}
|
|
52920
|
-
if (feature.maxWithChildren > max) {
|
|
52921
|
-
({ max } = feature);
|
|
52922
|
-
}
|
|
52923
|
-
}
|
|
52924
|
-
if (min !== undefined && max !== undefined) {
|
|
52925
|
-
return [min, max];
|
|
52926
|
-
}
|
|
52927
|
-
return;
|
|
52928
|
-
});
|
|
52929
|
-
},
|
|
52930
|
-
}))
|
|
52931
|
-
.actions((self) => ({
|
|
52932
|
-
addSeenFeature(feature) {
|
|
52933
|
-
self.seenFeatures.set(feature._id, feature);
|
|
52934
|
-
},
|
|
52935
|
-
deleteSeenFeature(featureId) {
|
|
52936
|
-
self.seenFeatures.delete(featureId);
|
|
52937
|
-
},
|
|
52938
|
-
}))
|
|
52939
|
-
.views((self) => ({
|
|
52940
|
-
get featureLayouts() {
|
|
52941
|
-
const { assemblyManager } = self.session;
|
|
52942
|
-
return self.lgv.displayedRegions.map((region, idx) => {
|
|
52943
|
-
const assembly = assemblyManager.get(region.assemblyName);
|
|
52944
|
-
const featureLayout = new Map();
|
|
52945
|
-
const minMax = self.featuresMinMax[idx];
|
|
52946
|
-
if (!minMax) {
|
|
52947
|
-
return featureLayout;
|
|
52948
|
-
}
|
|
52949
|
-
const [min, max] = minMax;
|
|
52950
|
-
const rows = [];
|
|
52951
|
-
const { end, refName, start } = region;
|
|
52952
|
-
for (const [id, feature] of self.seenFeatures.entries()) {
|
|
52953
|
-
if (!require$$1$3.isAlive(feature)) {
|
|
52954
|
-
self.deleteSeenFeature(id);
|
|
52955
|
-
continue;
|
|
52956
|
-
}
|
|
52957
|
-
if (refName !== assembly?.getCanonicalRefName(feature.refSeq) ||
|
|
52958
|
-
!require$$1$2.doesIntersect2(start, end, feature.min, feature.max)) {
|
|
52959
|
-
continue;
|
|
52960
|
-
}
|
|
52961
|
-
const rowCount = getGlyph(feature).getRowCount(feature, self.lgv.bpPerPx);
|
|
52962
|
-
let startingRow = 0;
|
|
52963
|
-
let placed = false;
|
|
52964
|
-
while (!placed) {
|
|
52965
|
-
let rowsForFeature = rows.slice(startingRow, startingRow + rowCount);
|
|
52966
|
-
if (rowsForFeature.length < rowCount) {
|
|
52967
|
-
for (let i = 0; i < rowCount - rowsForFeature.length; i++) {
|
|
52968
|
-
const newRowNumber = rows.length;
|
|
52969
|
-
rows[newRowNumber] = Array.from({ length: max - min });
|
|
52970
|
-
featureLayout.set(newRowNumber, []);
|
|
52971
|
-
}
|
|
52972
|
-
rowsForFeature = rows.slice(startingRow, startingRow + rowCount);
|
|
52973
|
-
}
|
|
52974
|
-
if (rowsForFeature
|
|
52975
|
-
.map((rowForFeature) => {
|
|
52976
|
-
// zero-length features are allowed in the spec
|
|
52977
|
-
const featureMax = feature.max - feature.min === 0
|
|
52978
|
-
? feature.min + 1
|
|
52979
|
-
: feature.max;
|
|
52980
|
-
let start = feature.min - min, end = featureMax - min;
|
|
52981
|
-
if (feature.min - min < 0) {
|
|
52982
|
-
start = 0;
|
|
52983
|
-
end = featureMax - feature.min;
|
|
52984
|
-
}
|
|
52985
|
-
return rowForFeature.slice(start, end).some(Boolean);
|
|
52986
|
-
})
|
|
52987
|
-
.some(Boolean)) {
|
|
52988
|
-
startingRow += 1;
|
|
52989
|
-
continue;
|
|
52990
|
-
}
|
|
52991
|
-
for (let rowNum = startingRow; rowNum < startingRow + rowCount; rowNum++) {
|
|
52992
|
-
const row = rows[rowNum];
|
|
52993
|
-
let start = feature.min - min, end = feature.max - min;
|
|
52994
|
-
if (feature.min - min < 0) {
|
|
52995
|
-
start = 0;
|
|
52996
|
-
end = feature.max - feature.min;
|
|
52997
|
-
}
|
|
52998
|
-
row.fill(true, start, end);
|
|
52999
|
-
const layoutRow = featureLayout.get(rowNum);
|
|
53000
|
-
layoutRow?.push([rowNum - startingRow, feature]);
|
|
53001
|
-
}
|
|
53002
|
-
placed = true;
|
|
53003
|
-
}
|
|
53004
|
-
}
|
|
53005
|
-
return featureLayout;
|
|
53006
|
-
});
|
|
53007
|
-
},
|
|
53008
|
-
getFeatureLayoutPosition(feature) {
|
|
53009
|
-
const { featureLayouts } = this;
|
|
53010
|
-
for (const [idx, layout] of featureLayouts.entries()) {
|
|
53011
|
-
for (const [layoutRowNum, layoutRow] of layout) {
|
|
53012
|
-
for (const [featureRowNum, layoutFeature] of layoutRow) {
|
|
53013
|
-
if (featureRowNum !== 0) {
|
|
53014
|
-
// Same top-level feature in all feature rows, so only need to
|
|
53015
|
-
// check the first one
|
|
53016
|
-
continue;
|
|
53017
|
-
}
|
|
53018
|
-
if (feature._id === layoutFeature._id) {
|
|
53019
|
-
return {
|
|
53020
|
-
layoutIndex: idx,
|
|
53021
|
-
layoutRow: layoutRowNum,
|
|
53022
|
-
featureRow: featureRowNum,
|
|
53023
|
-
};
|
|
53024
|
-
}
|
|
53025
|
-
if (layoutFeature.hasDescendant(feature._id)) {
|
|
53026
|
-
const row = getGlyph(layoutFeature).getRowForFeature(layoutFeature, feature);
|
|
53027
|
-
if (row !== undefined) {
|
|
53028
|
-
return {
|
|
53029
|
-
layoutIndex: idx,
|
|
53030
|
-
layoutRow: layoutRowNum,
|
|
53031
|
-
featureRow: row,
|
|
53032
|
-
};
|
|
53033
|
-
}
|
|
53034
|
-
}
|
|
53035
|
-
}
|
|
53036
|
-
}
|
|
53037
|
-
}
|
|
53038
|
-
return;
|
|
53039
|
-
},
|
|
53040
|
-
}))
|
|
53041
|
-
.views((self) => ({
|
|
53042
|
-
get highestRow() {
|
|
53043
|
-
return Math.max(0, ...self.featureLayouts.map((layout) => Math.max(...layout.keys())));
|
|
53044
|
-
},
|
|
53045
|
-
}))
|
|
53046
|
-
.actions((self) => ({
|
|
53047
|
-
afterAttach() {
|
|
53048
|
-
require$$1$3.addDisposer(self, mobx.autorun(() => {
|
|
53049
|
-
if (!self.lgv.initialized || self.regionCannotBeRendered()) {
|
|
53050
|
-
return;
|
|
53051
|
-
}
|
|
53052
|
-
for (const region of self.regions) {
|
|
53053
|
-
const assembly = self.session.apolloDataStore.assemblies.get(region.assemblyName);
|
|
53054
|
-
const ref = assembly?.getByRefName(region.refName);
|
|
53055
|
-
const features = ref?.features;
|
|
53056
|
-
if (!features) {
|
|
53057
|
-
continue;
|
|
53058
|
-
}
|
|
53059
|
-
for (const [, feature] of features) {
|
|
53060
|
-
if (require$$1$2.doesIntersect2(region.start, region.end, feature.min, feature.max) &&
|
|
53061
|
-
!self.seenFeatures.has(feature._id)) {
|
|
53062
|
-
self.addSeenFeature(feature);
|
|
53063
|
-
}
|
|
53064
|
-
}
|
|
53065
|
-
}
|
|
53066
|
-
}, { name: 'LinearApolloDisplaySetSeenFeatures', delay: 1000 }));
|
|
53067
|
-
},
|
|
53068
|
-
}));
|
|
53069
|
-
}
|
|
53070
|
-
|
|
53071
|
-
function renderingModelIntermediateFactory(pluginManager, configSchema) {
|
|
53072
|
-
const LinearApolloDisplayLayouts = layoutsModelFactory(pluginManager, configSchema);
|
|
53073
|
-
return LinearApolloDisplayLayouts.named('LinearApolloDisplayRendering')
|
|
53074
|
-
.props({
|
|
53075
|
-
sequenceRowHeight: 15,
|
|
53076
|
-
apolloRowHeight: 20,
|
|
53077
|
-
detailsMinHeight: 200,
|
|
53078
|
-
detailsHeight: 200,
|
|
53079
|
-
lastRowTooltipBufferHeight: 40,
|
|
53080
|
-
isShown: true,
|
|
53081
|
-
})
|
|
53082
|
-
.volatile(() => ({
|
|
53083
|
-
canvas: null,
|
|
53084
|
-
overlayCanvas: null,
|
|
53085
|
-
collaboratorCanvas: null,
|
|
53086
|
-
seqTrackCanvas: null,
|
|
53087
|
-
seqTrackOverlayCanvas: null,
|
|
53088
|
-
theme: undefined,
|
|
53089
|
-
}))
|
|
53090
|
-
.views((self) => ({
|
|
53091
|
-
get featuresHeight() {
|
|
53092
|
-
return ((self.highestRow + 1) * self.apolloRowHeight +
|
|
53093
|
-
self.lastRowTooltipBufferHeight);
|
|
53094
|
-
},
|
|
53095
|
-
}))
|
|
53096
|
-
.actions((self) => ({
|
|
53097
|
-
toggleShown() {
|
|
53098
|
-
self.isShown = !self.isShown;
|
|
53099
|
-
},
|
|
53100
|
-
setDetailsHeight(newHeight) {
|
|
53101
|
-
self.detailsHeight = self.isShown
|
|
53102
|
-
? Math.max(Math.min(newHeight, self.height - 100), Math.min(self.height, self.detailsMinHeight))
|
|
53103
|
-
: newHeight;
|
|
53104
|
-
},
|
|
53105
|
-
setCanvas(canvas) {
|
|
53106
|
-
self.canvas = canvas;
|
|
53107
|
-
},
|
|
53108
|
-
setOverlayCanvas(canvas) {
|
|
53109
|
-
self.overlayCanvas = canvas;
|
|
53110
|
-
},
|
|
53111
|
-
setCollaboratorCanvas(canvas) {
|
|
53112
|
-
self.collaboratorCanvas = canvas;
|
|
53113
|
-
},
|
|
53114
|
-
setSeqTrackCanvas(canvas) {
|
|
53115
|
-
self.seqTrackCanvas = canvas;
|
|
53116
|
-
},
|
|
53117
|
-
setSeqTrackOverlayCanvas(canvas) {
|
|
53118
|
-
self.seqTrackOverlayCanvas = canvas;
|
|
53119
|
-
},
|
|
53120
|
-
setTheme(theme) {
|
|
53121
|
-
self.theme = theme;
|
|
53122
|
-
},
|
|
53123
|
-
afterAttach() {
|
|
53124
|
-
require$$1$3.addDisposer(self, mobx.autorun(() => {
|
|
53125
|
-
if (!self.lgv.initialized || self.regionCannotBeRendered()) {
|
|
53126
|
-
return;
|
|
53127
|
-
}
|
|
53128
|
-
const ctx = self.collaboratorCanvas?.getContext('2d');
|
|
53129
|
-
if (!ctx) {
|
|
53130
|
-
return;
|
|
53131
|
-
}
|
|
53132
|
-
ctx.clearRect(0, 0, self.lgv.dynamicBlocks.totalWidthPx, self.featuresHeight);
|
|
53133
|
-
for (const collaborator of self.session.collaborators) {
|
|
53134
|
-
const { locations } = collaborator;
|
|
53135
|
-
if (locations.length === 0) {
|
|
53136
|
-
continue;
|
|
53137
|
-
}
|
|
53138
|
-
let idx = 0;
|
|
53139
|
-
for (const displayedRegion of self.lgv.displayedRegions) {
|
|
53140
|
-
for (const location of locations) {
|
|
53141
|
-
if (location.refSeq !== displayedRegion.refName) {
|
|
53142
|
-
continue;
|
|
53143
|
-
}
|
|
53144
|
-
const { end, refSeq, start } = location;
|
|
53145
|
-
const locationStartPxInfo = self.lgv.bpToPx({
|
|
53146
|
-
refName: refSeq,
|
|
53147
|
-
coord: start,
|
|
53148
|
-
regionNumber: idx,
|
|
53149
|
-
});
|
|
53150
|
-
if (!locationStartPxInfo) {
|
|
53151
|
-
continue;
|
|
53152
|
-
}
|
|
53153
|
-
const locationStartPx = locationStartPxInfo.offsetPx - self.lgv.offsetPx;
|
|
53154
|
-
const locationWidthPx = (end - start) / self.lgv.bpPerPx;
|
|
53155
|
-
ctx.fillStyle = 'rgba(0,255,0,.2)';
|
|
53156
|
-
ctx.fillRect(locationStartPx, 1, locationWidthPx, 100);
|
|
53157
|
-
ctx.fillStyle = 'black';
|
|
53158
|
-
ctx.fillText(collaborator.name, locationStartPx + 1, 11, locationWidthPx - 2);
|
|
53159
|
-
}
|
|
53160
|
-
idx++;
|
|
53161
|
-
}
|
|
53162
|
-
}
|
|
53163
|
-
}, { name: 'LinearApolloDisplayRenderCollaborators' }));
|
|
53164
|
-
},
|
|
53165
|
-
}));
|
|
53166
|
-
}
|
|
53167
|
-
function colorCode(letter, theme) {
|
|
53168
|
-
return (theme?.palette.bases[letter.toUpperCase()].main.toString() ?? 'lightgray');
|
|
53169
|
-
}
|
|
53170
|
-
function codonColorCode(letter) {
|
|
53171
|
-
const colorMap = {
|
|
53172
|
-
M: '#33ee33',
|
|
53173
|
-
'*': '#f44336',
|
|
53174
|
-
};
|
|
53175
|
-
return colorMap[letter.toUpperCase()];
|
|
53176
|
-
}
|
|
53177
|
-
function reverseCodonSeq(seq) {
|
|
53178
|
-
return [...seq]
|
|
53179
|
-
.map((c) => require$$1$2.revcom(c))
|
|
53180
|
-
.reverse()
|
|
53181
|
-
.join('');
|
|
53182
|
-
}
|
|
53183
|
-
function drawLetter(seqTrackctx, startPx, widthPx, letter, textY) {
|
|
53184
|
-
const fontSize = Math.min(widthPx, 10);
|
|
53185
|
-
seqTrackctx.fillStyle = '#000';
|
|
53186
|
-
seqTrackctx.font = `${fontSize}px`;
|
|
53187
|
-
const textWidth = seqTrackctx.measureText(letter).width;
|
|
53188
|
-
const textX = startPx + (widthPx - textWidth) / 2;
|
|
53189
|
-
seqTrackctx.fillText(letter, textX, textY + 10);
|
|
53190
|
-
}
|
|
53191
|
-
function drawTranslation(seqTrackctx, bpPerPx, trnslStartPx, trnslY, trnslWidthPx, sequenceRowHeight, seq, i, reverse) {
|
|
53192
|
-
let codonSeq = seq.slice(i, i + 3).toUpperCase();
|
|
53193
|
-
if (reverse) {
|
|
53194
|
-
codonSeq = reverseCodonSeq(codonSeq);
|
|
53195
|
-
}
|
|
53196
|
-
const codonLetter = require$$1$2.defaultCodonTable[codonSeq];
|
|
53197
|
-
if (!codonLetter) {
|
|
53198
|
-
return;
|
|
53199
|
-
}
|
|
53200
|
-
const fillColor = codonColorCode(codonLetter);
|
|
53201
|
-
if (fillColor) {
|
|
53202
|
-
seqTrackctx.fillStyle = fillColor;
|
|
53203
|
-
seqTrackctx.fillRect(trnslStartPx, trnslY, trnslWidthPx, sequenceRowHeight);
|
|
53204
|
-
}
|
|
53205
|
-
if (bpPerPx <= 0.1) {
|
|
53206
|
-
seqTrackctx.rect(trnslStartPx, trnslY, trnslWidthPx, sequenceRowHeight);
|
|
53207
|
-
seqTrackctx.stroke();
|
|
53208
|
-
drawLetter(seqTrackctx, trnslStartPx, trnslWidthPx, codonLetter, trnslY);
|
|
53209
|
-
}
|
|
53210
|
-
}
|
|
53211
|
-
function sequenceRenderingModelFactory(pluginManager, configSchema) {
|
|
53212
|
-
const LinearApolloDisplayRendering = renderingModelIntermediateFactory(pluginManager, configSchema);
|
|
53213
|
-
return LinearApolloDisplayRendering.actions((self) => ({
|
|
53214
|
-
afterAttach() {
|
|
53215
|
-
require$$1$3.addDisposer(self, mobx.autorun(async () => {
|
|
53216
|
-
if (!self.lgv.initialized || self.regionCannotBeRendered()) {
|
|
53217
|
-
return;
|
|
53218
|
-
}
|
|
53219
|
-
if (self.lgv.bpPerPx > 3) {
|
|
53220
|
-
return;
|
|
53221
|
-
}
|
|
53222
|
-
const seqTrackctx = self.seqTrackCanvas?.getContext('2d');
|
|
53223
|
-
if (!seqTrackctx) {
|
|
53224
|
-
return;
|
|
53225
|
-
}
|
|
53226
|
-
seqTrackctx.clearRect(0, 0, self.lgv.dynamicBlocks.totalWidthPx, self.lgv.bpPerPx <= 1 ? 125 : 95);
|
|
53227
|
-
const frames = self.lgv.bpPerPx <= 1
|
|
53228
|
-
? [3, 2, 1, 0, 0, -1, -2, -3]
|
|
53229
|
-
: [3, 2, 1, -1, -2, -3];
|
|
53230
|
-
let height = 0;
|
|
53231
|
-
for (const frame of frames) {
|
|
53232
|
-
const frameColor = self.theme?.palette.framesCDS.at(frame)?.main;
|
|
53233
|
-
if (frameColor) {
|
|
53234
|
-
seqTrackctx.fillStyle = frameColor;
|
|
53235
|
-
seqTrackctx.fillRect(0, height, self.lgv.dynamicBlocks.totalWidthPx, self.sequenceRowHeight);
|
|
53236
|
-
}
|
|
53237
|
-
height += self.sequenceRowHeight;
|
|
53238
|
-
}
|
|
53239
|
-
for (const [idx, region] of self.regions.entries()) {
|
|
53240
|
-
const driver = self.session.apolloDataStore.getBackendDriver(region.assemblyName);
|
|
53241
|
-
if (!driver) {
|
|
53242
|
-
throw new Error('Failed to get the backend driver');
|
|
53243
|
-
}
|
|
53244
|
-
const { seq } = await driver.getSequence(region);
|
|
53245
|
-
if (!seq) {
|
|
53246
|
-
return;
|
|
53247
|
-
}
|
|
53248
|
-
for (const [i, letter] of [...seq].entries()) {
|
|
53249
|
-
const trnslXOffset = (self.lgv.bpToPx({
|
|
53250
|
-
refName: region.refName,
|
|
53251
|
-
coord: region.start + i,
|
|
53252
|
-
regionNumber: idx,
|
|
53253
|
-
})?.offsetPx ?? 0) - self.lgv.offsetPx;
|
|
53254
|
-
const trnslWidthPx = 3 / self.lgv.bpPerPx;
|
|
53255
|
-
const trnslStartPx = self.lgv.displayedRegions[idx].reversed
|
|
53256
|
-
? trnslXOffset - trnslWidthPx
|
|
53257
|
-
: trnslXOffset;
|
|
53258
|
-
// Draw translation forward
|
|
53259
|
-
for (let j = 2; j >= 0; j--) {
|
|
53260
|
-
if ((region.start + i) % 3 === j) {
|
|
53261
|
-
drawTranslation(seqTrackctx, self.lgv.bpPerPx, trnslStartPx, self.sequenceRowHeight * (2 - j), trnslWidthPx, self.sequenceRowHeight, seq, i, false);
|
|
53262
|
-
}
|
|
53263
|
-
}
|
|
53264
|
-
if (self.lgv.bpPerPx <= 1) {
|
|
53265
|
-
const xOffset = (self.lgv.bpToPx({
|
|
53266
|
-
refName: region.refName,
|
|
53267
|
-
coord: region.start + i,
|
|
53268
|
-
regionNumber: idx,
|
|
53269
|
-
})?.offsetPx ?? 0) - self.lgv.offsetPx;
|
|
53270
|
-
const widthPx = 1 / self.lgv.bpPerPx;
|
|
53271
|
-
const startPx = self.lgv.displayedRegions[idx].reversed
|
|
53272
|
-
? xOffset - widthPx
|
|
53273
|
-
: xOffset;
|
|
53274
|
-
// Draw forward
|
|
53275
|
-
seqTrackctx.beginPath();
|
|
53276
|
-
seqTrackctx.fillStyle = colorCode(letter, self.theme);
|
|
53277
|
-
seqTrackctx.rect(startPx, self.sequenceRowHeight * 3, widthPx, self.sequenceRowHeight);
|
|
53278
|
-
seqTrackctx.fill();
|
|
53279
|
-
if (self.lgv.bpPerPx <= 0.1) {
|
|
53280
|
-
seqTrackctx.stroke();
|
|
53281
|
-
drawLetter(seqTrackctx, startPx, widthPx, letter, self.sequenceRowHeight * 3);
|
|
53282
|
-
}
|
|
53283
|
-
// Draw reverse
|
|
53284
|
-
const revLetter = require$$1$2.revcom(letter);
|
|
53285
|
-
seqTrackctx.beginPath();
|
|
53286
|
-
seqTrackctx.fillStyle = colorCode(revLetter, self.theme);
|
|
53287
|
-
seqTrackctx.rect(startPx, self.sequenceRowHeight * 4, widthPx, self.sequenceRowHeight);
|
|
53288
|
-
seqTrackctx.fill();
|
|
53289
|
-
if (self.lgv.bpPerPx <= 0.1) {
|
|
53290
|
-
seqTrackctx.stroke();
|
|
53291
|
-
drawLetter(seqTrackctx, startPx, widthPx, revLetter, self.sequenceRowHeight * 4);
|
|
53292
|
-
}
|
|
53293
|
-
}
|
|
53294
|
-
// Draw translation reverse
|
|
53295
|
-
for (let k = 0; k <= 2; k++) {
|
|
53296
|
-
const rowOffset = self.lgv.bpPerPx <= 1 ? 5 : 3;
|
|
53297
|
-
if ((region.start + i) % 3 === k) {
|
|
53298
|
-
drawTranslation(seqTrackctx, self.lgv.bpPerPx, trnslStartPx, self.sequenceRowHeight * (rowOffset + k), trnslWidthPx, self.sequenceRowHeight, seq, i, true);
|
|
53299
|
-
}
|
|
53300
|
-
}
|
|
53301
|
-
}
|
|
53302
|
-
}
|
|
53303
|
-
}, { name: 'LinearApolloDisplayRenderSequence' }));
|
|
53304
|
-
},
|
|
53305
|
-
}));
|
|
53306
|
-
}
|
|
53307
|
-
function renderingModelFactory(pluginManager, configSchema) {
|
|
53308
|
-
const LinearApolloDisplayRendering = sequenceRenderingModelFactory(pluginManager, configSchema);
|
|
53309
|
-
return LinearApolloDisplayRendering.actions((self) => ({
|
|
53310
|
-
afterAttach() {
|
|
53311
|
-
require$$1$3.addDisposer(self, mobx.autorun(() => {
|
|
53312
|
-
const { canvas, featureLayouts, featuresHeight, lgv } = self;
|
|
53313
|
-
if (!lgv.initialized || self.regionCannotBeRendered()) {
|
|
53314
|
-
return;
|
|
53315
|
-
}
|
|
53316
|
-
const { displayedRegions, dynamicBlocks } = lgv;
|
|
53317
|
-
const ctx = canvas?.getContext('2d');
|
|
53318
|
-
if (!ctx) {
|
|
53319
|
-
return;
|
|
53320
|
-
}
|
|
53321
|
-
ctx.clearRect(0, 0, dynamicBlocks.totalWidthPx, featuresHeight);
|
|
53322
|
-
for (const [idx, featureLayout] of featureLayouts.entries()) {
|
|
53323
|
-
const displayedRegion = displayedRegions[idx];
|
|
53324
|
-
for (const [row, featureLayoutRow] of featureLayout.entries()) {
|
|
53325
|
-
for (const [featureRow, feature] of featureLayoutRow) {
|
|
53326
|
-
if (featureRow > 0) {
|
|
53327
|
-
continue;
|
|
53328
|
-
}
|
|
53329
|
-
if (!require$$1$2.doesIntersect2(displayedRegion.start, displayedRegion.end, feature.min, feature.max)) {
|
|
53330
|
-
continue;
|
|
53331
|
-
}
|
|
53332
|
-
getGlyph(feature).draw(ctx, feature, row, self, idx);
|
|
53333
|
-
}
|
|
53334
|
-
}
|
|
53335
|
-
}
|
|
53336
|
-
}, { name: 'LinearApolloDisplayRenderFeatures' }));
|
|
53337
|
-
},
|
|
53338
|
-
}));
|
|
53339
|
-
}
|
|
53340
|
-
|
|
53341
|
-
function isMousePositionWithFeatureAndGlyph(mousePosition) {
|
|
53342
|
-
return 'featureAndGlyphUnderMouse' in mousePosition;
|
|
53343
|
-
}
|
|
53344
|
-
function getMousePosition(event, lgv) {
|
|
53345
|
-
const canvas = event.currentTarget;
|
|
53346
|
-
const { clientX, clientY } = event;
|
|
53347
|
-
const { left, top } = canvas.getBoundingClientRect();
|
|
53348
|
-
const x = clientX - left;
|
|
53349
|
-
const y = clientY - top;
|
|
53350
|
-
const { coord: bp, index: regionNumber, refName } = lgv.pxToBp(x);
|
|
53351
|
-
return { x, y, refName, bp, regionNumber };
|
|
53352
|
-
}
|
|
53353
|
-
function getTranslationRow(frame, bpPerPx) {
|
|
53354
|
-
const offset = bpPerPx <= 1 ? 2 : 0;
|
|
53355
|
-
switch (frame) {
|
|
53356
|
-
case 3: {
|
|
53357
|
-
return 0;
|
|
53358
|
-
}
|
|
53359
|
-
case 2: {
|
|
53360
|
-
return 1;
|
|
53361
|
-
}
|
|
53362
|
-
case 1: {
|
|
53363
|
-
return 2;
|
|
53364
|
-
}
|
|
53365
|
-
case -1: {
|
|
53366
|
-
return 3 + offset;
|
|
53367
|
-
}
|
|
53368
|
-
case -2: {
|
|
53369
|
-
return 4 + offset;
|
|
53370
|
-
}
|
|
53371
|
-
case -3: {
|
|
53372
|
-
return 5 + offset;
|
|
53373
|
-
}
|
|
53374
|
-
}
|
|
53375
|
-
}
|
|
53376
|
-
function getSeqRow(strand, bpPerPx) {
|
|
53377
|
-
if (bpPerPx > 1 || strand === undefined) {
|
|
53378
|
-
return;
|
|
53379
|
-
}
|
|
53380
|
-
return strand === 1 ? 3 : 4;
|
|
53381
|
-
}
|
|
53382
|
-
function highlightSeq(seqTrackOverlayctx, theme, startPx, sequenceRowHeight, row, widthPx) {
|
|
53383
|
-
if (row !== undefined) {
|
|
53384
|
-
seqTrackOverlayctx.fillStyle =
|
|
53385
|
-
theme?.palette.action.focus ?? 'rgba(0,0,0,0.04)';
|
|
53386
|
-
seqTrackOverlayctx.fillRect(startPx, sequenceRowHeight * row, widthPx, sequenceRowHeight);
|
|
53387
|
-
}
|
|
53388
|
-
}
|
|
53389
|
-
function mouseEventsModelIntermediateFactory(pluginManager, configSchema) {
|
|
53390
|
-
const LinearApolloDisplayRendering = renderingModelFactory(pluginManager, configSchema);
|
|
53391
|
-
return LinearApolloDisplayRendering.named('LinearApolloDisplayMouseEvents')
|
|
53392
|
-
.volatile(() => ({
|
|
53393
|
-
apolloDragging: null,
|
|
53394
|
-
cursor: undefined,
|
|
53395
|
-
apolloHover: undefined,
|
|
53396
|
-
}))
|
|
53397
|
-
.views((self) => ({
|
|
53398
|
-
getMousePosition(event) {
|
|
53399
|
-
const mousePosition = getMousePosition(event, self.lgv);
|
|
53400
|
-
const { bp, regionNumber, y } = mousePosition;
|
|
53401
|
-
const row = Math.floor(y / self.apolloRowHeight);
|
|
53402
|
-
const featureLayout = self.featureLayouts[regionNumber];
|
|
53403
|
-
const layoutRow = featureLayout.get(row);
|
|
53404
|
-
if (!layoutRow) {
|
|
53405
|
-
return mousePosition;
|
|
53406
|
-
}
|
|
53407
|
-
const foundFeature = layoutRow.find((f) => bp >= f[1].min && bp <= f[1].max);
|
|
53408
|
-
if (!foundFeature) {
|
|
53409
|
-
return mousePosition;
|
|
53410
|
-
}
|
|
53411
|
-
const [featureRow, topLevelFeature] = foundFeature;
|
|
53412
|
-
const glyph = getGlyph(topLevelFeature);
|
|
53413
|
-
const feature = glyph.getFeatureFromLayout(topLevelFeature, bp, featureRow);
|
|
53414
|
-
if (!feature) {
|
|
53415
|
-
return mousePosition;
|
|
53416
|
-
}
|
|
53417
|
-
return {
|
|
53418
|
-
...mousePosition,
|
|
53419
|
-
featureAndGlyphUnderMouse: { feature, topLevelFeature, glyph },
|
|
53420
|
-
};
|
|
53421
|
-
},
|
|
53422
|
-
}))
|
|
53423
|
-
.actions((self) => ({
|
|
53424
|
-
continueDrag(mousePosition, event) {
|
|
53425
|
-
if (!self.apolloDragging) {
|
|
53426
|
-
throw new Error('continueDrag() called with no current drag in progress');
|
|
53427
|
-
}
|
|
53428
|
-
event.stopPropagation();
|
|
53429
|
-
self.apolloDragging = { ...self.apolloDragging, current: mousePosition };
|
|
53430
|
-
},
|
|
53431
|
-
setDragging(dragInfo) {
|
|
53432
|
-
self.apolloDragging = dragInfo ?? null;
|
|
53433
|
-
},
|
|
53434
|
-
}))
|
|
53435
|
-
.actions((self) => ({
|
|
53436
|
-
setApolloHover(n) {
|
|
53437
|
-
self.apolloHover = n;
|
|
53438
|
-
},
|
|
53439
|
-
setCursor(cursor) {
|
|
53440
|
-
if (self.cursor !== cursor) {
|
|
53441
|
-
self.cursor = cursor;
|
|
53442
|
-
}
|
|
53443
|
-
},
|
|
53444
|
-
}))
|
|
53445
|
-
.actions(() => ({
|
|
53446
|
-
// onClick(event: CanvasMouseEvent) {
|
|
53447
|
-
onClick() {
|
|
53448
|
-
// TODO: set the selected feature
|
|
53449
|
-
},
|
|
53450
|
-
}));
|
|
53451
|
-
}
|
|
53452
|
-
function mouseEventsSeqHightlightModelFactory(pluginManager, configSchema) {
|
|
53453
|
-
const LinearApolloDisplayRendering = mouseEventsModelIntermediateFactory(pluginManager, configSchema);
|
|
53454
|
-
return LinearApolloDisplayRendering.actions((self) => ({
|
|
53455
|
-
afterAttach() {
|
|
53456
|
-
require$$1$3.addDisposer(self, mobx.autorun(() => {
|
|
53457
|
-
// This type is wrong in @jbrowse/core
|
|
53458
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
53459
|
-
if (!self.lgv.initialized || self.regionCannotBeRendered()) {
|
|
53460
|
-
return;
|
|
53461
|
-
}
|
|
53462
|
-
const seqTrackOverlayctx = self.seqTrackOverlayCanvas?.getContext('2d');
|
|
53463
|
-
if (!seqTrackOverlayctx) {
|
|
53464
|
-
return;
|
|
53465
|
-
}
|
|
53466
|
-
seqTrackOverlayctx.clearRect(0, 0, self.lgv.dynamicBlocks.totalWidthPx, self.lgv.bpPerPx <= 1 ? 125 : 95);
|
|
53467
|
-
const { apolloHover, lgv, regions, sequenceRowHeight, theme } = self;
|
|
53468
|
-
if (!apolloHover) {
|
|
53469
|
-
return;
|
|
53470
|
-
}
|
|
53471
|
-
const { feature } = apolloHover;
|
|
53472
|
-
for (const [idx, region] of regions.entries()) {
|
|
53473
|
-
if (feature.type === 'CDS') {
|
|
53474
|
-
const parentFeature = feature.parent;
|
|
53475
|
-
if (!parentFeature) {
|
|
53476
|
-
continue;
|
|
53477
|
-
}
|
|
53478
|
-
const cdsLocs = parentFeature.cdsLocations.find((loc) => feature.min === loc.at(0)?.min &&
|
|
53479
|
-
feature.max === loc.at(-1)?.max);
|
|
53480
|
-
if (!cdsLocs) {
|
|
53481
|
-
continue;
|
|
53482
|
-
}
|
|
53483
|
-
for (const dl of cdsLocs) {
|
|
53484
|
-
const frame = require$$1$2.getFrame(dl.min, dl.max, feature.strand ?? 1, dl.phase);
|
|
53485
|
-
const row = getTranslationRow(frame, lgv.bpPerPx);
|
|
53486
|
-
const offset = (lgv.bpToPx({
|
|
53487
|
-
refName: region.refName,
|
|
53488
|
-
coord: dl.min,
|
|
53489
|
-
regionNumber: idx,
|
|
53490
|
-
})?.offsetPx ?? 0) - lgv.offsetPx;
|
|
53491
|
-
const widthPx = (dl.max - dl.min) / lgv.bpPerPx;
|
|
53492
|
-
const startPx = lgv.displayedRegions[idx].reversed
|
|
53493
|
-
? offset - widthPx
|
|
53494
|
-
: offset;
|
|
53495
|
-
highlightSeq(seqTrackOverlayctx, theme, startPx, sequenceRowHeight, row, widthPx);
|
|
53496
|
-
}
|
|
53497
|
-
}
|
|
53498
|
-
else {
|
|
53499
|
-
const row = getSeqRow(feature.strand, lgv.bpPerPx);
|
|
53500
|
-
const offset = (lgv.bpToPx({
|
|
53501
|
-
refName: region.refName,
|
|
53502
|
-
coord: feature.min,
|
|
53503
|
-
regionNumber: idx,
|
|
53504
|
-
})?.offsetPx ?? 0) - lgv.offsetPx;
|
|
53505
|
-
const widthPx = feature.length / lgv.bpPerPx;
|
|
53506
|
-
const startPx = lgv.displayedRegions[idx].reversed
|
|
53507
|
-
? offset - widthPx
|
|
53508
|
-
: offset;
|
|
53509
|
-
highlightSeq(seqTrackOverlayctx, theme, startPx, sequenceRowHeight, row, widthPx);
|
|
53510
|
-
}
|
|
53511
|
-
}
|
|
53512
|
-
}, { name: 'LinearApolloDisplayRenderSeqHighlight' }));
|
|
53513
|
-
},
|
|
53514
|
-
}));
|
|
53515
|
-
}
|
|
53516
|
-
function mouseEventsModelFactory(pluginManager, configSchema) {
|
|
53517
|
-
const LinearApolloDisplayMouseEvents = mouseEventsSeqHightlightModelFactory(pluginManager, configSchema);
|
|
53518
|
-
return LinearApolloDisplayMouseEvents.views((self) => ({
|
|
53519
|
-
contextMenuItems(contextCoord) {
|
|
53520
|
-
const { apolloHover } = self;
|
|
53521
|
-
if (!(apolloHover && contextCoord)) {
|
|
53522
|
-
return [];
|
|
53523
|
-
}
|
|
53524
|
-
const { topLevelFeature } = apolloHover;
|
|
53525
|
-
const glyph = getGlyph(topLevelFeature);
|
|
53526
|
-
return glyph.getContextMenuItems(self);
|
|
53527
|
-
},
|
|
53528
|
-
}))
|
|
53529
|
-
.actions((self) => ({
|
|
53530
|
-
// explicitly pass in a feature in case it's not the same as the one in
|
|
53531
|
-
// mousePosition (e.g. if features are drawn overlapping).
|
|
53532
|
-
startDrag(mousePosition, feature, edge) {
|
|
53533
|
-
self.apolloDragging = {
|
|
53534
|
-
start: mousePosition,
|
|
53535
|
-
current: mousePosition,
|
|
53536
|
-
feature,
|
|
53537
|
-
edge,
|
|
53538
|
-
};
|
|
53539
|
-
},
|
|
53540
|
-
endDrag() {
|
|
53541
|
-
if (!self.apolloDragging) {
|
|
53542
|
-
throw new Error('endDrag() called with no current drag in progress');
|
|
53543
|
-
}
|
|
53544
|
-
const { current, edge, feature, start } = self.apolloDragging;
|
|
53545
|
-
// don't do anything if it was only dragged a tiny bit
|
|
53546
|
-
if (Math.abs(current.x - start.x) <= 4) {
|
|
53547
|
-
self.setDragging();
|
|
53548
|
-
self.setCursor();
|
|
53549
|
-
return;
|
|
53550
|
-
}
|
|
53551
|
-
const { displayedRegions } = self.lgv;
|
|
53552
|
-
const region = displayedRegions[start.regionNumber];
|
|
53553
|
-
const assembly = self.getAssemblyId(region.assemblyName);
|
|
53554
|
-
let change;
|
|
53555
|
-
if (edge === 'max') {
|
|
53556
|
-
const featureId = feature._id;
|
|
53557
|
-
const oldEnd = feature.max;
|
|
53558
|
-
const newEnd = current.bp;
|
|
53559
|
-
change = new dist$2.LocationEndChange({
|
|
53560
|
-
typeName: 'LocationEndChange',
|
|
53561
|
-
changedIds: [featureId],
|
|
53562
|
-
featureId,
|
|
53563
|
-
oldEnd,
|
|
53564
|
-
newEnd,
|
|
53565
|
-
assembly,
|
|
53566
|
-
});
|
|
53567
|
-
}
|
|
53568
|
-
else {
|
|
53569
|
-
const featureId = feature._id;
|
|
53570
|
-
const oldStart = feature.min;
|
|
53571
|
-
const newStart = current.bp;
|
|
53572
|
-
change = new dist$2.LocationStartChange({
|
|
53573
|
-
typeName: 'LocationStartChange',
|
|
53574
|
-
changedIds: [featureId],
|
|
53575
|
-
featureId,
|
|
53576
|
-
oldStart,
|
|
53577
|
-
newStart,
|
|
53578
|
-
assembly,
|
|
53579
|
-
});
|
|
53580
|
-
}
|
|
53581
|
-
void self.changeManager.submit(change);
|
|
53582
|
-
self.setDragging();
|
|
53583
|
-
self.setCursor();
|
|
53584
|
-
},
|
|
53585
|
-
}))
|
|
53586
|
-
.actions((self) => ({
|
|
53587
|
-
onMouseDown(event) {
|
|
53588
|
-
const mousePosition = self.getMousePosition(event);
|
|
53589
|
-
if (isMousePositionWithFeatureAndGlyph(mousePosition)) {
|
|
53590
|
-
mousePosition.featureAndGlyphUnderMouse.glyph.onMouseDown(self, mousePosition, event);
|
|
53591
|
-
}
|
|
53592
|
-
},
|
|
53593
|
-
onMouseMove(event) {
|
|
53594
|
-
const mousePosition = self.getMousePosition(event);
|
|
53595
|
-
if (self.apolloDragging) {
|
|
53596
|
-
self.setCursor('col-resize');
|
|
53597
|
-
self.continueDrag(mousePosition, event);
|
|
53598
|
-
return;
|
|
53599
|
-
}
|
|
53600
|
-
if (isMousePositionWithFeatureAndGlyph(mousePosition)) {
|
|
53601
|
-
mousePosition.featureAndGlyphUnderMouse.glyph.onMouseMove(self, mousePosition, event);
|
|
53602
|
-
}
|
|
53603
|
-
else {
|
|
53604
|
-
self.setApolloHover();
|
|
53605
|
-
self.setCursor();
|
|
53606
|
-
}
|
|
53607
|
-
},
|
|
53608
|
-
onMouseLeave(event) {
|
|
53609
|
-
self.setDragging();
|
|
53610
|
-
self.setApolloHover();
|
|
53611
|
-
const mousePosition = self.getMousePosition(event);
|
|
53612
|
-
if (isMousePositionWithFeatureAndGlyph(mousePosition)) {
|
|
53613
|
-
mousePosition.featureAndGlyphUnderMouse.glyph.onMouseLeave(self, mousePosition, event);
|
|
53614
|
-
}
|
|
53615
|
-
},
|
|
53616
|
-
onMouseUp(event) {
|
|
53617
|
-
const mousePosition = self.getMousePosition(event);
|
|
53618
|
-
if (isMousePositionWithFeatureAndGlyph(mousePosition)) {
|
|
53619
|
-
mousePosition.featureAndGlyphUnderMouse.glyph.onMouseUp(self, mousePosition, event);
|
|
53620
|
-
}
|
|
53621
|
-
if (self.apolloDragging) {
|
|
53622
|
-
self.endDrag();
|
|
53623
|
-
}
|
|
53624
|
-
},
|
|
53625
|
-
}))
|
|
53626
|
-
.actions((self) => ({
|
|
53627
|
-
afterAttach() {
|
|
53628
|
-
require$$1$3.addDisposer(self, mobx.autorun(() => {
|
|
53629
|
-
// This type is wrong in @jbrowse/core
|
|
53630
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
53631
|
-
if (!self.lgv.initialized || self.regionCannotBeRendered()) {
|
|
53632
|
-
return;
|
|
53633
|
-
}
|
|
53634
|
-
const ctx = self.overlayCanvas?.getContext('2d');
|
|
53635
|
-
if (!ctx) {
|
|
53636
|
-
return;
|
|
53637
|
-
}
|
|
53638
|
-
ctx.clearRect(0, 0, self.lgv.dynamicBlocks.totalWidthPx, self.featuresHeight);
|
|
53639
|
-
const { apolloDragging, apolloHover } = self;
|
|
53640
|
-
if (!apolloHover) {
|
|
53641
|
-
return;
|
|
53642
|
-
}
|
|
53643
|
-
const { glyph } = apolloHover;
|
|
53644
|
-
// draw mouseover hovers
|
|
53645
|
-
glyph.drawHover(self, ctx);
|
|
53646
|
-
// draw tooltip on hover
|
|
53647
|
-
glyph.drawTooltip(self, ctx);
|
|
53648
|
-
// dragging previews
|
|
53649
|
-
if (apolloDragging) {
|
|
53650
|
-
// NOTE: the glyph where the drag started is responsible for drawing the preview.
|
|
53651
|
-
// it can call methods in other glyphs to help with this though.
|
|
53652
|
-
const glyph = getGlyph(apolloDragging.feature.topLevelFeature);
|
|
53653
|
-
glyph.drawDragPreview(self, ctx);
|
|
53654
|
-
}
|
|
53655
|
-
}, { name: 'LinearApolloDisplayRenderMouseoverAndDrag' }));
|
|
53656
|
-
},
|
|
53657
|
-
}));
|
|
53658
|
-
}
|
|
53659
|
-
|
|
53660
53851
|
function drawBoxOutline(ctx, x, y, width, height, color) {
|
|
53661
53852
|
drawBox(ctx, x, y, width, height, color);
|
|
53662
53853
|
if (width <= 2) {
|
|
@@ -53921,7 +54112,12 @@
|
|
|
53921
54112
|
session.showWidget(apolloFeatureWidget);
|
|
53922
54113
|
},
|
|
53923
54114
|
});
|
|
53924
|
-
|
|
54115
|
+
const { featureTypeOntology } = session.apolloDataStore.ontologyManager;
|
|
54116
|
+
if (!featureTypeOntology) {
|
|
54117
|
+
throw new Error('featureTypeOntology is undefined');
|
|
54118
|
+
}
|
|
54119
|
+
if (featureTypeOntology.isTypeOf(sourceFeature.type, 'transcript') &&
|
|
54120
|
+
require$$1$2.isSessionModelWithWidgets(session)) {
|
|
53925
54121
|
menuItems.push({
|
|
53926
54122
|
label: 'Edit transcript details',
|
|
53927
54123
|
onClick: () => {
|
|
@@ -54079,6 +54275,11 @@
|
|
|
54079
54275
|
return;
|
|
54080
54276
|
}
|
|
54081
54277
|
const { apolloSelectedFeature } = session;
|
|
54278
|
+
const { apolloDataStore } = session;
|
|
54279
|
+
const { featureTypeOntology } = apolloDataStore.ontologyManager;
|
|
54280
|
+
if (!featureTypeOntology) {
|
|
54281
|
+
throw new Error('featureTypeOntology is undefined');
|
|
54282
|
+
}
|
|
54082
54283
|
// Draw background for gene
|
|
54083
54284
|
const topLevelFeatureMinX = (lgv.bpToPx({
|
|
54084
54285
|
refName,
|
|
@@ -54090,22 +54291,23 @@
|
|
|
54090
54291
|
? topLevelFeatureMinX - topLevelFeatureWidthPx
|
|
54091
54292
|
: topLevelFeatureMinX;
|
|
54092
54293
|
const topLevelFeatureTop = row * rowHeight;
|
|
54093
|
-
const topLevelFeatureHeight = getRowCount$1(feature) * rowHeight;
|
|
54294
|
+
const topLevelFeatureHeight = getRowCount$1(feature, featureTypeOntology) * rowHeight;
|
|
54094
54295
|
ctx.fillStyle = material.alpha(theme?.palette.background.paper ?? '#ffffff', 0.6);
|
|
54095
54296
|
ctx.fillRect(topLevelFeatureStartPx, topLevelFeatureTop, topLevelFeatureWidthPx, topLevelFeatureHeight);
|
|
54096
|
-
// Draw lines on different rows for each
|
|
54297
|
+
// Draw lines on different rows for each transcript
|
|
54097
54298
|
let currentRow = 0;
|
|
54098
|
-
for (const [,
|
|
54099
|
-
|
|
54299
|
+
for (const [, transcript] of children) {
|
|
54300
|
+
const isTranscript = featureTypeOntology.isTypeOf(transcript.type, 'transcript');
|
|
54301
|
+
if (!isTranscript) {
|
|
54100
54302
|
currentRow += 1;
|
|
54101
54303
|
continue;
|
|
54102
54304
|
}
|
|
54103
|
-
const { children:
|
|
54104
|
-
if (!
|
|
54305
|
+
const { children: childrenOfTranscript, min } = transcript;
|
|
54306
|
+
if (!childrenOfTranscript) {
|
|
54105
54307
|
continue;
|
|
54106
54308
|
}
|
|
54107
|
-
for (const [, cds] of
|
|
54108
|
-
if (cds.type
|
|
54309
|
+
for (const [, cds] of childrenOfTranscript) {
|
|
54310
|
+
if (!featureTypeOntology.isTypeOf(cds.type, 'CDS')) {
|
|
54109
54311
|
continue;
|
|
54110
54312
|
}
|
|
54111
54313
|
const minX = (lgv.bpToPx({
|
|
@@ -54113,7 +54315,7 @@
|
|
|
54113
54315
|
coord: min,
|
|
54114
54316
|
regionNumber: displayedRegionIndex,
|
|
54115
54317
|
})?.offsetPx ?? 0) - offsetPx;
|
|
54116
|
-
const widthPx =
|
|
54318
|
+
const widthPx = transcript.length / bpPerPx;
|
|
54117
54319
|
const startPx = reversed ? minX - widthPx : minX;
|
|
54118
54320
|
const height = Math.round((currentRow + 1 / 2) * rowHeight) + row * rowHeight;
|
|
54119
54321
|
ctx.strokeStyle = theme?.palette.text.primary ?? 'black';
|
|
@@ -54126,21 +54328,21 @@
|
|
|
54126
54328
|
}
|
|
54127
54329
|
const forwardFill = theme?.palette.mode === 'dark' ? forwardFillDark : forwardFillLight;
|
|
54128
54330
|
const backwardFill = theme?.palette.mode === 'dark' ? backwardFillDark : backwardFillLight;
|
|
54129
|
-
// Draw exon and CDS for each
|
|
54331
|
+
// Draw exon and CDS for each transcript
|
|
54130
54332
|
currentRow = 0;
|
|
54131
54333
|
for (const [, child] of children) {
|
|
54132
|
-
if (child.type
|
|
54334
|
+
if (!featureTypeOntology.isTypeOf(child.type, 'transcript')) {
|
|
54133
54335
|
boxGlyph.draw(ctx, child, row, stateModel, displayedRegionIndex);
|
|
54134
54336
|
currentRow += 1;
|
|
54135
54337
|
continue;
|
|
54136
54338
|
}
|
|
54137
54339
|
for (const cdsRow of child.cdsLocations) {
|
|
54138
|
-
const { _id, children:
|
|
54139
|
-
if (!
|
|
54340
|
+
const { _id, children: childrenOfTranscript } = child;
|
|
54341
|
+
if (!childrenOfTranscript) {
|
|
54140
54342
|
continue;
|
|
54141
54343
|
}
|
|
54142
|
-
for (const [, exon] of
|
|
54143
|
-
if (exon.type
|
|
54344
|
+
for (const [, exon] of childrenOfTranscript) {
|
|
54345
|
+
if (!featureTypeOntology.isTypeOf(exon.type, 'exon')) {
|
|
54144
54346
|
continue;
|
|
54145
54347
|
}
|
|
54146
54348
|
const minX = (lgv.bpToPx({
|
|
@@ -54236,7 +54438,8 @@
|
|
|
54236
54438
|
overlayCtx.fillRect(rectX, rectY, rectWidth, rectHeight);
|
|
54237
54439
|
}
|
|
54238
54440
|
function drawHover$1(stateModel, ctx) {
|
|
54239
|
-
const { apolloHover, apolloRowHeight, lgv, theme } = stateModel;
|
|
54441
|
+
const { apolloHover, apolloRowHeight, lgv, session, theme } = stateModel;
|
|
54442
|
+
const { featureTypeOntology } = session.apolloDataStore.ontologyManager;
|
|
54240
54443
|
if (!apolloHover) {
|
|
54241
54444
|
return;
|
|
54242
54445
|
}
|
|
@@ -54259,10 +54462,13 @@
|
|
|
54259
54462
|
const top = row * apolloRowHeight;
|
|
54260
54463
|
const widthPx = length / bpPerPx;
|
|
54261
54464
|
ctx.fillStyle = theme?.palette.action.selected ?? 'rgba(0,0,0,04)';
|
|
54262
|
-
|
|
54465
|
+
if (!featureTypeOntology) {
|
|
54466
|
+
throw new Error('featureTypeOntology is undefined');
|
|
54467
|
+
}
|
|
54468
|
+
ctx.fillRect(startPx, top, widthPx, apolloRowHeight * getRowCount$1(feature, featureTypeOntology));
|
|
54263
54469
|
}
|
|
54264
|
-
function getFeatureFromLayout$1(feature, bp, row) {
|
|
54265
|
-
const featureInThisRow = featuresForRow$1(feature)[row] || [];
|
|
54470
|
+
function getFeatureFromLayout$1(feature, bp, row, featureTypeOntology) {
|
|
54471
|
+
const featureInThisRow = featuresForRow$1(feature, featureTypeOntology)[row] || [];
|
|
54266
54472
|
for (const f of featureInThisRow) {
|
|
54267
54473
|
let featureObj;
|
|
54268
54474
|
if (bp >= f.min && bp <= f.max && f.parent) {
|
|
@@ -54271,9 +54477,9 @@
|
|
|
54271
54477
|
if (!featureObj) {
|
|
54272
54478
|
continue;
|
|
54273
54479
|
}
|
|
54274
|
-
if (featureObj.type
|
|
54480
|
+
if (featureTypeOntology.isTypeOf(featureObj.type, 'CDS') &&
|
|
54275
54481
|
featureObj.parent &&
|
|
54276
|
-
featureObj.parent.type
|
|
54482
|
+
featureTypeOntology.isTypeOf(featureObj.parent.type, 'transcript')) {
|
|
54277
54483
|
const { cdsLocations } = featureObj.parent;
|
|
54278
54484
|
for (const cdsLoc of cdsLocations) {
|
|
54279
54485
|
for (const loc of cdsLoc) {
|
|
@@ -54282,7 +54488,7 @@
|
|
|
54282
54488
|
}
|
|
54283
54489
|
}
|
|
54284
54490
|
}
|
|
54285
|
-
// If mouse position is in the intron region, return the
|
|
54491
|
+
// If mouse position is in the intron region, return the transcript
|
|
54286
54492
|
return featureObj.parent;
|
|
54287
54493
|
}
|
|
54288
54494
|
// If mouse position is in a feature that is not a CDS, return the feature
|
|
@@ -54290,33 +54496,36 @@
|
|
|
54290
54496
|
}
|
|
54291
54497
|
return feature;
|
|
54292
54498
|
}
|
|
54293
|
-
function getRowCount$1(feature, _bpPerPx) {
|
|
54499
|
+
function getRowCount$1(feature, featureTypeOntology, _bpPerPx) {
|
|
54294
54500
|
const { children, type } = feature;
|
|
54295
54501
|
if (!children) {
|
|
54296
54502
|
return 1;
|
|
54297
54503
|
}
|
|
54504
|
+
const isTranscript = featureTypeOntology.isTypeOf(type, 'transcript');
|
|
54298
54505
|
let rowCount = 0;
|
|
54299
|
-
if (
|
|
54506
|
+
if (isTranscript) {
|
|
54300
54507
|
for (const [, child] of children) {
|
|
54301
|
-
|
|
54508
|
+
const isCds = featureTypeOntology.isTypeOf(child.type, 'CDS');
|
|
54509
|
+
if (isCds) {
|
|
54302
54510
|
rowCount += 1;
|
|
54303
54511
|
}
|
|
54304
54512
|
}
|
|
54305
54513
|
return rowCount;
|
|
54306
54514
|
}
|
|
54307
54515
|
for (const [, child] of children) {
|
|
54308
|
-
rowCount += getRowCount$1(child);
|
|
54516
|
+
rowCount += getRowCount$1(child, featureTypeOntology);
|
|
54309
54517
|
}
|
|
54310
54518
|
return rowCount;
|
|
54311
54519
|
}
|
|
54312
54520
|
/**
|
|
54313
54521
|
* A list of all the subfeatures for each row for a given feature, as well as
|
|
54314
54522
|
* the feature itself.
|
|
54315
|
-
* If the row contains
|
|
54316
|
-
* If the row does not contain an
|
|
54523
|
+
* If the row contains a transcript, the order is CDS -\> exon -\> transcript -\> gene
|
|
54524
|
+
* If the row does not contain an transcript, the order is subfeature -\> gene
|
|
54317
54525
|
*/
|
|
54318
|
-
function featuresForRow$1(feature) {
|
|
54319
|
-
|
|
54526
|
+
function featuresForRow$1(feature, featureTypeOntology) {
|
|
54527
|
+
const isGene = featureTypeOntology.isTypeOf(feature.type, 'gene');
|
|
54528
|
+
if (!isGene) {
|
|
54320
54529
|
throw new Error('Top level feature for GeneGlyph must have type "gene"');
|
|
54321
54530
|
}
|
|
54322
54531
|
const { children } = feature;
|
|
@@ -54325,7 +54534,7 @@
|
|
|
54325
54534
|
}
|
|
54326
54535
|
const features = [];
|
|
54327
54536
|
for (const [, child] of children) {
|
|
54328
|
-
if (child.type
|
|
54537
|
+
if (!featureTypeOntology.isTypeOf(child.type, 'transcript')) {
|
|
54329
54538
|
features.push([child, feature]);
|
|
54330
54539
|
continue;
|
|
54331
54540
|
}
|
|
@@ -54335,10 +54544,10 @@
|
|
|
54335
54544
|
const cdss = [];
|
|
54336
54545
|
const exons = [];
|
|
54337
54546
|
for (const [, grandchild] of child.children) {
|
|
54338
|
-
if (grandchild.type
|
|
54547
|
+
if (featureTypeOntology.isTypeOf(grandchild.type, 'CDS')) {
|
|
54339
54548
|
cdss.push(grandchild);
|
|
54340
54549
|
}
|
|
54341
|
-
else if (grandchild.type
|
|
54550
|
+
else if (featureTypeOntology.isTypeOf(grandchild.type, 'exon')) {
|
|
54342
54551
|
exons.push(grandchild);
|
|
54343
54552
|
}
|
|
54344
54553
|
}
|
|
@@ -54348,599 +54557,1082 @@
|
|
|
54348
54557
|
}
|
|
54349
54558
|
return features;
|
|
54350
54559
|
}
|
|
54351
|
-
function getRowForFeature$1(feature, childFeature) {
|
|
54352
|
-
const rows = featuresForRow$1(feature);
|
|
54353
|
-
for (const [idx, row] of rows.entries()) {
|
|
54354
|
-
if (row.some((feature) => feature._id === childFeature._id)) {
|
|
54355
|
-
return idx;
|
|
54356
|
-
}
|
|
54357
|
-
}
|
|
54358
|
-
return;
|
|
54560
|
+
function getRowForFeature$1(feature, childFeature, featureTypeOntology) {
|
|
54561
|
+
const rows = featuresForRow$1(feature, featureTypeOntology);
|
|
54562
|
+
for (const [idx, row] of rows.entries()) {
|
|
54563
|
+
if (row.some((feature) => feature._id === childFeature._id)) {
|
|
54564
|
+
return idx;
|
|
54565
|
+
}
|
|
54566
|
+
}
|
|
54567
|
+
return;
|
|
54568
|
+
}
|
|
54569
|
+
function onMouseDown$1(stateModel, currentMousePosition, event) {
|
|
54570
|
+
const { featureAndGlyphUnderMouse } = currentMousePosition;
|
|
54571
|
+
// swallow the mouseDown if we are on the edge of the feature so that we
|
|
54572
|
+
// don't start dragging the view if we try to drag the feature edge
|
|
54573
|
+
const { feature } = featureAndGlyphUnderMouse;
|
|
54574
|
+
const draggableFeature = getDraggableFeatureInfo(currentMousePosition, feature, stateModel);
|
|
54575
|
+
if (draggableFeature) {
|
|
54576
|
+
event.stopPropagation();
|
|
54577
|
+
stateModel.startDrag(currentMousePosition, draggableFeature.feature, draggableFeature.edge);
|
|
54578
|
+
}
|
|
54579
|
+
}
|
|
54580
|
+
function onMouseMove$1(stateModel, mousePosition) {
|
|
54581
|
+
if (isMousePositionWithFeatureAndGlyph(mousePosition)) {
|
|
54582
|
+
const { featureAndGlyphUnderMouse } = mousePosition;
|
|
54583
|
+
stateModel.setApolloHover(featureAndGlyphUnderMouse);
|
|
54584
|
+
const { feature } = featureAndGlyphUnderMouse;
|
|
54585
|
+
const draggableFeature = getDraggableFeatureInfo(mousePosition, feature, stateModel);
|
|
54586
|
+
if (draggableFeature) {
|
|
54587
|
+
stateModel.setCursor('col-resize');
|
|
54588
|
+
return;
|
|
54589
|
+
}
|
|
54590
|
+
}
|
|
54591
|
+
stateModel.setCursor();
|
|
54592
|
+
}
|
|
54593
|
+
function onMouseUp$1(stateModel, mousePosition) {
|
|
54594
|
+
if (stateModel.apolloDragging) {
|
|
54595
|
+
return;
|
|
54596
|
+
}
|
|
54597
|
+
const { featureAndGlyphUnderMouse } = mousePosition;
|
|
54598
|
+
if (featureAndGlyphUnderMouse?.feature) {
|
|
54599
|
+
stateModel.setSelectedFeature(featureAndGlyphUnderMouse.feature);
|
|
54600
|
+
}
|
|
54601
|
+
}
|
|
54602
|
+
function getDraggableFeatureInfo(mousePosition, feature, stateModel) {
|
|
54603
|
+
const { session } = stateModel;
|
|
54604
|
+
const { apolloDataStore } = session;
|
|
54605
|
+
const { featureTypeOntology } = apolloDataStore.ontologyManager;
|
|
54606
|
+
if (!featureTypeOntology) {
|
|
54607
|
+
throw new Error('featureTypeOntology is undefined');
|
|
54608
|
+
}
|
|
54609
|
+
const isGene = featureTypeOntology.isTypeOf(feature.type, 'gene');
|
|
54610
|
+
const isTranscript = featureTypeOntology.isTypeOf(feature.type, 'transcript');
|
|
54611
|
+
const isCds = featureTypeOntology.isTypeOf(feature.type, 'CDS');
|
|
54612
|
+
if (isGene || isTranscript) {
|
|
54613
|
+
return;
|
|
54614
|
+
}
|
|
54615
|
+
const { bp, refName, regionNumber, x } = mousePosition;
|
|
54616
|
+
const { lgv } = stateModel;
|
|
54617
|
+
const { offsetPx } = lgv;
|
|
54618
|
+
const minPxInfo = lgv.bpToPx({ refName, coord: feature.min, regionNumber });
|
|
54619
|
+
const maxPxInfo = lgv.bpToPx({ refName, coord: feature.max, regionNumber });
|
|
54620
|
+
if (minPxInfo === undefined || maxPxInfo === undefined) {
|
|
54621
|
+
return;
|
|
54622
|
+
}
|
|
54623
|
+
const minPx = minPxInfo.offsetPx - offsetPx;
|
|
54624
|
+
const maxPx = maxPxInfo.offsetPx - offsetPx;
|
|
54625
|
+
if (Math.abs(maxPx - minPx) < 8) {
|
|
54626
|
+
return;
|
|
54627
|
+
}
|
|
54628
|
+
if (Math.abs(minPx - x) < 4) {
|
|
54629
|
+
return { feature, edge: 'min' };
|
|
54630
|
+
}
|
|
54631
|
+
if (Math.abs(maxPx - x) < 4) {
|
|
54632
|
+
return { feature, edge: 'max' };
|
|
54633
|
+
}
|
|
54634
|
+
if (isCds) {
|
|
54635
|
+
const transcript = feature.parent;
|
|
54636
|
+
if (!transcript?.children) {
|
|
54637
|
+
return;
|
|
54638
|
+
}
|
|
54639
|
+
const exonChildren = [];
|
|
54640
|
+
for (const child of transcript.children.values()) {
|
|
54641
|
+
const childIsExon = featureTypeOntology.isTypeOf(child.type, 'exon');
|
|
54642
|
+
if (childIsExon) {
|
|
54643
|
+
exonChildren.push(child);
|
|
54644
|
+
}
|
|
54645
|
+
}
|
|
54646
|
+
const overlappingExon = exonChildren.find((child) => {
|
|
54647
|
+
const [start, end] = require$$1$2.intersection2(bp, bp + 1, child.min, child.max);
|
|
54648
|
+
return start !== undefined && end !== undefined;
|
|
54649
|
+
});
|
|
54650
|
+
if (!overlappingExon) {
|
|
54651
|
+
return;
|
|
54652
|
+
}
|
|
54653
|
+
const minPxInfo = lgv.bpToPx({
|
|
54654
|
+
refName,
|
|
54655
|
+
coord: overlappingExon.min,
|
|
54656
|
+
regionNumber,
|
|
54657
|
+
});
|
|
54658
|
+
const maxPxInfo = lgv.bpToPx({
|
|
54659
|
+
refName,
|
|
54660
|
+
coord: overlappingExon.max,
|
|
54661
|
+
regionNumber,
|
|
54662
|
+
});
|
|
54663
|
+
if (minPxInfo === undefined || maxPxInfo === undefined) {
|
|
54664
|
+
return;
|
|
54665
|
+
}
|
|
54666
|
+
const minPx = minPxInfo.offsetPx - offsetPx;
|
|
54667
|
+
const maxPx = maxPxInfo.offsetPx - offsetPx;
|
|
54668
|
+
if (Math.abs(maxPx - minPx) < 8) {
|
|
54669
|
+
return;
|
|
54670
|
+
}
|
|
54671
|
+
if (Math.abs(minPx - x) < 4) {
|
|
54672
|
+
return { feature: overlappingExon, edge: 'min' };
|
|
54673
|
+
}
|
|
54674
|
+
if (Math.abs(maxPx - x) < 4) {
|
|
54675
|
+
return { feature: overlappingExon, edge: 'max' };
|
|
54676
|
+
}
|
|
54677
|
+
}
|
|
54678
|
+
return;
|
|
54679
|
+
}
|
|
54680
|
+
// False positive here, none of these functions use "this"
|
|
54681
|
+
/* eslint-disable @typescript-eslint/unbound-method */
|
|
54682
|
+
const { drawTooltip: drawTooltip$1, getContextMenuItems: getContextMenuItems$1, onMouseLeave: onMouseLeave$1 } = boxGlyph;
|
|
54683
|
+
/* eslint-enable @typescript-eslint/unbound-method */
|
|
54684
|
+
const geneGlyph = {
|
|
54685
|
+
draw: draw$1,
|
|
54686
|
+
drawDragPreview: drawDragPreview$1,
|
|
54687
|
+
drawHover: drawHover$1,
|
|
54688
|
+
drawTooltip: drawTooltip$1,
|
|
54689
|
+
getContextMenuItems: getContextMenuItems$1,
|
|
54690
|
+
getFeatureFromLayout: getFeatureFromLayout$1,
|
|
54691
|
+
getRowCount: getRowCount$1,
|
|
54692
|
+
getRowForFeature: getRowForFeature$1,
|
|
54693
|
+
onMouseDown: onMouseDown$1,
|
|
54694
|
+
onMouseLeave: onMouseLeave$1,
|
|
54695
|
+
onMouseMove: onMouseMove$1,
|
|
54696
|
+
onMouseUp: onMouseUp$1,
|
|
54697
|
+
};
|
|
54698
|
+
|
|
54699
|
+
function featuresForRow(feature) {
|
|
54700
|
+
const features = [[feature]];
|
|
54701
|
+
if (feature.children) {
|
|
54702
|
+
for (const [, child] of feature.children) {
|
|
54703
|
+
features.push(...featuresForRow(child));
|
|
54704
|
+
}
|
|
54705
|
+
}
|
|
54706
|
+
return features;
|
|
54707
|
+
}
|
|
54708
|
+
function getRowCount(feature) {
|
|
54709
|
+
return featuresForRow(feature).length;
|
|
54710
|
+
}
|
|
54711
|
+
function draw(ctx, feature, row, stateModel, displayedRegionIndex) {
|
|
54712
|
+
for (let i = 0; i < getRowCount(feature); i++) {
|
|
54713
|
+
drawRow(ctx, feature, row + i, row, stateModel, displayedRegionIndex);
|
|
54714
|
+
}
|
|
54715
|
+
}
|
|
54716
|
+
function drawRow(ctx, topLevelFeature, row, topRow, stateModel, displayedRegionIndex) {
|
|
54717
|
+
const features = featuresForRow(topLevelFeature)[row - topRow];
|
|
54718
|
+
for (const feature of features) {
|
|
54719
|
+
drawFeature(ctx, feature, row, stateModel, displayedRegionIndex);
|
|
54720
|
+
}
|
|
54721
|
+
}
|
|
54722
|
+
function drawFeature(ctx, feature, row, stateModel, displayedRegionIndex) {
|
|
54723
|
+
const { apolloRowHeight: heightPx, lgv, session } = stateModel;
|
|
54724
|
+
const { bpPerPx, displayedRegions, offsetPx } = lgv;
|
|
54725
|
+
const displayedRegion = displayedRegions[displayedRegionIndex];
|
|
54726
|
+
const minX = (lgv.bpToPx({
|
|
54727
|
+
refName: displayedRegion.refName,
|
|
54728
|
+
coord: feature.min,
|
|
54729
|
+
regionNumber: displayedRegionIndex,
|
|
54730
|
+
})?.offsetPx ?? 0) - offsetPx;
|
|
54731
|
+
const { reversed } = displayedRegion;
|
|
54732
|
+
const { apolloSelectedFeature } = session;
|
|
54733
|
+
const widthPx = feature.length / bpPerPx;
|
|
54734
|
+
const startPx = reversed ? minX - widthPx : minX;
|
|
54735
|
+
const top = row * heightPx;
|
|
54736
|
+
const rowCount = getRowCount(feature);
|
|
54737
|
+
const isSelected = isSelectedFeature(feature, apolloSelectedFeature);
|
|
54738
|
+
const groupingColor = isSelected ? 'rgba(130,0,0,0.45)' : 'rgba(255,0,0,0.25)';
|
|
54739
|
+
if (rowCount > 1) {
|
|
54740
|
+
// draw background that encapsulates all child features
|
|
54741
|
+
const featureHeight = rowCount * heightPx;
|
|
54742
|
+
drawBox(ctx, startPx, top, widthPx, featureHeight, groupingColor);
|
|
54743
|
+
}
|
|
54744
|
+
boxGlyph.draw(ctx, feature, row, stateModel, displayedRegionIndex);
|
|
54745
|
+
}
|
|
54746
|
+
function drawHover(stateModel, ctx) {
|
|
54747
|
+
const { apolloHover, apolloRowHeight, lgv } = stateModel;
|
|
54748
|
+
if (!apolloHover) {
|
|
54749
|
+
return;
|
|
54750
|
+
}
|
|
54751
|
+
const { feature } = apolloHover;
|
|
54752
|
+
const position = stateModel.getFeatureLayoutPosition(feature);
|
|
54753
|
+
if (!position) {
|
|
54754
|
+
return;
|
|
54755
|
+
}
|
|
54756
|
+
const { featureRow, layoutIndex, layoutRow } = position;
|
|
54757
|
+
const { bpPerPx, displayedRegions, offsetPx } = lgv;
|
|
54758
|
+
const displayedRegion = displayedRegions[layoutIndex];
|
|
54759
|
+
const { refName, reversed } = displayedRegion;
|
|
54760
|
+
const { length, max, min } = feature;
|
|
54761
|
+
const startPx = (lgv.bpToPx({
|
|
54762
|
+
refName,
|
|
54763
|
+
coord: reversed ? max : min,
|
|
54764
|
+
regionNumber: layoutIndex,
|
|
54765
|
+
})?.offsetPx ?? 0) - offsetPx;
|
|
54766
|
+
const top = (layoutRow + featureRow) * apolloRowHeight;
|
|
54767
|
+
const widthPx = length / bpPerPx;
|
|
54768
|
+
ctx.fillStyle = 'rgba(0,0,0,0.2)';
|
|
54769
|
+
ctx.fillRect(startPx, top, widthPx, apolloRowHeight * getRowCount(feature));
|
|
54770
|
+
}
|
|
54771
|
+
function getFeatureFromLayout(feature, bp, row) {
|
|
54772
|
+
const layoutRow = featuresForRow(feature)[row];
|
|
54773
|
+
return layoutRow.find((f) => bp >= f.min && bp <= f.max);
|
|
54774
|
+
}
|
|
54775
|
+
function getRowForFeature(feature, childFeature) {
|
|
54776
|
+
const rows = featuresForRow(feature);
|
|
54777
|
+
for (const [idx, row] of rows.entries()) {
|
|
54778
|
+
if (row.some((feature) => feature._id === childFeature._id)) {
|
|
54779
|
+
return idx;
|
|
54780
|
+
}
|
|
54781
|
+
}
|
|
54782
|
+
return;
|
|
54783
|
+
}
|
|
54784
|
+
// False positive here, none of these functions use "this"
|
|
54785
|
+
/* eslint-disable @typescript-eslint/unbound-method */
|
|
54786
|
+
const { drawDragPreview, drawTooltip, getContextMenuItems, onMouseDown, onMouseLeave, onMouseMove, onMouseUp, } = boxGlyph;
|
|
54787
|
+
/* eslint-enable @typescript-eslint/unbound-method */
|
|
54788
|
+
const genericChildGlyph = {
|
|
54789
|
+
draw,
|
|
54790
|
+
drawDragPreview,
|
|
54791
|
+
drawHover,
|
|
54792
|
+
drawTooltip,
|
|
54793
|
+
getContextMenuItems,
|
|
54794
|
+
getFeatureFromLayout,
|
|
54795
|
+
getRowCount,
|
|
54796
|
+
getRowForFeature,
|
|
54797
|
+
onMouseDown,
|
|
54798
|
+
onMouseLeave,
|
|
54799
|
+
onMouseMove,
|
|
54800
|
+
onMouseUp,
|
|
54801
|
+
};
|
|
54802
|
+
|
|
54803
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
54804
|
+
function layoutsModelFactory(pluginManager, configSchema) {
|
|
54805
|
+
const BaseLinearApolloDisplay = baseModelFactory(pluginManager, configSchema);
|
|
54806
|
+
return BaseLinearApolloDisplay.named('LinearApolloDisplayLayouts')
|
|
54807
|
+
.props({
|
|
54808
|
+
featuresMinMaxLimit: 500_000,
|
|
54809
|
+
})
|
|
54810
|
+
.volatile(() => ({
|
|
54811
|
+
seenFeatures: mobx.observable.map(),
|
|
54812
|
+
}))
|
|
54813
|
+
.views((self) => ({
|
|
54814
|
+
get featuresMinMax() {
|
|
54815
|
+
const { assemblyManager } = self.session;
|
|
54816
|
+
return self.lgv.displayedRegions.map((region) => {
|
|
54817
|
+
const assembly = assemblyManager.get(region.assemblyName);
|
|
54818
|
+
let min;
|
|
54819
|
+
let max;
|
|
54820
|
+
const { end, refName, start } = region;
|
|
54821
|
+
for (const [, feature] of self.seenFeatures) {
|
|
54822
|
+
if (refName !== assembly?.getCanonicalRefName(feature.refSeq) ||
|
|
54823
|
+
!require$$1$2.doesIntersect2(start, end, feature.min, feature.max) ||
|
|
54824
|
+
feature.length > self.featuresMinMaxLimit ||
|
|
54825
|
+
(self.filteredFeatureTypes.length > 0 &&
|
|
54826
|
+
!self.filteredFeatureTypes.includes(feature.type))) {
|
|
54827
|
+
continue;
|
|
54828
|
+
}
|
|
54829
|
+
if (min === undefined) {
|
|
54830
|
+
({ min } = feature);
|
|
54831
|
+
}
|
|
54832
|
+
if (max === undefined) {
|
|
54833
|
+
({ max } = feature);
|
|
54834
|
+
}
|
|
54835
|
+
if (feature.minWithChildren < min) {
|
|
54836
|
+
({ min } = feature);
|
|
54837
|
+
}
|
|
54838
|
+
if (feature.maxWithChildren > max) {
|
|
54839
|
+
({ max } = feature);
|
|
54840
|
+
}
|
|
54841
|
+
}
|
|
54842
|
+
if (min !== undefined && max !== undefined) {
|
|
54843
|
+
return [min, max];
|
|
54844
|
+
}
|
|
54845
|
+
return;
|
|
54846
|
+
});
|
|
54847
|
+
},
|
|
54848
|
+
getGlyph(feature) {
|
|
54849
|
+
if (this.looksLikeGene(feature)) {
|
|
54850
|
+
return geneGlyph;
|
|
54851
|
+
}
|
|
54852
|
+
if (feature.children?.size) {
|
|
54853
|
+
return genericChildGlyph;
|
|
54854
|
+
}
|
|
54855
|
+
return boxGlyph;
|
|
54856
|
+
},
|
|
54857
|
+
looksLikeGene(feature) {
|
|
54858
|
+
const { featureTypeOntology } = self.session.apolloDataStore.ontologyManager;
|
|
54859
|
+
if (!featureTypeOntology) {
|
|
54860
|
+
return false;
|
|
54861
|
+
}
|
|
54862
|
+
const { children } = feature;
|
|
54863
|
+
if (!children?.size) {
|
|
54864
|
+
return false;
|
|
54865
|
+
}
|
|
54866
|
+
const isGene = featureTypeOntology.isTypeOf(feature.type, 'gene');
|
|
54867
|
+
if (!isGene) {
|
|
54868
|
+
return false;
|
|
54869
|
+
}
|
|
54870
|
+
for (const [, child] of children) {
|
|
54871
|
+
if (featureTypeOntology.isTypeOf(child.type, 'transcript')) {
|
|
54872
|
+
const { children: grandChildren } = child;
|
|
54873
|
+
if (!grandChildren?.size) {
|
|
54874
|
+
return false;
|
|
54875
|
+
}
|
|
54876
|
+
const hasCDS = [...grandChildren.values()].some((grandchild) => featureTypeOntology.isTypeOf(grandchild.type, 'CDS'));
|
|
54877
|
+
const hasExon = [...grandChildren.values()].some((grandchild) => featureTypeOntology.isTypeOf(grandchild.type, 'exon'));
|
|
54878
|
+
if (hasCDS && hasExon) {
|
|
54879
|
+
return true;
|
|
54880
|
+
}
|
|
54881
|
+
}
|
|
54882
|
+
}
|
|
54883
|
+
return false;
|
|
54884
|
+
},
|
|
54885
|
+
}))
|
|
54886
|
+
.actions((self) => ({
|
|
54887
|
+
addSeenFeature(feature) {
|
|
54888
|
+
self.seenFeatures.set(feature._id, feature);
|
|
54889
|
+
},
|
|
54890
|
+
deleteSeenFeature(featureId) {
|
|
54891
|
+
self.seenFeatures.delete(featureId);
|
|
54892
|
+
},
|
|
54893
|
+
}))
|
|
54894
|
+
.views((self) => ({
|
|
54895
|
+
get featureLayouts() {
|
|
54896
|
+
const { assemblyManager } = self.session;
|
|
54897
|
+
return self.lgv.displayedRegions.map((region, idx) => {
|
|
54898
|
+
const assembly = assemblyManager.get(region.assemblyName);
|
|
54899
|
+
const featureLayout = new Map();
|
|
54900
|
+
const minMax = self.featuresMinMax[idx];
|
|
54901
|
+
if (!minMax) {
|
|
54902
|
+
return featureLayout;
|
|
54903
|
+
}
|
|
54904
|
+
const [min, max] = minMax;
|
|
54905
|
+
const rows = [];
|
|
54906
|
+
const { end, refName, start } = region;
|
|
54907
|
+
for (const [id, feature] of self.seenFeatures.entries()) {
|
|
54908
|
+
if (!require$$1$3.isAlive(feature)) {
|
|
54909
|
+
self.deleteSeenFeature(id);
|
|
54910
|
+
continue;
|
|
54911
|
+
}
|
|
54912
|
+
if (refName !== assembly?.getCanonicalRefName(feature.refSeq) ||
|
|
54913
|
+
!require$$1$2.doesIntersect2(start, end, feature.min, feature.max) ||
|
|
54914
|
+
(self.filteredFeatureTypes.length > 0 &&
|
|
54915
|
+
!self.filteredFeatureTypes.includes(feature.type))) {
|
|
54916
|
+
continue;
|
|
54917
|
+
}
|
|
54918
|
+
const { featureTypeOntology } = self.session.apolloDataStore.ontologyManager;
|
|
54919
|
+
if (!featureTypeOntology) {
|
|
54920
|
+
throw new Error('featureTypeOntology is undefined');
|
|
54921
|
+
}
|
|
54922
|
+
const rowCount = self
|
|
54923
|
+
.getGlyph(feature)
|
|
54924
|
+
.getRowCount(feature, featureTypeOntology, self.lgv.bpPerPx);
|
|
54925
|
+
let startingRow = 0;
|
|
54926
|
+
let placed = false;
|
|
54927
|
+
while (!placed) {
|
|
54928
|
+
let rowsForFeature = rows.slice(startingRow, startingRow + rowCount);
|
|
54929
|
+
if (rowsForFeature.length < rowCount) {
|
|
54930
|
+
for (let i = 0; i < rowCount - rowsForFeature.length; i++) {
|
|
54931
|
+
const newRowNumber = rows.length;
|
|
54932
|
+
rows[newRowNumber] = Array.from({ length: max - min });
|
|
54933
|
+
featureLayout.set(newRowNumber, []);
|
|
54934
|
+
}
|
|
54935
|
+
rowsForFeature = rows.slice(startingRow, startingRow + rowCount);
|
|
54936
|
+
}
|
|
54937
|
+
if (rowsForFeature
|
|
54938
|
+
.map((rowForFeature) => {
|
|
54939
|
+
// zero-length features are allowed in the spec
|
|
54940
|
+
const featureMax = feature.max - feature.min === 0
|
|
54941
|
+
? feature.min + 1
|
|
54942
|
+
: feature.max;
|
|
54943
|
+
let start = feature.min - min, end = featureMax - min;
|
|
54944
|
+
if (feature.min - min < 0) {
|
|
54945
|
+
start = 0;
|
|
54946
|
+
end = featureMax - feature.min;
|
|
54947
|
+
}
|
|
54948
|
+
return rowForFeature.slice(start, end).some(Boolean);
|
|
54949
|
+
})
|
|
54950
|
+
.some(Boolean)) {
|
|
54951
|
+
startingRow += 1;
|
|
54952
|
+
continue;
|
|
54953
|
+
}
|
|
54954
|
+
for (let rowNum = startingRow; rowNum < startingRow + rowCount; rowNum++) {
|
|
54955
|
+
const row = rows[rowNum];
|
|
54956
|
+
let start = feature.min - min, end = feature.max - min;
|
|
54957
|
+
if (feature.min - min < 0) {
|
|
54958
|
+
start = 0;
|
|
54959
|
+
end = feature.max - feature.min;
|
|
54960
|
+
}
|
|
54961
|
+
row.fill(true, start, end);
|
|
54962
|
+
const layoutRow = featureLayout.get(rowNum);
|
|
54963
|
+
layoutRow?.push([rowNum - startingRow, feature]);
|
|
54964
|
+
}
|
|
54965
|
+
placed = true;
|
|
54966
|
+
}
|
|
54967
|
+
}
|
|
54968
|
+
return featureLayout;
|
|
54969
|
+
});
|
|
54970
|
+
},
|
|
54971
|
+
getFeatureLayoutPosition(feature) {
|
|
54972
|
+
const { featureLayouts } = this;
|
|
54973
|
+
const { featureTypeOntology } = self.session.apolloDataStore.ontologyManager;
|
|
54974
|
+
for (const [idx, layout] of featureLayouts.entries()) {
|
|
54975
|
+
for (const [layoutRowNum, layoutRow] of layout) {
|
|
54976
|
+
for (const [featureRowNum, layoutFeature] of layoutRow) {
|
|
54977
|
+
if (featureRowNum !== 0) {
|
|
54978
|
+
// Same top-level feature in all feature rows, so only need to
|
|
54979
|
+
// check the first one
|
|
54980
|
+
continue;
|
|
54981
|
+
}
|
|
54982
|
+
if (feature._id === layoutFeature._id) {
|
|
54983
|
+
return {
|
|
54984
|
+
layoutIndex: idx,
|
|
54985
|
+
layoutRow: layoutRowNum,
|
|
54986
|
+
featureRow: featureRowNum,
|
|
54987
|
+
};
|
|
54988
|
+
}
|
|
54989
|
+
if (layoutFeature.hasDescendant(feature._id)) {
|
|
54990
|
+
if (!featureTypeOntology) {
|
|
54991
|
+
throw new Error('featureTypeOntology is undefined');
|
|
54992
|
+
}
|
|
54993
|
+
const row = self
|
|
54994
|
+
.getGlyph(layoutFeature)
|
|
54995
|
+
.getRowForFeature(layoutFeature, feature, featureTypeOntology);
|
|
54996
|
+
if (row !== undefined) {
|
|
54997
|
+
return {
|
|
54998
|
+
layoutIndex: idx,
|
|
54999
|
+
layoutRow: layoutRowNum,
|
|
55000
|
+
featureRow: row,
|
|
55001
|
+
};
|
|
55002
|
+
}
|
|
55003
|
+
}
|
|
55004
|
+
}
|
|
55005
|
+
}
|
|
55006
|
+
}
|
|
55007
|
+
return;
|
|
55008
|
+
},
|
|
55009
|
+
}))
|
|
55010
|
+
.views((self) => ({
|
|
55011
|
+
get highestRow() {
|
|
55012
|
+
return Math.max(0, ...self.featureLayouts.map((layout) => Math.max(...layout.keys())));
|
|
55013
|
+
},
|
|
55014
|
+
}))
|
|
55015
|
+
.actions((self) => ({
|
|
55016
|
+
afterAttach() {
|
|
55017
|
+
require$$1$3.addDisposer(self, mobx.autorun(() => {
|
|
55018
|
+
if (!self.lgv.initialized || self.regionCannotBeRendered()) {
|
|
55019
|
+
return;
|
|
55020
|
+
}
|
|
55021
|
+
for (const region of self.regions) {
|
|
55022
|
+
const assembly = self.session.apolloDataStore.assemblies.get(region.assemblyName);
|
|
55023
|
+
const ref = assembly?.getByRefName(region.refName);
|
|
55024
|
+
const features = ref?.features;
|
|
55025
|
+
if (!features) {
|
|
55026
|
+
continue;
|
|
55027
|
+
}
|
|
55028
|
+
for (const [, feature] of features) {
|
|
55029
|
+
if (require$$1$2.doesIntersect2(region.start, region.end, feature.min, feature.max) &&
|
|
55030
|
+
!self.seenFeatures.has(feature._id)) {
|
|
55031
|
+
self.addSeenFeature(feature);
|
|
55032
|
+
}
|
|
55033
|
+
}
|
|
55034
|
+
}
|
|
55035
|
+
}, { name: 'LinearApolloDisplaySetSeenFeatures', delay: 1000 }));
|
|
55036
|
+
},
|
|
55037
|
+
}));
|
|
55038
|
+
}
|
|
55039
|
+
|
|
55040
|
+
function renderingModelIntermediateFactory(pluginManager, configSchema) {
|
|
55041
|
+
const LinearApolloDisplayLayouts = layoutsModelFactory(pluginManager, configSchema);
|
|
55042
|
+
return LinearApolloDisplayLayouts.named('LinearApolloDisplayRendering')
|
|
55043
|
+
.props({
|
|
55044
|
+
sequenceRowHeight: 15,
|
|
55045
|
+
apolloRowHeight: 20,
|
|
55046
|
+
detailsMinHeight: 200,
|
|
55047
|
+
detailsHeight: 200,
|
|
55048
|
+
lastRowTooltipBufferHeight: 40,
|
|
55049
|
+
isShown: true,
|
|
55050
|
+
})
|
|
55051
|
+
.volatile(() => ({
|
|
55052
|
+
canvas: null,
|
|
55053
|
+
overlayCanvas: null,
|
|
55054
|
+
collaboratorCanvas: null,
|
|
55055
|
+
seqTrackCanvas: null,
|
|
55056
|
+
seqTrackOverlayCanvas: null,
|
|
55057
|
+
theme: undefined,
|
|
55058
|
+
}))
|
|
55059
|
+
.views((self) => ({
|
|
55060
|
+
get featuresHeight() {
|
|
55061
|
+
return ((self.highestRow + 1) * self.apolloRowHeight +
|
|
55062
|
+
self.lastRowTooltipBufferHeight);
|
|
55063
|
+
},
|
|
55064
|
+
}))
|
|
55065
|
+
.actions((self) => ({
|
|
55066
|
+
toggleShown() {
|
|
55067
|
+
self.isShown = !self.isShown;
|
|
55068
|
+
},
|
|
55069
|
+
setDetailsHeight(newHeight) {
|
|
55070
|
+
self.detailsHeight = self.isShown
|
|
55071
|
+
? Math.max(Math.min(newHeight, self.height - 100), Math.min(self.height, self.detailsMinHeight))
|
|
55072
|
+
: newHeight;
|
|
55073
|
+
},
|
|
55074
|
+
setCanvas(canvas) {
|
|
55075
|
+
self.canvas = canvas;
|
|
55076
|
+
},
|
|
55077
|
+
setOverlayCanvas(canvas) {
|
|
55078
|
+
self.overlayCanvas = canvas;
|
|
55079
|
+
},
|
|
55080
|
+
setCollaboratorCanvas(canvas) {
|
|
55081
|
+
self.collaboratorCanvas = canvas;
|
|
55082
|
+
},
|
|
55083
|
+
setSeqTrackCanvas(canvas) {
|
|
55084
|
+
self.seqTrackCanvas = canvas;
|
|
55085
|
+
},
|
|
55086
|
+
setSeqTrackOverlayCanvas(canvas) {
|
|
55087
|
+
self.seqTrackOverlayCanvas = canvas;
|
|
55088
|
+
},
|
|
55089
|
+
setTheme(theme) {
|
|
55090
|
+
self.theme = theme;
|
|
55091
|
+
},
|
|
55092
|
+
afterAttach() {
|
|
55093
|
+
require$$1$3.addDisposer(self, mobx.autorun(() => {
|
|
55094
|
+
if (!self.lgv.initialized || self.regionCannotBeRendered()) {
|
|
55095
|
+
return;
|
|
55096
|
+
}
|
|
55097
|
+
const ctx = self.collaboratorCanvas?.getContext('2d');
|
|
55098
|
+
if (!ctx) {
|
|
55099
|
+
return;
|
|
55100
|
+
}
|
|
55101
|
+
ctx.clearRect(0, 0, self.lgv.dynamicBlocks.totalWidthPx, self.featuresHeight);
|
|
55102
|
+
for (const collaborator of self.session.collaborators) {
|
|
55103
|
+
const { locations } = collaborator;
|
|
55104
|
+
if (locations.length === 0) {
|
|
55105
|
+
continue;
|
|
55106
|
+
}
|
|
55107
|
+
let idx = 0;
|
|
55108
|
+
for (const displayedRegion of self.lgv.displayedRegions) {
|
|
55109
|
+
for (const location of locations) {
|
|
55110
|
+
if (location.refSeq !== displayedRegion.refName) {
|
|
55111
|
+
continue;
|
|
55112
|
+
}
|
|
55113
|
+
const { end, refSeq, start } = location;
|
|
55114
|
+
const locationStartPxInfo = self.lgv.bpToPx({
|
|
55115
|
+
refName: refSeq,
|
|
55116
|
+
coord: start,
|
|
55117
|
+
regionNumber: idx,
|
|
55118
|
+
});
|
|
55119
|
+
if (!locationStartPxInfo) {
|
|
55120
|
+
continue;
|
|
55121
|
+
}
|
|
55122
|
+
const locationStartPx = locationStartPxInfo.offsetPx - self.lgv.offsetPx;
|
|
55123
|
+
const locationWidthPx = (end - start) / self.lgv.bpPerPx;
|
|
55124
|
+
ctx.fillStyle = 'rgba(0,255,0,.2)';
|
|
55125
|
+
ctx.fillRect(locationStartPx, 1, locationWidthPx, 100);
|
|
55126
|
+
ctx.fillStyle = 'black';
|
|
55127
|
+
ctx.fillText(collaborator.name, locationStartPx + 1, 11, locationWidthPx - 2);
|
|
55128
|
+
}
|
|
55129
|
+
idx++;
|
|
55130
|
+
}
|
|
55131
|
+
}
|
|
55132
|
+
}, { name: 'LinearApolloDisplayRenderCollaborators' }));
|
|
55133
|
+
},
|
|
55134
|
+
}));
|
|
55135
|
+
}
|
|
55136
|
+
function colorCode(letter, theme) {
|
|
55137
|
+
return (theme?.palette.bases[letter.toUpperCase()].main.toString() ?? 'lightgray');
|
|
54359
55138
|
}
|
|
54360
|
-
function
|
|
54361
|
-
const
|
|
54362
|
-
|
|
54363
|
-
|
|
54364
|
-
|
|
54365
|
-
|
|
54366
|
-
if (draggableFeature) {
|
|
54367
|
-
event.stopPropagation();
|
|
54368
|
-
stateModel.startDrag(currentMousePosition, draggableFeature.feature, draggableFeature.edge);
|
|
54369
|
-
}
|
|
55139
|
+
function codonColorCode(letter) {
|
|
55140
|
+
const colorMap = {
|
|
55141
|
+
M: '#33ee33',
|
|
55142
|
+
'*': '#f44336',
|
|
55143
|
+
};
|
|
55144
|
+
return colorMap[letter.toUpperCase()];
|
|
54370
55145
|
}
|
|
54371
|
-
function
|
|
54372
|
-
|
|
54373
|
-
|
|
54374
|
-
|
|
54375
|
-
|
|
54376
|
-
const draggableFeature = getDraggableFeatureInfo(mousePosition, feature, stateModel);
|
|
54377
|
-
if (draggableFeature) {
|
|
54378
|
-
stateModel.setCursor('col-resize');
|
|
54379
|
-
return;
|
|
54380
|
-
}
|
|
54381
|
-
}
|
|
54382
|
-
stateModel.setCursor();
|
|
55146
|
+
function reverseCodonSeq(seq) {
|
|
55147
|
+
return [...seq]
|
|
55148
|
+
.map((c) => require$$1$2.revcom(c))
|
|
55149
|
+
.reverse()
|
|
55150
|
+
.join('');
|
|
54383
55151
|
}
|
|
54384
|
-
function
|
|
54385
|
-
|
|
54386
|
-
|
|
54387
|
-
}
|
|
54388
|
-
const
|
|
54389
|
-
|
|
54390
|
-
|
|
54391
|
-
}
|
|
55152
|
+
function drawLetter(seqTrackctx, startPx, widthPx, letter, textY) {
|
|
55153
|
+
const fontSize = Math.min(widthPx, 10);
|
|
55154
|
+
seqTrackctx.fillStyle = '#000';
|
|
55155
|
+
seqTrackctx.font = `${fontSize}px`;
|
|
55156
|
+
const textWidth = seqTrackctx.measureText(letter).width;
|
|
55157
|
+
const textX = startPx + (widthPx - textWidth) / 2;
|
|
55158
|
+
seqTrackctx.fillText(letter, textX, textY + 10);
|
|
54392
55159
|
}
|
|
54393
|
-
function
|
|
54394
|
-
|
|
54395
|
-
|
|
54396
|
-
|
|
54397
|
-
const { bp, refName, regionNumber, x } = mousePosition;
|
|
54398
|
-
const { lgv } = stateModel;
|
|
54399
|
-
const { offsetPx } = lgv;
|
|
54400
|
-
const minPxInfo = lgv.bpToPx({ refName, coord: feature.min, regionNumber });
|
|
54401
|
-
const maxPxInfo = lgv.bpToPx({ refName, coord: feature.max, regionNumber });
|
|
54402
|
-
if (minPxInfo === undefined || maxPxInfo === undefined) {
|
|
54403
|
-
return;
|
|
55160
|
+
function drawTranslation(seqTrackctx, bpPerPx, trnslStartPx, trnslY, trnslWidthPx, sequenceRowHeight, seq, i, reverse) {
|
|
55161
|
+
let codonSeq = seq.slice(i, i + 3).toUpperCase();
|
|
55162
|
+
if (reverse) {
|
|
55163
|
+
codonSeq = reverseCodonSeq(codonSeq);
|
|
54404
55164
|
}
|
|
54405
|
-
const
|
|
54406
|
-
|
|
54407
|
-
if (Math.abs(maxPx - minPx) < 8) {
|
|
55165
|
+
const codonLetter = require$$1$2.defaultCodonTable[codonSeq];
|
|
55166
|
+
if (!codonLetter) {
|
|
54408
55167
|
return;
|
|
54409
55168
|
}
|
|
54410
|
-
|
|
54411
|
-
|
|
55169
|
+
const fillColor = codonColorCode(codonLetter);
|
|
55170
|
+
if (fillColor) {
|
|
55171
|
+
seqTrackctx.fillStyle = fillColor;
|
|
55172
|
+
seqTrackctx.fillRect(trnslStartPx, trnslY, trnslWidthPx, sequenceRowHeight);
|
|
54412
55173
|
}
|
|
54413
|
-
if (
|
|
54414
|
-
|
|
55174
|
+
if (bpPerPx <= 0.1) {
|
|
55175
|
+
seqTrackctx.rect(trnslStartPx, trnslY, trnslWidthPx, sequenceRowHeight);
|
|
55176
|
+
seqTrackctx.stroke();
|
|
55177
|
+
drawLetter(seqTrackctx, trnslStartPx, trnslWidthPx, codonLetter, trnslY);
|
|
54415
55178
|
}
|
|
54416
|
-
|
|
54417
|
-
|
|
54418
|
-
|
|
54419
|
-
|
|
54420
|
-
|
|
54421
|
-
|
|
54422
|
-
|
|
54423
|
-
|
|
54424
|
-
|
|
54425
|
-
|
|
54426
|
-
|
|
54427
|
-
|
|
55179
|
+
}
|
|
55180
|
+
function sequenceRenderingModelFactory(pluginManager, configSchema) {
|
|
55181
|
+
const LinearApolloDisplayRendering = renderingModelIntermediateFactory(pluginManager, configSchema);
|
|
55182
|
+
return LinearApolloDisplayRendering.actions((self) => ({
|
|
55183
|
+
afterAttach() {
|
|
55184
|
+
require$$1$3.addDisposer(self, mobx.autorun(async () => {
|
|
55185
|
+
if (!self.lgv.initialized || self.regionCannotBeRendered()) {
|
|
55186
|
+
return;
|
|
55187
|
+
}
|
|
55188
|
+
if (self.lgv.bpPerPx > 3) {
|
|
55189
|
+
return;
|
|
55190
|
+
}
|
|
55191
|
+
const seqTrackctx = self.seqTrackCanvas?.getContext('2d');
|
|
55192
|
+
if (!seqTrackctx) {
|
|
55193
|
+
return;
|
|
55194
|
+
}
|
|
55195
|
+
seqTrackctx.clearRect(0, 0, self.lgv.dynamicBlocks.totalWidthPx, self.lgv.bpPerPx <= 1 ? 125 : 95);
|
|
55196
|
+
const frames = self.lgv.bpPerPx <= 1
|
|
55197
|
+
? [3, 2, 1, 0, 0, -1, -2, -3]
|
|
55198
|
+
: [3, 2, 1, -1, -2, -3];
|
|
55199
|
+
let height = 0;
|
|
55200
|
+
for (const frame of frames) {
|
|
55201
|
+
const frameColor = self.theme?.palette.framesCDS.at(frame)?.main;
|
|
55202
|
+
if (frameColor) {
|
|
55203
|
+
seqTrackctx.fillStyle = frameColor;
|
|
55204
|
+
seqTrackctx.fillRect(0, height, self.lgv.dynamicBlocks.totalWidthPx, self.sequenceRowHeight);
|
|
55205
|
+
}
|
|
55206
|
+
height += self.sequenceRowHeight;
|
|
55207
|
+
}
|
|
55208
|
+
for (const [idx, region] of self.regions.entries()) {
|
|
55209
|
+
const driver = self.session.apolloDataStore.getBackendDriver(region.assemblyName);
|
|
55210
|
+
if (!driver) {
|
|
55211
|
+
throw new Error('Failed to get the backend driver');
|
|
55212
|
+
}
|
|
55213
|
+
const { seq } = await driver.getSequence(region);
|
|
55214
|
+
if (!seq) {
|
|
55215
|
+
return;
|
|
55216
|
+
}
|
|
55217
|
+
for (const [i, letter] of [...seq].entries()) {
|
|
55218
|
+
const trnslXOffset = (self.lgv.bpToPx({
|
|
55219
|
+
refName: region.refName,
|
|
55220
|
+
coord: region.start + i,
|
|
55221
|
+
regionNumber: idx,
|
|
55222
|
+
})?.offsetPx ?? 0) - self.lgv.offsetPx;
|
|
55223
|
+
const trnslWidthPx = 3 / self.lgv.bpPerPx;
|
|
55224
|
+
const trnslStartPx = self.lgv.displayedRegions[idx].reversed
|
|
55225
|
+
? trnslXOffset - trnslWidthPx
|
|
55226
|
+
: trnslXOffset;
|
|
55227
|
+
// Draw translation forward
|
|
55228
|
+
for (let j = 2; j >= 0; j--) {
|
|
55229
|
+
if ((region.start + i) % 3 === j) {
|
|
55230
|
+
drawTranslation(seqTrackctx, self.lgv.bpPerPx, trnslStartPx, self.sequenceRowHeight * (2 - j), trnslWidthPx, self.sequenceRowHeight, seq, i, false);
|
|
55231
|
+
}
|
|
55232
|
+
}
|
|
55233
|
+
if (self.lgv.bpPerPx <= 1) {
|
|
55234
|
+
const xOffset = (self.lgv.bpToPx({
|
|
55235
|
+
refName: region.refName,
|
|
55236
|
+
coord: region.start + i,
|
|
55237
|
+
regionNumber: idx,
|
|
55238
|
+
})?.offsetPx ?? 0) - self.lgv.offsetPx;
|
|
55239
|
+
const widthPx = 1 / self.lgv.bpPerPx;
|
|
55240
|
+
const startPx = self.lgv.displayedRegions[idx].reversed
|
|
55241
|
+
? xOffset - widthPx
|
|
55242
|
+
: xOffset;
|
|
55243
|
+
// Draw forward
|
|
55244
|
+
seqTrackctx.beginPath();
|
|
55245
|
+
seqTrackctx.fillStyle = colorCode(letter, self.theme);
|
|
55246
|
+
seqTrackctx.rect(startPx, self.sequenceRowHeight * 3, widthPx, self.sequenceRowHeight);
|
|
55247
|
+
seqTrackctx.fill();
|
|
55248
|
+
if (self.lgv.bpPerPx <= 0.1) {
|
|
55249
|
+
seqTrackctx.stroke();
|
|
55250
|
+
drawLetter(seqTrackctx, startPx, widthPx, letter, self.sequenceRowHeight * 3);
|
|
55251
|
+
}
|
|
55252
|
+
// Draw reverse
|
|
55253
|
+
const revLetter = require$$1$2.revcom(letter);
|
|
55254
|
+
seqTrackctx.beginPath();
|
|
55255
|
+
seqTrackctx.fillStyle = colorCode(revLetter, self.theme);
|
|
55256
|
+
seqTrackctx.rect(startPx, self.sequenceRowHeight * 4, widthPx, self.sequenceRowHeight);
|
|
55257
|
+
seqTrackctx.fill();
|
|
55258
|
+
if (self.lgv.bpPerPx <= 0.1) {
|
|
55259
|
+
seqTrackctx.stroke();
|
|
55260
|
+
drawLetter(seqTrackctx, startPx, widthPx, revLetter, self.sequenceRowHeight * 4);
|
|
55261
|
+
}
|
|
55262
|
+
}
|
|
55263
|
+
// Draw translation reverse
|
|
55264
|
+
for (let k = 0; k <= 2; k++) {
|
|
55265
|
+
const rowOffset = self.lgv.bpPerPx <= 1 ? 5 : 3;
|
|
55266
|
+
if ((region.start + i) % 3 === k) {
|
|
55267
|
+
drawTranslation(seqTrackctx, self.lgv.bpPerPx, trnslStartPx, self.sequenceRowHeight * (rowOffset + k), trnslWidthPx, self.sequenceRowHeight, seq, i, true);
|
|
55268
|
+
}
|
|
55269
|
+
}
|
|
55270
|
+
}
|
|
55271
|
+
}
|
|
55272
|
+
}, { name: 'LinearApolloDisplayRenderSequence' }));
|
|
55273
|
+
},
|
|
55274
|
+
}));
|
|
55275
|
+
}
|
|
55276
|
+
function renderingModelFactory(pluginManager, configSchema) {
|
|
55277
|
+
const LinearApolloDisplayRendering = sequenceRenderingModelFactory(pluginManager, configSchema);
|
|
55278
|
+
return LinearApolloDisplayRendering.actions((self) => ({
|
|
55279
|
+
afterAttach() {
|
|
55280
|
+
require$$1$3.addDisposer(self, mobx.autorun(() => {
|
|
55281
|
+
const { canvas, featureLayouts, featuresHeight, lgv } = self;
|
|
55282
|
+
if (!lgv.initialized || self.regionCannotBeRendered()) {
|
|
55283
|
+
return;
|
|
55284
|
+
}
|
|
55285
|
+
const { displayedRegions, dynamicBlocks } = lgv;
|
|
55286
|
+
const ctx = canvas?.getContext('2d');
|
|
55287
|
+
if (!ctx) {
|
|
55288
|
+
return;
|
|
55289
|
+
}
|
|
55290
|
+
ctx.clearRect(0, 0, dynamicBlocks.totalWidthPx, featuresHeight);
|
|
55291
|
+
for (const [idx, featureLayout] of featureLayouts.entries()) {
|
|
55292
|
+
const displayedRegion = displayedRegions[idx];
|
|
55293
|
+
for (const [row, featureLayoutRow] of featureLayout.entries()) {
|
|
55294
|
+
for (const [featureRow, feature] of featureLayoutRow) {
|
|
55295
|
+
if (featureRow > 0) {
|
|
55296
|
+
continue;
|
|
55297
|
+
}
|
|
55298
|
+
if (!require$$1$2.doesIntersect2(displayedRegion.start, displayedRegion.end, feature.min, feature.max)) {
|
|
55299
|
+
continue;
|
|
55300
|
+
}
|
|
55301
|
+
self.getGlyph(feature).draw(ctx, feature, row, self, idx);
|
|
55302
|
+
}
|
|
55303
|
+
}
|
|
55304
|
+
}
|
|
55305
|
+
}, { name: 'LinearApolloDisplayRenderFeatures' }));
|
|
55306
|
+
},
|
|
55307
|
+
}));
|
|
55308
|
+
}
|
|
55309
|
+
|
|
55310
|
+
function isMousePositionWithFeatureAndGlyph(mousePosition) {
|
|
55311
|
+
return 'featureAndGlyphUnderMouse' in mousePosition;
|
|
55312
|
+
}
|
|
55313
|
+
function getMousePosition(event, lgv) {
|
|
55314
|
+
const canvas = event.currentTarget;
|
|
55315
|
+
const { clientX, clientY } = event;
|
|
55316
|
+
const { left, top } = canvas.getBoundingClientRect();
|
|
55317
|
+
const x = clientX - left;
|
|
55318
|
+
const y = clientY - top;
|
|
55319
|
+
const { coord: bp, index: regionNumber, refName } = lgv.pxToBp(x);
|
|
55320
|
+
return { x, y, refName, bp, regionNumber };
|
|
55321
|
+
}
|
|
55322
|
+
function getTranslationRow(frame, bpPerPx) {
|
|
55323
|
+
const offset = bpPerPx <= 1 ? 2 : 0;
|
|
55324
|
+
switch (frame) {
|
|
55325
|
+
case 3: {
|
|
55326
|
+
return 0;
|
|
54428
55327
|
}
|
|
54429
|
-
|
|
54430
|
-
|
|
54431
|
-
coord: overlappingExon.min,
|
|
54432
|
-
regionNumber,
|
|
54433
|
-
});
|
|
54434
|
-
const maxPxInfo = lgv.bpToPx({
|
|
54435
|
-
refName,
|
|
54436
|
-
coord: overlappingExon.max,
|
|
54437
|
-
regionNumber,
|
|
54438
|
-
});
|
|
54439
|
-
if (minPxInfo === undefined || maxPxInfo === undefined) {
|
|
54440
|
-
return;
|
|
55328
|
+
case 2: {
|
|
55329
|
+
return 1;
|
|
54441
55330
|
}
|
|
54442
|
-
|
|
54443
|
-
|
|
54444
|
-
if (Math.abs(maxPx - minPx) < 8) {
|
|
54445
|
-
return;
|
|
55331
|
+
case 1: {
|
|
55332
|
+
return 2;
|
|
54446
55333
|
}
|
|
54447
|
-
|
|
54448
|
-
return
|
|
55334
|
+
case -1: {
|
|
55335
|
+
return 3 + offset;
|
|
54449
55336
|
}
|
|
54450
|
-
|
|
54451
|
-
return
|
|
55337
|
+
case -2: {
|
|
55338
|
+
return 4 + offset;
|
|
54452
55339
|
}
|
|
54453
|
-
|
|
54454
|
-
|
|
54455
|
-
}
|
|
54456
|
-
// False positive here, none of these functions use "this"
|
|
54457
|
-
/* eslint-disable @typescript-eslint/unbound-method */
|
|
54458
|
-
const { drawTooltip: drawTooltip$1, getContextMenuItems: getContextMenuItems$1, onMouseLeave: onMouseLeave$1 } = boxGlyph;
|
|
54459
|
-
/* eslint-enable @typescript-eslint/unbound-method */
|
|
54460
|
-
const geneGlyph = {
|
|
54461
|
-
draw: draw$1,
|
|
54462
|
-
drawDragPreview: drawDragPreview$1,
|
|
54463
|
-
drawHover: drawHover$1,
|
|
54464
|
-
drawTooltip: drawTooltip$1,
|
|
54465
|
-
getContextMenuItems: getContextMenuItems$1,
|
|
54466
|
-
getFeatureFromLayout: getFeatureFromLayout$1,
|
|
54467
|
-
getRowCount: getRowCount$1,
|
|
54468
|
-
getRowForFeature: getRowForFeature$1,
|
|
54469
|
-
onMouseDown: onMouseDown$1,
|
|
54470
|
-
onMouseLeave: onMouseLeave$1,
|
|
54471
|
-
onMouseMove: onMouseMove$1,
|
|
54472
|
-
onMouseUp: onMouseUp$1,
|
|
54473
|
-
};
|
|
54474
|
-
|
|
54475
|
-
function featuresForRow(feature) {
|
|
54476
|
-
const features = [[feature]];
|
|
54477
|
-
if (feature.children) {
|
|
54478
|
-
for (const [, child] of feature.children) {
|
|
54479
|
-
features.push(...featuresForRow(child));
|
|
55340
|
+
case -3: {
|
|
55341
|
+
return 5 + offset;
|
|
54480
55342
|
}
|
|
54481
55343
|
}
|
|
54482
|
-
return features;
|
|
54483
|
-
}
|
|
54484
|
-
function getRowCount(feature) {
|
|
54485
|
-
return featuresForRow(feature).length;
|
|
54486
|
-
}
|
|
54487
|
-
function draw(ctx, feature, row, stateModel, displayedRegionIndex) {
|
|
54488
|
-
for (let i = 0; i < getRowCount(feature); i++) {
|
|
54489
|
-
drawRow(ctx, feature, row + i, row, stateModel, displayedRegionIndex);
|
|
54490
|
-
}
|
|
54491
|
-
}
|
|
54492
|
-
function drawRow(ctx, topLevelFeature, row, topRow, stateModel, displayedRegionIndex) {
|
|
54493
|
-
const features = featuresForRow(topLevelFeature)[row - topRow];
|
|
54494
|
-
for (const feature of features) {
|
|
54495
|
-
drawFeature(ctx, feature, row, stateModel, displayedRegionIndex);
|
|
54496
|
-
}
|
|
54497
|
-
}
|
|
54498
|
-
function drawFeature(ctx, feature, row, stateModel, displayedRegionIndex) {
|
|
54499
|
-
const { apolloRowHeight: heightPx, lgv, session } = stateModel;
|
|
54500
|
-
const { bpPerPx, displayedRegions, offsetPx } = lgv;
|
|
54501
|
-
const displayedRegion = displayedRegions[displayedRegionIndex];
|
|
54502
|
-
const minX = (lgv.bpToPx({
|
|
54503
|
-
refName: displayedRegion.refName,
|
|
54504
|
-
coord: feature.min,
|
|
54505
|
-
regionNumber: displayedRegionIndex,
|
|
54506
|
-
})?.offsetPx ?? 0) - offsetPx;
|
|
54507
|
-
const { reversed } = displayedRegion;
|
|
54508
|
-
const { apolloSelectedFeature } = session;
|
|
54509
|
-
const widthPx = feature.length / bpPerPx;
|
|
54510
|
-
const startPx = reversed ? minX - widthPx : minX;
|
|
54511
|
-
const top = row * heightPx;
|
|
54512
|
-
const rowCount = getRowCount(feature);
|
|
54513
|
-
const isSelected = isSelectedFeature(feature, apolloSelectedFeature);
|
|
54514
|
-
const groupingColor = isSelected ? 'rgba(130,0,0,0.45)' : 'rgba(255,0,0,0.25)';
|
|
54515
|
-
if (rowCount > 1) {
|
|
54516
|
-
// draw background that encapsulates all child features
|
|
54517
|
-
const featureHeight = rowCount * heightPx;
|
|
54518
|
-
drawBox(ctx, startPx, top, widthPx, featureHeight, groupingColor);
|
|
54519
|
-
}
|
|
54520
|
-
boxGlyph.draw(ctx, feature, row, stateModel, displayedRegionIndex);
|
|
54521
55344
|
}
|
|
54522
|
-
function
|
|
54523
|
-
|
|
54524
|
-
if (!apolloHover) {
|
|
54525
|
-
return;
|
|
54526
|
-
}
|
|
54527
|
-
const { feature } = apolloHover;
|
|
54528
|
-
const position = stateModel.getFeatureLayoutPosition(feature);
|
|
54529
|
-
if (!position) {
|
|
55345
|
+
function getSeqRow(strand, bpPerPx) {
|
|
55346
|
+
if (bpPerPx > 1 || strand === undefined) {
|
|
54530
55347
|
return;
|
|
54531
55348
|
}
|
|
54532
|
-
|
|
54533
|
-
const { bpPerPx, displayedRegions, offsetPx } = lgv;
|
|
54534
|
-
const displayedRegion = displayedRegions[layoutIndex];
|
|
54535
|
-
const { refName, reversed } = displayedRegion;
|
|
54536
|
-
const { length, max, min } = feature;
|
|
54537
|
-
const startPx = (lgv.bpToPx({
|
|
54538
|
-
refName,
|
|
54539
|
-
coord: reversed ? max : min,
|
|
54540
|
-
regionNumber: layoutIndex,
|
|
54541
|
-
})?.offsetPx ?? 0) - offsetPx;
|
|
54542
|
-
const top = (layoutRow + featureRow) * apolloRowHeight;
|
|
54543
|
-
const widthPx = length / bpPerPx;
|
|
54544
|
-
ctx.fillStyle = 'rgba(0,0,0,0.2)';
|
|
54545
|
-
ctx.fillRect(startPx, top, widthPx, apolloRowHeight * getRowCount(feature));
|
|
54546
|
-
}
|
|
54547
|
-
function getFeatureFromLayout(feature, bp, row) {
|
|
54548
|
-
const layoutRow = featuresForRow(feature)[row];
|
|
54549
|
-
return layoutRow.find((f) => bp >= f.min && bp <= f.max);
|
|
55349
|
+
return strand === 1 ? 3 : 4;
|
|
54550
55350
|
}
|
|
54551
|
-
function
|
|
54552
|
-
|
|
54553
|
-
|
|
54554
|
-
|
|
54555
|
-
|
|
54556
|
-
}
|
|
55351
|
+
function highlightSeq(seqTrackOverlayctx, theme, startPx, sequenceRowHeight, row, widthPx) {
|
|
55352
|
+
if (row !== undefined) {
|
|
55353
|
+
seqTrackOverlayctx.fillStyle =
|
|
55354
|
+
theme?.palette.action.focus ?? 'rgba(0,0,0,0.04)';
|
|
55355
|
+
seqTrackOverlayctx.fillRect(startPx, sequenceRowHeight * row, widthPx, sequenceRowHeight);
|
|
54557
55356
|
}
|
|
54558
|
-
return;
|
|
54559
55357
|
}
|
|
54560
|
-
|
|
54561
|
-
|
|
54562
|
-
|
|
54563
|
-
|
|
54564
|
-
|
|
54565
|
-
|
|
54566
|
-
|
|
54567
|
-
|
|
54568
|
-
|
|
54569
|
-
|
|
54570
|
-
|
|
54571
|
-
|
|
54572
|
-
|
|
54573
|
-
|
|
54574
|
-
|
|
54575
|
-
|
|
54576
|
-
|
|
54577
|
-
|
|
54578
|
-
|
|
54579
|
-
|
|
54580
|
-
|
|
54581
|
-
|
|
54582
|
-
|
|
54583
|
-
|
|
54584
|
-
|
|
54585
|
-
|
|
54586
|
-
|
|
54587
|
-
|
|
55358
|
+
function mouseEventsModelIntermediateFactory(pluginManager, configSchema) {
|
|
55359
|
+
const LinearApolloDisplayRendering = renderingModelFactory(pluginManager, configSchema);
|
|
55360
|
+
return LinearApolloDisplayRendering.named('LinearApolloDisplayMouseEvents')
|
|
55361
|
+
.volatile(() => ({
|
|
55362
|
+
apolloDragging: null,
|
|
55363
|
+
cursor: undefined,
|
|
55364
|
+
apolloHover: undefined,
|
|
55365
|
+
}))
|
|
55366
|
+
.views((self) => ({
|
|
55367
|
+
getMousePosition(event) {
|
|
55368
|
+
const mousePosition = getMousePosition(event, self.lgv);
|
|
55369
|
+
const { bp, regionNumber, y } = mousePosition;
|
|
55370
|
+
const row = Math.floor(y / self.apolloRowHeight);
|
|
55371
|
+
const featureLayout = self.featureLayouts[regionNumber];
|
|
55372
|
+
const layoutRow = featureLayout.get(row);
|
|
55373
|
+
if (!layoutRow) {
|
|
55374
|
+
return mousePosition;
|
|
55375
|
+
}
|
|
55376
|
+
const foundFeature = layoutRow.find((f) => bp >= f[1].min && bp <= f[1].max);
|
|
55377
|
+
if (!foundFeature) {
|
|
55378
|
+
return mousePosition;
|
|
55379
|
+
}
|
|
55380
|
+
const [featureRow, topLevelFeature] = foundFeature;
|
|
55381
|
+
const glyph = self.getGlyph(topLevelFeature);
|
|
55382
|
+
const { featureTypeOntology } = self.session.apolloDataStore.ontologyManager;
|
|
55383
|
+
if (!featureTypeOntology) {
|
|
55384
|
+
throw new Error('featureTypeOntology is undefined');
|
|
55385
|
+
}
|
|
55386
|
+
const feature = glyph.getFeatureFromLayout(topLevelFeature, bp, featureRow, featureTypeOntology);
|
|
55387
|
+
if (!feature) {
|
|
55388
|
+
return mousePosition;
|
|
55389
|
+
}
|
|
55390
|
+
return {
|
|
55391
|
+
...mousePosition,
|
|
55392
|
+
featureAndGlyphUnderMouse: { feature, topLevelFeature, glyph },
|
|
55393
|
+
};
|
|
55394
|
+
},
|
|
55395
|
+
}))
|
|
55396
|
+
.actions((self) => ({
|
|
55397
|
+
continueDrag(mousePosition, event) {
|
|
55398
|
+
if (!self.apolloDragging) {
|
|
55399
|
+
throw new Error('continueDrag() called with no current drag in progress');
|
|
55400
|
+
}
|
|
55401
|
+
event.stopPropagation();
|
|
55402
|
+
self.apolloDragging = { ...self.apolloDragging, current: mousePosition };
|
|
55403
|
+
},
|
|
55404
|
+
setDragging(dragInfo) {
|
|
55405
|
+
self.apolloDragging = dragInfo ?? null;
|
|
55406
|
+
},
|
|
55407
|
+
}))
|
|
55408
|
+
.actions((self) => ({
|
|
55409
|
+
setApolloHover(n) {
|
|
55410
|
+
self.apolloHover = n;
|
|
55411
|
+
},
|
|
55412
|
+
setCursor(cursor) {
|
|
55413
|
+
if (self.cursor !== cursor) {
|
|
55414
|
+
self.cursor = cursor;
|
|
55415
|
+
}
|
|
55416
|
+
},
|
|
55417
|
+
}))
|
|
55418
|
+
.actions(() => ({
|
|
55419
|
+
// onClick(event: CanvasMouseEvent) {
|
|
55420
|
+
onClick() {
|
|
55421
|
+
// TODO: set the selected feature
|
|
55422
|
+
},
|
|
55423
|
+
}));
|
|
55424
|
+
}
|
|
55425
|
+
function mouseEventsSeqHightlightModelFactory(pluginManager, configSchema) {
|
|
55426
|
+
const LinearApolloDisplayRendering = mouseEventsModelIntermediateFactory(pluginManager, configSchema);
|
|
55427
|
+
return LinearApolloDisplayRendering.actions((self) => ({
|
|
55428
|
+
afterAttach() {
|
|
55429
|
+
require$$1$3.addDisposer(self, mobx.autorun(() => {
|
|
55430
|
+
// This type is wrong in @jbrowse/core
|
|
55431
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
55432
|
+
if (!self.lgv.initialized || self.regionCannotBeRendered()) {
|
|
55433
|
+
return;
|
|
55434
|
+
}
|
|
55435
|
+
const seqTrackOverlayctx = self.seqTrackOverlayCanvas?.getContext('2d');
|
|
55436
|
+
if (!seqTrackOverlayctx) {
|
|
55437
|
+
return;
|
|
55438
|
+
}
|
|
55439
|
+
seqTrackOverlayctx.clearRect(0, 0, self.lgv.dynamicBlocks.totalWidthPx, self.lgv.bpPerPx <= 1 ? 125 : 95);
|
|
55440
|
+
const { apolloHover, lgv, regions, sequenceRowHeight, session, theme, } = self;
|
|
55441
|
+
if (!apolloHover) {
|
|
55442
|
+
return;
|
|
55443
|
+
}
|
|
55444
|
+
const { feature } = apolloHover;
|
|
55445
|
+
const { featureTypeOntology } = session.apolloDataStore.ontologyManager;
|
|
55446
|
+
if (!featureTypeOntology) {
|
|
55447
|
+
throw new Error('featureTypeOntology is undefined');
|
|
55448
|
+
}
|
|
55449
|
+
for (const [idx, region] of regions.entries()) {
|
|
55450
|
+
if (featureTypeOntology.isTypeOf(feature.type, 'CDS')) {
|
|
55451
|
+
const parentFeature = feature.parent;
|
|
55452
|
+
if (!parentFeature) {
|
|
55453
|
+
continue;
|
|
55454
|
+
}
|
|
55455
|
+
const cdsLocs = parentFeature.cdsLocations.find((loc) => feature.min === loc.at(0)?.min &&
|
|
55456
|
+
feature.max === loc.at(-1)?.max);
|
|
55457
|
+
if (!cdsLocs) {
|
|
55458
|
+
continue;
|
|
55459
|
+
}
|
|
55460
|
+
for (const dl of cdsLocs) {
|
|
55461
|
+
const frame = require$$1$2.getFrame(dl.min, dl.max, feature.strand ?? 1, dl.phase);
|
|
55462
|
+
const row = getTranslationRow(frame, lgv.bpPerPx);
|
|
55463
|
+
const offset = (lgv.bpToPx({
|
|
55464
|
+
refName: region.refName,
|
|
55465
|
+
coord: dl.min,
|
|
55466
|
+
regionNumber: idx,
|
|
55467
|
+
})?.offsetPx ?? 0) - lgv.offsetPx;
|
|
55468
|
+
const widthPx = (dl.max - dl.min) / lgv.bpPerPx;
|
|
55469
|
+
const startPx = lgv.displayedRegions[idx].reversed
|
|
55470
|
+
? offset - widthPx
|
|
55471
|
+
: offset;
|
|
55472
|
+
highlightSeq(seqTrackOverlayctx, theme, startPx, sequenceRowHeight, row, widthPx);
|
|
55473
|
+
}
|
|
55474
|
+
}
|
|
55475
|
+
else {
|
|
55476
|
+
const row = getSeqRow(feature.strand, lgv.bpPerPx);
|
|
55477
|
+
const offset = (lgv.bpToPx({
|
|
55478
|
+
refName: region.refName,
|
|
55479
|
+
coord: feature.min,
|
|
55480
|
+
regionNumber: idx,
|
|
55481
|
+
})?.offsetPx ?? 0) - lgv.offsetPx;
|
|
55482
|
+
const widthPx = feature.length / lgv.bpPerPx;
|
|
55483
|
+
const startPx = lgv.displayedRegions[idx].reversed
|
|
55484
|
+
? offset - widthPx
|
|
55485
|
+
: offset;
|
|
55486
|
+
highlightSeq(seqTrackOverlayctx, theme, startPx, sequenceRowHeight, row, widthPx);
|
|
55487
|
+
}
|
|
55488
|
+
}
|
|
55489
|
+
}, { name: 'LinearApolloDisplayRenderSeqHighlight' }));
|
|
55490
|
+
},
|
|
55491
|
+
}));
|
|
54588
55492
|
}
|
|
54589
|
-
function
|
|
54590
|
-
const
|
|
54591
|
-
|
|
54592
|
-
|
|
54593
|
-
|
|
54594
|
-
|
|
54595
|
-
|
|
54596
|
-
const { children: grandChildren } = child;
|
|
54597
|
-
if (!grandChildren?.size) {
|
|
54598
|
-
return false;
|
|
54599
|
-
}
|
|
54600
|
-
const hasCDS = [...grandChildren.values()].some((grandchild) => grandchild.type === 'CDS');
|
|
54601
|
-
const hasExon = [...grandChildren.values()].some((grandchild) => grandchild.type === 'exon');
|
|
54602
|
-
if (hasCDS && hasExon) {
|
|
54603
|
-
return true;
|
|
55493
|
+
function mouseEventsModelFactory(pluginManager, configSchema) {
|
|
55494
|
+
const LinearApolloDisplayMouseEvents = mouseEventsSeqHightlightModelFactory(pluginManager, configSchema);
|
|
55495
|
+
return LinearApolloDisplayMouseEvents.views((self) => ({
|
|
55496
|
+
contextMenuItems(contextCoord) {
|
|
55497
|
+
const { apolloHover } = self;
|
|
55498
|
+
if (!(apolloHover && contextCoord)) {
|
|
55499
|
+
return [];
|
|
54604
55500
|
}
|
|
54605
|
-
|
|
54606
|
-
|
|
54607
|
-
|
|
54608
|
-
}
|
|
54609
|
-
|
|
54610
|
-
/* eslint-disable @typescript-eslint/use-unknown-in-catch-callback-variable */
|
|
54611
|
-
const useStyles$4 = mui.makeStyles()((theme) => ({
|
|
54612
|
-
typeContent: {
|
|
54613
|
-
display: 'inline-block',
|
|
54614
|
-
width: '174px',
|
|
54615
|
-
height: '100%',
|
|
54616
|
-
cursor: 'text',
|
|
54617
|
-
},
|
|
54618
|
-
feature: {
|
|
54619
|
-
td: {
|
|
54620
|
-
position: 'relative',
|
|
54621
|
-
verticalAlign: 'top',
|
|
54622
|
-
paddingLeft: '0.5em',
|
|
55501
|
+
const { topLevelFeature } = apolloHover;
|
|
55502
|
+
const glyph = self.getGlyph(topLevelFeature);
|
|
55503
|
+
return glyph.getContextMenuItems(self);
|
|
54623
55504
|
},
|
|
54624
|
-
}
|
|
54625
|
-
|
|
54626
|
-
|
|
54627
|
-
|
|
54628
|
-
|
|
54629
|
-
|
|
54630
|
-
|
|
54631
|
-
|
|
54632
|
-
|
|
54633
|
-
|
|
54634
|
-
|
|
54635
|
-
|
|
54636
|
-
|
|
54637
|
-
|
|
54638
|
-
|
|
54639
|
-
|
|
54640
|
-
|
|
54641
|
-
|
|
54642
|
-
|
|
54643
|
-
|
|
54644
|
-
|
|
54645
|
-
|
|
54646
|
-
|
|
54647
|
-
|
|
54648
|
-
|
|
54649
|
-
|
|
54650
|
-
|
|
54651
|
-
|
|
54652
|
-
|
|
54653
|
-
|
|
54654
|
-
|
|
54655
|
-
|
|
54656
|
-
|
|
54657
|
-
|
|
54658
|
-
|
|
54659
|
-
|
|
54660
|
-
|
|
54661
|
-
|
|
54662
|
-
const toggleExpanded = (e) => {
|
|
54663
|
-
e.stopPropagation();
|
|
54664
|
-
tabularEditorState.setFeatureCollapsed(_id, expanded);
|
|
54665
|
-
};
|
|
54666
|
-
// pop up a snackbar in the session notifying user of an error
|
|
54667
|
-
const notifyError = (e) => {
|
|
54668
|
-
session.notify(e.message, 'error');
|
|
54669
|
-
};
|
|
54670
|
-
return (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
54671
|
-
React__default["default"].createElement("tr", { onMouseEnter: (_e) => {
|
|
54672
|
-
displayState.setApolloHover({
|
|
54673
|
-
feature,
|
|
54674
|
-
topLevelFeature: getTopLevelFeature(feature),
|
|
54675
|
-
glyph: getGlyph(getTopLevelFeature(feature)),
|
|
55505
|
+
}))
|
|
55506
|
+
.actions((self) => ({
|
|
55507
|
+
// explicitly pass in a feature in case it's not the same as the one in
|
|
55508
|
+
// mousePosition (e.g. if features are drawn overlapping).
|
|
55509
|
+
startDrag(mousePosition, feature, edge) {
|
|
55510
|
+
self.apolloDragging = {
|
|
55511
|
+
start: mousePosition,
|
|
55512
|
+
current: mousePosition,
|
|
55513
|
+
feature,
|
|
55514
|
+
edge,
|
|
55515
|
+
};
|
|
55516
|
+
},
|
|
55517
|
+
endDrag() {
|
|
55518
|
+
if (!self.apolloDragging) {
|
|
55519
|
+
throw new Error('endDrag() called with no current drag in progress');
|
|
55520
|
+
}
|
|
55521
|
+
const { current, edge, feature, start } = self.apolloDragging;
|
|
55522
|
+
// don't do anything if it was only dragged a tiny bit
|
|
55523
|
+
if (Math.abs(current.x - start.x) <= 4) {
|
|
55524
|
+
self.setDragging();
|
|
55525
|
+
self.setCursor();
|
|
55526
|
+
return;
|
|
55527
|
+
}
|
|
55528
|
+
const { displayedRegions } = self.lgv;
|
|
55529
|
+
const region = displayedRegions[start.regionNumber];
|
|
55530
|
+
const assembly = self.getAssemblyId(region.assemblyName);
|
|
55531
|
+
let change;
|
|
55532
|
+
if (edge === 'max') {
|
|
55533
|
+
const featureId = feature._id;
|
|
55534
|
+
const oldEnd = feature.max;
|
|
55535
|
+
const newEnd = current.bp;
|
|
55536
|
+
change = new dist$2.LocationEndChange({
|
|
55537
|
+
typeName: 'LocationEndChange',
|
|
55538
|
+
changedIds: [featureId],
|
|
55539
|
+
featureId,
|
|
55540
|
+
oldEnd,
|
|
55541
|
+
newEnd,
|
|
55542
|
+
assembly,
|
|
54676
55543
|
});
|
|
54677
|
-
}
|
|
54678
|
-
|
|
54679
|
-
|
|
54680
|
-
|
|
54681
|
-
|
|
54682
|
-
|
|
54683
|
-
|
|
54684
|
-
|
|
54685
|
-
|
|
54686
|
-
|
|
54687
|
-
|
|
54688
|
-
|
|
54689
|
-
items: makeContextMenuItems(displayState, feature),
|
|
55544
|
+
}
|
|
55545
|
+
else {
|
|
55546
|
+
const featureId = feature._id;
|
|
55547
|
+
const oldStart = feature.min;
|
|
55548
|
+
const newStart = current.bp;
|
|
55549
|
+
change = new dist$2.LocationStartChange({
|
|
55550
|
+
typeName: 'LocationStartChange',
|
|
55551
|
+
changedIds: [featureId],
|
|
55552
|
+
featureId,
|
|
55553
|
+
oldStart,
|
|
55554
|
+
newStart,
|
|
55555
|
+
assembly,
|
|
54690
55556
|
});
|
|
54691
|
-
|
|
54692
|
-
|
|
54693
|
-
|
|
54694
|
-
|
|
54695
|
-
borderLeft: `${depth * 2}em solid transparent`,
|
|
54696
|
-
} },
|
|
54697
|
-
children?.size ? (
|
|
54698
|
-
// TODO: a11y
|
|
54699
|
-
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
|
54700
|
-
React__default["default"].createElement("div", { onClick: toggleExpanded, className: classes.arrow + (expanded ? ` ${classes.arrowExpanded}` : '') }, "\u276F")) : null,
|
|
54701
|
-
React__default["default"].createElement("div", { className: classes.typeContent },
|
|
54702
|
-
React__default["default"].createElement(OntologyTermAutocomplete, { session: session, ontologyName: "Sequence Ontology", style: { width: 170 }, value: type, filterTerms: isOntologyClass, fetchValidTerms: fetchValidTypeTerms.bind(null, feature), renderInput: (params) => {
|
|
54703
|
-
return (React__default["default"].createElement("div", { ref: params.InputProps.ref },
|
|
54704
|
-
React__default["default"].createElement("input", { type: "text", ...params.inputProps, className: classes.typeInputElement, style: { width: 170 } }),
|
|
54705
|
-
params.error ? (React__default["default"].createElement("div", { className: classes.typeErrorMessage }, params.errorMessage ?? 'unknown error')) : null));
|
|
54706
|
-
}, onChange: (oldValue, newValue) => {
|
|
54707
|
-
if (newValue) {
|
|
54708
|
-
handleFeatureTypeChange(changeManager, feature, oldValue, newValue).catch(notifyError);
|
|
54709
|
-
}
|
|
54710
|
-
} }))),
|
|
54711
|
-
React__default["default"].createElement("td", null,
|
|
54712
|
-
React__default["default"].createElement(NumberCell, { initialValue: min + 1, notifyError: notifyError, onChangeCommitted: (newStart) => handleFeatureStartChange(changeManager, feature, min, newStart - 1) })),
|
|
54713
|
-
React__default["default"].createElement("td", null,
|
|
54714
|
-
React__default["default"].createElement(NumberCell, { initialValue: max, notifyError: notifyError, onChangeCommitted: (newEnd) => handleFeatureEndChange(changeManager, feature, max, newEnd) })),
|
|
54715
|
-
React__default["default"].createElement("td", null, strand === 1 ? '+' : strand === -1 ? '-' : undefined),
|
|
54716
|
-
React__default["default"].createElement("td", null,
|
|
54717
|
-
React__default["default"].createElement(FeatureAttributes, { filterText: filterText, feature: feature }))),
|
|
54718
|
-
expanded && children
|
|
54719
|
-
? [...children.entries()]
|
|
54720
|
-
.filter((entry) => {
|
|
54721
|
-
if (!filterText) {
|
|
54722
|
-
return true;
|
|
54723
|
-
}
|
|
54724
|
-
const [, childFeature] = entry;
|
|
54725
|
-
// search feature and its subfeatures for the text
|
|
54726
|
-
const text = JSON.stringify(childFeature);
|
|
54727
|
-
return text.includes(filterText);
|
|
54728
|
-
})
|
|
54729
|
-
.map(([featureId, childFeature]) => {
|
|
54730
|
-
const childHovered = apolloHover?.feature._id === childFeature._id;
|
|
54731
|
-
const childSelected = selectedFeature?._id === childFeature._id;
|
|
54732
|
-
return (React__default["default"].createElement(Feature, { isHovered: childHovered, isSelected: childSelected, selectedFeatureClass: selectedFeatureClass, key: featureId, depth: (depth || 0) + 1, feature: childFeature, model: displayState, setContextMenu: setContextMenu }));
|
|
54733
|
-
})
|
|
54734
|
-
: null));
|
|
54735
|
-
});
|
|
54736
|
-
async function fetchValidTypeTerms(feature, ontologyStore, _signal) {
|
|
54737
|
-
const { parent: parentFeature } = feature;
|
|
54738
|
-
if (parentFeature) {
|
|
54739
|
-
// if this is a child of an existing feature, restrict the autocomplete choices to valid
|
|
54740
|
-
// parts of that feature
|
|
54741
|
-
const parentTypeTerms = await ontologyStore.getTermsWithLabelOrSynonym(parentFeature.type, { includeSubclasses: false });
|
|
54742
|
-
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
54743
|
-
const parentTypeClassTerms = parentTypeTerms.filter(isOntologyClass);
|
|
54744
|
-
if (parentTypeClassTerms.length > 0) {
|
|
54745
|
-
const subpartTerms = await ontologyStore.getClassesThat('part_of', parentTypeClassTerms);
|
|
54746
|
-
return subpartTerms;
|
|
54747
|
-
}
|
|
54748
|
-
}
|
|
54749
|
-
return;
|
|
54750
|
-
}
|
|
54751
|
-
|
|
54752
|
-
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
54753
|
-
const useStyles$3 = mui.makeStyles()((theme) => ({
|
|
54754
|
-
scrollableTable: {
|
|
54755
|
-
width: '100%',
|
|
54756
|
-
height: '100%',
|
|
54757
|
-
th: {
|
|
54758
|
-
position: 'sticky',
|
|
54759
|
-
top: 0,
|
|
54760
|
-
zIndex: 2,
|
|
54761
|
-
textAlign: 'left',
|
|
54762
|
-
background: theme.palette.background.paper,
|
|
54763
|
-
paddingTop: '3.2em',
|
|
55557
|
+
}
|
|
55558
|
+
void self.changeManager.submit(change);
|
|
55559
|
+
self.setDragging();
|
|
55560
|
+
self.setCursor();
|
|
54764
55561
|
},
|
|
54765
|
-
|
|
54766
|
-
|
|
54767
|
-
|
|
54768
|
-
|
|
54769
|
-
|
|
54770
|
-
|
|
54771
|
-
const HybridGrid = mobxReact.observer(function HybridGrid({ model, }) {
|
|
54772
|
-
const { apolloHover, seenFeatures, selectedFeature, tabularEditor } = model;
|
|
54773
|
-
const theme = material.useTheme();
|
|
54774
|
-
const { classes } = useStyles$3();
|
|
54775
|
-
const scrollContainerRef = React.useRef(null);
|
|
54776
|
-
const [contextMenu, setContextMenu] = React.useState(null);
|
|
54777
|
-
const { filterText } = tabularEditor;
|
|
54778
|
-
// scrolls to selected feature if one is selected and it's not already visible
|
|
54779
|
-
React.useEffect(() => {
|
|
54780
|
-
const scrollContainer = scrollContainerRef.current;
|
|
54781
|
-
if (scrollContainer && selectedFeature) {
|
|
54782
|
-
const selectedRow = scrollContainer.querySelector(`.${classes.selectedFeature}`);
|
|
54783
|
-
if (selectedRow) {
|
|
54784
|
-
const currScroll = scrollContainer.scrollTop;
|
|
54785
|
-
const newScrollTop = selectedRow.offsetTop - 25;
|
|
54786
|
-
const isVisible = newScrollTop > currScroll &&
|
|
54787
|
-
newScrollTop < currScroll + scrollContainer.offsetHeight;
|
|
54788
|
-
if (!isVisible) {
|
|
54789
|
-
scrollContainer.scroll({ top: newScrollTop - 40, behavior: 'smooth' });
|
|
54790
|
-
}
|
|
55562
|
+
}))
|
|
55563
|
+
.actions((self) => ({
|
|
55564
|
+
onMouseDown(event) {
|
|
55565
|
+
const mousePosition = self.getMousePosition(event);
|
|
55566
|
+
if (isMousePositionWithFeatureAndGlyph(mousePosition)) {
|
|
55567
|
+
mousePosition.featureAndGlyphUnderMouse.glyph.onMouseDown(self, mousePosition, event);
|
|
54791
55568
|
}
|
|
54792
|
-
}
|
|
54793
|
-
|
|
54794
|
-
|
|
54795
|
-
|
|
54796
|
-
|
|
54797
|
-
|
|
54798
|
-
|
|
54799
|
-
|
|
54800
|
-
|
|
54801
|
-
|
|
54802
|
-
|
|
54803
|
-
|
|
54804
|
-
.
|
|
54805
|
-
|
|
54806
|
-
|
|
55569
|
+
},
|
|
55570
|
+
onMouseMove(event) {
|
|
55571
|
+
const mousePosition = self.getMousePosition(event);
|
|
55572
|
+
if (self.apolloDragging) {
|
|
55573
|
+
self.setCursor('col-resize');
|
|
55574
|
+
self.continueDrag(mousePosition, event);
|
|
55575
|
+
return;
|
|
55576
|
+
}
|
|
55577
|
+
if (isMousePositionWithFeatureAndGlyph(mousePosition)) {
|
|
55578
|
+
mousePosition.featureAndGlyphUnderMouse.glyph.onMouseMove(self, mousePosition, event);
|
|
55579
|
+
}
|
|
55580
|
+
else {
|
|
55581
|
+
self.setApolloHover();
|
|
55582
|
+
self.setCursor();
|
|
55583
|
+
}
|
|
55584
|
+
},
|
|
55585
|
+
onMouseLeave(event) {
|
|
55586
|
+
self.setDragging();
|
|
55587
|
+
self.setApolloHover();
|
|
55588
|
+
const mousePosition = self.getMousePosition(event);
|
|
55589
|
+
if (isMousePositionWithFeatureAndGlyph(mousePosition)) {
|
|
55590
|
+
mousePosition.featureAndGlyphUnderMouse.glyph.onMouseLeave(self, mousePosition, event);
|
|
55591
|
+
}
|
|
55592
|
+
},
|
|
55593
|
+
onMouseUp(event) {
|
|
55594
|
+
const mousePosition = self.getMousePosition(event);
|
|
55595
|
+
if (isMousePositionWithFeatureAndGlyph(mousePosition)) {
|
|
55596
|
+
mousePosition.featureAndGlyphUnderMouse.glyph.onMouseUp(self, mousePosition, event);
|
|
55597
|
+
}
|
|
55598
|
+
if (self.apolloDragging) {
|
|
55599
|
+
self.endDrag();
|
|
55600
|
+
}
|
|
55601
|
+
},
|
|
55602
|
+
}))
|
|
55603
|
+
.actions((self) => ({
|
|
55604
|
+
afterAttach() {
|
|
55605
|
+
require$$1$3.addDisposer(self, mobx.autorun(() => {
|
|
55606
|
+
// This type is wrong in @jbrowse/core
|
|
55607
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
55608
|
+
if (!self.lgv.initialized || self.regionCannotBeRendered()) {
|
|
55609
|
+
return;
|
|
54807
55610
|
}
|
|
54808
|
-
const
|
|
54809
|
-
|
|
54810
|
-
|
|
54811
|
-
|
|
54812
|
-
|
|
54813
|
-
|
|
54814
|
-
|
|
54815
|
-
|
|
54816
|
-
|
|
54817
|
-
const
|
|
54818
|
-
|
|
54819
|
-
|
|
54820
|
-
|
|
54821
|
-
|
|
54822
|
-
|
|
54823
|
-
|
|
54824
|
-
|
|
54825
|
-
|
|
54826
|
-
|
|
54827
|
-
|
|
54828
|
-
|
|
54829
|
-
|
|
54830
|
-
|
|
54831
|
-
|
|
54832
|
-
|
|
54833
|
-
var Clear = {};
|
|
54834
|
-
|
|
54835
|
-
var _interopRequireDefault$5 = interopRequireDefault.exports;
|
|
54836
|
-
Object.defineProperty(Clear, "__esModule", {
|
|
54837
|
-
value: true
|
|
54838
|
-
});
|
|
54839
|
-
var default_1$5 = Clear["default"] = void 0;
|
|
54840
|
-
var _createSvgIcon$5 = /*#__PURE__*/_interopRequireDefault$5(createSvgIcon);
|
|
54841
|
-
var _jsxRuntime$5 = require$$2__default["default"];
|
|
54842
|
-
var _default$5 = /*#__PURE__*/(0, _createSvgIcon$5["default"])( /*#__PURE__*/(0, _jsxRuntime$5.jsx)("path", {
|
|
54843
|
-
d: "M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"
|
|
54844
|
-
}), 'Clear');
|
|
54845
|
-
default_1$5 = Clear["default"] = _default$5;
|
|
54846
|
-
|
|
54847
|
-
var UnfoldLess = {};
|
|
54848
|
-
|
|
54849
|
-
var _interopRequireDefault$4 = interopRequireDefault.exports;
|
|
54850
|
-
Object.defineProperty(UnfoldLess, "__esModule", {
|
|
54851
|
-
value: true
|
|
54852
|
-
});
|
|
54853
|
-
var default_1$4 = UnfoldLess["default"] = void 0;
|
|
54854
|
-
var _createSvgIcon$4 = /*#__PURE__*/_interopRequireDefault$4(createSvgIcon);
|
|
54855
|
-
var _jsxRuntime$4 = require$$2__default["default"];
|
|
54856
|
-
var _default$4 = /*#__PURE__*/(0, _createSvgIcon$4["default"])( /*#__PURE__*/(0, _jsxRuntime$4.jsx)("path", {
|
|
54857
|
-
d: "M7.41 18.59 8.83 20 12 16.83 15.17 20l1.41-1.41L12 14l-4.59 4.59zm9.18-13.18L15.17 4 12 7.17 8.83 4 7.41 5.41 12 10l4.59-4.59z"
|
|
54858
|
-
}), 'UnfoldLess');
|
|
54859
|
-
default_1$4 = UnfoldLess["default"] = _default$4;
|
|
54860
|
-
|
|
54861
|
-
/* eslint-disable @typescript-eslint/unbound-method */
|
|
54862
|
-
const useStyles$2 = mui.makeStyles()({
|
|
54863
|
-
toolbar: {
|
|
54864
|
-
width: '100%',
|
|
54865
|
-
display: 'flex',
|
|
54866
|
-
paddingRight: '2em',
|
|
54867
|
-
flexDirection: 'row',
|
|
54868
|
-
justifyContent: 'space-between',
|
|
54869
|
-
position: 'absolute',
|
|
54870
|
-
zIndex: 4,
|
|
54871
|
-
},
|
|
54872
|
-
filterText: {},
|
|
54873
|
-
});
|
|
54874
|
-
const ToolBar = mobxReact.observer(function ToolBar({ model: displayState, }) {
|
|
54875
|
-
const model = displayState.tabularEditor;
|
|
54876
|
-
const { classes } = useStyles$2();
|
|
54877
|
-
return (React__default["default"].createElement("div", { className: classes.toolbar },
|
|
54878
|
-
React__default["default"].createElement(material.Tooltip, { title: "Collapse all" },
|
|
54879
|
-
React__default["default"].createElement(material.IconButton, { "aria-label": "collapse", sx: { marginTop: 0 }, onClick: model.collapseAllFeatures },
|
|
54880
|
-
React__default["default"].createElement(default_1$4, null))),
|
|
54881
|
-
React__default["default"].createElement(material.TextField, { className: classes.filterText, label: "Filter features", value: model.filterText, sx: { marginTop: 0 }, variant: "outlined", onChange: (event) => {
|
|
54882
|
-
model.setFilterText(event.target.value);
|
|
54883
|
-
}, InputProps: {
|
|
54884
|
-
endAdornment: (React__default["default"].createElement(material.InputAdornment, { position: "end" },
|
|
54885
|
-
React__default["default"].createElement(material.IconButton, { onClick: () => {
|
|
54886
|
-
model.clearFilterText();
|
|
54887
|
-
} },
|
|
54888
|
-
React__default["default"].createElement(default_1$5, null)))),
|
|
54889
|
-
} })));
|
|
54890
|
-
});
|
|
54891
|
-
|
|
54892
|
-
function stopPropagation(e) {
|
|
54893
|
-
e.stopPropagation();
|
|
55611
|
+
const ctx = self.overlayCanvas?.getContext('2d');
|
|
55612
|
+
if (!ctx) {
|
|
55613
|
+
return;
|
|
55614
|
+
}
|
|
55615
|
+
ctx.clearRect(0, 0, self.lgv.dynamicBlocks.totalWidthPx, self.featuresHeight);
|
|
55616
|
+
const { apolloDragging, apolloHover } = self;
|
|
55617
|
+
if (!apolloHover) {
|
|
55618
|
+
return;
|
|
55619
|
+
}
|
|
55620
|
+
const { glyph } = apolloHover;
|
|
55621
|
+
// draw mouseover hovers
|
|
55622
|
+
glyph.drawHover(self, ctx);
|
|
55623
|
+
// draw tooltip on hover
|
|
55624
|
+
glyph.drawTooltip(self, ctx);
|
|
55625
|
+
// dragging previews
|
|
55626
|
+
if (apolloDragging) {
|
|
55627
|
+
// NOTE: the glyph where the drag started is responsible for drawing the preview.
|
|
55628
|
+
// it can call methods in other glyphs to help with this though.
|
|
55629
|
+
const glyph = self.getGlyph(apolloDragging.feature.topLevelFeature);
|
|
55630
|
+
glyph.drawDragPreview(self, ctx);
|
|
55631
|
+
}
|
|
55632
|
+
}, { name: 'LinearApolloDisplayRenderMouseoverAndDrag' }));
|
|
55633
|
+
},
|
|
55634
|
+
}));
|
|
54894
55635
|
}
|
|
54895
|
-
const TabularEditorPane = mobxReact.observer(function TabularEditorPane({ model: displayState, }) {
|
|
54896
|
-
const model = displayState.tabularEditor;
|
|
54897
|
-
if (!model.isShown) {
|
|
54898
|
-
return null;
|
|
54899
|
-
}
|
|
54900
|
-
return (
|
|
54901
|
-
// TODO: a11y
|
|
54902
|
-
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
|
54903
|
-
React__default["default"].createElement("div", { onMouseDown: stopPropagation, onClick: stopPropagation, style: { width: '100%', height: '100%', position: 'relative' } },
|
|
54904
|
-
React__default["default"].createElement(ToolBar, { model: displayState }),
|
|
54905
|
-
React__default["default"].createElement(HybridGrid, { model: displayState })));
|
|
54906
|
-
});
|
|
54907
|
-
|
|
54908
|
-
const TabularEditorStateModelType = require$$1$3.types
|
|
54909
|
-
.model('TabularEditor', {
|
|
54910
|
-
isShown: true,
|
|
54911
|
-
featureCollapsed: require$$1$3.types.map(require$$1$3.types.boolean),
|
|
54912
|
-
filterText: '',
|
|
54913
|
-
})
|
|
54914
|
-
.actions((self) => ({
|
|
54915
|
-
setFeatureCollapsed(id, state) {
|
|
54916
|
-
self.featureCollapsed.set(id, state);
|
|
54917
|
-
},
|
|
54918
|
-
setFilterText(text) {
|
|
54919
|
-
self.filterText = text;
|
|
54920
|
-
},
|
|
54921
|
-
clearFilterText() {
|
|
54922
|
-
self.filterText = '';
|
|
54923
|
-
},
|
|
54924
|
-
collapseAllFeatures() {
|
|
54925
|
-
// iterate over all seen features and set them to collapsed
|
|
54926
|
-
const display = require$$1$3.getParent(self);
|
|
54927
|
-
for (const [featureId] of display.seenFeatures.entries()) {
|
|
54928
|
-
self.featureCollapsed.set(featureId, true);
|
|
54929
|
-
}
|
|
54930
|
-
},
|
|
54931
|
-
togglePane() {
|
|
54932
|
-
self.isShown = !self.isShown;
|
|
54933
|
-
},
|
|
54934
|
-
hidePane() {
|
|
54935
|
-
self.isShown = false;
|
|
54936
|
-
},
|
|
54937
|
-
showPane() {
|
|
54938
|
-
self.isShown = true;
|
|
54939
|
-
},
|
|
54940
|
-
// onPatch(patch: any) {
|
|
54941
|
-
// console.log(patch)
|
|
54942
|
-
// },
|
|
54943
|
-
}));
|
|
54944
55636
|
|
|
54945
55637
|
function stateModelFactory$1(pluginManager, configSchema) {
|
|
54946
55638
|
// TODO: this needs to be refactored so that the final composition of the
|
|
@@ -54992,7 +55684,7 @@
|
|
|
54992
55684
|
}), 'Error');
|
|
54993
55685
|
default_1$1 = _Error["default"] = _default$1;
|
|
54994
55686
|
|
|
54995
|
-
/* eslint-disable @typescript-eslint/
|
|
55687
|
+
/* eslint-disable @typescript-eslint/unbound-method */
|
|
54996
55688
|
const useStyles$1 = mui.makeStyles()((theme) => ({
|
|
54997
55689
|
canvasContainer: {
|
|
54998
55690
|
position: 'relative',
|
|
@@ -56042,8 +56734,34 @@
|
|
|
56042
56734
|
configuration.readConfObject(ont, 'textIndexFields'),
|
|
56043
56735
|
];
|
|
56044
56736
|
if (!ontologyManager.findOntology(name)) {
|
|
56737
|
+
const session = require$$1$2.getSession(self);
|
|
56738
|
+
const { jobsManager } = session;
|
|
56739
|
+
const controller = new AbortController();
|
|
56740
|
+
const jobName = `Loading ontology "${name}"`;
|
|
56741
|
+
const job = {
|
|
56742
|
+
name: jobName,
|
|
56743
|
+
statusMessage: `Loading ontology "${name}", version "${version}", this may take a while`,
|
|
56744
|
+
progressPct: 0,
|
|
56745
|
+
cancelCallback: () => {
|
|
56746
|
+
controller.abort();
|
|
56747
|
+
jobsManager.abortJob(job.name);
|
|
56748
|
+
},
|
|
56749
|
+
};
|
|
56750
|
+
const update = (message, progress) => {
|
|
56751
|
+
if (progress === 0) {
|
|
56752
|
+
jobsManager.runJob(job);
|
|
56753
|
+
return;
|
|
56754
|
+
}
|
|
56755
|
+
if (progress === 100) {
|
|
56756
|
+
jobsManager.done(job);
|
|
56757
|
+
return;
|
|
56758
|
+
}
|
|
56759
|
+
jobsManager.update(jobName, message, progress);
|
|
56760
|
+
return;
|
|
56761
|
+
};
|
|
56045
56762
|
ontologyManager.addOntology(name, version, source, {
|
|
56046
56763
|
textIndexing: { indexFields },
|
|
56764
|
+
update,
|
|
56047
56765
|
});
|
|
56048
56766
|
}
|
|
56049
56767
|
}
|
|
@@ -56738,6 +57456,10 @@
|
|
|
56738
57456
|
return codonLayout;
|
|
56739
57457
|
},
|
|
56740
57458
|
get featureLayout() {
|
|
57459
|
+
const { featureTypeOntology } = self.session.apolloDataStore.ontologyManager;
|
|
57460
|
+
if (!featureTypeOntology) {
|
|
57461
|
+
throw new Error('featureTypeOntology is undefined');
|
|
57462
|
+
}
|
|
56741
57463
|
const featureLayout = new Map();
|
|
56742
57464
|
for (const [refSeq, featuresForRefSeq] of this.features || []) {
|
|
56743
57465
|
if (!featuresForRefSeq) {
|
|
@@ -56761,11 +57483,11 @@
|
|
|
56761
57483
|
return start1 - start2 || end1 - end2;
|
|
56762
57484
|
})) {
|
|
56763
57485
|
for (const [, childFeature] of feature.children ?? new Map()) {
|
|
56764
|
-
if (childFeature.type
|
|
57486
|
+
if (featureTypeOntology.isTypeOf(childFeature.type, 'transcript')) {
|
|
56765
57487
|
for (const [, grandChildFeature] of childFeature.children ||
|
|
56766
57488
|
new Map()) {
|
|
56767
57489
|
let startingRow;
|
|
56768
|
-
if (grandChildFeature.type
|
|
57490
|
+
if (featureTypeOntology.isTypeOf(grandChildFeature.type, 'CDS')) {
|
|
56769
57491
|
let discontinuousLocations;
|
|
56770
57492
|
if (grandChildFeature.discontinuousLocations.length > 0) {
|
|
56771
57493
|
({ discontinuousLocations } = grandChildFeature);
|
|
@@ -56954,7 +57676,7 @@
|
|
|
56954
57676
|
}));
|
|
56955
57677
|
}
|
|
56956
57678
|
|
|
56957
|
-
/* eslint-disable @typescript-eslint/
|
|
57679
|
+
/* eslint-disable @typescript-eslint/unbound-method */
|
|
56958
57680
|
function isApolloMessageData(data) {
|
|
56959
57681
|
return (typeof data === 'object' &&
|
|
56960
57682
|
data !== null &&
|
|
@@ -57088,6 +57810,7 @@
|
|
|
57088
57810
|
return pluggableElement;
|
|
57089
57811
|
});
|
|
57090
57812
|
pluginManager.addToExtensionPoint('Core-extendPluggableElement', annotationFromPileup);
|
|
57813
|
+
pluginManager.addToExtensionPoint('Core-extendPluggableElement', annotationFromJBrowseFeature);
|
|
57091
57814
|
if (!inWebWorker) {
|
|
57092
57815
|
pluginManager.addToExtensionPoint('Core-extendWorker', (handle) => {
|
|
57093
57816
|
if (!('on' in handle && handle.on)) {
|