UNPKG

higlass

Version:

HiGlass Hi-C / genomic / large data viewer

265 lines (233 loc) 6.58 kB
// @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; }