import { useState, useEffect, useCallback, Fragment } from 'react'; import sequenceProcessor, { type SequenceObject, } from '../sequence-utils/sequence-processor'; import Message from './message'; import '../styles/components/sequence-submission.scss'; type Props = { /** * The value, if needed. */ value?: string; /** * The default value, if needed. */ defaultValue?: string; /** * Triggers when the value is changed. */ onChange?: (processed: SequenceObject[]) => void; /** * Display text when the textarea is empty. */ placeholder?: string; /** * Minimum acceptable length for a sequence */ minimumLength?: number; /** * Minimum acceptable number of sequences */ minimumSequences?: number; /** * Maximum acceptable number of sequences */ maximumSequences?: number; /** * Error if the same ID is used twice */ noDuplicateID?: boolean; }; const SequenceSubmission = ({ value, onChange, placeholder, defaultValue, minimumLength, minimumSequences, maximumSequences, noDuplicateID, }: Props) => { const [processed, setProcessed] = useState([]); const onChangeWithValidation = useCallback( (newValue: string) => { const processed = sequenceProcessor(newValue, minimumLength); setProcessed(processed); // Calling the custom 'onChange' callback first onChange?.(processed); }, [onChange, minimumLength] ); useEffect(() => { if (value || value === '') { onChangeWithValidation(value); } else if (defaultValue || defaultValue === '') { onChangeWithValidation(defaultValue); } }, [value, defaultValue, onChangeWithValidation]); const errorMessages = []; const warningMessages = []; if ( processed.length > 1 || (processed.length === 1 && processed[0].sequence.length > 10) ) { // loop and match all the errors together, only once const uniqueErrors = new Map< string, Array<{ sequenceIndex: number; sequenceObject: SequenceObject }> >(); // loop and match all the identifiers together, only once const uniqueIdentifiers = new Map< string, Array<{ sequenceIndex: number; sequenceObject: SequenceObject }> >(); // loop and match all the sequences together, only once const uniqueSequences = new Map< string, Array<{ sequenceIndex: number; sequenceObject: SequenceObject }> >(); for (const [index, sequenceObject] of processed.entries()) { const { valid, message, name } = sequenceObject; if (!valid && message) { const itemList = uniqueErrors.get(message) ?? []; itemList.push({ sequenceIndex: index + 1, sequenceObject }); uniqueErrors.set(message, itemList); } const itemListID = uniqueIdentifiers.get(name) ?? []; itemListID.push({ sequenceIndex: index + 1, sequenceObject }); uniqueIdentifiers.set(name, itemListID); const sequence = sequenceObject.sequence.toLowerCase(); const itemListSeq = uniqueSequences.get(sequence) ?? []; itemListSeq.push({ sequenceIndex: index + 1, sequenceObject }); uniqueSequences.set(sequence, itemListSeq); } for (const [errorMessage, sameErrors] of uniqueErrors.entries()) { errorMessages.push( sequenceIndex).join('-')} > Error: {errorMessage}. Sequence{sameErrors.length === 1 ? ' ' : 's '} {sameErrors.map( ({ sequenceIndex, sequenceObject: { name } }, index) => ( {index ? ', ' : ''} {sequenceIndex}{' '} {name ? ( <> ({name}) ) : ( '' )} ) )} ); } if (noDuplicateID) { for (const [identifier, sameIdentifiers] of uniqueIdentifiers.entries()) { if (sameIdentifiers.length > 1) { warningMessages.push( sequenceIndex) .join('-id-')} > Sequences{' '} {sameIdentifiers.map(({ sequenceIndex }, index) => { let conjunction = ''; if (index) { if (sameIdentifiers.length === 2) { conjunction = ' and '; } else { conjunction = index + 1 === sameIdentifiers.length ? ', and ' : ', '; } } return ( {conjunction} {sequenceIndex} ); })}{' '} have the same identifier ({identifier}), this is invalid. ); } } } for (const sameSequences of uniqueSequences.values()) { if (sameSequences.length > 1) { warningMessages.push( sequenceIndex) .join('-seq-')} > Sequences{' '} {sameSequences.map( ({ sequenceIndex, sequenceObject: { name } }, index) => { let conjunction = ''; if (index) { if (sameSequences.length === 2) { conjunction = ' and '; } else { conjunction = index + 1 === sameSequences.length ? ', and ' : ', '; } } return ( {conjunction} {sequenceIndex}{' '} {name ? ( <> ({name}) ) : ( '' )} ); } )}{' '} are identical, this might be unintended. ); } } } const sequenceCountMessage = (maximumSequences && processed.length > maximumSequences && ( Your input contains {processed.length} sequences which exceeds the submission limit of {maximumSequences}. )) || (minimumSequences && processed.length && processed.length < minimumSequences && ( Your input contains {processed.length} sequence {processed.length === 1 ? '' : 's'}. At least {minimumSequences}{' '} sequences are required. )) || (processed[0] && processed[0].sequence.length > 10 && ( Your input contains {processed.length} sequence {processed.length === 1 ? '' : 's'}. )); return ( <>