style: Update button styles and add kebab menu for mobile
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { Add, Cast, Delete, Share } from '@mui/icons-material';
|
import { Add, Cast, Delete, MoreVert, Share } from '@mui/icons-material';
|
||||||
import { Button, Menu, MenuItem, Stack, Tooltip } from '@mui/material';
|
import { Button, IconButton, Menu, MenuItem, Stack, Tooltip, useMediaQuery, useTheme } from '@mui/material';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useLanguage } from '../../../contexts/LanguageContext';
|
import { useLanguage } from '../../../contexts/LanguageContext';
|
||||||
import { useSnackbar } from '../../../contexts/SnackbarContext';
|
import { useSnackbar } from '../../../contexts/SnackbarContext';
|
||||||
@@ -22,7 +22,10 @@ const VideoActionButtons: React.FC<VideoActionButtonsProps> = ({
|
|||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
const { handleShare } = useShareVideo(video);
|
const { handleShare } = useShareVideo(video);
|
||||||
const { showSnackbar } = useSnackbar();
|
const { showSnackbar } = useSnackbar();
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
const [playerMenuAnchor, setPlayerMenuAnchor] = useState<null | HTMLElement>(null);
|
const [playerMenuAnchor, setPlayerMenuAnchor] = useState<null | HTMLElement>(null);
|
||||||
|
const [kebabMenuAnchor, setKebabMenuAnchor] = useState<null | HTMLElement>(null);
|
||||||
|
|
||||||
const getVideoUrl = (): string => {
|
const getVideoUrl = (): string => {
|
||||||
if (video.videoPath) {
|
if (video.videoPath) {
|
||||||
@@ -44,6 +47,14 @@ const VideoActionButtons: React.FC<VideoActionButtonsProps> = ({
|
|||||||
setPlayerMenuAnchor(null);
|
setPlayerMenuAnchor(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleKebabMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
|
||||||
|
setKebabMenuAnchor(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKebabMenuClose = () => {
|
||||||
|
setKebabMenuAnchor(null);
|
||||||
|
};
|
||||||
|
|
||||||
const handlePlayerSelect = (player: string) => {
|
const handlePlayerSelect = (player: string) => {
|
||||||
const videoUrl = getVideoUrl();
|
const videoUrl = getVideoUrl();
|
||||||
|
|
||||||
@@ -122,7 +133,7 @@ const VideoActionButtons: React.FC<VideoActionButtonsProps> = ({
|
|||||||
handlePlayerMenuClose();
|
handlePlayerMenuClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const actionButtons = (
|
||||||
<Stack direction="row" spacing={1}>
|
<Stack direction="row" spacing={1}>
|
||||||
<Tooltip title={t('playWith')}>
|
<Tooltip title={t('playWith')}>
|
||||||
<Button
|
<Button
|
||||||
@@ -188,6 +199,129 @@ const VideoActionButtons: React.FC<VideoActionButtonsProps> = ({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Tooltip title="More actions">
|
||||||
|
<IconButton
|
||||||
|
onClick={handleKebabMenuOpen}
|
||||||
|
sx={{
|
||||||
|
color: kebabMenuAnchor ? 'primary.main' : 'text.secondary',
|
||||||
|
'&:hover': { color: 'primary.main' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MoreVert />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Menu
|
||||||
|
anchorEl={kebabMenuAnchor}
|
||||||
|
open={Boolean(kebabMenuAnchor)}
|
||||||
|
onClose={handleKebabMenuClose}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'bottom',
|
||||||
|
horizontal: 'right',
|
||||||
|
}}
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'right',
|
||||||
|
}}
|
||||||
|
slotProps={{
|
||||||
|
paper: {
|
||||||
|
sx: {
|
||||||
|
minWidth: 'auto',
|
||||||
|
p: 1,
|
||||||
|
px: 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack direction="row" spacing={2} sx={{ justifyContent: 'flex-end' }}>
|
||||||
|
<Tooltip title={t('playWith')}>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="inherit"
|
||||||
|
onClick={() => {
|
||||||
|
// Store the anchor before closing the kebab menu
|
||||||
|
const anchor = kebabMenuAnchor;
|
||||||
|
handleKebabMenuClose();
|
||||||
|
// Use the stored anchor for the player menu
|
||||||
|
if (anchor) {
|
||||||
|
setPlayerMenuAnchor(anchor);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
sx={{ minWidth: 'auto', p: 1, color: 'text.secondary', borderColor: 'text.secondary', '&:hover': { color: 'primary.main', borderColor: 'primary.main' } }}
|
||||||
|
>
|
||||||
|
<Cast />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title={t('share')}>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="inherit"
|
||||||
|
onClick={() => {
|
||||||
|
handleKebabMenuClose();
|
||||||
|
handleShare();
|
||||||
|
}}
|
||||||
|
sx={{ minWidth: 'auto', p: 1, color: 'text.secondary', borderColor: 'text.secondary', '&:hover': { color: 'primary.main', borderColor: 'primary.main' } }}
|
||||||
|
>
|
||||||
|
<Share />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title={t('addToCollection')}>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="inherit"
|
||||||
|
onClick={() => {
|
||||||
|
handleKebabMenuClose();
|
||||||
|
onAddToCollection();
|
||||||
|
}}
|
||||||
|
sx={{ minWidth: 'auto', p: 1, color: 'text.secondary', borderColor: 'text.secondary', '&:hover': { color: 'primary.main', borderColor: 'primary.main' } }}
|
||||||
|
>
|
||||||
|
<Add />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title={t('delete')}>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="inherit"
|
||||||
|
onClick={() => {
|
||||||
|
handleKebabMenuClose();
|
||||||
|
onDelete();
|
||||||
|
}}
|
||||||
|
disabled={isDeleting}
|
||||||
|
sx={{ minWidth: 'auto', p: 1, color: 'text.secondary', borderColor: 'text.secondary', '&:hover': { color: 'error.main', borderColor: 'error.main' } }}
|
||||||
|
>
|
||||||
|
<Delete />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Stack>
|
||||||
|
</Menu>
|
||||||
|
<Menu
|
||||||
|
anchorEl={playerMenuAnchor}
|
||||||
|
open={Boolean(playerMenuAnchor)}
|
||||||
|
onClose={handlePlayerMenuClose}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'bottom',
|
||||||
|
horizontal: 'left',
|
||||||
|
}}
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'left',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MenuItem onClick={() => handlePlayerSelect('vlc')}>VLC</MenuItem>
|
||||||
|
<MenuItem onClick={() => handlePlayerSelect('iina')}>IINA</MenuItem>
|
||||||
|
<MenuItem onClick={() => handlePlayerSelect('mpv')}>mpv</MenuItem>
|
||||||
|
<MenuItem onClick={() => handlePlayerSelect('potplayer')}>PotPlayer</MenuItem>
|
||||||
|
<MenuItem onClick={() => handlePlayerSelect('infuse')}>Infuse</MenuItem>
|
||||||
|
<MenuItem onClick={() => handlePlayerSelect('copy')}>{t('copyUrl')}</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return actionButtons;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default VideoActionButtons;
|
export default VideoActionButtons;
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ const VideoAuthorInfo: React.FC<VideoAuthorInfoProps> = ({
|
|||||||
sx={{
|
sx={{
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
'&:hover': { color: 'primary.main' },
|
'&:hover': { color: 'primary.main' },
|
||||||
maxWidth: { xs: '180px', sm: 'none' },
|
maxWidth: { xs: '200px', sm: 'none' },
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
textOverflow: 'ellipsis',
|
textOverflow: 'ellipsis',
|
||||||
whiteSpace: 'nowrap'
|
whiteSpace: 'nowrap'
|
||||||
@@ -86,7 +86,7 @@ const VideoAuthorInfo: React.FC<VideoAuthorInfoProps> = ({
|
|||||||
size="small"
|
size="small"
|
||||||
onClick={handleSubscribeClick}
|
onClick={handleSubscribeClick}
|
||||||
color={isSubscribed ? 'primary' : 'default'}
|
color={isSubscribed ? 'primary' : 'default'}
|
||||||
sx={{ ml: { xs: 0, sm: 1 } }}
|
sx={{ ml: { xs: 1, sm: 1 } }}
|
||||||
>
|
>
|
||||||
{isSubscribed ? <NotificationsActive /> : <Notifications />}
|
{isSubscribed ? <NotificationsActive /> : <Notifications />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|||||||
@@ -26,57 +26,93 @@ const VideoMetadata: React.FC<VideoMetadataProps> = ({
|
|||||||
<Box sx={{ bgcolor: 'background.paper', p: 2, borderRadius: 2 }}>
|
<Box sx={{ bgcolor: 'background.paper', p: 2, borderRadius: 2 }}>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'row', flexWrap: 'wrap', alignItems: 'center', columnGap: 3, rowGap: 1 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'row', flexWrap: 'wrap', alignItems: 'center', columnGap: 3, rowGap: 1 }}>
|
||||||
{video.sourceUrl && (
|
{video.sourceUrl && (
|
||||||
<Typography variant="body2" sx={{ display: 'flex', alignItems: 'center' }}>
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
fontSize: { xs: '0.75rem', sm: '0.875rem' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
<a href={video.sourceUrl} target="_blank" rel="noopener noreferrer" style={{ color: theme.palette.primary.main, textDecoration: 'none', display: 'flex', alignItems: 'center' }}>
|
<a href={video.sourceUrl} target="_blank" rel="noopener noreferrer" style={{ color: theme.palette.primary.main, textDecoration: 'none', display: 'flex', alignItems: 'center' }}>
|
||||||
<LinkIcon fontSize="small" sx={{ mr: 0.5 }} />
|
<LinkIcon sx={{ mr: 0.5, fontSize: { xs: '0.875rem', sm: '1rem' } }} />
|
||||||
<strong>{t('originalLink')}</strong>
|
<strong>{t('originalLink')}</strong>
|
||||||
</a>
|
</a>
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
{video.videoPath && (
|
{video.videoPath && (
|
||||||
<Typography variant="body2" sx={{ display: 'flex', alignItems: 'center' }}>
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
fontSize: { xs: '0.75rem', sm: '0.875rem' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
<a href={`${BACKEND_URL}${video.videoPath}`} download style={{ color: theme.palette.primary.main, textDecoration: 'none', display: 'flex', alignItems: 'center' }}>
|
<a href={`${BACKEND_URL}${video.videoPath}`} download style={{ color: theme.palette.primary.main, textDecoration: 'none', display: 'flex', alignItems: 'center' }}>
|
||||||
<Download fontSize="small" sx={{ mr: 0.5 }} />
|
<Download sx={{ mr: 0.5, fontSize: { xs: '0.875rem', sm: '1rem' } }} />
|
||||||
<strong>{t('download')}</strong>
|
<strong>{t('download')}</strong>
|
||||||
</a>
|
</a>
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
{videoCollections.length > 0 && (
|
{videoCollections.length > 0 && (
|
||||||
<Box sx={{ display: 'inline', alignItems: 'center' }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
|
||||||
{videoCollections.map((c, index) => (
|
{videoCollections.map((c, index) => (
|
||||||
<React.Fragment key={c.id}>
|
<React.Fragment key={c.id}>
|
||||||
<span
|
<Box
|
||||||
|
component="span"
|
||||||
onClick={() => onCollectionClick(c.id)}
|
onClick={() => onCollectionClick(c.id)}
|
||||||
style={{
|
sx={{
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
color: theme.palette.primary.main,
|
color: 'primary.main',
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
display: 'inline-flex',
|
display: 'inline-flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
verticalAlign: 'bottom'
|
fontSize: { xs: '0.75rem', sm: '0.875rem' }
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Folder fontSize="small" sx={{ mr: 0.5 }} />
|
<Folder sx={{ mr: 0.5, fontSize: { xs: '0.875rem', sm: '1rem' } }} />
|
||||||
{c.name}
|
{c.name}
|
||||||
</span>
|
</Box>
|
||||||
{index < videoCollections.length - 1 ? <span style={{ marginRight: '4px' }}>, </span> : ''}
|
{index < videoCollections.length - 1 ? <span style={{ marginRight: '4px' }}>, </span> : ''}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Typography variant="body2" sx={{ display: 'flex', alignItems: 'center' }}>
|
<Typography
|
||||||
<VideoLibrary fontSize="small" sx={{ mr: 0.5 }} />
|
variant="body2"
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
fontSize: { xs: '0.75rem', sm: '0.875rem' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<VideoLibrary sx={{ mr: 0.5, fontSize: { xs: '0.875rem', sm: '1rem' } }} />
|
||||||
{video.source ? video.source.charAt(0).toUpperCase() + video.source.slice(1) : 'Unknown'}
|
{video.source ? video.source.charAt(0).toUpperCase() + video.source.slice(1) : 'Unknown'}
|
||||||
</Typography>
|
</Typography>
|
||||||
{video.addedAt && (
|
{video.addedAt && (
|
||||||
<Typography variant="body2" sx={{ display: 'flex', alignItems: 'center' }}>
|
<Typography
|
||||||
<CalendarToday fontSize="small" sx={{ mr: 0.5 }} />
|
variant="body2"
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
fontSize: { xs: '0.75rem', sm: '0.875rem' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CalendarToday sx={{ mr: 0.5, fontSize: { xs: '0.875rem', sm: '1rem' } }} />
|
||||||
{new Date(video.addedAt).toISOString().split('T')[0]}
|
{new Date(video.addedAt).toISOString().split('T')[0]}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
{videoResolution && (
|
{videoResolution && (
|
||||||
<Typography variant="body2" sx={{ display: 'flex', alignItems: 'center' }}>
|
<Typography
|
||||||
<HighQuality fontSize="small" sx={{ mr: 0.5 }} />
|
variant="body2"
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
fontSize: { xs: '0.75rem', sm: '0.875rem' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<HighQuality sx={{ mr: 0.5, fontSize: { xs: '0.875rem', sm: '1rem' } }} />
|
||||||
{videoResolution && `${videoResolution}`}
|
{videoResolution && `${videoResolution}`}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user