mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-01-23 02:35:14 +00:00
big push of UI updates
This commit is contained in:
parent
36cc6da547
commit
a1a25799dd
10 changed files with 399 additions and 81 deletions
|
|
@ -112,6 +112,9 @@ REST_FRAMEWORK = {
|
|||
'rest_framework.renderers.JSONRenderer',
|
||||
'rest_framework.renderers.BrowsableAPIRenderer',
|
||||
],
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
||||
],
|
||||
}
|
||||
|
||||
SWAGGER_SETTINGS = {
|
||||
|
|
@ -158,12 +161,6 @@ CSRF_TRUSTED_ORIGINS = [
|
|||
]
|
||||
APPEND_SLASH = True
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
||||
],
|
||||
}
|
||||
|
||||
SIMPLE_JWT = {
|
||||
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
|
||||
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import Login from './pages/Login';
|
|||
import Channels from './pages/Channels';
|
||||
import M3U from './pages/M3U';
|
||||
import { ThemeProvider } from '@mui/material/styles';
|
||||
import { Box, CssBaseline } from '@mui/material';
|
||||
import { Box, CssBaseline, GlobalStyles } from '@mui/material';
|
||||
import theme from './theme';
|
||||
import EPG from './pages/EPG';
|
||||
import Guide from './pages/Guide';
|
||||
|
|
@ -80,6 +80,14 @@ const App = () => {
|
|||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<GlobalStyles
|
||||
styles={{
|
||||
'.Mui-TableHeadCell-Content': {
|
||||
height: '100%',
|
||||
alignItems: 'flex-end !important',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<WebsocketProvider>
|
||||
<Router>
|
||||
<Sidebar
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// frontend/src/components/FloatingVideo.js
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import Draggable from 'react-draggable';
|
||||
import useVideoStore from '../store/video';
|
||||
import useVideoStore from '../store/useVideoStore';
|
||||
import mpegts from 'mpegts.js';
|
||||
|
||||
export default function FloatingVideo() {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import {
|
|||
Snackbar,
|
||||
Popover,
|
||||
TextField,
|
||||
Autocomplete,
|
||||
InputAdornment,
|
||||
} from '@mui/material';
|
||||
import useChannelsStore from '../../store/channels';
|
||||
import {
|
||||
|
|
@ -24,14 +26,15 @@ import {
|
|||
SwapVert as SwapVertIcon,
|
||||
LiveTv as LiveTvIcon,
|
||||
ContentCopy,
|
||||
Tv as TvIcon, // <-- ADD THIS IMPORT
|
||||
Tv as TvIcon,
|
||||
Clear as ClearIcon,
|
||||
} from '@mui/icons-material';
|
||||
import API from '../../api';
|
||||
import ChannelForm from '../forms/Channel';
|
||||
import { TableHelper } from '../../helpers';
|
||||
import utils from '../../utils';
|
||||
import logo from '../../images/logo.png';
|
||||
import useVideoStore from '../../store/video';
|
||||
import useVideoStore from '../../store/useVideoStore';
|
||||
import useSettingsStore from '../../store/settings';
|
||||
import useStreamsStore from '../../store/streams';
|
||||
import usePlaylistsStore from '../../store/playlists';
|
||||
|
|
@ -85,7 +88,6 @@ const ChannelStreams = ({ channel, isExpanded }) => {
|
|||
[playlists]
|
||||
),
|
||||
enableKeyboardShortcuts: false,
|
||||
enableColumnActions: false,
|
||||
enableColumnFilters: false,
|
||||
enableSorting: false,
|
||||
enableBottomToolbar: false,
|
||||
|
|
@ -150,23 +152,44 @@ const ChannelStreams = ({ channel, isExpanded }) => {
|
|||
);
|
||||
};
|
||||
|
||||
const ChannelsTable = ({ setSelectedChannels }) => {
|
||||
const ChannelsTable = ({}) => {
|
||||
const [channel, setChannel] = useState(null);
|
||||
const [channelModalOpen, setChannelModalOpen] = useState(false);
|
||||
const [rowSelection, setRowSelection] = useState([]);
|
||||
const [channelGroupOptions, setChannelGroupOptions] = useState([]);
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const [textToCopy, setTextToCopy] = useState('');
|
||||
const [snackbarMessage, setSnackbarMessage] = useState('');
|
||||
const [snackbarOpen, setSnackbarOpen] = useState(false);
|
||||
|
||||
const { showVideo } = useVideoStore(); // or useVideoStore()
|
||||
const [filterValues, setFilterValues] = useState({});
|
||||
|
||||
const { showVideo } = useVideoStore();
|
||||
const {
|
||||
channels,
|
||||
isLoading: channelsLoading,
|
||||
fetchChannels,
|
||||
setChannelsPageSelection,
|
||||
} = useChannelsStore();
|
||||
|
||||
useEffect(() => {
|
||||
setChannelGroupOptions([
|
||||
...new Set(
|
||||
Object.values(channels).map((channel) => channel.channel_group?.name)
|
||||
),
|
||||
]);
|
||||
}, [channels]);
|
||||
|
||||
const handleFilterChange = (columnId, value) => {
|
||||
console.log(columnId);
|
||||
console.log(value);
|
||||
setFilterValues((prev) => ({
|
||||
...prev,
|
||||
[columnId]: value ? value.toLowerCase() : '',
|
||||
}));
|
||||
};
|
||||
|
||||
const outputUrlRef = useRef(null);
|
||||
|
||||
const {
|
||||
|
|
@ -184,14 +207,81 @@ const ChannelsTable = ({ setSelectedChannels }) => {
|
|||
{
|
||||
header: 'Name',
|
||||
accessorKey: 'channel_name',
|
||||
muiTableHeadCellProps: {
|
||||
sx: { textAlign: 'center' }, // Center-align the header
|
||||
},
|
||||
Header: ({ column }) => (
|
||||
<TextField
|
||||
variant="standard"
|
||||
label="Name"
|
||||
value={filterValues[column.id]}
|
||||
onChange={(e) => handleFilterChange(column.id, e.target.value)}
|
||||
size="small"
|
||||
margin="none"
|
||||
fullWidth
|
||||
sx={
|
||||
{
|
||||
// '& .MuiInputBase-root': { fontSize: '0.875rem' }, // Text size
|
||||
// '& .MuiInputLabel-root': { fontSize: '0.75rem' }, // Label size
|
||||
// width: '200px', // Optional: Adjust width
|
||||
}
|
||||
}
|
||||
slotProps={{
|
||||
input: {
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
onClick={() => handleFilterChange(column.id, '')} // Clear text on click
|
||||
edge="end"
|
||||
size="small"
|
||||
>
|
||||
<ClearIcon sx={{ fontSize: '1rem' }} />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
),
|
||||
meta: {
|
||||
filterVariant: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
header: 'Group',
|
||||
accessorFn: (row) => row.channel_group?.name || '',
|
||||
Header: ({ column }) => (
|
||||
<Autocomplete
|
||||
disablePortal
|
||||
options={channelGroupOptions}
|
||||
size="small"
|
||||
sx={{ width: 300 }}
|
||||
clearOnEscape
|
||||
onChange={(event, newValue) =>
|
||||
handleFilterChange(column.id, newValue)
|
||||
}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Group"
|
||||
size="small"
|
||||
variant="standard"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
sx={{
|
||||
pb: 0.8,
|
||||
// '& .MuiInputBase-root': { fontSize: '0.875rem' }, // Text size
|
||||
// '& .MuiInputLabel-root': { fontSize: '0.75rem' }, // Label size
|
||||
// width: '200px', // Optional: Adjust width
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: 'Logo',
|
||||
accessorKey: 'logo_url',
|
||||
enableSorting: false,
|
||||
size: 55,
|
||||
Cell: ({ cell }) => (
|
||||
<Grid2
|
||||
|
|
@ -210,7 +300,7 @@ const ChannelsTable = ({ setSelectedChannels }) => {
|
|||
},
|
||||
},
|
||||
],
|
||||
[]
|
||||
[channelGroupOptions]
|
||||
);
|
||||
|
||||
// Access the row virtualizer instance (optional)
|
||||
|
|
@ -370,22 +460,36 @@ const ChannelsTable = ({ setSelectedChannels }) => {
|
|||
);
|
||||
};
|
||||
|
||||
const onRowSelectionChange = (e, test) => {
|
||||
console.log(e());
|
||||
console.log(test);
|
||||
setRowSelection(e);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const selectedRows = table
|
||||
.getSelectedRowModel()
|
||||
.rows.map((row) => row.original);
|
||||
setSelectedChannels(selectedRows);
|
||||
setChannelsPageSelection(selectedRows);
|
||||
}, [rowSelection]);
|
||||
|
||||
// Configure the MaterialReactTable
|
||||
const filteredData = Object.values(channels).filter((row) =>
|
||||
columns.every(({ accessorKey }) =>
|
||||
filterValues[accessorKey]
|
||||
? row[accessorKey]?.toLowerCase().includes(filterValues[accessorKey])
|
||||
: true
|
||||
)
|
||||
);
|
||||
|
||||
const table = useMaterialReactTable({
|
||||
...TableHelper.defaultProperties,
|
||||
columns,
|
||||
data: Object.values(channels),
|
||||
data: filteredData,
|
||||
enablePagination: false,
|
||||
enableColumnActions: false,
|
||||
enableRowVirtualization: true,
|
||||
enableRowSelection: true,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
onRowSelectionChange: onRowSelectionChange,
|
||||
onSortingChange: setSorting,
|
||||
state: {
|
||||
isLoading: isLoading || channelsLoading,
|
||||
|
|
@ -400,18 +504,21 @@ const ChannelsTable = ({ setSelectedChannels }) => {
|
|||
enableRowActions: true,
|
||||
enableExpandAll: false,
|
||||
displayColumnDefOptions: {
|
||||
'mrt-row-select': {
|
||||
size: 50, // Set custom width (default is ~40px)
|
||||
},
|
||||
'mrt-row-expand': {
|
||||
size: 10, // Set custom width (default is ~40px)
|
||||
header: '',
|
||||
muiTableHeadCellProps: {
|
||||
sx: { width: 30, minWidth: 30, maxWidth: 30 },
|
||||
sx: { width: 38, minWidth: 38, maxWidth: 38, height: '100%' },
|
||||
},
|
||||
muiTableBodyCellProps: {
|
||||
sx: { width: 30, minWidth: 30, maxWidth: 30 },
|
||||
sx: { width: 38, minWidth: 38, maxWidth: 38 },
|
||||
},
|
||||
},
|
||||
'mrt-row-actions': {
|
||||
size: 50, // Set custom width (default is ~40px)
|
||||
size: 68, // Set custom width (default is ~40px)
|
||||
},
|
||||
},
|
||||
muiExpandButtonProps: ({ row, table }) => ({
|
||||
|
|
@ -429,32 +536,40 @@ const ChannelsTable = ({ setSelectedChannels }) => {
|
|||
),
|
||||
renderRowActions: ({ row }) => (
|
||||
<Box sx={{ justifyContent: 'right' }}>
|
||||
<IconButton
|
||||
size="small"
|
||||
color="warning"
|
||||
onClick={() => {
|
||||
editChannel(row.original);
|
||||
}}
|
||||
sx={{ p: 0 }}
|
||||
>
|
||||
<EditIcon fontSize="small" />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
size="small"
|
||||
color="error"
|
||||
onClick={() => deleteChannel(row.original.id)}
|
||||
sx={{ p: 0 }}
|
||||
>
|
||||
<DeleteIcon fontSize="small" />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
size="small"
|
||||
color="info"
|
||||
onClick={() => handleWatchStream(row.original.channel_number)}
|
||||
sx={{ p: 0 }}
|
||||
>
|
||||
<LiveTvIcon fontSize="small" />
|
||||
</IconButton>
|
||||
<Tooltip title="Edit Channel">
|
||||
<IconButton
|
||||
size="small"
|
||||
color="warning"
|
||||
onClick={() => {
|
||||
editChannel(row.original);
|
||||
}}
|
||||
sx={{ py: 0, px: 0.5 }}
|
||||
>
|
||||
<EditIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="Delete Channel">
|
||||
<IconButton
|
||||
size="small"
|
||||
color="error"
|
||||
onClick={() => deleteChannel(row.original.id)}
|
||||
sx={{ py: 0, px: 0.5 }}
|
||||
>
|
||||
<DeleteIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="Preview Channel">
|
||||
<IconButton
|
||||
size="small"
|
||||
color="info"
|
||||
onClick={() => handleWatchStream(row.original.channel_number)}
|
||||
sx={{ py: 0, px: 0.5 }}
|
||||
>
|
||||
<LiveTvIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
),
|
||||
muiTableContainerProps: {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,11 @@ import {
|
|||
IconButton,
|
||||
Tooltip,
|
||||
Button,
|
||||
Menu,
|
||||
MenuItem,
|
||||
TextField,
|
||||
Autocomplete,
|
||||
InputAdornment,
|
||||
} from '@mui/material';
|
||||
import useStreamsStore from '../../store/streams';
|
||||
import API from '../../api';
|
||||
|
|
@ -17,30 +22,158 @@ import {
|
|||
Delete as DeleteIcon,
|
||||
Edit as EditIcon,
|
||||
Add as AddIcon,
|
||||
MoreVert as MoreVertIcon,
|
||||
PlaylistAdd as PlaylistAddIcon,
|
||||
Clear as ClearIcon,
|
||||
} from '@mui/icons-material';
|
||||
import { TableHelper } from '../../helpers';
|
||||
import StreamForm from '../forms/Stream';
|
||||
import usePlaylistsStore from '../../store/playlists';
|
||||
import useChannelsStore from '../../store/channels';
|
||||
|
||||
const StreamsTable = ({ selectedChannels }) => {
|
||||
const StreamsTable = ({}) => {
|
||||
const [rowSelection, setRowSelection] = useState([]);
|
||||
const [stream, setStream] = useState(null);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [moreActionsAnchorEl, setMoreActionsAnchorEl] = useState(null);
|
||||
const [filterValues, setFilterValues] = useState({});
|
||||
const [groupOptions, setGroupOptions] = useState([]);
|
||||
const [m3uOptions, setM3uOptions] = useState([]);
|
||||
const [actionsOpenRow, setActionsOpenRow] = useState(null);
|
||||
|
||||
const { streams, isLoading: streamsLoading } = useStreamsStore();
|
||||
const { playlists } = usePlaylistsStore();
|
||||
const { channelsPageSelection } = useChannelsStore();
|
||||
|
||||
const isMoreActionsOpen = Boolean(moreActionsAnchorEl);
|
||||
|
||||
const handleFilterChange = (columnId, value) => {
|
||||
setFilterValues((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
[columnId]: value ? value.toLowerCase() : '',
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setGroupOptions([...new Set(streams.map((stream) => stream.group_name))]);
|
||||
setM3uOptions([...new Set(playlists.map((playlist) => playlist.name))]);
|
||||
}, [streams, playlists]);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{ header: 'Name', accessorKey: 'name' },
|
||||
{ header: 'Group', accessorKey: 'group_name' },
|
||||
{
|
||||
header: 'Name',
|
||||
accessorKey: 'name',
|
||||
muiTableHeadCellProps: {
|
||||
sx: { textAlign: 'center' }, // Center-align the header
|
||||
},
|
||||
Header: ({ column }) => (
|
||||
<TextField
|
||||
variant="standard"
|
||||
label="Name"
|
||||
value={filterValues[column.id]}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onChange={(e) => handleFilterChange(column.id, e.target.value)}
|
||||
size="small"
|
||||
margin="none"
|
||||
fullWidth
|
||||
sx={
|
||||
{
|
||||
// '& .MuiInputBase-root': { fontSize: '0.875rem' }, // Text size
|
||||
// '& .MuiInputLabel-root': { fontSize: '0.75rem' }, // Label size
|
||||
// width: '200px', // Optional: Adjust width
|
||||
}
|
||||
}
|
||||
slotProps={{
|
||||
input: {
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
onClick={() => handleFilterChange(column.id, '')} // Clear text on click
|
||||
edge="end"
|
||||
size="small"
|
||||
sx={{ p: 0 }}
|
||||
>
|
||||
<ClearIcon sx={{ fontSize: '1rem' }} />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
),
|
||||
meta: {
|
||||
filterVariant: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
header: 'Group',
|
||||
accessorKey: 'group_name',
|
||||
Header: ({ column }) => (
|
||||
<Autocomplete
|
||||
disablePortal
|
||||
options={groupOptions}
|
||||
size="small"
|
||||
sx={{ width: 300 }}
|
||||
clearOnEscape
|
||||
onChange={(event, newValue) =>
|
||||
handleFilterChange(column.id, newValue)
|
||||
}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Group"
|
||||
size="small"
|
||||
variant="standard"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
sx={{
|
||||
pb: 0.8,
|
||||
'& .MuiInputBase-root': { fontSize: '0.875rem' }, // Text size
|
||||
'& .MuiInputLabel-root': { fontSize: '0.75rem' }, // Label size
|
||||
width: '200px', // Optional: Adjust width
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: 'M3U',
|
||||
size: 100,
|
||||
accessorFn: (row) =>
|
||||
playlists.find((playlist) => playlist.id === row.m3u_account)?.name,
|
||||
Header: ({ column }) => (
|
||||
<Autocomplete
|
||||
disablePortal
|
||||
options={m3uOptions}
|
||||
size="small"
|
||||
sx={{ width: 300 }}
|
||||
clearOnEscape
|
||||
onChange={(event, newValue) =>
|
||||
handleFilterChange(column.id, newValue)
|
||||
}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="M3U"
|
||||
size="small"
|
||||
variant="standard"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
sx={{
|
||||
pb: 0.8,
|
||||
'& .MuiInputBase-root': { fontSize: '0.875rem' }, // Text size
|
||||
'& .MuiInputLabel-root': { fontSize: '0.75rem' }, // Label size
|
||||
width: '200px', // Optional: Adjust width
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
],
|
||||
[playlists]
|
||||
[playlists, groupOptions, m3uOptions]
|
||||
);
|
||||
|
||||
const rowVirtualizerInstanceRef = useRef(null);
|
||||
|
|
@ -110,8 +243,8 @@ const StreamsTable = ({ selectedChannels }) => {
|
|||
}
|
||||
}, [sorting]);
|
||||
|
||||
const addStreamsToChannel = async (stream) => {
|
||||
const channel = selectedChannels[0];
|
||||
const addStreamsToChannel = async () => {
|
||||
const channel = channelsPageSelection[0];
|
||||
const selectedRows = table.getSelectedRowModel().rows;
|
||||
await API.updateChannel({
|
||||
...channel,
|
||||
|
|
@ -123,10 +256,36 @@ const StreamsTable = ({ selectedChannels }) => {
|
|||
});
|
||||
};
|
||||
|
||||
const addStreamToChannel = async (streamId) => {
|
||||
const channel = channelsPageSelection[0];
|
||||
await API.updateChannel({
|
||||
...channel,
|
||||
streams: [...new Set(channel.stream_ids.concat([streamId]))],
|
||||
});
|
||||
};
|
||||
|
||||
const handleMoreActionsClick = (event, rowId) => {
|
||||
setMoreActionsAnchorEl(event.currentTarget);
|
||||
setActionsOpenRow(rowId);
|
||||
};
|
||||
|
||||
const handleMoreActionsClose = () => {
|
||||
setMoreActionsAnchorEl(null);
|
||||
setActionsOpenRow(null);
|
||||
};
|
||||
|
||||
const filteredData = streams.filter((row) =>
|
||||
columns.every(({ accessorKey }) =>
|
||||
filterValues[accessorKey]
|
||||
? row[accessorKey]?.toLowerCase().includes(filterValues[accessorKey])
|
||||
: true
|
||||
)
|
||||
);
|
||||
|
||||
const table = useMaterialReactTable({
|
||||
...TableHelper.defaultProperties,
|
||||
columns,
|
||||
data: streams,
|
||||
data: filteredData,
|
||||
enablePagination: false,
|
||||
enableRowVirtualization: true,
|
||||
enableRowSelection: true,
|
||||
|
|
@ -140,33 +299,57 @@ const StreamsTable = ({ selectedChannels }) => {
|
|||
rowVirtualizerInstanceRef,
|
||||
rowVirtualizerOptions: { overscan: 5 },
|
||||
enableRowActions: true,
|
||||
positionActionsColumn: 'first',
|
||||
renderRowActions: ({ row }) => (
|
||||
<>
|
||||
<Tooltip title="Add to Channel">
|
||||
<IconButton
|
||||
size="small"
|
||||
color="info"
|
||||
onClick={() => addStreamToChannel(row.original.id)}
|
||||
sx={{ py: 0, px: 0.5 }}
|
||||
disabled={
|
||||
channelsPageSelection.length != 1 ||
|
||||
channelsPageSelection[0]?.stream_ids.includes(row.original.id)
|
||||
}
|
||||
>
|
||||
<PlaylistAddIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="Create New Channel">
|
||||
<IconButton
|
||||
size="small"
|
||||
color="success"
|
||||
onClick={() => createChannelFromStream(row.original)}
|
||||
sx={{ py: 0, px: 0.5 }}
|
||||
>
|
||||
<AddIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
<IconButton
|
||||
onClick={(event) => handleMoreActionsClick(event, row.original.id)}
|
||||
size="small"
|
||||
color="warning"
|
||||
onClick={() => editStream(row.original)}
|
||||
disabled={row.original.m3u_account ? true : false}
|
||||
sx={{ p: 0 }}
|
||||
sx={{ py: 0, px: 0.5 }}
|
||||
>
|
||||
<EditIcon fontSize="small" />
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
size="small"
|
||||
color="error"
|
||||
onClick={() => deleteStream(row.original.id)}
|
||||
sx={{ p: 0 }}
|
||||
<Menu
|
||||
anchorEl={moreActionsAnchorEl}
|
||||
open={isMoreActionsOpen && actionsOpenRow == row.original.id}
|
||||
onClose={handleMoreActionsClose}
|
||||
>
|
||||
<DeleteIcon fontSize="small" />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
size="small"
|
||||
color="success"
|
||||
onClick={() => createChannelFromStream(row.original)}
|
||||
sx={{ p: 0 }}
|
||||
>
|
||||
<AddIcon fontSize="small" />
|
||||
</IconButton>
|
||||
<MenuItem
|
||||
onClick={() => editStream(row.original.id)}
|
||||
disabled={row.original.m3u_account ? true : false}
|
||||
>
|
||||
Edit
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => deleteStream(row.original.id)}>
|
||||
Delete Stream
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</>
|
||||
),
|
||||
muiTableContainerProps: {
|
||||
|
|
@ -175,6 +358,14 @@ const StreamsTable = ({ selectedChannels }) => {
|
|||
overflowY: 'auto',
|
||||
},
|
||||
},
|
||||
displayColumnDefOptions: {
|
||||
'mrt-row-actions': {
|
||||
size: 68,
|
||||
},
|
||||
'mrt-row-select': {
|
||||
size: 50,
|
||||
},
|
||||
},
|
||||
renderTopToolbarCustomActions: ({ table }) => {
|
||||
const selectedRowCount = table.getSelectedRowModel().rows.length;
|
||||
|
||||
|
|
@ -216,7 +407,9 @@ const StreamsTable = ({ selectedChannels }) => {
|
|||
onClick={addStreamsToChannel}
|
||||
size="small"
|
||||
sx={{ marginLeft: 1 }}
|
||||
disabled={selectedChannels.length != 1 || selectedRowCount == 0}
|
||||
disabled={
|
||||
channelsPageSelection.length != 1 || selectedRowCount == 0
|
||||
}
|
||||
>
|
||||
Add to Channel
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -5,9 +5,12 @@ export default {
|
|||
enableDensityToggle: false,
|
||||
enableFullScreenToggle: false,
|
||||
positionToolbarAlertBanner: 'none',
|
||||
columnFilterDisplayMode: 'popover',
|
||||
// columnFilterDisplayMode: 'popover',
|
||||
enableRowNumbers: false,
|
||||
positionActionsColumn: 'last',
|
||||
enableColumnActions: false,
|
||||
enableColumnFilters: false,
|
||||
enableGlobalFilter: false,
|
||||
initialState: {
|
||||
density: 'compact',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ import StreamsTable from '../components/tables/StreamsTable';
|
|||
import { Grid2, Box } from '@mui/material';
|
||||
|
||||
const ChannelsPage = () => {
|
||||
const [selectedChannels, setSelectedChannels] = useState([]);
|
||||
|
||||
return (
|
||||
<Grid2 container>
|
||||
<Grid2 size={6}>
|
||||
|
|
@ -20,7 +18,7 @@ const ChannelsPage = () => {
|
|||
overflow: 'hidden', // Prevent parent scrolling
|
||||
}}
|
||||
>
|
||||
<ChannelsTable setSelectedChannels={setSelectedChannels} />
|
||||
<ChannelsTable />
|
||||
</Box>
|
||||
</Grid2>
|
||||
<Grid2 size={6}>
|
||||
|
|
@ -35,7 +33,7 @@ const ChannelsPage = () => {
|
|||
overflow: 'hidden', // Prevent parent scrolling
|
||||
}}
|
||||
>
|
||||
<StreamsTable selectedChannels={selectedChannels} />
|
||||
<StreamsTable />
|
||||
</Box>
|
||||
</Grid2>
|
||||
</Grid2>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import dayjs from 'dayjs';
|
|||
import API from '../api';
|
||||
import useChannelsStore from '../store/channels';
|
||||
import logo from '../images/logo.png';
|
||||
import useVideoStore from '../store/video'; // NEW import
|
||||
import useVideoStore from '../store/useVideoStore'; // NEW import
|
||||
import useAlertStore from '../store/alerts';
|
||||
import useSettingsStore from '../store/settings';
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import api from '../api';
|
|||
const useChannelsStore = create((set) => ({
|
||||
channels: [],
|
||||
channelGroups: [],
|
||||
channelsPageSelection: [],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
|
||||
|
|
@ -80,6 +81,9 @@ const useChannelsStore = create((set) => ({
|
|||
group.id === channelGroup.id ? channelGroup : group
|
||||
),
|
||||
})),
|
||||
|
||||
setChannelsPageSelection: (channelsPageSelection) =>
|
||||
set((state) => ({ channelsPageSelection })),
|
||||
}));
|
||||
|
||||
export default useChannelsStore;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue