import { DialogBody, DialogFooter } from '@blueprintjs/core'; import { yupResolver } from '@hookform/resolvers/yup'; import type { ParseResult } from 'papaparse'; import type { CSSProperties } from 'react'; import { useEffect, useMemo, useState } from 'react'; import type { FileError, FileWithPath } from 'react-dropzone'; import { useForm } from 'react-hook-form'; import { DropZone } from 'react-science/ui'; import * as Yup from 'yup'; import { TargetPathError, isMetaFile, linkMetaWithSpectra, mapErrors, parseMetaFile, } from '../../../data/parseMeta/index.js'; import { useChartData } from '../../context/ChartContext.js'; import { useDispatch } from '../../context/DispatchContext.js'; import { useToaster } from '../../context/ToasterContext.js'; import Button from '../../elements/Button.js'; import { Input2Controller } from '../../elements/Input2Controller.js'; import Label from '../../elements/Label.js'; import type { Column } from '../../elements/ReactTable/ReactTable.js'; import ReactTable from '../../elements/ReactTable/ReactTable.js'; import { Select2Controller } from '../../elements/Select2Controller.js'; import { StandardDialog } from '../../elements/StandardDialog.tsx'; import { convertPathArrayToString } from '../../utility/convertPathArrayToString.js'; import { getSpectraObjectPaths } from '../../utility/getSpectraObjectPaths.js'; import { mapColumnToSelectItems } from './utils/mapColumnToSelectItems.js'; const styles: Record<'container' | 'column', CSSProperties> = { container: { width: '100%', flex: 1, overflow: 'hidden', border: 'none', padding: '10px', display: 'flex', flexDirection: 'column', }, column: { minWidth: '100px', maxWidth: '100px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', width: '0px', }, }; const rowColors = { match: { activated: { backgroundColor: '#d6ffe6', color: '#28ba62', }, hover: { backgroundColor: '#2dd36f', color: 'white', }, }, noMatch: { activated: { backgroundColor: '#ffd6db', color: '#cf3c4f', }, hover: { backgroundColor: '#eb445a', color: 'white', }, }, }; interface CompareResultItem { key: string; isDuplicated: boolean; spectraIDs: string[]; } type CompareResult = Record; interface ImportLinkItem { source: string; target: string[]; } const validationSchema = Yup.object({ source: Yup.string().required(), target: Yup.array(Yup.string().required()).min(1).required(), }); interface InnerMetaImportationModalPropsProps { file?: File; onCloseDialog: () => void; } interface MetaImportationModalPropsProps extends InnerMetaImportationModalPropsProps { isOpen: boolean; } export function MetaImportationModal(props: MetaImportationModalPropsProps) { const { isOpen, onCloseDialog, file } = props; if (!isOpen) return null; return ( ); } function InnerMetaImportationModal({ file, onCloseDialog, }: InnerMetaImportationModalPropsProps) { const toaster = useToaster(); const dispatch = useDispatch(); const [parseResult, setParseResult] = useState | null>(); const [compareResults, setCompareResults] = useState({}); const [matches, setMatchesResults] = useState>({}); const { data } = useChartData(); const { datalist, paths } = getSpectraObjectPaths(data); function handleParseFile(file: FileWithPath | File) { void (async () => { const results = await parseMetaFile(file); setParseResult(results); })(); } const { handleSubmit, control, setValue } = useForm({ defaultValues: { source: '', target: [], }, resolver: yupResolver(validationSchema), }); useEffect(() => { if (file) { handleParseFile(file); } return () => { setParseResult(null); }; }, [file]); function handleDrop(files: FileWithPath[]) { if (files[0]) { handleParseFile(files[0]); } } const errors = mapErrors(parseResult?.errors || []); const metaData: ParseResult['data'] = parseResult?.data || []; const columns = useMemo(() => { const fields = parseResult?.meta.fields || []; const columns: Array> = [ { Header: '#', accessor: (_: any, index) => index + 1 }, ]; function getRowValue(val: any) { if (typeof val === 'string') return val; return val; } for (const fieldName of fields) { if (fieldName) { columns.push({ Header: fieldName, accessor: (row: any) => getRowValue(row[fieldName]), style: styles.column, }); } } return columns; }, [parseResult?.meta.fields]); function handleLinkSpectra(fields: any) { const { source, target } = fields; if (data && parseResult) { try { const { compareResult, matches } = linkMetaWithSpectra({ source, target, spectra: data, parseMetaFileResult: parseResult, }); setMatchesResults(matches); if (Object.keys(compareResult).length > 0) { setCompareResults(compareResult); } else { toaster.show({ message: `No matches found: Source field [${source}] => Target field [${target}]`, intent: 'danger', }); } } catch (error: any) { if (error instanceof TargetPathError) { setValue('target', []); } toaster.show({ message: error.message, intent: 'danger', }); } } } function handleActiveRow(data: any) { const record = compareResults[data.index] || null; if ( record?.isDuplicated || record?.spectraIDs.length > 0 || errors?.[data.index] ) { return true; } return false; } function handleRowStyle(data: any) { const record = compareResults[data.index] || null; if ( record?.isDuplicated || record?.spectraIDs.length > 1 || errors?.[data.index] ) { return rowColors.noMatch; } else if (record) { return rowColors.match; } return undefined; } function handleImport() { dispatch({ type: 'IMPORT_SPECTRA_META_INFO', payload: { spectraMeta: matches }, }); onCloseDialog?.(); } return ( <>
) => e.preventDefault()} > {!parseResult ? ( ) : ( <>
handleSubmit(handleLinkSpectra)()}> Link Spectra
)}
Import ); } function fileValidator(file: File): FileError | null { if (!isMetaFile(file)) { return { message: 'import a CSV or tab-delimited file', code: 'file - not - allow', }; } return null; }