Reverted styling/small syntax changes

This commit is contained in:
Nick Sandstrom 2026-03-04 08:44:50 -08:00
parent 8d7752c004
commit a54bc2c513
7 changed files with 163 additions and 141 deletions

View file

@ -1,9 +1,9 @@
// frontend/src/components/FloatingVideo.js
import React, {useCallback, useEffect, useRef, useState} from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import Draggable from 'react-draggable';
import useVideoStore from '../store/useVideoStore';
import mpegts from 'mpegts.js';
import {Box, CloseButton, Flex, Loader, Text} from '@mantine/core';
import { CloseButton, Flex, Loader, Text, Box } from '@mantine/core';
import {
applyConstraints,
calculateNewDimensions,
@ -644,15 +644,20 @@ export default function FloatingVideo() {
}}
>
{/* Simple header row with a close button */}
<Flex justify="flex-end" p={3}>
<Flex
justify="flex-end"
style={{
padding: 3,
}}
>
<CloseButton
onClick={handleClose}
onTouchEnd={handleClose}
onMouseDown={(e) => e.stopPropagation()}
onTouchStart={(e) => e.stopPropagation()}
mih={32}
miw={32}
style={{
minHeight: '32px',
minWidth: '32px',
cursor: 'pointer',
touchAction: 'manipulation',
}}
@ -661,7 +666,7 @@ export default function FloatingVideo() {
{/* Video container with relative positioning for the overlay */}
<Box
pos="relative"
style={{ position: 'relative' }}
onMouseEnter={() => {
if (contentType === 'vod' && !isLoading) {
setShowOverlay(true);
@ -701,17 +706,17 @@ export default function FloatingVideo() {
{/* VOD title overlay when not loading - auto-hides after 4 seconds */}
{!isLoading && metadata && contentType === 'vod' && showOverlay && (
<Box
pos="absolute"
top={0}
left={0}
right={0}
p={'10px 10px 20px'}
color={'white'}
opacity={showOverlay ? 1 : 0}
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
background: 'linear-gradient(rgba(0,0,0,0.8), transparent)',
padding: '10px 10px 20px',
color: 'white',
pointerEvents: 'none', // Allow clicks to pass through to video controls
transition: 'opacity 0.3s ease-in-out',
opacity: showOverlay ? 1 : 0,
}}
>
<Text
@ -724,7 +729,7 @@ export default function FloatingVideo() {
{metadata.year && (
<Text
size="xs"
c="dimmed"
color="dimmed"
style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.8)' }}
>
{metadata.year}
@ -736,14 +741,14 @@ export default function FloatingVideo() {
{/* Loading overlay - only show when loading */}
{isLoading && (
<Box
pos="absolute"
top={0}
left={0}
w={'100%'}
h={'100%'}
display="flex"
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
backgroundColor: 'rgba(0, 0, 0, 0.7)',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
@ -751,7 +756,7 @@ export default function FloatingVideo() {
}}
>
<Loader color="cyan" size="md" />
<Text c="white" size="sm" mt={10}>
<Text color="white" size="sm" mt={10}>
Loading {contentType === 'vod' ? 'video' : 'stream'}...
</Text>
</Box>
@ -761,13 +766,13 @@ export default function FloatingVideo() {
{/* Error message below video - doesn't block controls */}
{!isLoading && loadError && (
<Box
p={10}
style={{
padding: '10px',
backgroundColor: '#2d1b2e',
borderTop: '1px solid #444',
}}
>
<Text c="red" size="xs" ta="center">
<Text color="red" size="xs" style={{ textAlign: 'center' }}>
{loadError}
</Text>
</Box>

View file

@ -57,12 +57,16 @@ export default function M3URefreshNotification() {
const handleM3UUpdate = (data) => {
// Skip if status hasn't changed
if (JSON.stringify(notificationStatus[data.account]) == JSON.stringify(data)) {
if (
JSON.stringify(notificationStatus[data.account]) == JSON.stringify(data)
) {
return;
}
const playlist = playlists.find((pl) => pl.id == data.account);
if (!playlist) return;
if (!playlist) {
return;
}
// Update notification status
setNotificationStatus(prev => ({
@ -82,7 +86,8 @@ export default function M3URefreshNotification() {
}
// Skip if already errored
if (notificationStatus[data.account]?.status === 'error') {
const currentStatus = notificationStatus[data.account];
if (currentStatus && currentStatus.status === 'error') {
return;
}

View file

@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useState } from 'react';
import React, { useEffect, useState, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import {
ActionIcon,
@ -20,19 +20,19 @@ import {
useMantineTheme,
} from '@mantine/core';
import {
AlertTriangle,
ArrowRight,
Bell,
Check,
CheckCheck,
Download,
ExternalLink,
Info,
Settings,
AlertTriangle,
Megaphone,
X,
Eye,
EyeOff,
Info,
Megaphone,
Settings,
X,
ArrowRight,
} from 'lucide-react';
import useNotificationsStore from '../store/notifications';
import {
@ -145,9 +145,7 @@ const NotificationItem = ({ notification, onDismiss, onAction, onClose }) => {
color="gray"
size="sm"
onClick={handleDismiss}
pos='absolute'
top={8}
right={8}
style={{ position: 'absolute', top: 8, right: 8 }}
>
<X size={14} />
</ActionIcon>
@ -157,7 +155,7 @@ const NotificationItem = ({ notification, onDismiss, onAction, onClose }) => {
<ThemeIcon color={typeColor} variant="light" size="md" radius="xl">
{getNotificationIcon(notification.notification_type)}
</ThemeIcon>
<Box flex={1}>
<Box style={{ flex: 1 }}>
<Group gap="xs" mb={4}>
<Text size="sm" fw={600} lineClamp={1}>
{notification.title}

View file

@ -1,31 +1,31 @@
import React, { useEffect, useState } from 'react';
import React, { useState, useEffect } from 'react';
import {
ActionIcon,
Badge,
Box,
Button,
Divider,
Flex,
Group,
Image,
Loader,
Modal,
Text,
Title,
Select,
Badge,
Loader,
Stack,
ActionIcon,
Modal,
Tabs,
Table,
Divider,
TableTbody,
TableTd,
TableTh,
TableThead,
TableTr,
Tabs,
TabsList,
TabsPanel,
TabsTab,
Text,
Title,
} from '@mantine/core';
import { Copy, Play } from 'lucide-react';
import { Play, Copy } from 'lucide-react';
import { copyToClipboard } from '../utils';
import useVODStore from '../store/useVODStore';
import useVideoStore from '../store/useVideoStore';
@ -486,7 +486,7 @@ const SeriesModal = ({ series, opened, onClose }) => {
size="xl"
centered
>
<Box pos="relative" mih={400}>
<Box style={{ position: 'relative', minHeight: 400 }}>
{/* Backdrop image as background */}
{displaySeries.backdrop_path &&
displaySeries.backdrop_path.length > 0 && (
@ -495,42 +495,42 @@ const SeriesModal = ({ series, opened, onClose }) => {
src={displaySeries.backdrop_path[0]}
alt={`${displaySeries.name} backdrop`}
fit="cover"
pos="absolute"
top={0}
left={0}
w={'100%'}
h={'100%'}
bdrs={8}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
objectFit: 'cover',
zIndex: 0,
borderRadius: 8,
filter: 'blur(2px) brightness(0.5)',
}}
/>
{/* Overlay for readability */}
<Box
pos="absolute"
top={0}
left={0}
w={'100%'}
h={'100%'}
bdrs={8}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
background:
'linear-gradient(180deg, rgba(24,24,27,0.85) 60%, rgba(24,24,27,1) 100%)',
zIndex: 1,
borderRadius: 8,
}}
/>
</>
)}
{/* Modal content above backdrop */}
<Box pos="relative" style={{ zIndex: 2 }}>
<Box style={{ position: 'relative', zIndex: 2 }}>
<Stack spacing="md">
{loadingDetails && (
<Group spacing="xs" mb={8}>
<Loader size="xs" />
<Text size="xs" c="dimmed">
<Text size="xs" color="dimmed">
Loading series details and episodes...
</Text>
</Group>
@ -546,7 +546,9 @@ const SeriesModal = ({ series, opened, onClose }) => {
<Box mt="md">
<Text size="sm" weight={500} mb={4}>
Stream Selection
{loadingProviders && <Loader size="xs" ml={8} />}
{loadingProviders && (
<Loader size="xs" style={{ marginLeft: 8 }} />
)}
</Text>
{providers.length === 0 &&
!loadingProviders &&
@ -576,7 +578,7 @@ const SeriesModal = ({ series, opened, onClose }) => {
value={selectedProvider?.id?.toString() || ''}
onChange={(value) => onChangeSelectedProvider(value)}
placeholder="Select stream..."
maw={350}
style={{ maxWidth: 350 }}
disabled={loadingProviders}
/>
) : null}
@ -608,11 +610,11 @@ const SeriesModal = ({ series, opened, onClose }) => {
<Table striped highlightOnHover>
<TableThead>
<TableTr>
<TableTh w={60}>Ep</TableTh>
<TableTh style={{ width: '60px' }}>Ep</TableTh>
<TableTh>Title</TableTh>
<TableTh w={80}>Duration</TableTh>
<TableTh w={60}>Date</TableTh>
<TableTh w={80}>Action</TableTh>
<TableTh style={{ width: '80px' }}>Duration</TableTh>
<TableTh style={{ width: '60px' }}>Date</TableTh>
<TableTh style={{ width: '80px' }}>Action</TableTh>
</TableTr>
</TableThead>
<TableTbody>
@ -633,19 +635,19 @@ const SeriesModal = ({ series, opened, onClose }) => {
{episode.name}
</Text>
{episode.genre && (
<Text size="xs" c="dimmed">
<Text size="xs" color="dimmed">
{episode.genre}
</Text>
)}
</Stack>
</TableTd>
<TableTd>
<Text size="xs" c="dimmed">
<Text size="xs" color="dimmed">
{formatDuration(episode.duration_secs)}
</Text>
</TableTd>
<TableTd>
<Text size="xs" c="dimmed">
<Text size="xs" color="dimmed">
{getEpisodeAirdate(episode)}
</Text>
</TableTd>
@ -704,7 +706,7 @@ const SeriesModal = ({ series, opened, onClose }) => {
))}
</Tabs>
) : (
<Text c="dimmed" align="center" py="xl">
<Text color="dimmed" align="center" py="xl">
No episodes found for this series.
</Text>
)}

View file

@ -66,7 +66,7 @@ const NavLink = ({ item, isActive, collapsed }) => {
</Text>
)}
{!collapsed && item.badge && (
<Text size="sm" c='#D4D4D8' style={{ whiteSpace: 'nowrap' }}>
<Text size="sm" style={{ color: '#D4D4D8', whiteSpace: 'nowrap' }}>
{item.badge}
</Text>
)}
@ -84,16 +84,17 @@ function NavGroup({ label, icon, paths, location, collapsed }) {
.includes(location.pathname);
return (
<Box w='100%' pr={2}
className={open ? 'navgroup-open' : ''}
<Box
style={{ width: '100%', paddingRight: 2 }}
>
<UnstyledButton w='100%'
<UnstyledButton
onClick={() => setOpen((o) => !o)}
className={`navlink ${parentActive ? 'navlink-parent-active' : ''} ${open ? 'navlink-collapsed' : ''}`}
style={{ width: '100%' }}
>
{icon}
{!collapsed && (
<Group justify="space-between" w='100%'>
<Group justify="space-between" style={{ width: '100%' }}>
<Text
sx={{
opacity: open ? 0 : 1,
@ -107,7 +108,7 @@ function NavGroup({ label, icon, paths, location, collapsed }) {
{label}
</Text>
<Box alignItems="center" display='flex'>
<Box alignItems="center" style={{ display: 'flex' }}>
{open ? <ChevronDown size={16} /> : <ChevronRight size={16} />}
</Box>
</Group>
@ -115,13 +116,13 @@ function NavGroup({ label, icon, paths, location, collapsed }) {
</UnstyledButton>
{open && (
<Box pt={10}>
<Box style={{ paddingTop: 10 }}>
<Stack gap="xs" pl={open ? 0 : 'lg'}>
{paths.map((child) => {
const active = location.pathname === child.path;
return (
<Box
pl={collapsed ? 0 : 35}
style={{ paddingLeft: collapsed ? 0 : 35 }}
key={child.path}
>
<NavLink
@ -247,11 +248,12 @@ const Sidebar = ({ collapsed, toggleDrawer, drawerWidth, miniDrawerWidth }) => {
<AppShellNavbar
width={{ base: collapsed ? miniDrawerWidth : drawerWidth }}
p="xs"
mih="100vh"
display='flex'
br='1px solid #2A2A2E'
style={{
backgroundColor: '#1A1A1E',
// transition: 'width 0.3s ease',
borderRight: '1px solid #2A2A2E',
minHeight: '100vh',
display: 'flex',
flexDirection: 'column',
}}
>
@ -259,15 +261,15 @@ const Sidebar = ({ collapsed, toggleDrawer, drawerWidth, miniDrawerWidth }) => {
<Group
onClick={toggleDrawer}
spacing="sm"
display="flex"
p={'16px 12px'}
fz={18}
fw={600}
color="#FFFFFF"
style={{
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: 12,
padding: '16px 12px',
fontSize: 18,
fontWeight: 600,
color: '#FFFFFF',
justifyContent: collapsed ? 'center' : 'flex-start',
whiteSpace: 'nowrap',
}}
@ -295,9 +297,9 @@ const Sidebar = ({ collapsed, toggleDrawer, drawerWidth, miniDrawerWidth }) => {
<Stack
gap="xs"
mt="lg"
flex={1}
mih={0}
style={{
flex: 1,
minHeight: 0,
overflowY: 'auto',
overflowX: 'hidden',
}}
@ -332,13 +334,13 @@ const Sidebar = ({ collapsed, toggleDrawer, drawerWidth, miniDrawerWidth }) => {
{/* Profile Section */}
<Box
mt='auto'
p={16}
display='flex'
bt='1px solid #2A2A2E'
style={{
marginTop: 'auto',
padding: '16px',
display: 'flex',
alignItems: 'center',
gap: 10,
borderTop: '1px solid #2A2A2E',
justifyContent: collapsed ? 'center' : 'flex-start',
}}
>
@ -375,9 +377,7 @@ const Sidebar = ({ collapsed, toggleDrawer, drawerWidth, miniDrawerWidth }) => {
{!collapsed && authUser && (
<Group
gap="xs"
w='100%'
style={{ justifyContent: 'space-between' }}
style={{ justifyContent: 'space-between', width: '100%' }}
>
<Group gap="xs">
<Avatar src="" radius="xl" />
@ -402,9 +402,7 @@ const Sidebar = ({ collapsed, toggleDrawer, drawerWidth, miniDrawerWidth }) => {
{/* Version and Notification */}
{!collapsed && (
<Group
gap="xs"
p={'0 16px 16px'}
style={{ justifyContent: 'space-between' }}
style={{ padding: '0 16px 16px', justifyContent: 'space-between' }}
>
<Text size="xs" c="dimmed">
v{appVersion?.version || '0.0.0'}
@ -415,9 +413,9 @@ const Sidebar = ({ collapsed, toggleDrawer, drawerWidth, miniDrawerWidth }) => {
)}
{collapsed && isAuthenticated && (
<Box
p={'0 16px 16px'}
display='flex'
style={{
padding: '0 16px 16px',
display: 'flex',
justifyContent: 'center',
}}
>

View file

@ -171,17 +171,16 @@ const SystemEvents = () => {
const [events, setEvents] = useState([]);
const [totalEvents, setTotalEvents] = useState(0);
const [isExpanded, setIsExpanded] = useState(false);
const { ref: cardRef, width: cardWidth } = useElementSize();
const isNarrow = cardWidth < 650;
const [isLoading, setIsLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [eventsRefreshInterval, setEventsRefreshInterval] = useLocalStorage(
'events-refresh-interval',
0
);
const [eventsLimit, setEventsLimit] = useLocalStorage('events-limit', 100);
const { ref: cardRef, width: cardWidth } = useElementSize();
const isNarrow = cardWidth < 650;
const [currentPage, setCurrentPage] = useState(1);
// Calculate offset based on current page and limit
const offset = (currentPage - 1) * eventsLimit;
@ -225,13 +224,13 @@ const SystemEvents = () => {
padding="sm"
radius="md"
withBorder
color="#fff"
w={'100%'}
maw={isExpanded ? '100%' : 800}
ml="auto"
mr="auto"
style={{
color: '#fff',
backgroundColor: '#27272A',
width: '100%',
maxWidth: isExpanded ? '100%' : '800px',
marginLeft: 'auto',
marginRight: 'auto',
transition: 'max-width 0.3s ease',
}}
>
@ -251,7 +250,7 @@ const SystemEvents = () => {
min={10}
max={1000}
step={10}
w={130}
style={{ width: 130 }}
/>
<Select
size="xs"
@ -265,14 +264,14 @@ const SystemEvents = () => {
{ value: '30', label: '30s' },
{ value: '60', label: '1m' },
]}
w={120}
style={{ width: 120 }}
/>
<Button
size="xs"
variant="subtle"
onClick={fetchEvents}
loading={isLoading}
mt="auto"
style={{ marginTop: 'auto' }}
>
Refresh
</Button>
@ -312,8 +311,8 @@ const SystemEvents = () => {
<Stack
gap="xs"
mt="sm"
mah="60vh"
style={{
maxHeight: '60vh',
overflowY: 'auto',
}}
>

View file

@ -1,6 +1,19 @@
import React, { useEffect, useState } from 'react';
import { Badge, Box, Button, Flex, Group, Image, Loader, Modal, Select, Stack, Text, Title, } from '@mantine/core';
import { Copy, Play } from 'lucide-react';
import React, { useState, useEffect } from 'react';
import {
Box,
Button,
Flex,
Group,
Image,
Text,
Title,
Select,
Badge,
Loader,
Stack,
Modal,
} from '@mantine/core';
import { Play, Copy } from 'lucide-react';
import { copyToClipboard } from '../utils';
import useVODStore from '../store/useVODStore';
import useVideoStore from '../store/useVideoStore';
@ -327,7 +340,7 @@ const VODModal = ({ vod, opened, onClose }) => {
size="xl"
centered
>
<Box pos="relative" mih={400}>
<Box style={{ position: 'relative', minHeight: 400 }}>
{/* Backdrop image as background */}
{displayVOD.backdrop_path && displayVOD.backdrop_path.length > 0 && (
<>
@ -335,41 +348,41 @@ const VODModal = ({ vod, opened, onClose }) => {
src={displayVOD.backdrop_path[0]}
alt={`${displayVOD.name} backdrop`}
fit="cover"
pos="absolute"
top={0}
left={0}
w="100%"
h="100%"
bdrs={8}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
objectFit: 'cover',
zIndex: 0,
borderRadius: 8,
filter: 'blur(2px) brightness(0.5)',
}}
/>
{/* Overlay for readability */}
<Box
pos="absolute"
top={0}
left={0}
w="100%"
h="100%"
bdrs={8}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
background:
'linear-gradient(180deg, rgba(24,24,27,0.85) 60%, rgba(24,24,27,1) 100%)',
zIndex: 1,
borderRadius: 8,
}}
/>
</>
)}
{/* Modal content above backdrop */}
<Box pos="relative" style={{ zIndex: 2 }}>
<Box style={{ position: 'relative', zIndex: 2 }}>
<Stack spacing="md">
{loadingDetails && (
<Group spacing="xs" mb={8}>
<Loader size="xs" />
<Text size="xs" c="dimmed">
<Text size="xs" color="dimmed">
Loading additional details...
</Text>
</Group>
@ -386,19 +399,19 @@ const VODModal = ({ vod, opened, onClose }) => {
height={300}
alt={displayVOD.name}
fit="contain"
bdrs={8}
style={{ borderRadius: '8px' }}
/>
</Box>
) : (
<Box
w={200}
h={300}
display="flex"
bdrs={8}
style={{
width: 200,
height: 300,
backgroundColor: '#404040',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '8px',
flexShrink: 0,
}}
>
@ -419,10 +432,12 @@ const VODModal = ({ vod, opened, onClose }) => {
<Group spacing="md" align="flex-end" mt="md">
{/* Provider Selection */}
{providers.length > 0 && (
<Box miw={200}>
<Box style={{ minWidth: 200 }}>
<Text size="sm" weight={500} mb={8}>
Stream Selection
{loadingProviders && <Loader size="xs" ml={8} />}
{loadingProviders && (
<Loader size="xs" style={{ marginLeft: 8 }} />
)}
</Text>
{providers.length === 1 ? (
<Group spacing="md">
@ -439,7 +454,7 @@ const VODModal = ({ vod, opened, onClose }) => {
value={selectedProvider?.id?.toString() || ''}
onChange={(value) => onChangeSelectedProvider(value)}
placeholder="Select stream..."
miw={250}
style={{ minWidth: 250 }}
disabled={loadingProviders}
/>
)}