mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-08-02 14:52:15 +00:00
Allow single channel creation from stream to specify channel number
This commit is contained in:
parent
4886426ea0
commit
58a3da386a
2 changed files with 190 additions and 45 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
>
|
||||
<SquarePlus size="18" fontSize="small" />
|
||||
</ActionIcon>
|
||||
|
|
@ -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 = () => {
|
|||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
|
||||
{/* Single Channel Numbering Modal */}
|
||||
<Modal
|
||||
opened={singleChannelModalOpen}
|
||||
onClose={() => setSingleChannelModalOpen(false)}
|
||||
title="Channel Number Assignment"
|
||||
size="md"
|
||||
centered
|
||||
>
|
||||
<Stack spacing="md">
|
||||
<Text size="sm" c="dimmed">
|
||||
Choose how to assign the channel number for "
|
||||
{currentStreamForChannel?.name}":
|
||||
</Text>
|
||||
|
||||
<Radio.Group
|
||||
value={singleChannelMode}
|
||||
onChange={setSingleChannelMode}
|
||||
label="Number Assignment"
|
||||
>
|
||||
<Stack mt="xs" spacing="xs">
|
||||
<Radio
|
||||
value="provider"
|
||||
label="Use Provider Number"
|
||||
description="Use tvg-chno or channel-number from stream metadata, auto-assign if not available"
|
||||
/>
|
||||
<Radio
|
||||
value="auto"
|
||||
label="Auto-Assign Next Available"
|
||||
description="Automatically assign the next available channel number"
|
||||
/>
|
||||
<Radio
|
||||
value="specific"
|
||||
label="Use Specific Number"
|
||||
description="Use a specific channel number"
|
||||
/>
|
||||
</Stack>
|
||||
</Radio.Group>
|
||||
|
||||
{singleChannelMode === 'specific' && (
|
||||
<NumberInput
|
||||
label="Channel Number"
|
||||
description="The specific channel number to assign"
|
||||
value={specificChannelNumber}
|
||||
onChange={setSpecificChannelNumber}
|
||||
min={1}
|
||||
max={9999}
|
||||
placeholder="Enter channel number..."
|
||||
/>
|
||||
)}
|
||||
|
||||
<Checkbox
|
||||
checked={rememberSingleChoice}
|
||||
onChange={(event) =>
|
||||
setRememberSingleChoice(event.currentTarget.checked)
|
||||
}
|
||||
label="Remember this choice and don't ask again"
|
||||
/>
|
||||
|
||||
<Group justify="flex-end" mt="md">
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={() => setSingleChannelModalOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleSingleChannelNumberingConfirm}>
|
||||
Create Channel
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue