loom-browser 0.0.7 → 0.0.9
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/loom-react.esm.js +181 -33
- package/dist/loom-react.esm.min.js +1 -1
- package/dist/loom-react.esm.min.js.map +1 -1
- package/dist/loom-worker.js +6 -1
- package/dist/loom-worker.min.js +1 -1
- package/dist/loom-worker.min.js.map +1 -1
- package/dist/loom.esm.js +109 -18
- package/dist/loom.esm.min.js +1 -1
- package/dist/loom.esm.min.js.map +1 -1
- package/dist/loom.js +109 -18
- package/dist/loom.min.js +1 -1
- package/dist/loom.min.js.map +1 -1
- package/dist/tsconfig.src.tsbuildinfo +1 -1
- package/dist/types/headlessGenomeBrowser.d.ts +6 -4
- package/dist/types/react/LoomBrowser.d.ts +1 -0
- package/dist/types/react/ROISet.d.ts +14 -0
- package/dist/types/react/hooks/index.d.ts +1 -0
- package/dist/types/react/hooks/useTrackManager.d.ts +13 -1
- package/dist/types/react/index.d.ts +3 -0
- package/dist/types/react/tracks/BedTrack.d.ts +10 -5
- package/dist/types/react/tracks/GeneTrack.d.ts +7 -2
- package/dist/types/react/tracks/GtxTrack.d.ts +7 -2
- package/dist/types/react/tracks/InteractionTrack.d.ts +6 -1
- package/dist/types/react/tracks/WigTrack.d.ts +6 -1
- package/dist/types/tracks/annotation/annotationTrackCanvas.d.ts +1 -0
- package/dist/types/tracks/baseTrackCanvas.d.ts +6 -0
- package/dist/types/tracks/interaction/interactionTrackCanvas.d.ts +1 -0
- package/dist/types/tracks/wig/wigTrackCanvas.d.ts +1 -0
- package/dist/types/types.d.ts +1 -1
- package/package.json +1 -1
package/dist/loom-react.esm.js
CHANGED
|
@@ -2555,10 +2555,12 @@ class BaseTrackCanvas {
|
|
|
2555
2555
|
ctx.fillRect(0, 0, width, height);
|
|
2556
2556
|
if (this._error) {
|
|
2557
2557
|
this.renderError(ctx, width, height);
|
|
2558
|
+
this.renderLabelOverlay(ctx, width, height);
|
|
2558
2559
|
return;
|
|
2559
2560
|
}
|
|
2560
2561
|
if (this._zoomedOut) {
|
|
2561
2562
|
this.renderZoomInNotice(ctx, width, height);
|
|
2563
|
+
this.renderLabelOverlay(ctx, width, height);
|
|
2562
2564
|
return;
|
|
2563
2565
|
}
|
|
2564
2566
|
const bpPerPixel = (this._locus.end - this._locus.start) / width;
|
|
@@ -2589,6 +2591,14 @@ class BaseTrackCanvas {
|
|
|
2589
2591
|
ctx.textBaseline = 'middle';
|
|
2590
2592
|
ctx.fillText('Zoom in to see features', width / 2, height / 2);
|
|
2591
2593
|
}
|
|
2594
|
+
/**
|
|
2595
|
+
* Render the track name label overlay. Called after error/zoom-in notices
|
|
2596
|
+
* so labels remain visible even when the track can't render data.
|
|
2597
|
+
* No-op by default — subclasses with name labels should override.
|
|
2598
|
+
*/
|
|
2599
|
+
renderLabelOverlay(_ctx, _width, _height) {
|
|
2600
|
+
// No-op — subclasses override
|
|
2601
|
+
}
|
|
2592
2602
|
/**
|
|
2593
2603
|
* Render this track onto an arbitrary context (e.g. Canvas2SVG for SVG export).
|
|
2594
2604
|
* Skips DPR scaling and canvas-element lifecycle — draws directly at the given
|
|
@@ -2603,10 +2613,12 @@ class BaseTrackCanvas {
|
|
|
2603
2613
|
ctx.fillRect(0, 0, width, height);
|
|
2604
2614
|
if (this._error) {
|
|
2605
2615
|
this.renderError(ctx, width, height);
|
|
2616
|
+
this.renderLabelOverlay(ctx, width, height);
|
|
2606
2617
|
return;
|
|
2607
2618
|
}
|
|
2608
2619
|
if (this._zoomedOut) {
|
|
2609
2620
|
this.renderZoomInNotice(ctx, width, height);
|
|
2621
|
+
this.renderLabelOverlay(ctx, width, height);
|
|
2610
2622
|
return;
|
|
2611
2623
|
}
|
|
2612
2624
|
const bpPerPixel = (this._locus.end - this._locus.start) / width;
|
|
@@ -3415,15 +3427,18 @@ class WigTrackCanvas extends BaseTrackCanvas {
|
|
|
3415
3427
|
getBackground() {
|
|
3416
3428
|
return this.config.background;
|
|
3417
3429
|
}
|
|
3430
|
+
renderLabelOverlay(ctx, _width, height) {
|
|
3431
|
+
if (this._name) {
|
|
3432
|
+
renderTrackNameLabel(ctx, {
|
|
3433
|
+
name: this._name,
|
|
3434
|
+
background: this.config.background,
|
|
3435
|
+
labelColor: this.config.labelColor,
|
|
3436
|
+
}, height);
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3418
3439
|
doRender(ctx, _width, height, rc) {
|
|
3419
3440
|
if (this.features.length === 0) {
|
|
3420
|
-
|
|
3421
|
-
renderTrackNameLabel(ctx, {
|
|
3422
|
-
name: this._name,
|
|
3423
|
-
background: this.config.background,
|
|
3424
|
-
labelColor: this.config.labelColor,
|
|
3425
|
-
}, height);
|
|
3426
|
-
}
|
|
3441
|
+
this.renderLabelOverlay(ctx, _width, height);
|
|
3427
3442
|
return;
|
|
3428
3443
|
}
|
|
3429
3444
|
// Apply value scaling (normalize then scaleFactor), matching igv.js getFeatures() order.
|
|
@@ -4026,6 +4041,16 @@ class AnnotationTrackCanvas extends BaseTrackCanvas {
|
|
|
4026
4041
|
getBackground() {
|
|
4027
4042
|
return this.background;
|
|
4028
4043
|
}
|
|
4044
|
+
renderLabelOverlay(ctx, _width, height) {
|
|
4045
|
+
var _a;
|
|
4046
|
+
if (this._name) {
|
|
4047
|
+
renderTrackNameLabel(ctx, {
|
|
4048
|
+
name: this._name,
|
|
4049
|
+
background: this.background,
|
|
4050
|
+
labelColor: (_a = this.config.labelColor) !== null && _a !== void 0 ? _a : '#333',
|
|
4051
|
+
}, height);
|
|
4052
|
+
}
|
|
4053
|
+
}
|
|
4029
4054
|
doRender(ctx, _width, _height, rc) {
|
|
4030
4055
|
var _a;
|
|
4031
4056
|
// Ensure label background matches track background so clearRect doesn't
|
|
@@ -8728,6 +8753,15 @@ class InteractionTrackCanvas extends BaseTrackCanvas {
|
|
|
8728
8753
|
getBackground() {
|
|
8729
8754
|
return this.config.background;
|
|
8730
8755
|
}
|
|
8756
|
+
renderLabelOverlay(ctx, _width, height) {
|
|
8757
|
+
if (this._name) {
|
|
8758
|
+
renderTrackNameLabel(ctx, {
|
|
8759
|
+
name: this._name,
|
|
8760
|
+
background: this._config.background,
|
|
8761
|
+
labelColor: '#333',
|
|
8762
|
+
}, height);
|
|
8763
|
+
}
|
|
8764
|
+
}
|
|
8731
8765
|
doRender(ctx, _width, _height, rc) {
|
|
8732
8766
|
renderInteractionTrack(ctx, this.features, this._config, rc, this._name ? {
|
|
8733
8767
|
name: this._name,
|
|
@@ -9492,6 +9526,19 @@ function isDataSourceWorkerProvider(obj) {
|
|
|
9492
9526
|
&& typeof o.fetch === 'function'
|
|
9493
9527
|
&& typeof o.destroy === 'function';
|
|
9494
9528
|
}
|
|
9529
|
+
/** Check if an error is an AbortError (from fetch abort, signal abort, or worker abort). */
|
|
9530
|
+
function isAbortError(err) {
|
|
9531
|
+
if (err instanceof DOMException && err.name === 'AbortError')
|
|
9532
|
+
return true;
|
|
9533
|
+
if (err instanceof Error && err.name === 'AbortError')
|
|
9534
|
+
return true;
|
|
9535
|
+
// Worker-relayed abort errors may arrive as plain Error with the browser's
|
|
9536
|
+
// abort message (e.g., "signal is aborted without reason") because the
|
|
9537
|
+
// worker-side DOMException check can miss non-DOMException abort errors.
|
|
9538
|
+
if (err instanceof Error && err.message.includes('aborted'))
|
|
9539
|
+
return true;
|
|
9540
|
+
return false;
|
|
9541
|
+
}
|
|
9495
9542
|
const BrowserEvent = {
|
|
9496
9543
|
LocusChange: 'locuschange',
|
|
9497
9544
|
TrackAdded: 'trackadded',
|
|
@@ -9552,6 +9599,8 @@ class HeadlessGenomeBrowser {
|
|
|
9552
9599
|
this.inflightFetches = new Map();
|
|
9553
9600
|
/** Timer for debouncing data loads during rapid zoom/pan. */
|
|
9554
9601
|
this.loadDebounceTimer = null;
|
|
9602
|
+
/** Set to true by dispose() to suppress post-dispose promise handlers. */
|
|
9603
|
+
this._disposed = false;
|
|
9555
9604
|
/** When true, sortTracks() is a no-op. Used for batch track additions (e.g. loadSession). */
|
|
9556
9605
|
this._deferSort = false;
|
|
9557
9606
|
this.events = new EventEmitter();
|
|
@@ -9916,6 +9965,17 @@ class HeadlessGenomeBrowser {
|
|
|
9916
9965
|
}
|
|
9917
9966
|
return undefined;
|
|
9918
9967
|
}
|
|
9968
|
+
/** Remove a specific ROI set by instance. Returns true if found and removed. */
|
|
9969
|
+
removeROISet(set) {
|
|
9970
|
+
const idx = this.roiSets.indexOf(set);
|
|
9971
|
+
if (idx < 0)
|
|
9972
|
+
return false;
|
|
9973
|
+
this.roiSets.splice(idx, 1);
|
|
9974
|
+
for (const roi of set.features) {
|
|
9975
|
+
this.events.emit(BrowserEvent.ROIRemoved, { roi, set });
|
|
9976
|
+
}
|
|
9977
|
+
return true;
|
|
9978
|
+
}
|
|
9919
9979
|
/** Remove all ROI sets. */
|
|
9920
9980
|
clearROIs() {
|
|
9921
9981
|
this.roiSets = [];
|
|
@@ -10457,6 +10517,11 @@ class HeadlessGenomeBrowser {
|
|
|
10457
10517
|
}
|
|
10458
10518
|
/** Clean up event listeners, abort in-flight requests, clear tracks and ROIs. */
|
|
10459
10519
|
dispose() {
|
|
10520
|
+
this._disposed = true;
|
|
10521
|
+
if (this.loadDebounceTimer !== null) {
|
|
10522
|
+
clearTimeout(this.loadDebounceTimer);
|
|
10523
|
+
this.loadDebounceTimer = null;
|
|
10524
|
+
}
|
|
10460
10525
|
this.events.removeAllListeners();
|
|
10461
10526
|
if (this.popupProvider)
|
|
10462
10527
|
this.popupProvider.dispose();
|
|
@@ -10600,12 +10665,16 @@ class HeadlessGenomeBrowser {
|
|
|
10600
10665
|
this.events.emit(BrowserEvent.DataLoaded, { track: mt.track });
|
|
10601
10666
|
})
|
|
10602
10667
|
.catch(err => {
|
|
10603
|
-
|
|
10604
|
-
if (err instanceof DOMException && err.name === 'AbortError')
|
|
10668
|
+
if (this._disposed || isAbortError(err))
|
|
10605
10669
|
return;
|
|
10606
10670
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
10607
|
-
|
|
10608
|
-
|
|
10671
|
+
if (mt.cache) {
|
|
10672
|
+
console.warn('[loom] Data fetch failed (dedup), showing cached data:', error.message);
|
|
10673
|
+
}
|
|
10674
|
+
else {
|
|
10675
|
+
console.error('[loom] Data fetch error (dedup):', error);
|
|
10676
|
+
mt.track.setError(error);
|
|
10677
|
+
}
|
|
10609
10678
|
this.events.emit(BrowserEvent.DataError, { track: mt.track, error });
|
|
10610
10679
|
});
|
|
10611
10680
|
return;
|
|
@@ -10632,12 +10701,20 @@ class HeadlessGenomeBrowser {
|
|
|
10632
10701
|
this.events.emit(BrowserEvent.DataLoaded, { track: mt.track });
|
|
10633
10702
|
})
|
|
10634
10703
|
.catch(err => {
|
|
10635
|
-
if (
|
|
10636
|
-
|
|
10704
|
+
if (this._disposed || controller.signal.aborted || isAbortError(err))
|
|
10705
|
+
return;
|
|
10706
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
10707
|
+
// If we have cached data, keep showing it (stale) rather than
|
|
10708
|
+
// replacing it with an error message. Only show error if there's
|
|
10709
|
+
// nothing to display at all.
|
|
10710
|
+
if (mt.cache) {
|
|
10711
|
+
console.warn('[loom] Data fetch failed, showing cached data:', error.message);
|
|
10712
|
+
}
|
|
10713
|
+
else {
|
|
10637
10714
|
console.error('[loom] Data fetch error:', error);
|
|
10638
10715
|
mt.track.setError(error);
|
|
10639
|
-
this.events.emit(BrowserEvent.DataError, { track: mt.track, error });
|
|
10640
10716
|
}
|
|
10717
|
+
this.events.emit(BrowserEvent.DataError, { track: mt.track, error });
|
|
10641
10718
|
})
|
|
10642
10719
|
.finally(() => {
|
|
10643
10720
|
var _a;
|
|
@@ -10696,8 +10773,11 @@ class WebWorkerPool {
|
|
|
10696
10773
|
this.readyPromises = [];
|
|
10697
10774
|
for (let i = 0; i < count; i++) {
|
|
10698
10775
|
const worker = createWorker();
|
|
10699
|
-
// Ready handshake
|
|
10700
|
-
|
|
10776
|
+
// Ready handshake — rejects on worker error so awaiting fetches
|
|
10777
|
+
// don't hang forever if the worker script fails to load.
|
|
10778
|
+
let rejectReady;
|
|
10779
|
+
const readyPromise = new Promise((resolveReady, rej) => {
|
|
10780
|
+
rejectReady = rej;
|
|
10701
10781
|
const onReady = (e) => {
|
|
10702
10782
|
if (e.data.type === 'ready') {
|
|
10703
10783
|
resolveReady();
|
|
@@ -10705,6 +10785,8 @@ class WebWorkerPool {
|
|
|
10705
10785
|
};
|
|
10706
10786
|
worker.addEventListener('message', onReady, { once: true });
|
|
10707
10787
|
});
|
|
10788
|
+
// Prevent unhandled rejection warnings if worker fails before any fetch
|
|
10789
|
+
readyPromise.catch(() => { });
|
|
10708
10790
|
this.readyPromises.push(readyPromise);
|
|
10709
10791
|
worker.onmessage = (e) => {
|
|
10710
10792
|
const msg = e.data;
|
|
@@ -10744,6 +10826,7 @@ class WebWorkerPool {
|
|
|
10744
10826
|
};
|
|
10745
10827
|
worker.onerror = (e) => {
|
|
10746
10828
|
const error = new Error(`Worker error: ${e.message}`);
|
|
10829
|
+
rejectReady(error);
|
|
10747
10830
|
for (const { reject } of this.pending.values())
|
|
10748
10831
|
reject(error);
|
|
10749
10832
|
this.pending.clear();
|
|
@@ -11979,8 +12062,16 @@ class GenomeBrowser extends HeadlessGenomeBrowser {
|
|
|
11979
12062
|
document.addEventListener("mousedown", this.handleDocMouseDown);
|
|
11980
12063
|
}
|
|
11981
12064
|
}
|
|
11982
|
-
// Re-render tracks when container resizes (e.g. first layout in Shadow DOM)
|
|
11983
|
-
|
|
12065
|
+
// Re-render tracks when container resizes (e.g. first layout in Shadow DOM).
|
|
12066
|
+
// Also trigger data loads if width went from 0 → non-zero (common in React
|
|
12067
|
+
// flex layouts where the container hasn't laid out when GenomeBrowser is created).
|
|
12068
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
12069
|
+
const prevWidth = this._viewportWidth;
|
|
12070
|
+
this.render(); // updates _viewportWidth from container.clientWidth
|
|
12071
|
+
if (prevWidth === 0 && this._viewportWidth > 0) {
|
|
12072
|
+
this.loadAllTracksIfNeeded();
|
|
12073
|
+
}
|
|
12074
|
+
});
|
|
11984
12075
|
this.resizeObserver.observe(container);
|
|
11985
12076
|
// Axis content is updated in two places:
|
|
11986
12077
|
// 1. After super.render() in render() — handles resize, pan, initial load
|
|
@@ -13428,6 +13519,7 @@ const LoomBrowser = forwardRef(function LoomBrowser(props, ref) {
|
|
|
13428
13519
|
// ROI
|
|
13429
13520
|
addROI(roi, setName) { return browserRef.current.addROI(roi, setName); },
|
|
13430
13521
|
addROISet(config) { return browserRef.current.addROISet(config); },
|
|
13522
|
+
removeROISet(set) { var _a, _b; return (_b = (_a = browserRef.current) === null || _a === void 0 ? void 0 : _a.removeROISet(set)) !== null && _b !== void 0 ? _b : false; },
|
|
13431
13523
|
removeROI(roiId) { var _a, _b; return (_b = (_a = browserRef.current) === null || _a === void 0 ? void 0 : _a.removeROI(roiId)) !== null && _b !== void 0 ? _b : false; },
|
|
13432
13524
|
updateROI(roiId, changes) { var _a; return (_a = browserRef.current) === null || _a === void 0 ? void 0 : _a.updateROI(roiId, changes); },
|
|
13433
13525
|
clearROIs() { var _a; (_a = browserRef.current) === null || _a === void 0 ? void 0 : _a.clearROIs(); },
|
|
@@ -13511,12 +13603,15 @@ function useLocus() {
|
|
|
13511
13603
|
* @param recreationDeps When these change, the track is removed and re-added (new data source).
|
|
13512
13604
|
* @param updateTrack Called when only updateDeps change (in-place config update).
|
|
13513
13605
|
* @param updateDeps When these change (but recreationDeps don't), updateTrack is called.
|
|
13606
|
+
* @param eventHandlers Optional typed click/context-menu handlers scoped to this track.
|
|
13514
13607
|
*/
|
|
13515
|
-
function useTrackManager(addTrack, recreationDeps, updateTrack, updateDeps) {
|
|
13608
|
+
function useTrackManager(addTrack, recreationDeps, updateTrack, updateDeps, eventHandlers) {
|
|
13516
13609
|
const browser = useGenomeBrowser();
|
|
13517
13610
|
const trackRef = useRef(null);
|
|
13518
13611
|
const prevRecreationDeps = useRef(recreationDeps);
|
|
13519
13612
|
const isFirstRender = useRef(true);
|
|
13613
|
+
const handlersRef = useRef(eventHandlers);
|
|
13614
|
+
handlersRef.current = eventHandlers;
|
|
13520
13615
|
// Add track on mount or when recreation deps change
|
|
13521
13616
|
useEffect(() => {
|
|
13522
13617
|
if (!browser)
|
|
@@ -13547,6 +13642,29 @@ function useTrackManager(addTrack, recreationDeps, updateTrack, updateDeps) {
|
|
|
13547
13642
|
updateTrack(trackRef.current, browser);
|
|
13548
13643
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
13549
13644
|
}, updateDeps);
|
|
13645
|
+
// Subscribe to track click/context-menu events, filtered to this track
|
|
13646
|
+
useEffect(() => {
|
|
13647
|
+
if (!browser)
|
|
13648
|
+
return;
|
|
13649
|
+
function handleEvent(event, handler) {
|
|
13650
|
+
if (!handler || event.track !== trackRef.current)
|
|
13651
|
+
return;
|
|
13652
|
+
handler({
|
|
13653
|
+
features: event.features,
|
|
13654
|
+
genomicLocation: event.genomicLocation,
|
|
13655
|
+
x: event.x,
|
|
13656
|
+
y: event.y,
|
|
13657
|
+
});
|
|
13658
|
+
}
|
|
13659
|
+
const onClickListener = (e) => { var _a; return handleEvent(e, (_a = handlersRef.current) === null || _a === void 0 ? void 0 : _a.onClick); };
|
|
13660
|
+
const onContextMenuListener = (e) => { var _a; return handleEvent(e, (_a = handlersRef.current) === null || _a === void 0 ? void 0 : _a.onContextMenu); };
|
|
13661
|
+
browser.on(BrowserEvent.TrackClick, onClickListener);
|
|
13662
|
+
browser.on(BrowserEvent.TrackContextMenu, onContextMenuListener);
|
|
13663
|
+
return () => {
|
|
13664
|
+
browser.off(BrowserEvent.TrackClick, onClickListener);
|
|
13665
|
+
browser.off(BrowserEvent.TrackContextMenu, onContextMenuListener);
|
|
13666
|
+
};
|
|
13667
|
+
}, [browser]);
|
|
13550
13668
|
return trackRef.current;
|
|
13551
13669
|
}
|
|
13552
13670
|
|
|
@@ -13556,7 +13674,7 @@ function RulerTrack({ config, maxTrackHeight }) {
|
|
|
13556
13674
|
return null;
|
|
13557
13675
|
}
|
|
13558
13676
|
|
|
13559
|
-
function WigTrack({ url, features, config, height, background, windowFunction, maxTrackHeight, name, metadata }) {
|
|
13677
|
+
function WigTrack({ url, features, config, height, background, windowFunction, maxTrackHeight, name, metadata, onClick, onContextMenu }) {
|
|
13560
13678
|
useTrackManager((browser) => {
|
|
13561
13679
|
if (features) {
|
|
13562
13680
|
return browser.addWigTrackWithFeatures(features, { config, height, background, maxTrackHeight, name, metadata });
|
|
@@ -13565,26 +13683,28 @@ function WigTrack({ url, features, config, height, background, windowFunction, m
|
|
|
13565
13683
|
throw new Error('WigTrack requires either a `url` or `features` prop');
|
|
13566
13684
|
return browser.addWigTrack(url, { config, height, background, windowFunction, maxTrackHeight, name, metadata });
|
|
13567
13685
|
}, [url, features, windowFunction], (track) => { if (config)
|
|
13568
|
-
track.setConfig(config); }, [config, height, background, name]);
|
|
13686
|
+
track.setConfig(config); }, [config, height, background, name], { onClick, onContextMenu });
|
|
13569
13687
|
return null;
|
|
13570
13688
|
}
|
|
13571
13689
|
|
|
13572
|
-
function GeneTrack({ config, height, background, genome, track, maxTrackHeight, name, metadata }) {
|
|
13690
|
+
function GeneTrack({ config, height, background, genome, track, maxTrackHeight, name, metadata, onClick, onContextMenu }) {
|
|
13573
13691
|
useTrackManager((browser) => browser.addGeneTrack({ config, height, background, genome, track, maxTrackHeight, name, metadata }), [genome, track], (t) => { if (config)
|
|
13574
|
-
t.setConfig(config); }, [config, height, background, name]);
|
|
13692
|
+
t.setConfig(config); }, [config, height, background, name], { onClick, onContextMenu });
|
|
13575
13693
|
return null;
|
|
13576
13694
|
}
|
|
13577
13695
|
|
|
13578
|
-
function BedTrack({ url, features, config, height, background, format, indexURL, indexed, maxTrackHeight, name, metadata }) {
|
|
13696
|
+
function BedTrack({ url, features, config, height, background, format, indexURL, indexed, maxTrackHeight, name, metadata, colorBy, onClick, onContextMenu }) {
|
|
13697
|
+
// Apply colorBy to in-memory features
|
|
13698
|
+
const coloredFeatures = useMemo(() => features && colorBy ? features.map(f => ({ ...f, color: colorBy(f) })) : features, [features, colorBy]);
|
|
13579
13699
|
useTrackManager((browser) => {
|
|
13580
|
-
if (
|
|
13581
|
-
return browser.addBedTrackWithFeatures(
|
|
13700
|
+
if (coloredFeatures) {
|
|
13701
|
+
return browser.addBedTrackWithFeatures(coloredFeatures, { config, height, background, maxTrackHeight, name, metadata });
|
|
13582
13702
|
}
|
|
13583
13703
|
if (!url)
|
|
13584
13704
|
throw new Error('BedTrack requires either a `url` or `features` prop');
|
|
13585
13705
|
return browser.addBedTrack(url, { config, height, background, format, indexURL, indexed, maxTrackHeight, name, metadata });
|
|
13586
|
-
}, [url,
|
|
13587
|
-
track.setConfig(config); }, [config, height, background, name]);
|
|
13706
|
+
}, [url, coloredFeatures, format, indexURL, indexed], (track) => { if (config)
|
|
13707
|
+
track.setConfig(config); }, [config, height, background, name], { onClick, onContextMenu });
|
|
13588
13708
|
return null;
|
|
13589
13709
|
}
|
|
13590
13710
|
|
|
@@ -13594,15 +13714,43 @@ function SequenceTrack({ config, maxTrackHeight }) {
|
|
|
13594
13714
|
return null;
|
|
13595
13715
|
}
|
|
13596
13716
|
|
|
13597
|
-
function InteractionTrack({ url, config, background, format, indexURL, indexed, name, metadata }) {
|
|
13717
|
+
function InteractionTrack({ url, config, background, format, indexURL, indexed, name, metadata, onClick, onContextMenu }) {
|
|
13598
13718
|
useTrackManager((browser) => browser.addInteractionTrack(url, { config, background, format, indexURL, indexed, name, metadata }), [url, format, indexURL, indexed], (track) => { if (config)
|
|
13599
|
-
track.setConfig(config); }, [config, background, name]);
|
|
13719
|
+
track.setConfig(config); }, [config, background, name], { onClick, onContextMenu });
|
|
13600
13720
|
return null;
|
|
13601
13721
|
}
|
|
13602
13722
|
|
|
13603
|
-
function GtxTrack({ url, experimentId, config, height, background, windowFunction, maxTrackHeight, name, metadata }) {
|
|
13723
|
+
function GtxTrack({ url, experimentId, config, height, background, windowFunction, maxTrackHeight, name, metadata, onClick, onContextMenu }) {
|
|
13604
13724
|
useTrackManager((browser) => browser.addGtxTrack(url, { experimentId, config, height, background, windowFunction, maxTrackHeight, name, metadata }), [url, experimentId, windowFunction], (track) => { if (config)
|
|
13605
|
-
track.setConfig(config); }, [config, height, background, name]);
|
|
13725
|
+
track.setConfig(config); }, [config, height, background, name], { onClick, onContextMenu });
|
|
13726
|
+
return null;
|
|
13727
|
+
}
|
|
13728
|
+
|
|
13729
|
+
/**
|
|
13730
|
+
* Declarative ROI set component. Renders nothing — manages ROIs on the
|
|
13731
|
+
* GenomeBrowser via context. Add/remove `<ROISet>` children to control ROIs.
|
|
13732
|
+
*/
|
|
13733
|
+
function DeclarativeROISet({ rois, name = 'Declarative', color }) {
|
|
13734
|
+
const browser = useGenomeBrowser();
|
|
13735
|
+
const setRef = useRef(null);
|
|
13736
|
+
useEffect(() => {
|
|
13737
|
+
if (!browser)
|
|
13738
|
+
return;
|
|
13739
|
+
// Remove previous set if it exists
|
|
13740
|
+
if (setRef.current) {
|
|
13741
|
+
browser.removeROISet(setRef.current);
|
|
13742
|
+
setRef.current = null;
|
|
13743
|
+
}
|
|
13744
|
+
const config = { name, color, features: rois };
|
|
13745
|
+
const set = browser.addROISet(config);
|
|
13746
|
+
setRef.current = set;
|
|
13747
|
+
return () => {
|
|
13748
|
+
if (setRef.current) {
|
|
13749
|
+
browser.removeROISet(setRef.current);
|
|
13750
|
+
setRef.current = null;
|
|
13751
|
+
}
|
|
13752
|
+
};
|
|
13753
|
+
}, [browser, name, color, rois]);
|
|
13606
13754
|
return null;
|
|
13607
13755
|
}
|
|
13608
13756
|
|
|
@@ -14922,4 +15070,4 @@ var LoomInputDialog$1 = /*#__PURE__*/Object.freeze({
|
|
|
14922
15070
|
LoomInputDialog: LoomInputDialog
|
|
14923
15071
|
});
|
|
14924
15072
|
|
|
14925
|
-
export { BedTrack, ChromosomeSelect, ExportControls, GeneTrack, GenomeBrowserContext, GtxTrack, InteractionTrack, LocusInput, LoomBrowser, Navbar, RulerTrack, SequenceTrack, WigTrack, WindowSize, ZoomControls, useBrowserEvent, useGenomeBrowser, useLocus };
|
|
15073
|
+
export { BedTrack, ChromosomeSelect, ExportControls, GeneTrack, GenomeBrowserContext, GtxTrack, InteractionTrack, LocusInput, LoomBrowser, Navbar, DeclarativeROISet as ROISet, RulerTrack, SequenceTrack, WigTrack, WindowSize, ZoomControls, useBrowserEvent, useGenomeBrowser, useLocus };
|