vitessce
Version:
Vitessce app and React component library
160 lines (152 loc) • 5.21 kB
JavaScript
import React, { useCallback } from 'react';
import PopoverMenu from './PopoverMenu';
import {
handleImportJSON,
handleImportTabular,
} from './io';
import {
MIME_TYPE_JSON,
MIME_TYPE_TABULAR,
} from './constants';
import { ReactComponent as SetUnionSVG } from '../../assets/sets/union.svg';
import { ReactComponent as SetIntersectionSVG } from '../../assets/sets/intersection.svg';
import { ReactComponent as SetComplementSVG } from '../../assets/sets/complement.svg';
/**
* A plus button for creating or importing set hierarchies.
* @param {object} props
* @param {string} props.datatype The data type to validate imported hierarchies against.
* @param {function} props.onError A callback to pass error message strings.
* @param {function} props.onImportTree A callback to pass successfully-validated tree objects.
* @param {function} props.onCreateLevelZeroNode A callback to create a new empty
* level zero node.
* @param {boolean} props.importable Is importing allowed?
* If not, the import button will not be rendered.
* @param {boolean} props.editable Is editing allowed?
* If not, the create button will not be rendered.
*/
export function PlusButton(props) {
const {
datatype, onError, onImportTree, onCreateLevelZeroNode,
importable, editable,
} = props;
/**
* Import a file, then process the imported data via the supplied handler function.
* @param {Function} importHandler The function to process the imported data.
* @param {string} mimeType The accepted mime type for the file upload input.
* @returns {Function} An import function corresponding to the supplied parameters.
*/
const onImport = useCallback((importHandler, mimeType) => () => {
const uploadInputNode = document.createElement('input');
uploadInputNode.setAttribute('type', 'file');
uploadInputNode.setAttribute('accept', mimeType);
document.body.appendChild(uploadInputNode); // required for firefox
uploadInputNode.click();
uploadInputNode.addEventListener('change', (event) => {
if (!(window.File && window.FileReader && window.FileList && window.Blob)) {
onError('Local file reading APIs are not fully supported in this browser.');
return;
}
const { files } = event.target;
if (!files || files.length !== 1) {
onError('Incorrect number of files selected.');
return;
}
const reader = new FileReader();
reader.addEventListener('load', () => {
const { result } = reader;
try {
const treeToImport = importHandler(result, datatype);
onError(false); // Clear any previous import error.
onImportTree(treeToImport);
} catch (e) {
onError(e.message);
}
}, false);
reader.readAsText(files[0]);
});
uploadInputNode.remove();
}, [datatype, onError, onImportTree]);
const menuConfig = [
...(editable ? [
{
title: 'Create hierarchy',
handler: onCreateLevelZeroNode,
handlerKey: 'n',
},
] : []),
...(importable ? [
{
title: 'Import hierarchy',
subtitle: '(from CSV file)',
handler: onImport(handleImportTabular, MIME_TYPE_TABULAR),
handlerKey: 'c',
},
{
title: 'Import hierarchy',
subtitle: '(from JSON file)',
handler: onImport(handleImportJSON, MIME_TYPE_JSON),
handlerKey: 'j',
},
] : []),
];
return (menuConfig.length > 0 ? (
<PopoverMenu
menuConfig={menuConfig}
>
<button className="plus-button" type="submit">+</button>
</PopoverMenu>
) : null);
}
/**
* Set operations buttons (union, intersection, complement)
* and a view checked sets button.
* @param {object} props
* @param {function} props.onUnion A callback for the union button.
* @param {function} props.onIntersection A callback for the intersection button.
* @param {function} props.onComplement A callback for the complement button.
* @param {boolean} props.operatable Are set operations allowed?
* If not, the union, intersection, and complement buttons will not be rendered.
*/
export function SetOperationButtons(props) {
const {
onUnion,
onIntersection,
onComplement,
operatable,
hasCheckedSetsToUnion,
hasCheckedSetsToIntersect,
hasCheckedSetsToComplement,
} = props;
return (
<>
{operatable && (
<>
<button
onClick={onUnion}
title="New set from union of checked sets"
type="submit"
disabled={!hasCheckedSetsToUnion}
>
<SetUnionSVG />
</button>
<button
onClick={onIntersection}
title="New set from intersection of checked sets"
type="submit"
disabled={!hasCheckedSetsToIntersect}
>
<SetIntersectionSVG />
</button>
<button
onClick={onComplement}
title="New set from complement of checked sets"
type="submit"
disabled={!hasCheckedSetsToComplement}
>
<SetComplementSVG />
</button>
</>
)}
</>
);
}