UNPKG

higlass

Version:

HiGlass Hi-C / genomic / large data viewer

428 lines (366 loc) 11.3 kB
// @ts-nocheck import PropTypes from 'prop-types'; import React from 'react'; import CheckboxTree from 'react-checkbox-tree'; import slugid from 'slugid'; import { tileProxy } from './services'; import '../styles/TilesetFinder.css'; import withPubSub from './hocs/with-pub-sub'; // Configs import { TRACKS_INFO } from './configs'; export class TilesetFinder extends React.Component { constructor(props) { super(props); // this.localTracks = TRACKS_INFO.filter // local tracks are ones that don't have a filetype associated with them this.localTracks = TRACKS_INFO.filter((x) => x.local && !x.hidden).map( (x) => { const y = { ...x }; y.datatype = x.datatype[0]; return y; }, ); this.augmentedTracksInfo = TRACKS_INFO; if (window.higlassTracksByType) { Object.keys(window.higlassTracksByType).forEach((pluginTrackType) => { this.augmentedTracksInfo.push( window.higlassTracksByType[pluginTrackType].config, ); }); } if (props.datatype) { this.localTracks = this.localTracks.filter( (x) => x.datatype[0] === props.datatype, ); } else { this.localTracks = this.localTracks.filter( (x) => x.orientation === this.props.orientation, ); } this.localTracks.forEach((x) => { x.uuid = slugid.nice(); }); const newOptions = this.prepareNewEntries('', this.localTracks, {}); const availableTilesetKeys = Object.keys(newOptions); const selectedUuid = availableTilesetKeys.length ? [availableTilesetKeys[0]] : null; this.mounted = false; this.state = { selectedUuid, options: newOptions, filter: '', checked: [], expanded: [], }; this.requestTilesetLists(); } componentDidMount() { // we want to query for a list of tracks that are compatible with this // track orientation this.mounted = true; this.requestTilesetLists(); this.searchBox.focus(); } componentWillUnmount() { this.mounted = false; } prepareNewEntries(sourceServer, newEntries, existingOptions) { /** * Add meta data to new tileset entries before adding * them to the list of available options. */ const newOptions = existingOptions; const entries = newEntries.map((ne) => { const ane = { ...ne, server: sourceServer, tilesetUid: ne.uuid, serverUidKey: this.serverUidKey(sourceServer, ne.uuid), datatype: ne.datatype, name: ne.name, uid: slugid.nice(), }; return ane; }); entries.forEach((ne) => { newOptions[ne.serverUidKey] = ne; }); return newOptions; } serverUidKey(server, uid) { /** * Create a key for a server and uid */ return `${server}/${uid}`; } requestTilesetLists() { let datatypesQuery = null; if (this.props.datatype) { datatypesQuery = `dt=${this.props.datatype}`; } else { const datatypes = new Set( [].concat( ...this.augmentedTracksInfo .filter((x) => x.datatype) .filter((x) => { return ( x.orientation === this.props.orientation || (this.props.orientation === '1d-vertical' && x.orientation === '1d-horizontal') ); }) .map((x) => x.datatype), ), ); datatypesQuery = [...datatypes].map((x) => `dt=${x}`).join('&'); } if (!this.props.trackSourceServers) { console.warn('No track source servers specified in the viewconf'); return; } this.props.trackSourceServers.forEach((sourceServer) => { tileProxy.json( `${sourceServer}/tilesets/?limit=10000&${datatypesQuery}`, (error, data) => { if (error) { console.error('ERROR:', error); } else { const newOptions = this.prepareNewEntries( sourceServer, data.results, this.state.options, ); const availableTilesetKeys = Object.keys(newOptions); let { selectedUuid } = this.state; // if there isn't a selected tileset, select the first received one if (!selectedUuid) { selectedUuid = availableTilesetKeys.length ? [availableTilesetKeys[0]] : null; const selectedTileset = this.state.options[selectedUuid[0]]; this.props.selectedTilesetChanged([selectedTileset]); } if (this.mounted) { this.setState({ selectedUuid, options: newOptions, }); } } }, this.props.pubSub, ); }); } /** * Double clicked on an element. Should be selected * and this window will be closed. */ handleOptionDoubleClick(x /* , y */) { const value = this.state.options[x.target.value]; this.props.onDoubleClick(value); } handleSelectedOptions(selectedOptions) { const selectedValues = []; const selectedTilesets = []; // I don't know why selectedOptions.map doesn't work for (let i = 0; i < selectedOptions.length; i++) { selectedValues.push(selectedOptions[i]); selectedTilesets.push(this.state.options[selectedOptions[i]]); } this.props.selectedTilesetChanged(selectedTilesets); this.setState({ selectedUuid: selectedValues }); } handleSelect() { const { selectedOptions } = this.multiSelect; const selectedOptionsList = []; for (let i = 0; i < selectedOptions.length; i++) { const selectedOption = selectedOptions[i]; selectedOptionsList.push(selectedOption.value); } this.handleSelectedOptions(selectedOptionsList); } handleSearchChange() { const domElement = this.searchBox; this.setState({ filter: domElement.value }); } /* * Create the nested checkbox tree by partitioning the * list of available datasets according to their group * * @param {object} datasetsDict A dictionary of id -> tileset * def mappings * @param {string} filter A string to filter the results by * @returns {array} A list of items that define the nested checkbox * structure * { * 'name': 'blah', * 'value': 'blah', * 'children': [], * } */ partitionByGroup(datasetsDict, filter) { const itemsByGroup = { '': { name: '', value: '', children: [], }, }; for (const uuid of Object.keys(datasetsDict)) { const item = datasetsDict[uuid]; if (!item.name.toLowerCase().includes(filter)) { continue; } if ('project_name' in item) { const group = item.project_name; if (!(group in itemsByGroup)) { itemsByGroup[group] = { value: group, label: group, children: [], }; } itemsByGroup[group].children.push({ label: item.name, value: uuid, }); } else { itemsByGroup[''].children.push({ label: item.name, value: uuid, }); } } const allItems = itemsByGroup[''].children; // coollapse the group lists into one list of objects for (const group of Object.keys(itemsByGroup)) { if (group !== '') { itemsByGroup[group].children.sort((a, b) => a.label.toLowerCase().localeCompare(b.label.toLowerCase(), 'en'), ); allItems.push(itemsByGroup[group]); } } allItems.sort((a, b) => a.label.toLowerCase().localeCompare(b.label.toLowerCase(), 'en'), ); return allItems; } handleChecked(checked) { this.handleSelectedOptions(checked); this.setState({ checked }); } handleExpanded(expanded) { this.setState({ expanded }); } render() { const optionsList = []; for (const key in this.state.options) { optionsList.push(this.state.options[key]); } const nestedItems = this.partitionByGroup( this.state.options, this.state.filter, ); const svgStyle = { width: 15, height: 15, top: 2, right: 2, position: 'relative', }; const halfSvgStyle = JSON.parse(JSON.stringify(svgStyle)); halfSvgStyle.opacity = 0.5; const form = ( <form onSubmit={(evt) => { evt.preventDefault(); }} > <div className="tileset-finder-search-bar"> <span className="tileset-finder-label">Select tileset:</span> <input ref={(c) => { this.searchBox = c; }} className="tileset-finder-search-box" onChange={this.handleSearchChange.bind(this)} placeholder="Search Term" type="text" /> </div> <div className="tileset-finder-checkbox-tree"> <CheckboxTree checked={this.state.checked} expanded={this.state.expanded} icons={{ uncheck: ( <svg style={svgStyle}> <title>Uncheck</title> <use xlinkHref="#square_o" /> </svg> ), check: ( <svg style={svgStyle}> <title>Check</title> <use xlinkHref="#check_square_o" /> </svg> ), halfCheck: ( <svg style={halfSvgStyle}> <title>Half Check</title> <use xlinkHref="#check_square_o" /> </svg> ), leaf: ( <svg style={svgStyle}> <title>Leaf</title> <use xlinkHref="#file_o" /> </svg> ), expandClose: ( <svg style={svgStyle}> <title>Expand Close</title> <use xlinkHref="#chevron_right" /> </svg> ), expandOpen: ( <svg style={svgStyle}> <title>Expand Open </title> <use xlinkHref="#chevron_down" /> </svg> ), parentClose: ( <svg style={svgStyle}> <title>Parent Close</title> <use xlinkHref="#folder_o" /> </svg> ), parentOpen: ( <svg style={svgStyle}> <title>Parent Open</title> <use xlinkHref="#folder_open_o" /> </svg> ), }} nodes={nestedItems} onCheck={this.handleChecked.bind(this)} onExpand={this.handleExpanded.bind(this)} /> </div> </form> ); return <div>{form}</div>; } } TilesetFinder.propTypes = { datatype: PropTypes.string, orientation: PropTypes.string, onDoubleClick: PropTypes.func, pubSub: PropTypes.object.isRequired, selectedTilesetChanged: PropTypes.func, trackSourceServers: PropTypes.array, }; export default withPubSub(TilesetFinder);