diff --git a/apps/m3u/signals.py b/apps/m3u/signals.py index c2abc4c8..c07c1c4c 100644 --- a/apps/m3u/signals.py +++ b/apps/m3u/signals.py @@ -12,4 +12,4 @@ def refresh_account_on_save(sender, instance, created, **kwargs): if it is active or newly created. """ if created: - refresh_m3u_groups(instance.id) + refresh_single_m3u_account.delay(instance.id) diff --git a/apps/m3u/tasks.py b/apps/m3u/tasks.py index e0995543..82f2310b 100644 --- a/apps/m3u/tasks.py +++ b/apps/m3u/tasks.py @@ -8,6 +8,7 @@ from celery.result import AsyncResult from celery import shared_task, current_app, group from django.conf import settings from django.core.cache import cache +from django.db import transaction from .models import M3UAccount from apps.channels.models import Stream, ChannelGroup, ChannelGroupM3UAccount from asgiref.sync import async_to_sync @@ -179,7 +180,7 @@ def process_m3u_batch(account_id, batch, group_names, hash_keys): account = M3UAccount.objects.get(id=account_id) existing_groups = {group.name: group for group in ChannelGroup.objects.filter( m3u_account__m3u_account=account, # Filter by the M3UAccount - m3u_account__enabled=True # Filter by the enabled flag in the join table + m3u_account__enabled=True # Filter by the enabled flag in the join table )} streams_to_create = [] @@ -269,9 +270,29 @@ def process_m3u_batch(account_id, batch, group_names, hash_keys): return f"Batch processed: {len(streams_to_create)} created, {len(streams_to_update)} updated." +def cleanup_streams(account_id): + account = M3UAccount.objects.get(id=account_id, is_active=True) + existing_groups = ChannelGroup.objects.filter( + m3u_account__m3u_account=account, + m3u_account__enabled=True, + ).values_list('id', flat=True) + logger.info(f"Found {len(existing_groups)} active groups") + streams = Stream.objects.filter(m3u_account=account) + + streams_to_delete = Stream.objects.filter( + m3u_account=account + ).exclude( + channel_group__in=existing_groups # Exclude products having any of the excluded tags + ) + + # Delete the filtered products + streams_to_delete.delete() + + logger.info(f"Cleanup complete") + def refresh_m3u_groups(account_id): if not acquire_lock('refresh_m3u_account_groups', account_id): - return f"Task already running for account_id={account_id}." + return f"Task already running for account_id={account_id}.", None # Record start time start_time = time.time() @@ -345,7 +366,10 @@ def refresh_single_m3u_account(account_id, use_cache=False): groups = data['groups'] if not extinf_data: - extinf_data, groups = refresh_m3u_groups(account_id) + try: + extinf_data, groups = refresh_m3u_groups(account_id) + except: + return "Failed to update m3u account" hash_keys = CoreSettings.get_m3u_hash_key().split(",") @@ -371,6 +395,10 @@ def refresh_single_m3u_account(account_id, use_cache=False): progress = int((completed_batches / total_batches) * 100) # Send progress update via Channels + # Don't send 100% because we want to clean up after + if progress == 100: + progress = 99 + send_progress_update(progress, account_id) # Optionally remove completed task from the group to prevent processing it again @@ -378,6 +406,10 @@ def refresh_single_m3u_account(account_id, use_cache=False): else: logger.debug(f"Task is still running.") + # Run cleanup + cleanup_streams(account_id) + send_progress_update(100, account_id) + end_time = time.time() # Calculate elapsed time diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d4ccfe78..1b91bb9b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,10 +8,11 @@ "name": "vite", "version": "0.0.0", "dependencies": { - "@mantine/core": "^7.17.0", - "@mantine/dates": "^7.17.0", - "@mantine/hooks": "^7.17.0", - "@mantine/notifications": "^7.17.1", + "@mantine/core": "^7.17.2", + "@mantine/dates": "^7.17.2", + "@mantine/dropzone": "^7.17.2", + "@mantine/hooks": "^7.17.2", + "@mantine/notifications": "^7.17.2", "@tabler/icons-react": "^3.31.0", "allotment": "^1.20.3", "axios": "^1.8.2", @@ -1082,9 +1083,9 @@ "license": "Apache-2.0" }, "node_modules/@mantine/core": { - "version": "7.17.1", - "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.17.1.tgz", - "integrity": "sha512-V8O3Ftq4la4I4wNDkTfH4Slkt/pCEU32pTE/DkO46zua0VFxfOAJeLjaol0s11//T+bXx82DtjMsd9APWPuFhA==", + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.17.2.tgz", + "integrity": "sha512-R6MYhitJ0JEgrhadd31Nw9FhRaQwDHjXUs5YIlitKH/fTOz9gKSxKjzmNng3bEBQCcbEDOkZj3FRcBgTUh/F0Q==", "license": "MIT", "dependencies": { "@floating-ui/react": "^0.26.28", @@ -1095,56 +1096,71 @@ "type-fest": "^4.27.0" }, "peerDependencies": { - "@mantine/hooks": "7.17.1", + "@mantine/hooks": "7.17.2", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" } }, "node_modules/@mantine/dates": { - "version": "7.17.1", - "resolved": "https://registry.npmjs.org/@mantine/dates/-/dates-7.17.1.tgz", - "integrity": "sha512-L9MlIDb528RpznUeeW71xS4q3lYGolElz/f7xGRXEu9gHLaNJufbxroTw2N8RC6p/+RN1ZrSXEsjlr2euiofAw==", + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@mantine/dates/-/dates-7.17.2.tgz", + "integrity": "sha512-7bB992j8f+uEi280jab0/8i5yfsN/3oSrMDFwatZ+7XSDUwiP0YFib/FVX0pNSSqdFpbXhUmsZEECX71QtHw+Q==", "license": "MIT", "dependencies": { "clsx": "^2.1.1" }, "peerDependencies": { - "@mantine/core": "7.17.1", - "@mantine/hooks": "7.17.1", + "@mantine/core": "7.17.2", + "@mantine/hooks": "7.17.2", "dayjs": ">=1.0.0", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" } }, + "node_modules/@mantine/dropzone": { + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@mantine/dropzone/-/dropzone-7.17.2.tgz", + "integrity": "sha512-NMQ1SDmnW0sf3GO6p1r/VIcg/xWqlRmfnWCr00/bGRbBEGbyaUwL3LSn+KYBJdY+3/jNGvGa+xflWDvnby5tzw==", + "license": "MIT", + "dependencies": { + "react-dropzone-esm": "15.2.0" + }, + "peerDependencies": { + "@mantine/core": "7.17.2", + "@mantine/hooks": "7.17.2", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, "node_modules/@mantine/hooks": { - "version": "7.17.1", - "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.17.1.tgz", - "integrity": "sha512-mkHLrXMPd5xdI5WD7UOLwNEpdh/i6A7HaRDTXvjDE2/S0N8VmAE+BlvdyvWRMi7ODp2zVqJdP8cF1tgUn+Z0fA==", + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.17.2.tgz", + "integrity": "sha512-tbErVcGZu0E4dSmE6N0k6Tv1y9R3SQmmQgwqorcc+guEgKMdamc36lucZGlJnSGUmGj+WLUgELkEQ0asdfYBDA==", "license": "MIT", "peerDependencies": { "react": "^18.x || ^19.x" } }, "node_modules/@mantine/notifications": { - "version": "7.17.1", - "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-7.17.1.tgz", - "integrity": "sha512-jsCNkkjgtsGYIMbCrzBY0UBckoXyeaSWbEoJdvMlfA+LaeOQrSLxa+ot+1+wPaoZxR+1Q1xOwC1X5bTxHKudBA==", + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-7.17.2.tgz", + "integrity": "sha512-vg0L8cmihz0ODg4WJ9MAyK06WPt/6g67ksIUFxd4F8RfdJbIMLTsNG9yWoSfuhtXenUg717KaA917IWLjDSaqw==", "license": "MIT", "dependencies": { - "@mantine/store": "7.17.1", + "@mantine/store": "7.17.2", "react-transition-group": "4.4.5" }, "peerDependencies": { - "@mantine/core": "7.17.1", - "@mantine/hooks": "7.17.1", + "@mantine/core": "7.17.2", + "@mantine/hooks": "7.17.2", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" } }, "node_modules/@mantine/store": { - "version": "7.17.1", - "resolved": "https://registry.npmjs.org/@mantine/store/-/store-7.17.1.tgz", - "integrity": "sha512-is1c0FycakMsbTElKGWO59LarjMIk24JUXfjP/QIrB0XqpXreq3u7aN4hoNqr1IftTZSfVBii4W8pVFeWaV55g==", + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@mantine/store/-/store-7.17.2.tgz", + "integrity": "sha512-UoMUYQK/z58hMueCkpDIXc49gPgrVO/zcpb0k+B7MFU51EIUiFzHLxLFBmWrgCAM6rzJORqN8JjyCd/PB9j4aw==", "license": "MIT", "peerDependencies": { "react": "^18.x || ^19.x" @@ -3603,6 +3619,21 @@ "node": ">=6" } }, + "node_modules/react-dropzone-esm": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/react-dropzone-esm/-/react-dropzone-esm-15.2.0.tgz", + "integrity": "sha512-pPwR8xWVL+tFLnbAb8KVH5f6Vtl397tck8dINkZ1cPMxHWH+l9dFmIgRWgbh7V7jbjIcuKXCsVrXbhQz68+dVA==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "react": ">= 16.8 || 18.0.0" + } + }, "node_modules/react-fast-compare": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", diff --git a/frontend/package.json b/frontend/package.json index 26e99b33..de53fcc4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,10 +10,11 @@ "preview": "vite preview" }, "dependencies": { - "@mantine/core": "^7.17.0", - "@mantine/dates": "^7.17.0", - "@mantine/hooks": "^7.17.0", - "@mantine/notifications": "^7.17.1", + "@mantine/core": "^7.17.2", + "@mantine/dates": "^7.17.2", + "@mantine/dropzone": "^7.17.2", + "@mantine/hooks": "^7.17.2", + "@mantine/notifications": "^7.17.2", "@tabler/icons-react": "^3.31.0", "allotment": "^1.20.3", "axios": "^1.8.2", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index bfe43fc1..c769dcc6 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -22,6 +22,7 @@ import { Box, AppShell, MantineProvider } from '@mantine/core'; import '@mantine/core/styles.css'; // Ensure Mantine global styles load import '@mantine/notifications/styles.css'; import 'mantine-react-table/styles.css'; +import '@mantine/dropzone/styles.css'; import './index.css'; import mantineTheme from './mantineTheme'; import API from './api'; diff --git a/frontend/src/WebSocket.jsx b/frontend/src/WebSocket.jsx index 0538887d..bd9f0e5d 100644 --- a/frontend/src/WebSocket.jsx +++ b/frontend/src/WebSocket.jsx @@ -19,7 +19,7 @@ export const WebsocketProvider = ({ children }) => { const { fetchStreams } = useStreamsStore(); const { setChannelStats, fetchChannelGroups } = useChannelsStore(); - const { setRefreshProgress } = usePlaylistsStore(); + const { fetchPlaylists, setRefreshProgress } = usePlaylistsStore(); const { fetchEPGData } = useEPGsStore(); const ws = useRef(null); @@ -68,6 +68,7 @@ export const WebsocketProvider = ({ children }) => { fetchStreams(); fetchChannelGroups(); fetchEPGData(); + fetchPlaylists(); } setRefreshProgress(event.data.account, event.data.progress); } diff --git a/frontend/src/api.js b/frontend/src/api.js index 0590e2d4..369c49c2 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -197,13 +197,33 @@ export default class API { static async updateChannel(values) { const { id, ...payload } = values; + + let body = null; + if (values.logo_file) { + // Must send FormData for file upload + body = new FormData(); + for (const prop in values) { + body.append(prop, values[prop]); + } + } else { + body = { ...values }; + delete body.logo_file; + body = JSON.stringify(body); + } + + console.log(body); + const response = await fetch(`${host}/api/channels/channels/${id}/`, { method: 'PUT', headers: { Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', + ...(values.logo_file + ? {} + : { + 'Content-Type': 'application/json', + }), }, - body: JSON.stringify(payload), + body: body, }); const retval = await response.json(); diff --git a/frontend/src/components/FloatingVideo.jsx b/frontend/src/components/FloatingVideo.jsx index 62e53e3e..451df5cd 100644 --- a/frontend/src/components/FloatingVideo.jsx +++ b/frontend/src/components/FloatingVideo.jsx @@ -3,8 +3,7 @@ import React, { useEffect, useRef } from 'react'; import Draggable from 'react-draggable'; import useVideoStore from '../store/useVideoStore'; import mpegts from 'mpegts.js'; -import { ActionIcon, Flex } from '@mantine/core'; -import { SquareX } from 'lucide-react'; +import { CloseButton, Flex } from '@mantine/core'; export default function FloatingVideo() { const { isVisible, streamUrl, hideVideo } = useVideoStore(); @@ -68,9 +67,7 @@ export default function FloatingVideo() { > {/* Simple header row with a close button */} - - - + {/* The element used by mpegts.js */} diff --git a/frontend/src/components/forms/Channel.jsx b/frontend/src/components/forms/Channel.jsx index 3f778032..a2cfeb75 100644 --- a/frontend/src/components/forms/Channel.jsx +++ b/frontend/src/components/forms/Channel.jsx @@ -22,11 +22,17 @@ import { Grid, Flex, Select, + Divider, + Stack, + useMantineTheme, } from '@mantine/core'; -import { SquarePlus } from 'lucide-react'; +import { ListOrdered, SquarePlus, SquareX } from 'lucide-react'; import useEPGsStore from '../../store/epgs'; +import { Dropzone } from '@mantine/dropzone'; const Channel = ({ channel = null, isOpen, onClose }) => { + const theme = useMantineTheme(); + const channelGroups = useChannelsStore((state) => state.channelGroups); const streams = useStreamsStore((state) => state.streams); const { profiles: streamProfiles } = useStreamProfilesStore(); @@ -34,7 +40,7 @@ const Channel = ({ channel = null, isOpen, onClose }) => { const { tvgs } = useEPGsStore(); const [logoFile, setLogoFile] = useState(null); - const [logoPreview, setLogoPreview] = useState(logo); + const [logoPreview, setLogoPreview] = useState(null); const [channelStreams, setChannelStreams] = useState([]); const [channelGroupModelOpen, setChannelGroupModalOpen] = useState(false); @@ -50,11 +56,14 @@ const Channel = ({ channel = null, isOpen, onClose }) => { setChannelStreams(Array.from(streamSet)); }; - const handleLogoChange = (e) => { - const file = e.target.files[0]; - if (file) { - setLogoFile(file); - setLogoPreview(URL.createObjectURL(file)); + const handleLogoChange = (files) => { + if (files.length === 1) { + console.log(files[0]); + setLogoFile(files[0]); + setLogoPreview(URL.createObjectURL(files[0])); + } else { + setLogoFile(null); + setLogoPreview(null); } }; @@ -77,6 +86,10 @@ const Channel = ({ channel = null, isOpen, onClose }) => { values.stream_profile_id = null; } + if (values.stream_profile_id == null) { + delete values.stream_profile_id; + } + if (channel?.id) { await API.updateChannel({ id: channel.id, @@ -94,7 +107,7 @@ const Channel = ({ channel = null, isOpen, onClose }) => { resetForm(); setLogoFile(null); - setLogoPreview(logo); + setLogoPreview(null); setSubmitting(false); onClose(); }, @@ -250,14 +263,23 @@ const Channel = ({ channel = null, isOpen, onClose }) => { return <>>; } - console.log(streamProfiles); - return ( <> - + + + Channels + + } + styles={{ content: { '--mantine-color-body': '#27272A' } }} + > - - + + { value={formik.values.name} onChange={formik.handleChange} error={formik.errors.name ? formik.touched.name : ''} + size="xs" /> - - - ({ - value: `${option.id}`, - label: option.name, - }))} - /> - - + + { + formik.setFieldValue('channel_group_id', value); // Update Formik's state with the new value + }} + error={ + formik.errors.channel_group_id + ? formik.touched.channel_group_id + : '' + } + data={Object.values(channelGroups).map((option, index) => ({ + value: `${option.id}`, + label: option.name, + }))} + size="xs" + style={{ flex: 1 }} + /> + setChannelGroupModalOpen(true)} title="Create new group" size="small" - variant="light" - style={{ marginTop: '175%' }} // @TODO: I don't like this, figure out better placement + variant="transparent" + style={{ marginBottom: 5 }} > - + - - + + { label: option.name, })) )} + size="xs" /> { ? formik.touched.channel_number : '' } + size="xs" /> - + - + + + + + + + + + OR + + + + + + + Upload Logo + {logoPreview && ( + + + + )} + + console.log('rejected files', files)} + maxSize={5 * 1024 ** 2} + > + + + {logoPreview && ( + + + + )} + {!logoPreview && ( + + Drag images here or click to select files + + )} + + + + + + + + + + + { value={formik.values.tvg_name} onChange={formik.handleChange} error={formik.errors.tvg_name ? formik.touched.tvg_name : ''} + size="xs" /> { value: tvg.name, label: tvg.tvg_id, }))} + size="xs" /> { style={{ marginBottom: 2 }} value={formik.values.logo_url} onChange={formik.handleChange} + size="xs" /> - - - Logo - {/* Display selected image */} - - - - handleLogoChange(event)} - style={{ display: 'none' }} - /> - - - Browse... - - - - - + + {/* @@ -412,8 +487,7 @@ const Channel = ({ channel = null, isOpen, onClose }) => { Submit diff --git a/frontend/src/components/forms/M3U.jsx b/frontend/src/components/forms/M3U.jsx index b05f011e..b717aa3b 100644 --- a/frontend/src/components/forms/M3U.jsx +++ b/frontend/src/components/forms/M3U.jsx @@ -14,7 +14,6 @@ import { Flex, NativeSelect, FileInput, - Select, Space, } from '@mantine/core'; import M3UGroupFilter from './M3UGroupFilter'; @@ -30,6 +29,7 @@ const M3U = ({ playlist = null, isOpen, onClose, playlistCreated = false }) => { const [loadingText, setLoadingText] = useState(''); const handleFileChange = (file) => { + console.log(file); if (file) { setFile(file); } @@ -57,13 +57,16 @@ const M3U = ({ playlist = null, isOpen, onClose, playlistCreated = false }) => { uploaded_file: file, }); } else { - setLoadingText('Loading groups...'); newPlaylist = await API.addPlaylist({ ...values, uploaded_file: file, }); await fetchChannelGroups(); + + // Don't prompt for group filters, but keeping this here + // in case we want to revive it + newPlaylist = null; } resetForm(); @@ -204,12 +207,23 @@ const M3U = ({ playlist = null, isOpen, onClose, playlistCreated = false }) => { > )} + {!playlist && ( + + Save & Select Groups + + )} Save diff --git a/frontend/src/components/sidebar.css b/frontend/src/components/sidebar.css index fa3756be..f24edd4a 100644 --- a/frontend/src/components/sidebar.css +++ b/frontend/src/components/sidebar.css @@ -1,41 +1,41 @@ .mantine-Stack-root .navlink { - display: flex; - flex-direction: row; /* Ensures horizontal layout */ - flex-wrap: nowrap; - align-items: center; - gap: 12px; - padding: 5px 8px !important; - border-radius: 6px; - color: #D4D4D8; /* Default color when not active */ - background-color: transparent; /* Default background when not active */ - border: 1px solid transparent; - transition: all 0.3s ease; - } + display: flex; + flex-direction: row; /* Ensures horizontal layout */ + flex-wrap: nowrap; + align-items: center; + gap: 12px; + padding: 5px 8px !important; + border-radius: 6px; + color: #D4D4D8; /* Default color when not active */ + background-color: transparent; /* Default background when not active */ + border: 1px solid transparent; + transition: all 0.3s ease; +} - /* Active state styles */ - .navlink.navlink-active { - color: #FFFFFF; - background-color: #245043; - border: 1px solid #3BA882; - } +/* Active state styles */ +.navlink.navlink-active { + color: #FFFFFF; + background-color: #245043; + border: 1px solid #3BA882; +} - /* Hover effect */ - .navlink:hover { - background-color: #2A2F34; /* Gray hover effect when not active */ - border: 1px solid #3D3D42; - } +/* Hover effect */ +.navlink:hover { + background-color: #2A2F34; /* Gray hover effect when not active */ + border: 1px solid #3D3D42; +} - /* Hover effect for active state */ - .navlink.navlink-active:hover { - background-color: #3A3A40; - border: 1px solid #3BA882; - } +/* Hover effect for active state */ +.navlink.navlink-active:hover { + background-color: #3A3A40; + border: 1px solid #3BA882; +} - /* Collapse condition for justifyContent */ - .navlink.navlink-collapsed { - justify-content: center; - } +/* Collapse condition for justifyContent */ +.navlink.navlink-collapsed { + justify-content: center; +} - .navlink:not(.navlink-collapsed) { - justify-content: flex-start; - } +.navlink:not(.navlink-collapsed) { + justify-content: flex-start; +} diff --git a/frontend/src/components/tables/ChannelsTable.jsx b/frontend/src/components/tables/ChannelsTable.jsx index 62ca5341..b5377fa0 100644 --- a/frontend/src/components/tables/ChannelsTable.jsx +++ b/frontend/src/components/tables/ChannelsTable.jsx @@ -245,6 +245,8 @@ const ChannelsTable = ({}) => { handleFilterChange(column.id, e.target.value); }} size="xs" + variant="unstyled" + className="table-input-header" /> ), Cell: ({ cell }) => ( @@ -273,6 +275,8 @@ const ChannelsTable = ({}) => { handleGroupChange(value); }} data={channelGroupOptions} + variant="unstyled" + className="table-input-header" /> ), }, @@ -490,6 +494,12 @@ const ChannelsTable = ({}) => { 'mrt-row-expand': { size: 10, header: '', + mantineTableHeadCellProps: { + padding: 0, + }, + mantineTableBodyCellProps: { + padding: 0, + }, }, 'mrt-row-actions': { size: 74, @@ -516,7 +526,7 @@ const ChannelsTable = ({}) => { { editChannel(row.original); }} @@ -529,7 +539,7 @@ const ChannelsTable = ({}) => { deleteChannel(row.original.id)} > @@ -540,7 +550,7 @@ const ChannelsTable = ({}) => { handleWatchStream(row.original.uuid)} > @@ -701,6 +711,7 @@ const ChannelsTable = ({}) => { {/* Top toolbar with Remove, Assign, Auto-match, and Add buttons */} @@ -751,10 +762,10 @@ const ChannelsTable = ({}) => { size="xs" onClick={() => editChannel()} p={5} - color="green" + color={theme.tailwind.green[5]} style={{ borderWidth: '1px', - borderColor: 'green', + borderColor: theme.tailwind.green[5], color: 'white', }} > diff --git a/frontend/src/components/tables/StreamsTable.jsx b/frontend/src/components/tables/StreamsTable.jsx index 17ca73a3..2f28f573 100644 --- a/frontend/src/components/tables/StreamsTable.jsx +++ b/frontend/src/components/tables/StreamsTable.jsx @@ -33,6 +33,7 @@ import { NumberInput, NativeSelect, MultiSelect, + useMantineTheme, } from '@mantine/core'; import { IconArrowDown, @@ -45,6 +46,8 @@ import { import { useNavigate } from 'react-router-dom'; const StreamsTable = ({}) => { + const theme = useMantineTheme(); + /** * useState */ @@ -118,6 +121,11 @@ const StreamsTable = ({}) => { onClick={(e) => e.stopPropagation()} onChange={handleFilterChange} size="xs" + variant="unstyled" + className="table-input-header" + style={{ + paddingLeft: 10, + }} /> ), Cell: ({ cell }) => ( @@ -146,6 +154,8 @@ const StreamsTable = ({}) => { onClick={handleSelectClick} onChange={handleGroupChange} data={groupOptions} + variant="unstyled" + className="table-input-header" /> ), @@ -179,6 +189,8 @@ const StreamsTable = ({}) => { label: playlist.name, value: `${playlist.id}`, }))} + variant="unstyled" + className="table-input-header" /> ), @@ -498,7 +510,7 @@ const StreamsTable = ({}) => { addStreamToChannel(row.original.id)} disabled={ @@ -516,7 +528,7 @@ const StreamsTable = ({}) => { createChannelFromStream(row.original)} > @@ -563,6 +575,7 @@ const StreamsTable = ({}) => { displayColumnDefOptions: { 'mrt-row-actions': { size: 30, + color: 'white', }, 'mrt-row-select': { size: 20, @@ -618,6 +631,7 @@ const StreamsTable = ({}) => { {/* Top toolbar with Remove, Assign, Auto-match, and Add buttons */} @@ -655,10 +669,10 @@ const StreamsTable = ({}) => { size="xs" onClick={() => editStream()} p={5} - color="green" + color={theme.tailwind.green[5]} style={{ borderWidth: '1px', - borderColor: 'green', + borderColor: theme.tailwind.green[5], color: 'white', }} > diff --git a/frontend/src/helpers/table.jsx b/frontend/src/helpers/table.jsx index 1db0061c..208a4100 100644 --- a/frontend/src/helpers/table.jsx +++ b/frontend/src/helpers/table.jsx @@ -17,6 +17,7 @@ export default { mantinePaperProps: { style: { '--mrt-selected-row-background-color': '#163632', + '--mrt-base-background-color': '#27272A', }, }, mantineSelectAllCheckboxProps: { diff --git a/frontend/src/index.css b/frontend/src/index.css index f37ed233..25abf33b 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -39,3 +39,9 @@ code { table.mrt-table tr.mantine-Table-tr.mantine-Table-tr-detail-panel td.mantine-Table-td-detail-panel { width: 100% !important; } + +.table-input-header input::placeholder { + color: rgb(207,207,207); + font-weight: normal; + font-size: 14px; +} diff --git a/frontend/src/mantineTheme.jsx b/frontend/src/mantineTheme.jsx index e89455d8..280320c8 100644 --- a/frontend/src/mantineTheme.jsx +++ b/frontend/src/mantineTheme.jsx @@ -1,6 +1,165 @@ import { createTheme, MantineProvider, rem } from '@mantine/core'; const theme = createTheme({ + tailwind: { + red: [ + 'oklch(0.971 0.013 17.38)', + 'oklch(0.936 0.032 17.717)', + 'oklch(0.885 0.062 18.334)', + 'oklch(0.808 0.114 19.571)', + 'oklch(0.704 0.191 22.216)', + 'oklch(0.637 0.237 25.331)', + 'oklch(0.577 0.245 27.325)', + 'oklch(0.505 0.213 27.518)', + 'oklch(0.444 0.177 26.899)', + 'oklch(0.396 0.141 25.723)', + 'oklch(0.258 0.092 26.042)', + ], + orange: [ + 'oklch(0.98 0.016 73.684)', + 'oklch(0.954 0.038 75.164)', + 'oklch(0.901 0.076 70.697)', + 'oklch(0.837 0.128 66.29)', + 'oklch(0.75 0.183 55.934)', + 'oklch(0.705 0.213 47.604)', + 'oklch(0.646 0.222 41.116)', + 'oklch(0.553 0.195 38.402)', + 'oklch(0.47 0.157 37.304)', + 'oklch(0.408 0.123 38.172)', + 'oklch(0.266 0.079 36.259)', + ], + amber: [ + 'oklch(0.987 0.022 95.277)', + 'oklch(0.962 0.059 95.617)', + 'oklch(0.924 0.12 95.746)', + 'oklch(0.879 0.169 91.605)', + 'oklch(0.828 0.189 84.429)', + 'oklch(0.769 0.188 70.08)', + 'oklch(0.666 0.179 58.318)', + 'oklch(0.555 0.163 48.998)', + 'oklch(0.473 0.137 46.201)', + 'oklch(0.414 0.112 45.904)', + 'oklch(0.279 0.077 45.635)', + ], + yellow: [ + 'oklch(0.987 0.026 102.212)', + 'oklch(0.973 0.071 103.193)', + 'oklch(0.945 0.129 101.54)', + 'oklch(0.905 0.182 98.111)', + 'oklch(0.852 0.199 91.936)', + 'oklch(0.795 0.184 86.047)', + 'oklch(0.681 0.162 75.834)', + 'oklch(0.554 0.135 66.442)', + 'oklch(0.476 0.114 61.907)', + 'oklch(0.421 0.095 57.708)', + 'oklch(0.286 0.066 53.813)', + ], + lime: [ + 'oklch(0.986 0.031 120.757)', + 'oklch(0.967 0.067 122.328)', + 'oklch(0.938 0.127 124.321)', + 'oklch(0.897 0.196 126.665)', + 'oklch(0.841 0.238 128.85)', + 'oklch(0.768 0.233 130.85)', + 'oklch(0.648 0.2 131.684)', + 'oklch(0.532 0.157 131.589)', + 'oklch(0.453 0.124 130.933)', + 'oklch(0.405 0.101 131.063)', + 'oklch(0.274 0.072 132.109)', + ], + green: [ + 'oklch(0.982 0.018 155.826)', + 'oklch(0.962 0.044 156.743)', + 'oklch(0.925 0.084 155.995)', + 'oklch(0.871 0.15 154.449)', + 'oklch(0.792 0.209 151.711)', + 'oklch(0.723 0.219 149.579)', + 'oklch(0.627 0.194 149.214)', + 'oklch(0.527 0.154 150.069)', + 'oklch(0.448 0.119 151.328)', + 'oklch(0.393 0.095 152.535)', + 'oklch(0.266 0.065 152.934)', + ], + emerald: [ + 'oklch(0.979 0.021 166.113)', + 'oklch(0.95 0.052 163.051)', + 'oklch(0.905 0.093 164.15)', + 'oklch(0.845 0.143 164.978)', + 'oklch(0.765 0.177 163.223)', + 'oklch(0.696 0.17 162.48)', + 'oklch(0.596 0.145 163.225)', + 'oklch(0.508 0.118 165.612)', + 'oklch(0.432 0.095 166.913)', + 'oklch(0.378 0.077 168.94)', + 'oklch(0.262 0.051 172.552)', + ], + teal: [ + 'oklch(0.984 0.014 180.72)', + 'oklch(0.953 0.051 180.801)', + 'oklch(0.91 0.096 180.426)', + 'oklch(0.855 0.138 181.071)', + 'oklch(0.777 0.152 181.912)', + 'oklch(0.704 0.14 182.503)', + 'oklch(0.6 0.118 184.704)', + 'oklch(0.511 0.096 186.391)', + 'oklch(0.437 0.078 188.216)', + 'oklch(0.386 0.063 188.416)', + 'oklch(0.277 0.046 192.524)', + ], + cyan: [ + 'oklch(0.984 0.019 200.873)', + 'oklch(0.956 0.045 203.388)', + 'oklch(0.917 0.08 205.041)', + 'oklch(0.865 0.127 207.078)', + 'oklch(0.789 0.154 211.53)', + 'oklch(0.715 0.143 215.221)', + 'oklch(0.609 0.126 221.723)', + 'oklch(0.52 0.105 223.128)', + 'oklch(0.45 0.085 224.283)', + 'oklch(0.398 0.07 227.392)', + 'oklch(0.302 0.056 229.695)', + ], + sky: [ + 'oklch(0.977 0.013 236.62)', + 'oklch(0.951 0.026 236.824)', + 'oklch(0.901 0.058 230.902)', + 'oklch(0.828 0.111 230.318)', + 'oklch(0.746 0.16 232.661)', + 'oklch(0.685 0.169 237.323)', + 'oklch(0.588 0.158 241.966)', + 'oklch(0.5 0.134 242.749)', + 'oklch(0.443 0.11 240.79)', + 'oklch(0.391 0.09 240.876)', + 'oklch(0.293 0.066 243.157)', + ], + blue: [ + 'oklch(0.97 0.014 254.604)', + 'oklch(0.932 0.032 255.585)', + 'oklch(0.882 0.059 254.128)', + 'oklch(0.809 0.105 251.813)', + 'oklch(0.707 0.165 254.624)', + 'oklch(0.623 0.214 259.815)', + 'oklch(0.546 0.214 264.39)', + 'oklch(0.466 0.188 267.053)', + 'oklch(0.396 0.156 269.132)', + 'oklch(0.342 0.125 270.105)', + 'oklch(0.246 0.092 272.445)', + ], + indigo: [ + 'oklch(0.938 0.015 268.693)', + 'oklch(0.901 0.036 269.522)', + 'oklch(0.843 0.072 267.564)', + 'oklch(0.761 0.12 264.179)', + 'oklch(0.652 0.159 267.585)', + 'oklch(0.573 0.194 271.68)', + 'oklch(0.492 0.205 275.687)', + 'oklch(0.418 0.179 278.963)', + 'oklch(0.357 0.145 282.163)', + 'oklch(0.305 0.112 283.928)', + 'oklch(0.213 0.078 287.434)', + ], + }, + palette: { mode: 'dark', background: {