mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-21 09:09:22 +00:00
New feature: Allow specifying channel number during channel creation.
Closes #377 Closes #169 [Feature]: Starting channel number/continue auto numbering from channel number X (user defined)
This commit is contained in:
parent
0411fe003a
commit
4886426ea0
2 changed files with 144 additions and 7 deletions
|
|
@ -1604,13 +1604,13 @@ def bulk_create_channels_from_streams(self, stream_ids, channel_profile_ids=None
|
|||
"""
|
||||
Asynchronously create channels from a list of stream IDs.
|
||||
Provides progress updates via WebSocket.
|
||||
|
||||
|
||||
Args:
|
||||
stream_ids: List of stream IDs to create channels from
|
||||
channel_profile_ids: Optional list of channel profile IDs to assign channels to
|
||||
starting_channel_number: Optional starting channel number behavior:
|
||||
- None: Use provider channel numbers, then auto-assign from 1
|
||||
- 0: Start with lowest available number and increment by 1
|
||||
- 0: Start with lowest available number and increment by 1
|
||||
- Other number: Use as starting number for auto-assignment
|
||||
"""
|
||||
from apps.channels.models import Stream, Channel, ChannelGroup, ChannelProfile, ChannelProfileMembership, Logo
|
||||
|
|
@ -1637,7 +1637,7 @@ def bulk_create_channels_from_streams(self, stream_ids, channel_profile_ids=None
|
|||
|
||||
# Gather current used numbers once
|
||||
used_numbers = set(Channel.objects.all().values_list("channel_number", flat=True))
|
||||
|
||||
|
||||
# Initialize next_number based on starting_channel_number mode
|
||||
if starting_channel_number is None:
|
||||
# Mode 1: Use provider numbers when available, auto-assign when not
|
||||
|
|
@ -1688,7 +1688,7 @@ def bulk_create_channels_from_streams(self, stream_ids, channel_profile_ids=None
|
|||
|
||||
# Determine channel number based on starting_channel_number mode
|
||||
channel_number = None
|
||||
|
||||
|
||||
if starting_channel_number is None:
|
||||
# Mode 1: Use provider numbers when available
|
||||
if "tvg-chno" in stream_custom_props:
|
||||
|
|
@ -1697,7 +1697,7 @@ def bulk_create_channels_from_streams(self, stream_ids, channel_profile_ids=None
|
|||
channel_number = float(stream_custom_props["channel-number"])
|
||||
elif "num" in stream_custom_props:
|
||||
channel_number = float(stream_custom_props["num"])
|
||||
|
||||
|
||||
# For modes 2 and 3 (starting_channel_number == 0 or specific number),
|
||||
# ignore provider numbers and use sequential assignment
|
||||
|
||||
|
|
|
|||
|
|
@ -39,11 +39,16 @@ import {
|
|||
UnstyledButton,
|
||||
LoadingOverlay,
|
||||
Skeleton,
|
||||
Modal,
|
||||
NumberInput,
|
||||
Radio,
|
||||
Checkbox,
|
||||
} from '@mantine/core';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import useSettingsStore from '../../store/settings';
|
||||
import useVideoStore from '../../store/useVideoStore';
|
||||
import useChannelsTableStore from '../../store/channelsTable';
|
||||
import useWarningsStore from '../../store/warnings';
|
||||
import { CustomTable, useTable } from './CustomTable';
|
||||
import useLocalStorage from '../../hooks/useLocalStorage';
|
||||
|
||||
|
|
@ -197,6 +202,13 @@ const StreamsTable = () => {
|
|||
const [sorting, setSorting] = useState([{ id: 'name', desc: '' }]);
|
||||
const [selectedStreamIds, setSelectedStreamIds] = useState([]);
|
||||
|
||||
// Channel numbering modal state
|
||||
const [channelNumberingModalOpen, setChannelNumberingModalOpen] =
|
||||
useState(false);
|
||||
const [numberingMode, setNumberingMode] = useState('provider'); // 'provider', 'auto', or 'custom'
|
||||
const [customStartNumber, setCustomStartNumber] = useState(1);
|
||||
const [rememberChoice, setRememberChoice] = useState(false);
|
||||
|
||||
// const [allRowsSelected, setAllRowsSelected] = useState(false);
|
||||
|
||||
// Add local storage for page size
|
||||
|
|
@ -249,6 +261,10 @@ const StreamsTable = () => {
|
|||
const showVideo = useVideoStore((s) => s.showVideo);
|
||||
const [tableSize, _] = useLocalStorage('table-size', 'default');
|
||||
|
||||
// Warnings store for "remember choice" functionality
|
||||
const suppressWarning = useWarningsStore((s) => s.suppressWarning);
|
||||
const isWarningSuppressed = useWarningsStore((s) => s.isWarningSuppressed);
|
||||
|
||||
const handleSelectClick = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
|
@ -419,11 +435,35 @@ const StreamsTable = () => {
|
|||
fetchChannelGroups,
|
||||
]);
|
||||
|
||||
// Bulk creation: create channels from selected streams in one API call
|
||||
// Bulk creation: create channels from selected streams asynchronously
|
||||
const createChannelsFromStreams = async () => {
|
||||
if (selectedStreamIds.length === 0) return;
|
||||
|
||||
// Check if user has suppressed the channel numbering dialog
|
||||
const actionKey = 'channel-numbering-choice';
|
||||
if (isWarningSuppressed(actionKey)) {
|
||||
// Use the remembered settings or default to 'provider' mode
|
||||
const savedMode =
|
||||
localStorage.getItem('channel-numbering-mode') || 'provider';
|
||||
const savedStartNumber =
|
||||
localStorage.getItem('channel-numbering-start') || '1';
|
||||
|
||||
const startingChannelNumberValue =
|
||||
savedMode === 'provider'
|
||||
? null
|
||||
: savedMode === 'auto'
|
||||
? 0
|
||||
: Number(savedStartNumber);
|
||||
|
||||
await executeChannelCreation(startingChannelNumberValue);
|
||||
} else {
|
||||
// Show the modal to let user choose
|
||||
setChannelNumberingModalOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
// Separate function to actually execute the channel creation
|
||||
const executeChannelCreation = async (startingChannelNumberValue) => {
|
||||
try {
|
||||
const selectedChannelProfileId =
|
||||
useChannelsStore.getState().selectedProfileId;
|
||||
|
|
@ -431,7 +471,8 @@ const StreamsTable = () => {
|
|||
// Use the async API for all bulk operations
|
||||
const response = await API.createChannelsFromStreamsAsync(
|
||||
selectedStreamIds,
|
||||
selectedChannelProfileId !== '0' ? [selectedChannelProfileId] : null
|
||||
selectedChannelProfileId !== '0' ? [selectedChannelProfileId] : null,
|
||||
startingChannelNumberValue
|
||||
);
|
||||
|
||||
console.log(
|
||||
|
|
@ -446,6 +487,32 @@ const StreamsTable = () => {
|
|||
}
|
||||
};
|
||||
|
||||
// Handle confirming the channel numbering modal
|
||||
const handleChannelNumberingConfirm = async () => {
|
||||
// Save the choice if user wants to remember it
|
||||
if (rememberChoice) {
|
||||
suppressWarning('channel-numbering-choice');
|
||||
localStorage.setItem('channel-numbering-mode', numberingMode);
|
||||
if (numberingMode === 'custom') {
|
||||
localStorage.setItem(
|
||||
'channel-numbering-start',
|
||||
customStartNumber.toString()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert mode to API value
|
||||
const startingChannelNumberValue =
|
||||
numberingMode === 'provider'
|
||||
? null
|
||||
: numberingMode === 'auto'
|
||||
? 0
|
||||
: Number(customStartNumber);
|
||||
|
||||
setChannelNumberingModalOpen(false);
|
||||
await executeChannelCreation(startingChannelNumberValue);
|
||||
};
|
||||
|
||||
const editStream = async (stream = null) => {
|
||||
setStream(stream);
|
||||
setModalOpen(true);
|
||||
|
|
@ -900,6 +967,76 @@ const StreamsTable = () => {
|
|||
isOpen={modalOpen}
|
||||
onClose={closeStreamForm}
|
||||
/>
|
||||
|
||||
{/* Channel Numbering Modal */}
|
||||
<Modal
|
||||
opened={channelNumberingModalOpen}
|
||||
onClose={() => setChannelNumberingModalOpen(false)}
|
||||
title="Channel Numbering Options"
|
||||
size="md"
|
||||
centered
|
||||
>
|
||||
<Stack spacing="md">
|
||||
<Text size="sm" c="dimmed">
|
||||
Choose how to assign channel numbers to the{' '}
|
||||
{selectedStreamIds.length} selected streams:
|
||||
</Text>
|
||||
|
||||
<Radio.Group
|
||||
value={numberingMode}
|
||||
onChange={setNumberingMode}
|
||||
label="Numbering Mode"
|
||||
>
|
||||
<Stack mt="xs" spacing="xs">
|
||||
<Radio
|
||||
value="provider"
|
||||
label="Use Provider Numbers"
|
||||
description="Use tvg-chno or channel-number from stream metadata, auto-assign for conflicts"
|
||||
/>
|
||||
<Radio
|
||||
value="auto"
|
||||
label="Auto-Assign Sequential"
|
||||
description="Start from the lowest available channel number and increment by 1"
|
||||
/>
|
||||
<Radio
|
||||
value="custom"
|
||||
label="Start from Custom Number"
|
||||
description="Start sequential numbering from a specific channel number"
|
||||
/>
|
||||
</Stack>
|
||||
</Radio.Group>
|
||||
|
||||
{numberingMode === 'custom' && (
|
||||
<NumberInput
|
||||
label="Starting Channel Number"
|
||||
description="Channel numbers will be assigned starting from this number"
|
||||
value={customStartNumber}
|
||||
onChange={setCustomStartNumber}
|
||||
min={1}
|
||||
max={9999}
|
||||
placeholder="Enter starting number..."
|
||||
/>
|
||||
)}
|
||||
|
||||
<Checkbox
|
||||
checked={rememberChoice}
|
||||
onChange={(event) => setRememberChoice(event.currentTarget.checked)}
|
||||
label="Remember this choice and don't ask again"
|
||||
/>
|
||||
|
||||
<Group justify="flex-end" mt="md">
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={() => setChannelNumberingModalOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleChannelNumberingConfirm}>
|
||||
Create Channels
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue