higlass
Version:
HiGlass Hi-C / genomic / large data viewer
265 lines (233 loc) • 6.58 kB
JSX
// @ts-nocheck
import React from 'react';
import ReactDOM from 'react-dom';
import { mount } from 'enzyme';
import { requestsInFlight } from '../services';
import { getTrackObjectFromHGC, getTrackRenderer } from '../utils';
import HiGlassComponent from '../HiGlassComponent';
const TILE_LOADING_CHECK_INTERVAL = 100;
/**
* Change the options of a track in higlass
* @param hgc enzyme wrapper for a HiGlassComponent
* @param viewUid The view uid
* @param trackUid The track uid
* @param options An object of new options (e.g. { color: 'black'})
* @return nothing
*/
export const changeOptions = (hgc, viewUid, trackUid, options) => {
for (const { viewId, trackId, track } of hgc.instance().iterateOverTracks()) {
if (viewId === viewUid && trackId === trackUid) {
track.options = {
...track.options,
...options,
};
}
}
hgc.setState(hgc.instance().state);
};
/**
* Check if there are any active transitions that we
* need to wait on
*
* Parameters
* ----------
* hgc: enzyme wrapper for a HiGlassComponent
*
* Returns
* -------
* True if any of the tracks have active transtions. False otherwise.
*/
export const areTransitionsActive = (hgc) => {
for (const track of hgc.iterateOverTracks()) {
const trackRenderer = getTrackRenderer(hgc, track.viewId, track.trackId);
if (trackRenderer.activeTransitions > 0) return true;
}
return false;
};
/**
* Wait until all transitions have finished before
* calling the callback
*
* Arguments
* ---------
* hgc: Enzyme wrapper for a HiGlassComponent
* The componentthat we're waiting on
* tilesLoadedCallback: function
* The callback to call whenever all of the tiles
* have been loaded.
* Returns
* -------
* Nothing
*/
export const waitForTransitionsFinished = (hgc, callback) => {
if (areTransitionsActive(hgc)) {
setTimeout(() => {
waitForTransitionsFinished(hgc, callback);
}, TILE_LOADING_CHECK_INTERVAL);
} else {
callback();
}
};
/**
* Wait until all open JSON requests are finished
*
* Parameters
* ----------
* finished: function
* A callback to call when there's no more JSON requests
* open
*
*/
export const waitForJsonComplete = (finished) => {
if (requestsInFlight > 0) {
setTimeout(
() => waitForJsonComplete(finished),
TILE_LOADING_CHECK_INTERVAL,
);
} else {
finished();
}
};
/**
* Check if a HiGlassComponent is still waiting on tiles from a remote
* server.
*
* Arguments
* ---------
* hgc: enzyme wrapper for a HiGlassComponent
*
* Returns
* -------
* True if any of the tracks are waiting for tiles, false otherwise.
*/
export const isWaitingOnTiles = (hgc) => {
for (const track of hgc.iterateOverTracks()) {
let trackObj = getTrackObjectFromHGC(hgc, track.viewId, track.trackId);
if (
!track.track.server &&
!track.track.tilesetUid &&
!(track.track.data && track.track.data.type === 'divided')
) {
continue;
}
if (
(track.track.data && track.track.data.type === 'divided') ||
(track.track.server && track.track.tilesetUid)
) {
if (trackObj.originalTrack) {
trackObj = trackObj.originalTrack;
}
if (!(trackObj.tilesetInfo || trackObj.chromInfo)) {
// console.warn(
// `Track uuid:${trackObj.uuid} has no tileset or chromosome info`
// );
return true;
}
if (trackObj.fetching?.size) {
return true;
}
} else {
throw Error('"server" and "tilesetUid" belong together');
}
}
return false;
};
export const waitForTilesLoaded = (hgc, tilesLoadedCallback) => {
/**
* Wait until all of the tiles in the HiGlassComponent are loaded
* until calling the callback
*
* Arguments
* ---------
* hgc: Enzyme wrapper for a HiGlassComponent
* The componentthat we're waiting on
* tilesLoadedCallback: function
* The callback to call whenever all of the tiles
* have been loaded.
* Returns
* -------
* Nothing
*/
if (isWaitingOnTiles(hgc)) {
setTimeout(() => {
waitForTilesLoaded(hgc, tilesLoadedCallback);
}, TILE_LOADING_CHECK_INTERVAL);
} else {
// console.log('finished');
tilesLoadedCallback();
}
};
/**
* Mount a new HiGlassComponent and unmount the previously visible one.
*
* @param {HTMLElement | null} prevDiv A div element to detach and recreate for the component
* @param {import('enzyme').ReactWrapper | null} prevHgc An already mounted
* hgc component
* @param {function} done The callback to call when the component is fully loaded
*/
export const mountHGComponent = (
prevDiv,
prevHgc,
viewConf,
done,
options = {},
) => {
const {
style = 'width:800px; background-color: lightgreen;',
bounded = false,
extendedDelay = false,
} = options;
if (prevHgc) {
prevHgc.unmount();
prevHgc.detach();
}
if (prevDiv) {
global.document.body.removeChild(prevDiv);
}
// console.log('check:', options && options.style)
// console.log('style:', style, "options:", options, "style", options.style);
const div = global.document.createElement('div');
global.document.body.appendChild(div);
div.setAttribute('style', style);
div.setAttribute('id', 'simple-hg-component');
const hgc = mount(
<HiGlassComponent options={{ bounded }} viewConfig={viewConf} />,
{ attachTo: div },
);
hgc.update();
waitForJsonComplete(() => {
if (extendedDelay) {
// Waiting for tiles to be loaded does not always mean
// that the compoment is mounted (especially if we load the tiles
// from the filesystem, which is quick). Wait 1000ms to make sure
// we are really done
const doneWithDelay = () =>
setTimeout(() => {
done();
}, 1000);
waitForTilesLoaded(hgc.instance(), doneWithDelay);
} else {
waitForTilesLoaded(hgc.instance(), done);
}
});
return /** @type {const} */ ([div, hgc]);
};
export const removeHGComponent = (div) => {
if (!div) return;
ReactDOM.unmountComponentAtNode(div);
document.body.removeChild(div);
};
// ideally the "await-ers" avoid would be promises (rather than polling)
// and that way `mountHGComponent` would be async by default.
export async function mountHGComponentAsync(
prevDiv,
prevHgc,
viewConf,
options,
) {
let res;
await new Promise((resolve) => {
res = mountHGComponent(prevDiv, prevHgc, viewConf, resolve, options);
});
return res;
}