import { useCallback, useEffect, useRef, useState } from 'react' import { Label } from '../../../lib/networking/fragments/labelFragment' import { SpanBox, VStack } from '../../elements/LayoutPrimitives' import { ModalRoot, ModalOverlay, ModalContent, ModalTitleBar, } from '../../elements/ModalPrimitives' import { LabelsProvider, SetLabelsControl } from './SetLabelsControl' import { createLabelMutation } from '../../../lib/networking/mutations/createLabelMutation' import { showSuccessToast } from '../../../lib/toastHelpers' import { useGetLabelsQuery } from '../../../lib/networking/queries/useGetLabelsQuery' import { v4 as uuidv4 } from 'uuid' import { randomLabelColorHex } from '../../../utils/settings-page/labels/labelColorObjects' import { LabelsDispatcher } from '../../../lib/hooks/useSetPageLabels' import * as Dialog from '@radix-ui/react-dialog' type SetLabelsModalProps = { provider: LabelsProvider onOpenChange: (open: boolean) => void selectedLabels: Label[] dispatchLabels: LabelsDispatcher } export function SetLabelsModal(props: SetLabelsModalProps): JSX.Element { const [inputValue, setInputValue] = useState('') const { selectedLabels, dispatchLabels } = props const availableLabels = useGetLabelsQuery() const [tabCount, setTabCount] = useState(-1) const [tabStartValue, setTabStartValue] = useState('') const [errorMessage, setErrorMessage] = useState( undefined ) const errorTimeoutRef = useRef() const [highlightLastLabel, setHighlightLastLabel] = useState(false) const showMessage = useCallback( (msg: string, timeout?: number) => { if (errorTimeoutRef.current) { clearTimeout(errorTimeoutRef.current) errorTimeoutRef.current = undefined } setErrorMessage(msg) if (timeout) { errorTimeoutRef.current = setTimeout(() => { setErrorMessage(undefined) if (errorTimeoutRef.current) { clearTimeout(errorTimeoutRef.current) errorTimeoutRef.current = undefined } }, timeout) } }, [errorTimeoutRef] ) useEffect(() => { const maxLengthMessage = 'Max label length: 48 chars' if (inputValue.length >= 48) { showMessage(maxLengthMessage) } else if (errorMessage === maxLengthMessage) { setErrorMessage(undefined) } if (inputValue.length > 0) { setHighlightLastLabel(false) } }, [errorMessage, inputValue, showMessage]) const clearInputState = useCallback(() => { setTabCount(-1) setInputValue('') setTabStartValue('') setHighlightLastLabel(false) }, []) const createLabelAsync = useCallback( (newLabels: Label[], tempLabel: Label) => { ;(async () => { const currentLabels = newLabels const newLabel = await createLabelMutation( tempLabel.name, tempLabel.color ) const idx = currentLabels.findIndex((l) => l.id === tempLabel.id) if (newLabel) { showSuccessToast(`Created label ${newLabel.name}`, { position: 'bottom-right', }) if (idx !== -1) { currentLabels[idx] = newLabel dispatchLabels({ type: 'SAVE', labels: [...currentLabels] }) } else { dispatchLabels({ type: 'SAVE', labels: [...currentLabels, newLabel], }) } } else { showMessage(`Error creating label ${tempLabel.name}`, 5000) if (idx !== -1) { currentLabels.splice(idx, 1) dispatchLabels({ type: 'SAVE', labels: [...currentLabels] }) } } })() }, [dispatchLabels, showMessage] ) const selectOrCreateLabel = useCallback( (value: string) => { const current = selectedLabels ?? [] const lowerCasedValue = value.toLowerCase() const existing = availableLabels.labels.find( (l) => l.name.toLowerCase() == lowerCasedValue ) if (lowerCasedValue.length < 1) { return } if (existing) { const isAdded = selectedLabels.find( (l) => l.name.toLowerCase() == lowerCasedValue ) if (!isAdded) { dispatchLabels({ type: 'SAVE', labels: [...current, existing] }) clearInputState() } else { showMessage(`label ${value} already added.`, 5000) } } else { const tempLabel = { id: uuidv4(), name: value, color: randomLabelColorHex(), description: '', createdAt: new Date(), } const newLabels = [...current, tempLabel] dispatchLabels({ type: 'TEMP', labels: newLabels }) clearInputState() createLabelAsync(newLabels, tempLabel) } }, [ availableLabels, selectedLabels, dispatchLabels, clearInputState, createLabelAsync, showMessage, ] ) const deleteLastLabel = useCallback(() => { if (highlightLastLabel) { const current = selectedLabels current.pop() dispatchLabels({ type: 'SAVE', labels: [...current] }) setHighlightLastLabel(false) } else { setHighlightLastLabel(true) } }, [highlightLastLabel, selectedLabels, dispatchLabels]) return ( { event.preventDefault() props.onOpenChange(false) }} onEscapeKeyDown={(event) => { props.onOpenChange(false) event.preventDefault() event.stopPropagation() }} > ) }