From 58a3da386a0081756c9259cd1435ad8e77bc5d9d Mon Sep 17 00:00:00 2001 From: SergeantPanda Date: Sun, 14 Sep 2025 13:24:11 -0500 Subject: [PATCH] Allow single channel creation from stream to specify channel number --- apps/channels/api_views.py | 61 +++--- .../src/components/tables/StreamsTable.jsx | 174 ++++++++++++++++-- 2 files changed, 190 insertions(+), 45 deletions(-) diff --git a/apps/channels/api_views.py b/apps/channels/api_views.py index 2cbde99b..43f0eca6 100644 --- a/apps/channels/api_views.py +++ b/apps/channels/api_views.py @@ -588,40 +588,47 @@ class ChannelViewSet(viewsets.ModelViewSet): channel_group = stream.channel_group name = request.data.get("name") + + if name is None: name = stream.name # Check if client provided a channel_number; if not, auto-assign one. stream_custom_props = stream.custom_properties or {} - - channel_number = None - if "tvg-chno" in stream_custom_props: - channel_number = float(stream_custom_props["tvg-chno"]) - elif "channel-number" in stream_custom_props: - channel_number = float(stream_custom_props["channel-number"]) - elif "num" in stream_custom_props: - channel_number = float(stream_custom_props["num"]) + channel_number = request.data.get("channel_number") if channel_number is None: - provided_number = request.data.get("channel_number") - if provided_number is None: - channel_number = Channel.get_next_available_channel_number() - else: - try: - channel_number = float(provided_number) - except ValueError: - return Response( - {"error": "channel_number must be an integer."}, - status=status.HTTP_400_BAD_REQUEST, - ) - # If the provided number is already used, return an error. - if Channel.objects.filter(channel_number=channel_number).exists(): - return Response( - { - "error": f"Channel number {channel_number} is already in use. Please choose a different number." - }, - status=status.HTTP_400_BAD_REQUEST, - ) + # Channel number not provided by client, check stream properties or auto-assign + if "tvg-chno" in stream_custom_props: + channel_number = float(stream_custom_props["tvg-chno"]) + elif "channel-number" in stream_custom_props: + channel_number = float(stream_custom_props["channel-number"]) + elif "num" in stream_custom_props: + channel_number = float(stream_custom_props["num"]) + elif channel_number == 0: + # Special case: 0 means ignore provider numbers and auto-assign + channel_number = None + + if channel_number is None: + # Still None, auto-assign the next available channel number + channel_number = Channel.get_next_available_channel_number() + + + try: + channel_number = float(channel_number) + except ValueError: + return Response( + {"error": "channel_number must be an integer."}, + status=status.HTTP_400_BAD_REQUEST, + ) + # If the provided number is already used, return an error. + if Channel.objects.filter(channel_number=channel_number).exists(): + return Response( + { + "error": f"Channel number {channel_number} is already in use. Please choose a different number." + }, + status=status.HTTP_400_BAD_REQUEST, + ) # Get the tvc_guide_stationid from custom properties if it exists tvc_guide_stationid = None if "tvc-guide-stationid" in stream_custom_props: diff --git a/frontend/src/components/tables/StreamsTable.jsx b/frontend/src/components/tables/StreamsTable.jsx index 0214c7cd..ef944bbd 100644 --- a/frontend/src/components/tables/StreamsTable.jsx +++ b/frontend/src/components/tables/StreamsTable.jsx @@ -59,6 +59,7 @@ const StreamRowActions = ({ deleteStream, handleWatchStream, selectedChannelIds, + createChannelFromStream, }) => { const [tableSize, _] = useLocalStorage('table-size', 'default'); const channelSelectionStreams = useChannelsTableStore( @@ -67,23 +68,6 @@ const StreamRowActions = ({ ); const fetchLogos = useLogosStore((s) => s.fetchLogos); - const createChannelFromStream = async () => { - const selectedChannelProfileId = - useChannelsStore.getState().selectedProfileId; - - await API.createChannelFromStream({ - name: row.original.name, - channel_number: null, - stream_id: row.original.id, - // Only pass channel_profile_ids if a specific profile is selected (not "All") - ...(selectedChannelProfileId !== '0' && { - channel_profile_ids: selectedChannelProfileId, - }), - }); - await API.requeryChannels(); - fetchLogos(); - }; - const addStreamToChannel = async () => { await API.updateChannel({ id: selectedChannelIds[0], @@ -145,7 +129,7 @@ const StreamRowActions = ({ size={iconSize} color={theme.tailwind.green[5]} variant="transparent" - onClick={createChannelFromStream} + onClick={() => createChannelFromStream(row.original)} > @@ -209,6 +193,13 @@ const StreamsTable = () => { const [customStartNumber, setCustomStartNumber] = useState(1); const [rememberChoice, setRememberChoice] = useState(false); + // Single channel numbering modal state + const [singleChannelModalOpen, setSingleChannelModalOpen] = useState(false); + const [singleChannelMode, setSingleChannelMode] = useState('provider'); // 'provider', 'auto', or 'specific' + const [specificChannelNumber, setSpecificChannelNumber] = useState(1); + const [rememberSingleChoice, setRememberSingleChoice] = useState(false); + const [currentStreamForChannel, setCurrentStreamForChannel] = useState(null); + // const [allRowsSelected, setAllRowsSelected] = useState(false); // Add local storage for page size @@ -534,6 +525,80 @@ const StreamsTable = () => { fetchData(); }; + // Single channel creation functions + const createChannelFromStream = async (stream) => { + // Check if user has suppressed the single channel numbering dialog + const actionKey = 'single-channel-numbering-choice'; + if (isWarningSuppressed(actionKey)) { + // Use the remembered settings or default to 'provider' mode + const savedMode = + localStorage.getItem('single-channel-numbering-mode') || 'provider'; + const savedChannelNumber = + localStorage.getItem('single-channel-numbering-specific') || '1'; + + const channelNumberValue = + savedMode === 'provider' + ? null + : savedMode === 'auto' + ? 0 + : Number(savedChannelNumber); + + await executeSingleChannelCreation(stream, channelNumberValue); + } else { + // Show the modal to let user choose + setCurrentStreamForChannel(stream); + setSingleChannelModalOpen(true); + } + }; + + // Separate function to actually execute single channel creation + const executeSingleChannelCreation = async (stream, channelNumber = null) => { + const selectedChannelProfileId = + useChannelsStore.getState().selectedProfileId; + + await API.createChannelFromStream({ + name: stream.name, + channel_number: channelNumber, + stream_id: stream.id, + // Only pass channel_profile_ids if a specific profile is selected (not "All") + ...(selectedChannelProfileId !== '0' && { + channel_profile_ids: selectedChannelProfileId, + }), + }); + await API.requeryChannels(); + const fetchLogos = useChannelsStore.getState().fetchLogos; + fetchLogos(); + }; + + // Handle confirming the single channel numbering modal + const handleSingleChannelNumberingConfirm = async () => { + // Save the choice if user wants to remember it + if (rememberSingleChoice) { + suppressWarning('single-channel-numbering-choice'); + localStorage.setItem('single-channel-numbering-mode', singleChannelMode); + if (singleChannelMode === 'specific') { + localStorage.setItem( + 'single-channel-numbering-specific', + specificChannelNumber.toString() + ); + } + } + + // Convert mode to API value + const channelNumberValue = + singleChannelMode === 'provider' + ? null + : singleChannelMode === 'auto' + ? 0 + : Number(specificChannelNumber); + + setSingleChannelModalOpen(false); + await executeSingleChannelCreation( + currentStreamForChannel, + channelNumberValue + ); + }; + const addStreamsToChannel = async () => { await API.updateChannel({ id: selectedChannelIds[0], @@ -689,6 +754,7 @@ const StreamsTable = () => { deleteStream={deleteStream} handleWatchStream={handleWatchStream} selectedChannelIds={selectedChannelIds} + createChannelFromStream={createChannelFromStream} /> ); } @@ -1037,6 +1103,78 @@ const StreamsTable = () => { + + {/* Single Channel Numbering Modal */} + setSingleChannelModalOpen(false)} + title="Channel Number Assignment" + size="md" + centered + > + + + Choose how to assign the channel number for " + {currentStreamForChannel?.name}": + + + + + + + + + + + {singleChannelMode === 'specific' && ( + + )} + + + setRememberSingleChoice(event.currentTarget.checked) + } + label="Remember this choice and don't ask again" + /> + + + + + + + ); };