diff --git a/frontend/src/components/forms/ChannelBatch.jsx b/frontend/src/components/forms/ChannelBatch.jsx index ad61fb26..134807d4 100644 --- a/frontend/src/components/forms/ChannelBatch.jsx +++ b/frontend/src/components/forms/ChannelBatch.jsx @@ -29,18 +29,32 @@ import { FixedSizeList as List } from 'react-window'; import { useForm } from '@mantine/form'; import { notifications } from '@mantine/notifications'; import { USER_LEVELS, USER_LEVEL_LABELS } from '../../constants'; +import { useChannelLogoSelection } from '../../hooks/useSmartLogos'; +import LazyLogo from '../LazyLogo'; +import logo from '../../images/logo.png'; const ChannelBatchForm = ({ channelIds, isOpen, onClose }) => { const theme = useMantineTheme(); const groupListRef = useRef(null); + const logoListRef = useRef(null); const channelGroups = useChannelsStore((s) => s.channelGroups); + const { + logos: channelLogos, + ensureLogosLoaded, + isLoading: logosLoading, + } = useChannelLogoSelection(); + + useEffect(() => { + ensureLogosLoaded(); + }, [ensureLogosLoaded]); const streamProfiles = useStreamProfilesStore((s) => s.profiles); const [channelGroupModelOpen, setChannelGroupModalOpen] = useState(false); const [selectedChannelGroup, setSelectedChannelGroup] = useState('-1'); + const [selectedLogoId, setSelectedLogoId] = useState('-1'); const [isSubmitting, setIsSubmitting] = useState(false); const [regexFind, setRegexFind] = useState(''); const [regexReplace, setRegexReplace] = useState(''); @@ -49,10 +63,14 @@ const ChannelBatchForm = ({ channelIds, isOpen, onClose }) => { const [groupFilter, setGroupFilter] = useState(''); const groupOptions = Object.values(channelGroups); + const [logoPopoverOpened, setLogoPopoverOpened] = useState(false); + const [logoFilter, setLogoFilter] = useState(''); + const form = useForm({ mode: 'uncontrolled', initialValues: { channel_group: '(no change)', + logo: '(no change)', stream_profile_id: '-1', user_level: '-1', }, @@ -70,6 +88,15 @@ const ChannelBatchForm = ({ channelIds, isOpen, onClose }) => { delete values.channel_group_id; } + if (selectedLogoId && selectedLogoId !== '-1') { + if (selectedLogoId === '0') { + values.logo_id = null; + } else { + values.logo_id = parseInt(selectedLogoId); + } + } + delete values.logo; + // Handle stream profile ID - convert special values if (!values.stream_profile_id || values.stream_profile_id === '-1') { delete values.stream_profile_id; @@ -242,6 +269,18 @@ const ChannelBatchForm = ({ channelIds, isOpen, onClose }) => { ), ]; + const logoOptions = useMemo(() => { + return [ + { id: '-1', name: '(no change)' }, + { id: '0', name: '(remove logo)' }, + ...Object.values(channelLogos), + ]; + }, [channelLogos]); + + const filteredLogos = logoOptions.filter((logo) => + logo.name.toLowerCase().includes(logoFilter.toLowerCase()) + ); + if (!isOpen) { return <>; } @@ -445,6 +484,153 @@ const ChannelBatchForm = ({ channelIds, isOpen, onClose }) => { + + + + setLogoPopoverOpened(true)} + size="xs" + style={{ flex: 1 }} + rightSection={ + selectedLogoId !== '-1' && ( + { + e.stopPropagation(); + setSelectedLogoId('-1'); + form.setValues({ logo: '(no change)' }); + }} + > + + + ) + } + /> + + e.stopPropagation()}> + + + setLogoFilter(event.currentTarget.value) + } + mb="xs" + size="xs" + /> + {logosLoading && ( + + Loading... + + )} + + + {filteredLogos.length === 0 ? ( +
+ + {logoFilter + ? 'No logos match your filter' + : 'No logos available'} + +
+ ) : ( + + {({ index, style }) => ( +
{ + setSelectedLogoId(filteredLogos[index].id); + form.setValues({ + logo: filteredLogos[index].name, + }); + setLogoPopoverOpened(false); + }} + onMouseEnter={(e) => { + e.currentTarget.style.backgroundColor = + 'rgb(68, 68, 68)'; + }} + onMouseLeave={(e) => { + e.currentTarget.style.backgroundColor = + 'transparent'; + }} + > +
+ {filteredLogos[index].id > 0 ? ( + {filteredLogos[index].name { + if (e.target.src !== logo) { + e.target.src = logo; + } + }} + /> + ) : ( + + )} + + {filteredLogos[index].name} + +
+
+ )} +
+ )} +
+
+
+ {selectedLogoId > 0 && ( + + )} +
+ +