3
0
mirror of https://github.com/Qortal/q-tube.git synced 2025-02-11 17:55:51 +00:00

Merge pull request #54 from QortalSeth/main

Mobile support for Home, Video, and Channel pages.
This commit is contained in:
Qortal Dev 2024-12-13 16:14:56 -07:00 committed by GitHub
commit 29de8a8a9f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 1265 additions and 1036 deletions

View File

@ -1,7 +1,7 @@
{ {
"name": "qtube", "name": "qtube",
"private": true, "private": true,
"version": "2.0.0", "version": "2.1.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@ -1,10 +1,11 @@
import React from "react"; import React from "react";
import { smallScreenSizeString } from "../../constants/Misc.ts";
import { CardContentContainerComment } from "../common/Comments/Comments-styles"; import { CardContentContainerComment } from "../common/Comments/Comments-styles";
import { import {
CrowdfundSubTitle, CrowdfundSubTitle,
CrowdfundSubTitleRow, CrowdfundSubTitleRow,
} from "../Publish/PublishVideo/PublishVideo-styles.tsx"; } from "../Publish/PublishVideo/PublishVideo-styles.tsx";
import { Box, Typography, useTheme } from "@mui/material"; import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
export const Playlists = ({ export const Playlists = ({
@ -13,7 +14,8 @@ export const Playlists = ({
onClick, onClick,
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const navigate = useNavigate(); const isScreenSmall = !useMediaQuery(`(min-width:700px)`);
const videoPlayerHeight = "33.75vw"; // This is videoplayer width * 9/16 (inverse of aspect ratio)
return ( return (
<Box <Box
@ -21,7 +23,7 @@ export const Playlists = ({
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
width: "100%", width: "100%",
maxHeight: "30.94vw", height: isScreenSmall ? "200px" : videoPlayerHeight,
}} }}
> >
<CardContentContainerComment <CardContentContainerComment
@ -32,7 +34,7 @@ export const Playlists = ({
}} }}
> >
{playlistData?.videos?.map((vid, index) => { {playlistData?.videos?.map((vid, index) => {
const isCurrentVidPlayling = const isCurrentVidPlaying =
vid?.identifier === currentVideoIdentifier; vid?.identifier === currentVideoIdentifier;
return ( return (
@ -42,15 +44,15 @@ export const Playlists = ({
display: "flex", display: "flex",
gap: "10px", gap: "10px",
width: "100%", width: "100%",
background: isCurrentVidPlayling && theme.palette.primary.main, background: isCurrentVidPlaying && theme.palette.primary.main,
alignItems: "center", alignItems: "center",
padding: "10px", padding: "10px",
borderRadius: "5px", borderRadius: "5px",
cursor: isCurrentVidPlayling ? "default" : "pointer", cursor: isCurrentVidPlaying ? "default" : "pointer",
userSelect: "none", userSelect: "none",
}} }}
onClick={() => { onClick={() => {
if (isCurrentVidPlayling) return; if (isCurrentVidPlaying) return;
onClick(vid.name, vid.identifier); onClick(vid.name, vid.identifier);
// navigate(`/video/${vid.name}/${vid.identifier}`) // navigate(`/video/${vid.name}/${vid.identifier}`)
}} }}

View File

@ -15,8 +15,10 @@ import {
Typography, Typography,
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import { useSignal } from "@preact/signals-react";
import { useSignals } from "@preact/signals-react/runtime";
import Compressor from "compressorjs"; import Compressor from "compressorjs";
import React, { useEffect, useState } from "react"; import React, { useState } from "react";
import { useDropzone } from "react-dropzone"; import { useDropzone } from "react-dropzone";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import ShortUniqueId from "short-unique-id"; import ShortUniqueId from "short-unique-id";
@ -27,6 +29,7 @@ import {
} from "../../../constants/Identifiers.ts"; } from "../../../constants/Identifiers.ts";
import { import {
maxSize, maxSize,
menuIconSize,
titleFormatter, titleFormatter,
videoMaxSize, videoMaxSize,
} from "../../../constants/Misc.ts"; } from "../../../constants/Misc.ts";
@ -38,7 +41,6 @@ import {
import { setNotification } from "../../../state/features/notificationsSlice.ts"; import { setNotification } from "../../../state/features/notificationsSlice.ts";
import { RootState } from "../../../state/store.ts"; import { RootState } from "../../../state/store.ts";
import { formatBytes } from "../../../utils/numberFunctions.ts";
import { objectToBase64 } from "../../../utils/PublishFormatter.ts"; import { objectToBase64 } from "../../../utils/PublishFormatter.ts";
import { getFileName } from "../../../utils/stringFunctions.ts"; import { getFileName } from "../../../utils/stringFunctions.ts";
import { CardContentContainerComment } from "../../common/Comments/Comments-styles.tsx"; import { CardContentContainerComment } from "../../common/Comments/Comments-styles.tsx";
@ -66,8 +68,7 @@ import {
StyledButton, StyledButton,
TimesIcon, TimesIcon,
} from "./PublishVideo-styles.tsx"; } from "./PublishVideo-styles.tsx";
import { signal, Signal, useSignal } from "@preact/signals-react"; import VideoLibraryIcon from "@mui/icons-material/VideoLibrary";
import { useSignals } from "@preact/signals-react/runtime";
export const toBase64 = (file: File): Promise<string | ArrayBuffer | null> => export const toBase64 = (file: File): Promise<string | ArrayBuffer | null> =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
@ -82,13 +83,14 @@ export const toBase64 = (file: File): Promise<string | ArrayBuffer | null> =>
const uid = new ShortUniqueId(); const uid = new ShortUniqueId();
const shortuid = new ShortUniqueId({ length: 5 }); const shortuid = new ShortUniqueId({ length: 5 });
interface NewCrowdfundProps { interface PublishVideoProps {
editId?: string; editId?: string;
editContent?: null | { editContent?: null | {
title: string; title: string;
user: string; user: string;
coverImage: string | null; coverImage: string | null;
}; };
afterClose?: () => void;
} }
interface VideoFile { interface VideoFile {
@ -97,7 +99,11 @@ interface VideoFile {
description: string; description: string;
coverImage?: string; coverImage?: string;
} }
export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => { export const PublishVideo = ({
editId,
editContent,
afterClose,
}: PublishVideoProps) => {
const theme = useTheme(); const theme = useTheme();
const dispatch = useDispatch(); const dispatch = useDispatch();
const [isOpenMultiplePublish, setIsOpenMultiplePublish] = useState(false); const [isOpenMultiplePublish, setIsOpenMultiplePublish] = useState(false);
@ -188,13 +194,9 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
}, },
}); });
// useEffect(() => {
// if (editContent) {
// }
// }, [editContent]);
const onClose = () => { const onClose = () => {
setIsOpen(false); setIsOpen(false);
if (afterClose) afterClose();
}; };
const search = async () => { const search = async () => {
@ -633,12 +635,20 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
{editId ? null : ( {editId ? null : (
<StyledButton <StyledButton
color="primary" color="primary"
startIcon={<AddBoxIcon />} startIcon={
<VideoLibraryIcon
sx={{
color: "#FF0033",
width: menuIconSize,
height: menuIconSize,
}}
/>
}
onClick={() => { onClick={() => {
setIsOpen(true); setIsOpen(true);
}} }}
> >
add video Video
</StyledButton> </StyledButton>
)} )}
</> </>

View File

@ -159,7 +159,7 @@ export const BlockIconContainer = styled(Box)({
}); });
export const CommentsContainer = styled(Box)({ export const CommentsContainer = styled(Box)({
width: "70%", width: "90%",
maxWidth: "1000px", maxWidth: "1000px",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",

View File

@ -160,10 +160,10 @@ export const LikeAndDislike = ({ name, identifier }: LikeAndDislikeProps) => {
sx={{ sx={{
padding: "5px", padding: "5px",
borderRadius: "7px", borderRadius: "7px",
gap: "5px", gap: "10px",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
marginRight: "10px", marginRight: "20px",
height: "53px", height: "53px",
}} }}
> >

View File

@ -1,32 +1,29 @@
import React, { useEffect, useState } from "react";
import ThumbUpIcon from "@mui/icons-material/ThumbUp"; import ThumbUpIcon from "@mui/icons-material/ThumbUp";
import { import {
Box, Box,
Button,
Dialog,
DialogActions,
DialogContent, DialogContent,
DialogTitle,
FormControl,
Input,
InputAdornment, InputAdornment,
InputLabel, InputLabel,
MenuItem,
Modal, Modal,
Select,
Tooltip, Tooltip,
} from "@mui/material"; } from "@mui/material";
import qortImg from "../../../assets/img/qort.png"; import React, { useEffect, useState } from "react";
import { MultiplePublish } from "../../Publish/MultiplePublish/MultiplePublishAll.tsx";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { setNotification } from "../../../state/features/notificationsSlice.ts";
import ShortUniqueId from "short-unique-id"; import ShortUniqueId from "short-unique-id";
import qortImg from "../../../assets/img/qort.png";
import { import {
objectToBase64, FOR,
objectToFile, FOR_SUPER_LIKE,
} from "../../../utils/PublishFormatter.ts"; SUPER_LIKE_BASE,
} from "../../../constants/Identifiers.ts";
import { minPriceSuperlike } from "../../../constants/Misc.ts"; import { minPriceSuperlike } from "../../../constants/Misc.ts";
import { CommentInput } from "../Comments/Comments-styles.tsx"; import { setNotification } from "../../../state/features/notificationsSlice.ts";
import { RootState } from "../../../state/store.ts";
import BoundedNumericTextField from "../../../utils/BoundedNumericTextField.tsx";
import { numberToInt, truncateNumber } from "../../../utils/numberFunctions.ts";
import { objectToBase64 } from "../../../utils/PublishFormatter.ts";
import { getUserBalance } from "../../../utils/qortalRequestFunctions.ts";
import { MultiplePublish } from "../../Publish/MultiplePublish/MultiplePublishAll.tsx";
import { import {
CrowdfundActionButton, CrowdfundActionButton,
CrowdfundActionButtonRow, CrowdfundActionButtonRow,
@ -34,16 +31,7 @@ import {
NewCrowdfundTitle, NewCrowdfundTitle,
Spacer, Spacer,
} from "../../Publish/PublishVideo/PublishVideo-styles.tsx"; } from "../../Publish/PublishVideo/PublishVideo-styles.tsx";
import { utf8ToBase64 } from "../SuperLikesList/CommentEditor.tsx"; import { CommentInput } from "../Comments/Comments-styles.tsx";
import { RootState } from "../../../state/store.ts";
import {
FOR,
FOR_SUPER_LIKE,
SUPER_LIKE_BASE,
} from "../../../constants/Identifiers.ts";
import BoundedNumericTextField from "../../../utils/BoundedNumericTextField.tsx";
import { numberToInt, truncateNumber } from "../../../utils/numberFunctions.ts";
import { getUserBalance } from "../../../utils/qortalRequestFunctions.ts";
const uid = new ShortUniqueId({ length: 4 }); const uid = new ShortUniqueId({ length: 4 });

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from "react";
import { import {
Accordion, Accordion,
AccordionDetails, AccordionDetails,
@ -11,29 +11,28 @@ import {
ListItemIcon, ListItemIcon,
Popover, Popover,
Typography, Typography,
useTheme useTheme,
} from '@mui/material' } from "@mui/material";
import { Movie } from '@mui/icons-material' import { Movie } from "@mui/icons-material";
import { useSelector } from 'react-redux' import { useSelector } from "react-redux";
import { RootState } from '../../state/store' import { RootState } from "../../state/store";
import ExpandMoreIcon from '@mui/icons-material/ExpandMore' import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from "react-router-dom";
import { DownloadingLight } from '../../assets/svgs/DownloadingLight' import { DownloadingLight } from "../../assets/svgs/DownloadingLight";
import { DownloadedLight } from '../../assets/svgs/DownloadedLight' import { DownloadedLight } from "../../assets/svgs/DownloadedLight";
export const DownloadTaskManager: React.FC = () => { export const DownloadTaskManager: React.FC = () => {
const { downloads } = useSelector((state: RootState) => state.global) const { downloads } = useSelector((state: RootState) => state.global);
const location = useLocation() const theme = useTheme();
const theme = useTheme() const [visible, setVisible] = useState(false);
const [visible, setVisible] = useState(false) const [hidden, setHidden] = useState(true);
const [hidden, setHidden] = useState(true) const navigate = useNavigate();
const navigate = useNavigate()
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null); const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
const [openDownload, setOpenDownload] = useState<boolean>(false); const [openDownload, setOpenDownload] = useState<boolean>(false);
const hashMapVideos = useSelector(
(state: RootState) => state.video.hashMapVideos
);
const handleClick = (event?: React.MouseEvent<HTMLDivElement>) => { const handleClick = (event?: React.MouseEvent<HTMLDivElement>) => {
const target = event?.currentTarget as unknown as HTMLButtonElement | null; const target = event?.currentTarget as unknown as HTMLButtonElement | null;
setAnchorEl(target); setAnchorEl(target);
@ -49,155 +48,159 @@ export const DownloadTaskManager: React.FC = () => {
if (visible) { if (visible) {
setTimeout(() => { setTimeout(() => {
setHidden(true) setHidden(true);
setVisible(false) setVisible(false);
}, 3000) }, 3000);
} }
}, [visible]) }, [visible]);
useEffect(() => { useEffect(() => {
if (Object.keys(downloads).length === 0) return if (Object.keys(downloads).length === 0) return;
setVisible(true) setVisible(true);
setHidden(false) setHidden(false);
}, [downloads]) }, [downloads]);
if (!downloads || Object.keys(downloads).length === 0) return null;
let downloadInProgress = false;
if ( if (
!downloads || Object.keys(downloads).find(
Object.keys(downloads).length === 0 key =>
) downloads[key]?.status?.status !== "READY" &&
return null downloads[key]?.status?.status !== "DOWNLOADED"
)
) {
let downloadInProgress = false downloadInProgress = true;
if(Object.keys(downloads).find((key)=> (downloads[key]?.status?.status !== 'READY' && downloads[key]?.status?.status !== 'DOWNLOADED'))){
downloadInProgress = true
} }
return ( return (
<Box> <Box>
<Button onClick={(e: any) => { <Button
handleClick(e); sx={{ padding: "0px 0px", minWidth: "0px" }}
setOpenDownload(true); onClick={(e: any) => {
}}> handleClick(e);
{downloadInProgress ? ( setOpenDownload(true);
<DownloadingLight height='24px' width='24px' className='download-icon' /> }}
) : ( >
<DownloadedLight height='24px' width='24px' /> {downloadInProgress ? (
)} <DownloadingLight
height="24px"
</Button> width="24px"
className="download-icon"
<Popover />
id={"download-popover"} ) : (
open={openDownload} <DownloadedLight height="24px" width="24px" />
anchorEl={anchorEl} )}
onClose={handleCloseDownload} </Button>
anchorOrigin={{
vertical: "bottom", <Popover
horizontal: "left" id={"download-popover"}
open={openDownload}
anchorEl={anchorEl}
onClose={handleCloseDownload}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
sx={{ marginTop: "12px" }}
>
<List
sx={{
maxHeight: "50vh",
overflow: "auto",
width: "100%",
maxWidth: "400px",
gap: "5px",
display: "flex",
flexDirection: "column",
backgroundColor: "#555555",
}} }}
> >
<List {Object.keys(downloads).map((download: any) => {
sx={{ const downloadObj = downloads[download];
maxHeight: '50vh', const progress = downloads[download]?.status?.percentLoaded || 0;
overflow: 'auto', const status = downloads[download]?.status?.status;
width: '250px', const service = downloads[download]?.service;
gap: '5px', const id =
display: 'flex', downloadObj?.identifier + "_metadata-" + downloadObj?.name;
flexDirection: 'column', const videoTitle = hashMapVideos[id]?.title;
}}
>
{Object.keys(downloads)
.map((download: any) => {
const downloadObj = downloads[download]
const progress = downloads[download]?.status?.percentLoaded || 0
const status = downloads[download]?.status?.status
const service = downloads[download]?.service
return (
<ListItem
key={downloadObj?.identifier}
sx={{
display: 'flex',
flexDirection: 'column',
width: '100%',
justifyContent: 'center',
background: theme.palette.primary.main,
color: theme.palette.text.primary,
cursor: 'pointer',
padding: '2px',
}}
onClick={() => {
const id = downloadObj?.properties?.jsonId
if (!id) return
navigate(
`/video/${downloadObj?.properties?.user}/${id}`
)
}}
>
<Box
sx={{
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between'
}}
>
<ListItemIcon>
{service === 'VIDEO' && (
<Movie sx={{ color: theme.palette.text.primary }} />
)}
</ListItemIcon>
<Box return (
sx={{ width: '100px', marginLeft: 1, marginRight: 1 }} <ListItem
> key={downloadObj?.identifier}
<LinearProgress sx={{
variant="determinate" display: "flex",
value={progress} flexDirection: "column",
sx={{ width: "100%",
borderRadius: '5px', justifyContent: "center",
color: theme.palette.secondary.main background: theme.palette.primary.main,
}} color: theme.palette.text.primary,
/> cursor: "pointer",
</Box> padding: "2px",
<Typography }}
sx={{ onClick={() => {
fontFamily: 'Arial', const userName = downloadObj?.name;
color: theme.palette.text.primary const identifier = downloadObj?.identifier;
}}
variant="caption" if (identifier && userName)
> navigate(`/video/${userName}/${identifier}_metadata`);
{`${progress?.toFixed(0)}%`}{' '} }}
{status && status === 'REFETCHING' && '- refetching'} >
{status && status === 'DOWNLOADED' && '- building'} <Box
</Typography> sx={{
</Box> width: "100%",
<Typography display: "flex",
alignItems: "center",
justifyContent: "space-between",
}}
>
<ListItemIcon>
{service === "VIDEO" && (
<Movie sx={{ color: theme.palette.text.primary }} />
)}
</ListItemIcon>
<Box sx={{ width: "100px", marginLeft: 1, marginRight: 1 }}>
<LinearProgress
variant="determinate"
value={progress}
sx={{ sx={{
fontSize: '10px', borderRadius: "5px",
width: '100%', color: theme.palette.secondary.main,
textAlign: 'end',
fontFamily: 'Arial',
color: theme.palette.text.primary,
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden',
}} }}
> />
{downloadObj?.identifier} </Box>
</Typography> <Typography
</ListItem> sx={{
) fontFamily: "Arial",
})} color: theme.palette.text.primary,
</List> }}
</Popover> variant="caption"
>
{`${progress?.toFixed(0)}%`}{" "}
{status && status === "REFETCHING" && "- refetching"}
{status && status === "DOWNLOADED" && "- building"}
</Typography>
</Box>
<Typography
sx={{
fontSize: "10px",
width: "100%",
textAlign: "start",
fontFamily: "Arial",
color: theme.palette.text.primary,
textOverflow: "ellipsis",
whiteSpace: "nowrap",
overflow: "hidden",
}}
>
{videoTitle || downloadObj?.identifier}
</Typography>
</ListItem>
);
})}
</List>
</Popover>
</Box> </Box>
);
) };
}

View File

@ -1,16 +1,18 @@
import { Box, Typography } from "@mui/material"; import { Box, Tooltip, Typography, useMediaQuery } from "@mui/material";
import React from "react"; import React from "react";
import { PopMenu } from "../PopMenu.tsx";
import ListSuperLikes from "./ListSuperLikes"; import ListSuperLikes from "./ListSuperLikes";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { RootState } from "../../../state/store"; import { RootState } from "../../../state/store";
import ThumbUpIcon from "@mui/icons-material/ThumbUp";
export const ListSuperLikeContainer = () => { export const ListSuperLikeContainer = () => {
const superlikelist = useSelector( const superlikelist = useSelector(
(state: RootState) => state.global.superlikelistAll (state: RootState) => state.global.superlikelistAll
); );
return ( const isScreenLarge = useMediaQuery("(min-width:1200px)");
<Box> const superlikeListComponent = (
<>
<Typography <Typography
sx={{ sx={{
fontSize: "18px", fontSize: "18px",
@ -20,6 +22,53 @@ export const ListSuperLikeContainer = () => {
Recent Super likes Recent Super likes
</Typography> </Typography>
<ListSuperLikes superlikes={superlikelist} /> <ListSuperLikes superlikes={superlikelist} />
</>
);
// @ts-ignore
return (
<Box sx={{ paddingLeft: "5px" }}>
{isScreenLarge ? (
<>{superlikeListComponent}</>
) : (
<PopMenu
showExpandIcon={false}
popoverProps={{
open: undefined,
sx: {
display: "flex",
justifyContent: "center",
alignItems: "center",
},
anchorReference: "none",
}}
MenuHeader={
<Tooltip title={"Show recent Superlikes"} placement={"left"} arrow>
<Box
sx={{
padding: "5px",
borderRadius: "7px",
outline: "1px gold solid",
height: "53px",
position: "absolute",
top: "60px",
right: "2%",
display: "flex",
alignItems: "center",
}}
>
<ThumbUpIcon
style={{
color: "gold",
}}
/>
</Box>
</Tooltip>
}
>
{superlikeListComponent}
</PopMenu>
)}
</Box> </Box>
); );
}; };

View File

@ -64,7 +64,6 @@ export function extractIdValue(metadescription) {
} }
export const Notifications = () => { export const Notifications = () => {
const dispatch = useDispatch();
const [anchorElNotification, setAnchorElNotification] = const [anchorElNotification, setAnchorElNotification] =
useState<HTMLButtonElement | null>(null); useState<HTMLButtonElement | null>(null);
const [notifications, setNotifications] = useState<any[]>([]); const [notifications, setNotifications] = useState<any[]>([]);
@ -238,7 +237,7 @@ export const Notifications = () => {
badgeContent={notificationBadgeLength} badgeContent={notificationBadgeLength}
color="primary" color="primary"
sx={{ sx={{
margin: "0px 12px", margin: "0px",
}} }}
> >
<Button <Button
@ -267,6 +266,7 @@ export const Notifications = () => {
vertical: "bottom", vertical: "bottom",
horizontal: "left", horizontal: "left",
}} }}
sx={{ marginTop: "12px" }}
> >
<Box> <Box>
<List <List

View File

@ -0,0 +1,77 @@
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import { Box, Popover, PopoverProps, SxProps, Theme } from "@mui/material";
import {
forwardRef,
PropsWithChildren,
useImperativeHandle,
useState,
} from "react";
import { AvatarContainer } from "../layout/Navbar/Navbar-styles.tsx";
export interface PopMenuProps {
MenuHeader: React.ReactNode;
containerSX?: SxProps<Theme>;
showExpandIcon?: boolean;
popoverProps?: PopoverProps;
}
export type PopMenuRefType = {
closePopover: () => void;
};
export const PopMenu = forwardRef<
PopMenuRefType,
PropsWithChildren<PopMenuProps>
>(
(
{
containerSX,
popoverProps,
MenuHeader,
showExpandIcon = true,
children,
}: PropsWithChildren<PopMenuProps>,
ref
) => {
const [isOpenUserDropdown, setIsOpenUserDropdown] =
useState<boolean>(false);
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
const openPopover = (event: React.MouseEvent<HTMLDivElement>) => {
const target = event.currentTarget as unknown as HTMLButtonElement | null;
setAnchorEl(target);
setIsOpenUserDropdown(true);
};
const closePopover = () => {
setAnchorEl(null);
setIsOpenUserDropdown(false);
};
useImperativeHandle(ref, () => ({
closePopover: () => closePopover(),
}));
return (
<>
<AvatarContainer sx={containerSX} onClick={openPopover}>
{MenuHeader}
{showExpandIcon && <ExpandMoreIcon sx={{ color: "#ACB6BF" }} />}
</AvatarContainer>
<Popover
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
{...popoverProps}
open={isOpenUserDropdown}
anchorEl={anchorEl}
onClose={closePopover}
>
<Box sx={{ display: "contents" }}>{children}</Box>
</Popover>
</>
);
}
);

View File

@ -7,10 +7,15 @@ import {
DialogContent, DialogContent,
DialogTitle, DialogTitle,
Typography, Typography,
useMediaQuery,
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import React, { useCallback, useState, useEffect } from "react"; import React, { useCallback, useState, useEffect } from "react";
import ThumbUpIcon from "@mui/icons-material/ThumbUp"; import ThumbUpIcon from "@mui/icons-material/ThumbUp";
import {
fontSizeSmall,
smallScreenSizeString,
} from "../../../constants/Misc.ts";
import { CommentEditor } from "./CommentEditor"; import { CommentEditor } from "./CommentEditor";
import { import {
@ -36,9 +41,9 @@ interface CommentProps {
postId: string; postId: string;
postName: string; postName: string;
onSubmit: (obj?: any, isEdit?: boolean) => void; onSubmit: (obj?: any, isEdit?: boolean) => void;
amount?: null | number amount?: null | number;
isSuperLike?: boolean isSuperLike?: boolean;
hasHash?: boolean hasHash?: boolean;
} }
export const Comment = ({ export const Comment = ({
comment, comment,
@ -47,7 +52,7 @@ export const Comment = ({
onSubmit, onSubmit,
amount, amount,
isSuperLike, isSuperLike,
hasHash hasHash,
}: CommentProps) => { }: CommentProps) => {
const [isReplying, setIsReplying] = useState<boolean>(false); const [isReplying, setIsReplying] = useState<boolean>(false);
const [isEditing, setIsEditing] = useState<boolean>(false); const [isEditing, setIsEditing] = useState<boolean>(false);
@ -199,7 +204,7 @@ export const CommentCard = ({
children, children,
setCurrentEdit, setCurrentEdit,
isReply, isReply,
amount amount,
}: any) => { }: any) => {
const [avatarUrl, setAvatarUrl] = React.useState<string>(""); const [avatarUrl, setAvatarUrl] = React.useState<string>("");
const { user } = useSelector((state: RootState) => state.auth); const { user } = useSelector((state: RootState) => state.auth);
@ -225,6 +230,13 @@ export const CommentCard = ({
getAvatar(name); getAvatar(name);
}, [name]); }, [name]);
const isScreenSmall = !useMediaQuery(`(min-width:${smallScreenSizeString})`);
const superLikeHeaderSX = {
display: "flex",
flexDirection: isScreenSmall ? "column" : "row",
alignItems: "center",
};
return ( return (
<CardContentContainerComment> <CardContentContainerComment>
<StyledCardHeaderComment <StyledCardHeaderComment
@ -234,40 +246,48 @@ export const CommentCard = ({
}, },
}} }}
> >
<Box> <Box sx={superLikeHeaderSX}>
<Avatar <Box
src={avatarUrl} sx={{
alt={`${name}'s avatar`} display: "flex",
sx={{ width: "35px", height: "35px" }} gap: "10px",
/> alignItems: "center",
</Box> }}
<StyledCardColComment> >
<Box sx={{ <Avatar
display: 'flex', src={avatarUrl}
gap: '10px', alt={`${name}'s avatar`}
alignItems: 'center' sx={{ width: "35px", height: "35px" }}
}}> />
<AuthorTextComment>{name}</AuthorTextComment> <AuthorTextComment>{name}</AuthorTextComment>
{!isReply && (
<ThumbUpIcon
style={{
color: "gold",
cursor: "pointer",
}}
/>
)}
{amount && (
<Typography sx={{
fontSize: '20px',
color: 'gold'
}}>
{parseFloat(amount)?.toFixed(2)} QORT
</Typography>
)}
</Box> </Box>
<StyledCardColComment
</StyledCardColComment> sx={{
marginTop: isScreenSmall ? "10px" : "0px",
marginLeft: isScreenSmall ? "0px" : "10px",
}}
>
{!isReply && (
<ThumbUpIcon
style={{
color: "gold",
cursor: "pointer",
marginRight: "10px",
}}
/>
)}
{amount && (
<Typography
sx={{
fontSize: fontSizeSmall,
color: "gold",
}}
>
{parseFloat(amount)?.toFixed(2)} QORT
</Typography>
)}
</StyledCardColComment>
</Box>
</StyledCardHeaderComment> </StyledCardHeaderComment>
<StyledCardContentComment> <StyledCardContentComment>
<StyledCardComment>{message}</StyledCardComment> <StyledCardComment>{message}</StyledCardComment>

View File

@ -63,9 +63,9 @@ export const StyledCardCol = styled(Box)({
export const StyledCardColComment = styled(Box)({ export const StyledCardColComment = styled(Box)({
display: "flex", display: "flex",
overflow: "hidden", overflow: "hidden",
flexDirection: "column", flexDirection: "row",
gap: "2px", gap: "2px",
alignItems: "flex-start", alignItems: "center",
width: "100%", width: "100%",
}); });
@ -159,7 +159,7 @@ export const BlockIconContainer = styled(Box)({
}); });
export const CommentsContainer = styled(Box)({ export const CommentsContainer = styled(Box)({
width: "70%", width: "98%",
maxWidth: "1000px", maxWidth: "1000px",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",

View File

@ -42,7 +42,6 @@ export const MobileControls = () => {
<IconButton <IconButton
sx={{ sx={{
color: "rgba(255, 255, 255, 0.7)", color: "rgba(255, 255, 255, 0.7)",
marginLeft: "15px",
}} }}
onClick={reloadVideo} onClick={reloadVideo}
> >
@ -55,11 +54,26 @@ export const MobileControls = () => {
max={videoRef.current?.duration || 100} max={videoRef.current?.duration || 100}
sx={{ flexGrow: 1, mx: 2 }} sx={{ flexGrow: 1, mx: 2 }}
/> />
<Typography
sx={{
color: "rgba(255, 255, 255, 0.7)",
fontSize: "14px",
userSelect: "none",
minWidth: "30px",
}}
onClick={() => increaseSpeed()}
>
{playbackRate}x
</Typography>
<Fullscreen onClick={toggleFullscreen} />
<IconButton <IconButton
edge="end" edge="end"
color="inherit" color="inherit"
aria-label="menu" aria-label="menu"
onClick={handleMenuOpen} onClick={handleMenuOpen}
sx={{ minWidth: "30px" }}
> >
<MoreIcon /> <MoreIcon />
</IconButton> </IconButton>
@ -85,22 +99,10 @@ export const MobileControls = () => {
step={0.01} step={0.01}
/> />
</MenuItem> </MenuItem>
<MenuItem onClick={() => increaseSpeed()}>
<Typography
sx={{
color: "rgba(255, 255, 255, 0.7)",
fontSize: "14px",
}}
>
Speed: {playbackRate}x
</Typography>
</MenuItem>
<MenuItem onClick={togglePictureInPicture}> <MenuItem onClick={togglePictureInPicture}>
<PictureInPicture /> <PictureInPicture />
</MenuItem> </MenuItem>
<MenuItem onClick={toggleFullscreen}>
<Fullscreen />
</MenuItem>
</Menu> </Menu>
</> </>
); );

View File

@ -3,11 +3,13 @@ import { useSignalEffect, useSignals } from "@preact/signals-react/runtime";
import { useEffect } from "react"; import { useEffect } from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { Key } from "ts-key-enum"; import { Key } from "ts-key-enum";
import { useIsMobile } from "../../../../hooks/useIsMobile.ts"; import { useIsMobile } from "../../../../hooks/useIsMobile.ts";
import { setVideoPlaying } from "../../../../state/features/globalSlice.ts"; import { setVideoPlaying } from "../../../../state/features/globalSlice.ts";
import { import {
setIsMuted,
setMutedVolumeSetting,
setReduxPlaybackRate, setReduxPlaybackRate,
setStretchVideoSetting, setStretchVideoSetting,
setVolumeSetting, setVolumeSetting,
@ -36,13 +38,12 @@ export const useVideoControlsState = (
progress, progress,
videoObjectFit, videoObjectFit,
canPlay, canPlay,
isMobileView,
} = videoPlayerState; } = videoPlayerState;
const { identifier, autoPlay } = props; const { identifier, autoPlay } = props;
const isMobile = useIsMobile();
const showControlsFullScreen = useSignal(true); const showControlsFullScreen = useSignal(true);
const persistSelector = store.getState().persist; const dispatch = useDispatch();
const persistSelector = useSelector((root: RootState) => root.persist);
const videoPlaying = useSelector( const videoPlaying = useSelector(
(state: RootState) => state.global.videoPlaying (state: RootState) => state.global.videoPlaying
@ -58,7 +59,6 @@ export const useVideoControlsState = (
videoRef.current.playbackRate = newSpeed; videoRef.current.playbackRate = newSpeed;
playbackRate.value = newSpeed; playbackRate.value = newSpeed;
store.dispatch(setReduxPlaybackRate(newSpeed));
} }
}; };
@ -156,10 +156,9 @@ export const useVideoControlsState = (
const onVolumeChange = (_: any, value: number | number[]) => { const onVolumeChange = (_: any, value: number | number[]) => {
if (!videoRef.current) return; if (!videoRef.current) return;
const newVolume = value as number; const newVolume = value as number;
videoRef.current.volume = newVolume;
volume.value = newVolume;
isMuted.value = false; isMuted.value = false;
store.dispatch(setVolumeSetting(newVolume)); mutedVolume.value = newVolume;
volume.value = newVolume;
}; };
useEffect(() => { useEffect(() => {
@ -170,12 +169,10 @@ export const useVideoControlsState = (
isMuted.value = true; isMuted.value = true;
mutedVolume.value = volume.value; mutedVolume.value = volume.value;
volume.value = 0; volume.value = 0;
if (videoRef.current) videoRef.current.volume = 0;
}; };
const unMute = () => { const unMute = () => {
isMuted.value = false; isMuted.value = false;
volume.value = mutedVolume.value; volume.value = mutedVolume.value;
if (videoRef.current) videoRef.current.volume = mutedVolume.value;
}; };
const toggleMute = () => { const toggleMute = () => {
@ -191,12 +188,10 @@ export const useVideoControlsState = (
newVolume = Math.max(newVolume, minVolume); newVolume = Math.max(newVolume, minVolume);
newVolume = Math.min(newVolume, maxVolume); newVolume = Math.min(newVolume, maxVolume);
newVolume = +newVolume.toFixed(2);
isMuted.value = false; isMuted.value = false;
mutedVolume.value = newVolume; mutedVolume.value = newVolume;
videoRef.current.volume = newVolume;
volume.value = newVolume; volume.value = newVolume;
store.dispatch(setVolumeSetting(newVolume));
} }
}; };
const setProgressRelative = (secondsChange: number) => { const setProgressRelative = (secondsChange: number) => {
@ -228,7 +223,6 @@ export const useVideoControlsState = (
persistSelector.stretchVideoSetting === "contain" ? "fill" : "contain"; persistSelector.stretchVideoSetting === "contain" ? "fill" : "contain";
videoObjectFit.value = newStretchVideoSetting; videoObjectFit.value = newStretchVideoSetting;
store.dispatch(setStretchVideoSetting(newStretchVideoSetting));
}; };
const keyboardShortcutsDown = (e: React.KeyboardEvent<HTMLDivElement>) => { const keyboardShortcutsDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
e.preventDefault(); e.preventDefault();
@ -350,14 +344,13 @@ export const useVideoControlsState = (
videoRef.current.play().then(() => { videoRef.current.play().then(() => {
playing.value = true; playing.value = true;
startPlay.value = true; startPlay.value = true;
store.dispatch(setVideoPlaying(null)); dispatch(setVideoPlaying(null));
}); });
} }
}, [videoPlaying, identifier, src]); }, [videoPlaying, identifier, src]);
useSignalEffect(() => { useSignalEffect(() => {
console.log("canPlay is: ", canPlay.value); // makes the function execute when canPlay changes console.log("canPlay is: ", canPlay.value); // makes the function execute when canPlay changes
if (isMobile) isMobileView.value = true;
}); });
return { return {

View File

@ -7,12 +7,14 @@ import {
VolumeOff, VolumeOff,
VolumeUp, VolumeUp,
} from "@mui/icons-material"; } from "@mui/icons-material";
import { IconButton, Slider, Typography } from "@mui/material"; import { IconButton, Slider, Typography, useMediaQuery } from "@mui/material";
import { smallScreenSizeString } from "../../../../constants/Misc.ts";
import { formatTime } from "../../../../utils/numberFunctions.ts"; import { formatTime } from "../../../../utils/numberFunctions.ts";
import { ControlsContainer } from "../VideoPlayer-styles.ts"; import { ControlsContainer } from "../VideoPlayer-styles.ts";
import { MobileControls } from "./MobileControls.tsx"; import { MobileControls } from "./MobileControls.tsx";
import { useVideoContext } from "./VideoContext.ts"; import { useVideoContext } from "./VideoContext.ts";
import { useSignalEffect } from "@preact/signals-react";
export const VideoControls = () => { export const VideoControls = () => {
const { const {
@ -28,7 +30,6 @@ export const VideoControls = () => {
from, from,
videoRef, videoRef,
canPlay, canPlay,
isMobileView,
isMuted, isMuted,
playbackRate, playbackRate,
playing, playing,
@ -37,8 +38,8 @@ export const VideoControls = () => {
showControlsFullScreen, showControlsFullScreen,
} = useVideoContext(); } = useVideoContext();
const showMobileControls = const isScreenSmall = !useMediaQuery(`(min-width:580px)`);
isMobileView.value && canPlay.value && showControlsFullScreen.value; const showMobileControls = isScreenSmall && canPlay.value;
return ( return (
<ControlsContainer <ControlsContainer

View File

@ -1,4 +1,8 @@
import { useSignal, useSignals } from "@preact/signals-react/runtime"; import {
useSignal,
useSignalEffect,
useSignals,
} from "@preact/signals-react/runtime";
import React, { import React, {
useContext, useContext,
useEffect, useEffect,
@ -9,7 +13,14 @@ import React, {
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { setVideoPlaying } from "../../../state/features/globalSlice.ts"; import { setVideoPlaying } from "../../../state/features/globalSlice.ts";
import { StretchVideoType } from "../../../state/features/persistSlice.ts"; import {
setIsMuted,
setMutedVolumeSetting,
setReduxPlaybackRate,
setStretchVideoSetting,
setVolumeSetting,
StretchVideoType,
} from "../../../state/features/persistSlice.ts";
import { RootState } from "../../../state/store.ts"; import { RootState } from "../../../state/store.ts";
import { MyContext } from "../../../wrappers/DownloadWrapper.tsx"; import { MyContext } from "../../../wrappers/DownloadWrapper.tsx";
import { VideoPlayerProps } from "./VideoPlayer.tsx"; import { VideoPlayerProps } from "./VideoPlayer.tsx";
@ -19,20 +30,40 @@ export const useVideoPlayerState = (props: VideoPlayerProps, ref: any) => {
const persistSelector = useSelector((state: RootState) => state.persist); const persistSelector = useSelector((state: RootState) => state.persist);
const playing = useSignal(false); const playing = useSignal(false);
const isMuted = useSignal(false);
const progress = useSignal(0); const progress = useSignal(0);
const isLoading = useSignal(false); const isLoading = useSignal(false);
const canPlay = useSignal(false); const canPlay = useSignal(false);
const startPlay = useSignal(false); const startPlay = useSignal(false);
const isMobileView = useSignal(false);
const isMuted = useSignal(persistSelector.isMuted);
const volume = useSignal(persistSelector.volume); const volume = useSignal(persistSelector.volume);
const mutedVolume = useSignal(persistSelector.volume); const mutedVolume = useSignal(persistSelector.mutedVolume);
const playbackRate = useSignal(persistSelector.playbackRate); const playbackRate = useSignal(persistSelector.playbackRate);
const anchorEl = useSignal(null);
const videoObjectFit = useSignal<StretchVideoType>( const videoObjectFit = useSignal<StretchVideoType>(
persistSelector.stretchVideoSetting persistSelector.stretchVideoSetting
); );
useSignalEffect(() => {
dispatch(setIsMuted(isMuted.value));
});
useSignalEffect(() => {
dispatch(setVolumeSetting(volume.value));
if (videoRef.current) videoRef.current.volume = volume.value;
});
useSignalEffect(() => {
dispatch(setMutedVolumeSetting(mutedVolume.value));
});
useSignalEffect(() => {
if (videoRef.current) videoRef.current.playbackRate = playbackRate.value;
dispatch(setReduxPlaybackRate(playbackRate.value));
});
useSignalEffect(() => {
dispatch(setStretchVideoSetting(videoObjectFit.value));
});
const anchorEl = useSignal(null);
const { const {
name, name,
identifier, identifier,
@ -279,7 +310,6 @@ export const useVideoPlayerState = (props: VideoPlayerProps, ref: any) => {
isLoading, isLoading,
canPlay, canPlay,
startPlay, startPlay,
isMobileView,
volume, volume,
mutedVolume, mutedVolume,
playbackRate, playbackRate,

View File

@ -55,7 +55,6 @@ export const VideoPlayer = forwardRef<videoRefType, VideoPlayerProps>(
startPlay, startPlay,
videoObjectFit, videoObjectFit,
showControlsFullScreen, showControlsFullScreen,
duration,
} = contextData; } = contextData;
return ( return (

View File

@ -0,0 +1,73 @@
import { useDispatch } from "react-redux";
import { headerIconSize, menuIconSize } from "../../../../constants/Misc.ts";
import { setEditPlaylist } from "../../../../state/features/videoSlice.ts";
import { StyledButton } from "../../../Publish/PublishVideo/PublishVideo-styles.tsx";
import { PublishVideo } from "../../../Publish/PublishVideo/PublishVideo.tsx";
import {
AvatarContainer,
DropdownContainer,
DropdownText,
NavbarName,
} from "../Navbar-styles.tsx";
import AddBoxIcon from "@mui/icons-material/AddBox";
import { PopMenu, PopMenuRefType } from "../../../common/PopMenu.tsx";
import { useRef } from "react";
import { useMediaQuery } from "@mui/material";
export interface PublishButtonsProps {
isDisplayed: boolean;
}
import PlaylistAddIcon from "@mui/icons-material/PlaylistAdd";
export const PublishMenu = ({ isDisplayed }: PublishButtonsProps) => {
const dispatch = useDispatch();
const popMenuRef = useRef<PopMenuRefType>(null);
const isScreenSmall = !useMediaQuery(`(min-width:600px)`);
return (
<>
{isDisplayed && (
<PopMenu
MenuHeader={
<>
{!isScreenSmall && (
<NavbarName sx={{ marginRight: "5px" }}>Publish</NavbarName>
)}
<AddBoxIcon
sx={{
color: "DarkGreen",
width: headerIconSize,
height: headerIconSize,
}}
/>
</>
}
ref={popMenuRef}
>
<DropdownContainer>
<PublishVideo afterClose={popMenuRef?.current?.closePopover} />
</DropdownContainer>
<DropdownContainer onClick={popMenuRef?.current?.closePopover}>
<StyledButton
color="primary"
startIcon={
<PlaylistAddIcon
sx={{
color: "#00BFFF",
width: menuIconSize,
height: menuIconSize,
}}
/>
}
onClick={() => {
dispatch(setEditPlaylist({ mode: "new" }));
}}
>
Playlist
</StyledButton>
</DropdownContainer>
</PopMenu>
)}
</>
);
};

View File

@ -0,0 +1,40 @@
import { useNavigate } from "react-router-dom";
import Logo from "../../../../assets/img/logo.webp";
import {
addFilteredVideos,
setFilterValue,
setIsFiltering,
} from "../../../../state/features/videoSlice.ts";
import { LogoContainer, ThemeSelectRow } from "../Navbar-styles.tsx";
import { useDispatch } from "react-redux";
import { useMediaQuery } from "@mui/material";
export const QtubeLogo = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const isScreenSmall = !useMediaQuery(`(min-width:600px)`);
return (
<ThemeSelectRow>
<LogoContainer
onClick={() => {
navigate("/");
dispatch(setIsFiltering(false));
dispatch(setFilterValue(""));
dispatch(addFilteredVideos([]));
}}
>
<img
src={Logo}
style={{
width: isScreenSmall ? "50px" : "auto",
height: "45px",
padding: "2px",
marginTop: "5px",
}}
/>
</LogoContainer>
</ThemeSelectRow>
);
};

View File

@ -0,0 +1,124 @@
import { Popover, useMediaQuery, useTheme } from "@mui/material";
import { AccountCircleSVG } from "../../../../assets/svgs/AccountCircleSVG.tsx";
import { headerIconSize, menuIconSize } from "../../../../constants/Misc.ts";
import { BlockedNamesModal } from "../../../common/BlockedNamesModal/BlockedNamesModal.tsx";
import {
AvatarContainer,
DropdownContainer,
DropdownText,
NavbarName,
} from "../Navbar-styles.tsx";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import { useRef, useState } from "react";
import PersonOffIcon from "@mui/icons-material/PersonOff";
import { useNavigate } from "react-router-dom";
import { PopMenu, PopMenuRefType } from "../../../common/PopMenu.tsx";
export interface NavBarMenuProps {
isShowMenu: boolean;
userAvatar: string;
userName: string | null;
}
export const UserMenu = ({
isShowMenu,
userAvatar,
userName,
}: NavBarMenuProps) => {
const isScreenSmall = !useMediaQuery(`(min-width:600px)`);
const theme = useTheme();
const [isOpenBlockedNamesModal, setIsOpenBlockedNamesModal] =
useState<boolean>(false);
const popMenuRef = useRef<PopMenuRefType>(null);
const navigate = useNavigate();
const handleMyChannelLink = () => {
navigate(`/channel/${userName}`);
};
const onCloseBlockedNames = () => {
setIsOpenBlockedNamesModal(false);
};
return (
<>
{isShowMenu && (
<>
<PopMenu
ref={popMenuRef}
MenuHeader={
<AvatarContainer>
{!isScreenSmall && <NavbarName>{userName}</NavbarName>}
{!userAvatar ? (
<AccountCircleSVG
color={theme.palette.text.primary}
width={headerIconSize}
height={headerIconSize}
/>
) : (
<img
src={userAvatar}
alt="User Avatar"
width={headerIconSize}
height={headerIconSize}
style={{
borderRadius: "50%",
}}
/>
)}
</AvatarContainer>
}
>
<DropdownContainer
onClick={() => {
handleMyChannelLink();
popMenuRef.current.closePopover();
}}
>
{!userAvatar ? (
<AccountCircleSVG
color={theme.palette.text.primary}
width={menuIconSize}
height={menuIconSize}
/>
) : (
<img
src={userAvatar}
alt="User Avatar"
width={menuIconSize}
height={menuIconSize}
style={{
borderRadius: "50%",
}}
/>
)}
<DropdownText>{userName}</DropdownText>
</DropdownContainer>
<DropdownContainer
onClick={() => {
setIsOpenBlockedNamesModal(true);
popMenuRef.current.closePopover();
}}
>
<PersonOffIcon
sx={{
color: "#e35050",
width: menuIconSize,
height: menuIconSize,
}}
/>
<DropdownText>Blocked Names</DropdownText>
</DropdownContainer>
</PopMenu>
{isOpenBlockedNamesModal && (
<BlockedNamesModal
open={isOpenBlockedNamesModal}
onClose={onCloseBlockedNames}
/>
)}
</>
)}
</>
);
};

View File

@ -2,20 +2,18 @@ import { AppBar, Button, Typography, Box } from "@mui/material";
import { styled } from "@mui/system"; import { styled } from "@mui/system";
import { LightModeSVG } from "../../../assets/svgs/LightModeSVG"; import { LightModeSVG } from "../../../assets/svgs/LightModeSVG";
import { DarkModeSVG } from "../../../assets/svgs/DarkModeSVG"; import { DarkModeSVG } from "../../../assets/svgs/DarkModeSVG";
import { fontSizeSmall } from "../../../constants/Misc.ts";
export const CustomAppBar = styled(AppBar)(({ theme }) => ({ export const CustomAppBar = styled(AppBar)(({ theme }) => ({
display: "flex", display: "flex",
flexDirection: "row", flexDirection: "row",
justifyContent: "space-between", justifyContent: "start",
alignItems: "center", alignItems: "center",
width: "100%", width: "100%",
padding: "5px 16px", padding: "5px 16px 5px 5px",
backgroundImage: "none", backgroundImage: "none",
borderBottom: `1px solid ${theme.palette.primary.light}`, borderBottom: `1px solid ${theme.palette.primary.light}`,
backgroundColor: theme.palette.background.default, backgroundColor: theme.palette.background.default,
[theme.breakpoints.only("xs")]: {
gap: "15px",
},
height: "50px", height: "50px",
})); }));
export const LogoContainer = styled("div")({ export const LogoContainer = styled("div")({
@ -85,16 +83,15 @@ export const DropdownText = styled(Typography)(({ theme }) => ({
export const NavbarName = styled(Typography)(({ theme }) => ({ export const NavbarName = styled(Typography)(({ theme }) => ({
fontFamily: "Raleway", fontFamily: "Raleway",
fontSize: "18px", fontSize: fontSizeSmall,
color: theme.palette.text.primary, color: theme.palette.text.primary,
margin: "0 10px", marginRight: "10px",
})); }));
export const ThemeSelectRow = styled(Box)({ export const ThemeSelectRow = styled(Box)({
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
gap: "5px", gap: "10px",
flexBasis: 0,
height: "100%", height: "100%",
}); });

View File

@ -1,43 +1,12 @@
import React, { useState, useRef } from "react"; import { Box, useMediaQuery } from "@mui/material";
import { Box, Button, Input, Popover, useTheme } from "@mui/material"; import React from "react";
import ExitToAppIcon from "@mui/icons-material/ExitToApp";
import { BlockedNamesModal } from "../../common/BlockedNamesModal/BlockedNamesModal";
import AddBoxIcon from "@mui/icons-material/AddBox";
import {
AvatarContainer,
CustomAppBar,
DropdownContainer,
DropdownText,
AuthenticateButton,
NavbarName,
LightModeIcon,
DarkModeIcon,
ThemeSelectRow,
LogoContainer,
} from "./Navbar-styles";
import { AccountCircleSVG } from "../../../assets/svgs/AccountCircleSVG";
import BackspaceIcon from "@mui/icons-material/Backspace";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import PersonOffIcon from "@mui/icons-material/PersonOff";
import { useNavigate } from "react-router-dom";
import SearchIcon from "@mui/icons-material/Search";
import { DownloadTaskManager } from "../../common/DownloadTaskManager"; import { DownloadTaskManager } from "../../common/DownloadTaskManager";
import Logo from "../../../assets/img/logo.webp";
import { useDispatch, useSelector } from "react-redux";
import {
addFilteredVideos,
setEditPlaylist,
setFilterValue,
setIsFiltering,
} from "../../../state/features/videoSlice";
import { RootState } from "../../../state/store";
import { useWindowSize } from "../../../hooks/useWindowSize";
import { PublishVideo } from "../../Publish/PublishVideo/PublishVideo.tsx";
import { StyledButton } from "../../Publish/PublishVideo/PublishVideo-styles.tsx";
import { Notifications } from "../../common/Notifications/Notifications"; import { Notifications } from "../../common/Notifications/Notifications";
import { PublishMenu } from "./Components/PublishMenu.tsx";
import { QtubeLogo } from "./Components/QtubeLogo.tsx";
import { UserMenu } from "./Components/UserMenu.tsx";
import { CustomAppBar } from "./Navbar-styles";
interface Props { interface Props {
isAuthenticated: boolean; isAuthenticated: boolean;
userName: string | null; userName: string | null;
@ -46,296 +15,39 @@ interface Props {
setTheme: (val: string) => void; setTheme: (val: string) => void;
} }
const NavBar: React.FC<Props> = ({ const NavBar: React.FC<Props> = ({ isAuthenticated, userName, userAvatar }) => {
isAuthenticated, const isScreenSmall = !useMediaQuery(`(min-width:600px)`);
userName, const isSecure = isAuthenticated && !!userName;
userAvatar, const gapSize = 10;
authenticate,
setTheme,
}) => {
const windowSize = useWindowSize();
const searchValRef = useRef("");
const inputRef = useRef<HTMLInputElement>(null);
const theme = useTheme();
const dispatch = useDispatch();
const navigate = useNavigate();
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(
null
);
const [openUserDropdown, setOpenUserDropdown] = useState<boolean>(false);
const [isOpenBlockedNamesModal, setIsOpenBlockedNamesModal] =
useState<boolean>(false);
const [anchorElNotification, setAnchorElNotification] =
React.useState<HTMLButtonElement | null>(null);
const filterValue = useSelector(
(state: RootState) => state.video.filterValue
);
const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
const target = event.currentTarget as unknown as HTMLButtonElement | null;
setAnchorEl(target);
};
const openNotificationPopover = (event: any) => {
const target = event.currentTarget as unknown as HTMLButtonElement | null;
setAnchorElNotification(target);
};
const closeNotificationPopover = () => {
setAnchorElNotification(null);
};
const openPopover = Boolean(anchorElNotification);
const idNotification = openPopover
? "simple-popover-notification"
: undefined;
const handleCloseUserDropdown = () => {
setAnchorEl(null);
setOpenUserDropdown(false);
};
const handleMyChannelLink = () => {
navigate(`/channel/${userName}`);
};
const onCloseBlockedNames = () => {
setIsOpenBlockedNamesModal(false);
};
return ( return (
<CustomAppBar position="sticky" elevation={2}> <CustomAppBar position="sticky" elevation={2}>
<ThemeSelectRow>
<LogoContainer
onClick={() => {
navigate("/");
dispatch(setIsFiltering(false));
dispatch(setFilterValue(""));
dispatch(addFilteredVideos([]));
searchValRef.current = "";
if (!inputRef.current) return;
inputRef.current.value = "";
}}
>
<img
src={Logo}
style={{
width: "auto",
height: "45px",
padding: "2px",
marginTop: "5px",
}}
/>
</LogoContainer>
</ThemeSelectRow>
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
alignItems: "center", width: "100%",
gap: "10px", justifyContent: "space-between",
gap: `${gapSize}px`,
}} }}
> >
<Popover <QtubeLogo />
id={idNotification} <Box
open={openPopover} sx={{
anchorEl={anchorElNotification} display: "flex",
onClose={closeNotificationPopover} gap: `${isScreenSmall ? gapSize : gapSize * 2}px`,
anchorOrigin={{ alignItems: "center",
vertical: "bottom",
horizontal: "left",
}} }}
> >
<Box {isSecure && <Notifications />}
sx={{
display: "flex",
alignItems: "center",
gap: 1,
padding: "5px",
}}
>
<Input
id="standard-adornment-name"
inputRef={inputRef}
onChange={e => {
searchValRef.current = e.target.value;
}}
onKeyDown={event => {
if (event.key === "Enter" || event.keyCode === 13) {
if (!searchValRef.current) {
dispatch(setIsFiltering(false));
dispatch(setFilterValue(""));
dispatch(addFilteredVideos([]));
searchValRef.current = "";
if (!inputRef.current) return;
inputRef.current.value = "";
return;
}
navigate("/");
dispatch(setIsFiltering(true));
dispatch(addFilteredVideos([]));
dispatch(setFilterValue(searchValRef.current));
}
}}
placeholder="Search"
sx={{
"&&:before": {
borderBottom: "none",
},
"&&:after": {
borderBottom: "none",
},
"&&:hover:before": {
borderBottom: "none",
},
"&&.Mui-focused:before": {
borderBottom: "none",
},
"&&.Mui-focused": {
outline: "none",
},
fontSize: "18px",
}}
/>
<SearchIcon <DownloadTaskManager />
sx={{ <UserMenu
cursor: "pointer", isShowMenu={isSecure}
}} userAvatar={userAvatar}
onClick={() => { userName={userName}
if (!searchValRef.current) {
dispatch(setIsFiltering(false));
dispatch(setFilterValue(""));
dispatch(addFilteredVideos([]));
searchValRef.current = "";
if (!inputRef.current) return;
inputRef.current.value = "";
return;
}
navigate("/");
dispatch(setIsFiltering(true));
dispatch(addFilteredVideos([]));
dispatch(setFilterValue(searchValRef.current));
}}
/>
<BackspaceIcon
sx={{
cursor: "pointer",
}}
onClick={() => {
dispatch(setIsFiltering(false));
dispatch(setFilterValue(""));
dispatch(addFilteredVideos([]));
searchValRef.current = "";
if (!inputRef.current) return;
inputRef.current.value = "";
}}
/>
</Box>
</Popover>
{isAuthenticated && userName && <Notifications />}
<DownloadTaskManager />
{isAuthenticated && userName && (
<>
<AvatarContainer
onClick={(e: any) => {
handleClick(e);
setOpenUserDropdown(true);
}}
>
<NavbarName>{userName}</NavbarName>
{!userAvatar ? (
<AccountCircleSVG
color={theme.palette.text.primary}
width="40"
height="40"
/>
) : (
<img
src={userAvatar}
alt="User Avatar"
width="40"
height="40"
style={{
borderRadius: "50%",
}}
/>
)}
<ExpandMoreIcon id="expand-icon" sx={{ color: "#ACB6BF" }} />
</AvatarContainer>
</>
)}
<AvatarContainer>
{isAuthenticated && userName && (
<>
<PublishVideo />
<StyledButton
color="primary"
startIcon={<AddBoxIcon />}
onClick={() => {
dispatch(setEditPlaylist({ mode: "new" }));
}}
>
create playlist
</StyledButton>
</>
)}
</AvatarContainer>
<Popover
id={"user-popover"}
open={openUserDropdown}
anchorEl={anchorEl}
onClose={handleCloseUserDropdown}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
>
<DropdownContainer
onClick={() => {
handleMyChannelLink();
handleCloseUserDropdown();
}}
>
{!userAvatar ? (
<AccountCircleSVG
color={theme.palette.text.primary}
width="28"
height="28"
/>
) : (
<img
src={userAvatar}
alt="User Avatar"
width="28"
height="28"
style={{
borderRadius: "50%",
}}
/>
)}
<DropdownText>My Channel</DropdownText>
</DropdownContainer>
<DropdownContainer
onClick={() => {
setIsOpenBlockedNamesModal(true);
handleCloseUserDropdown();
}}
>
<PersonOffIcon
sx={{
color: "#e35050",
}}
/>
<DropdownText>Blocked Names</DropdownText>
</DropdownContainer>
</Popover>
{isOpenBlockedNamesModal && (
<BlockedNamesModal
open={isOpenBlockedNamesModal}
onClose={onCloseBlockedNames}
/> />
)} <PublishMenu isDisplayed={isSecure} />
</Box>
</Box> </Box>
</CustomAppBar> </CustomAppBar>
); );

View File

@ -14,3 +14,12 @@ export const fontSizeExLarge = "150%";
export const maxCommentLength = 10_000; export const maxCommentLength = 10_000;
export const minFileSize = 1_000; export const minFileSize = 1_000;
export const minDuration = 5; export const minDuration = 5;
const newUIWidthDiff = 120;
const smallScreenSize = 700 - newUIWidthDiff;
const largeScreenSize = 1400 - newUIWidthDiff;
export const smallScreenSizeString = `${smallScreenSize}px`;
export const largeScreenSizeString = `${largeScreenSize}px`;
export const headerIconSize = "40px";
export const menuIconSize = "28px";

View File

@ -1,10 +1,16 @@
import { Box, Typography } from "@mui/material"; import { Box, Grid, Typography, useMediaQuery } from "@mui/material";
import React from "react"; import React from "react";
import { CommentSection } from "../../../components/common/Comments/CommentSection.tsx"; import { CommentSection } from "../../../components/common/Comments/CommentSection.tsx";
import { SuperLikesSection } from "../../../components/common/SuperLikesList/SuperLikesSection.tsx"; import { SuperLikesSection } from "../../../components/common/SuperLikesList/SuperLikesSection.tsx";
import { DisplayHtml } from "../../../components/common/TextEditor/DisplayHtml.tsx"; import { DisplayHtml } from "../../../components/common/TextEditor/DisplayHtml.tsx";
import { VideoPlayer } from "../../../components/common/VideoPlayer/VideoPlayer.tsx"; import { VideoPlayer } from "../../../components/common/VideoPlayer/VideoPlayer.tsx";
import { Playlists } from "../../../components/Playlists/Playlists.tsx"; import { Playlists } from "../../../components/Playlists/Playlists.tsx";
import {
fontSizeSmall,
minFileSize,
smallScreenSizeString,
} from "../../../constants/Misc.ts";
import { formatBytes } from "../../../utils/numberFunctions.ts";
import { formatDate } from "../../../utils/time.ts"; import { formatDate } from "../../../utils/time.ts";
import { VideoActionsBar } from "../VideoContent/VideoActionsBar.tsx"; import { VideoActionsBar } from "../VideoContent/VideoActionsBar.tsx";
import { usePlaylistContentState } from "./PlaylistContent-State.ts"; import { usePlaylistContentState } from "./PlaylistContent-State.ts";
@ -40,6 +46,8 @@ export const PlaylistContent = () => {
loadingSuperLikes, loadingSuperLikes,
} = usePlaylistContentState(); } = usePlaylistContentState();
const isScreenSmall = !useMediaQuery(`(min-width:700px)`);
return videoData && videoData?.videos?.length === 0 ? ( return videoData && videoData?.videos?.length === 0 ? (
<Box <Box
sx={{ sx={{
@ -55,197 +63,202 @@ export const PlaylistContent = () => {
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
flexDirection: "column", flexDirection: "column",
padding: "0px 10px", padding: "0px",
marginLeft: "5%", marginLeft: "2%",
}} }}
onClick={focusVideo} onClick={focusVideo}
> >
<VideoPlayerContainer <VideoPlayerContainer
sx={{ sx={{
marginBottom: "30px", width: "100%",
display: "grid",
gridTemplateColumns: isScreenSmall ? "1fr" : "60vw auto",
gap: "20px",
}} }}
> >
<> {videoReference && (
<Box <Box
sx={{ sx={{
display: "grid", aspectRatio: "16/9",
gridTemplateColumns: "55vw 35vw",
width: "100vw",
gap: "3vw",
}} }}
> >
{videoReference && ( <VideoPlayer
<Box name={videoReference?.name}
sx={{ service={videoReference?.service}
aspectRatio: "16/9", identifier={videoReference?.identifier}
}} user={channelName}
> jsonId={id}
<VideoPlayer poster={videoCover || ""}
name={videoReference?.name} nextVideo={nextVideo}
service={videoReference?.service} onEnd={onEndVideo}
identifier={videoReference?.identifier} autoPlay={doAutoPlay}
user={channelName} ref={containerRef}
jsonId={id} videoStyles={{
poster={videoCover || ""} videoContainer: { aspectRatio: "16 / 9" },
nextVideo={nextVideo} video: { aspectRatio: "16 / 9" },
onEnd={onEndVideo} }}
autoPlay={doAutoPlay} />
ref={containerRef}
videoStyles={{
videoContainer: { aspectRatio: "16 / 9" },
video: { aspectRatio: "16 / 9" },
}}
/>
</Box>
)}
{playlistData && (
<Playlists
playlistData={playlistData}
currentVideoIdentifier={videoData?.id}
onClick={getVideoData}
/>
)}
</Box> </Box>
<VideoActionsBar
channelName={channelName}
videoData={videoData}
videoReference={videoReference}
superLikeList={superLikeList}
setSuperLikeList={setSuperLikeList}
sx={{ width: "100%" }}
/>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
width: "100%",
marginTop: "10px",
gap: "10px",
}}
>
<VideoTitle
variant="h1"
color="textPrimary"
sx={{
textAlign: "center",
}}
>
{videoData?.title}
</VideoTitle>
</Box>
{videoData?.created && (
<Typography
variant="h6"
sx={{
fontSize: "16px",
}}
color={theme.palette.text.primary}
>
{formatDate(videoData.created)}
</Typography>
)}
<Spacer height="30px" />
{videoData?.fullDescription && (
<Box
sx={{
background: "#333333",
borderRadius: "5px",
padding: "5px",
width: "100%",
cursor: !descriptionHeight
? "default"
: isExpandedDescription
? "default"
: "pointer",
position: "relative",
}}
className={
!descriptionHeight
? ""
: isExpandedDescription
? ""
: "hover-click"
}
>
{descriptionHeight && !isExpandedDescription && (
<Box
sx={{
position: "absolute",
top: "0px",
right: "0px",
left: "0px",
bottom: "0px",
cursor: "pointer",
}}
onClick={() => {
if (isExpandedDescription) return;
setIsExpandedDescription(true);
}}
/>
)}
<Box
ref={contentRef}
sx={{
height: !descriptionHeight
? "auto"
: isExpandedDescription
? "auto"
: `${descriptionHeight}px`,
overflow: "hidden",
}}
>
{videoData?.htmlDescription ? (
<DisplayHtml html={videoData?.htmlDescription} />
) : (
<VideoDescription
variant="body1"
color="textPrimary"
sx={{
cursor: "default",
}}
>
{videoData?.fullDescription}
</VideoDescription>
)}
</Box>
{descriptionHeight >= descriptionThreshold && (
<Typography
onClick={() => {
setIsExpandedDescription(prev => !prev);
}}
sx={{
fontWeight: "bold",
fontSize: "16px",
cursor: "pointer",
paddingLeft: "15px",
paddingTop: "15px",
}}
>
{isExpandedDescription ? "Show less" : "...more"}
</Typography>
)}
</Box>
)}
</>
{videoData?.id && videoData?.user && (
<SuperLikesSection
loadingSuperLikes={loadingSuperLikes}
superlikes={superLikeList}
postId={videoData?.id || ""}
postName={videoData?.user || ""}
/>
)} )}
{videoData?.id && channelName && ( {playlistData && (
<CommentSection <Playlists
postId={videoData?.id || ""} playlistData={playlistData}
postName={channelName || ""} currentVideoIdentifier={videoData?.id}
onClick={getVideoData}
/> />
)} )}
</VideoPlayerContainer> </VideoPlayerContainer>
<VideoActionsBar
channelName={channelName}
videoData={videoData}
videoReference={videoReference}
superLikeList={superLikeList}
setSuperLikeList={setSuperLikeList}
sx={{ width: "100%" }}
/>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
width: "100%",
marginTop: "10px",
gap: "10px",
}}
>
<VideoTitle
variant="h1"
color="textPrimary"
sx={{
textAlign: "center",
}}
>
{videoData?.title}
</VideoTitle>
</Box>
<Box
sx={{
display: "flex",
width: "100%",
gap: "20px",
}}
>
{videoData?.created && (
<Typography
variant="h2"
sx={{
fontSize: fontSizeSmall,
}}
color={theme.palette.text.primary}
>
{formatDate(videoData.created)}
</Typography>
)}
{videoData?.fileSize > minFileSize && (
<Typography
variant="h1"
sx={{
fontSize: "90%",
}}
color={"green"}
>
{formatBytes(videoData.fileSize, 2, "Decimal")}
</Typography>
)}
</Box>
<Spacer height="30px" />
{videoData?.fullDescription && (
<Box
sx={{
background: "#333333",
borderRadius: "5px",
padding: "5px",
width: "95%",
alignSelf: "flex-start",
cursor: !descriptionHeight
? "default"
: isExpandedDescription
? "default"
: "pointer",
}}
className={
!descriptionHeight ? "" : isExpandedDescription ? "" : "hover-click"
}
>
{descriptionHeight && !isExpandedDescription && (
<Box
sx={{
position: "absolute",
top: "0px",
right: "0px",
left: "0px",
bottom: "0px",
cursor: "pointer",
}}
onClick={() => {
if (isExpandedDescription) return;
setIsExpandedDescription(true);
}}
/>
)}
<Box
ref={contentRef}
sx={{
height: !descriptionHeight
? "auto"
: isExpandedDescription
? "auto"
: `${descriptionHeight}px`,
overflow: "hidden",
}}
>
{videoData?.htmlDescription ? (
<DisplayHtml html={videoData?.htmlDescription} />
) : (
<VideoDescription
variant="body1"
color="textPrimary"
sx={{
cursor: "default",
}}
>
{videoData?.fullDescription}
</VideoDescription>
)}
</Box>
{descriptionHeight >= descriptionThreshold && (
<Typography
onClick={() => {
setIsExpandedDescription(prev => !prev);
}}
sx={{
fontWeight: "bold",
fontSize: "16px",
cursor: "pointer",
paddingLeft: "15px",
paddingTop: "15px",
}}
>
{isExpandedDescription ? "Show less" : "...more"}
</Typography>
)}
</Box>
)}
{videoData?.id && videoData?.user && (
<SuperLikesSection
loadingSuperLikes={loadingSuperLikes}
superlikes={superLikeList}
postId={videoData?.id || ""}
postName={videoData?.user || ""}
/>
)}
{videoData?.id && channelName && (
<CommentSection
postId={videoData?.id || ""}
postName={channelName || ""}
/>
)}
</Box> </Box>
); );
}; };

View File

@ -1,75 +1,22 @@
import { Avatar, Box, useTheme } from "@mui/material"; import { Box, SxProps, Theme, useMediaQuery } from "@mui/material";
import { FollowButton } from "../../../components/common/ContentButtons/FollowButton.tsx"; import { smallScreenSizeString } from "../../../constants/Misc.ts";
import { SubscribeButton } from "../../../components/common/ContentButtons/SubscribeButton.tsx"; import { ChannelButtons } from "./ChannelButtons.tsx";
import { RootState } from "../../../state/store.ts"; import { ChannelName, ChannelParams } from "./ChannelName.tsx";
import { import { Spacer } from "./VideoContent-styles.tsx";
AuthorTextComment,
StyledCardColComment,
StyledCardHeaderComment,
} from "./VideoContent-styles.tsx";
import { useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
export interface ChannelActionsParams { export const ChannelActions = ({ channelName, sx }: ChannelParams) => {
channelName: string; const boxSX: SxProps<Theme> = {
} display: "flex",
export const ChannelActions = ({ channelName }: ChannelActionsParams) => { flexDirection: "row",
const navigate = useNavigate(); flexWrap: "wrap",
const theme = useTheme(); rowGap: "10px",
const userName = useSelector((state: RootState) => state.auth.user?.name); columnGap: "20px",
};
return ( return (
<Box> <Box sx={{ ...boxSX, ...sx }}>
<StyledCardHeaderComment <ChannelName channelName={channelName} />
sx={{ <ChannelButtons channelName={channelName} />
"& .MuiCardHeader-content": {
overflow: "hidden",
},
}}
>
<Box
sx={{
cursor: "pointer",
}}
onClick={() => {
navigate(`/channel/${channelName}`);
}}
>
<Avatar
src={`/arbitrary/THUMBNAIL/${channelName}/qortal_avatar`}
alt={`${channelName}'s avatar`}
/>
</Box>
<StyledCardColComment>
<AuthorTextComment
color={
theme.palette.mode === "light"
? theme.palette.text.secondary
: "#d6e8ff"
}
sx={{
cursor: "pointer",
}}
onClick={() => {
navigate(`/channel/${channelName}`);
}}
>
{channelName}
{channelName !== userName && (
<>
<SubscribeButton
subscriberName={channelName}
sx={{ marginLeft: "20px" }}
/>
<FollowButton
followerName={channelName}
sx={{ marginLeft: "20px" }}
/>
</>
)}
</AuthorTextComment>
</StyledCardColComment>
</StyledCardHeaderComment>
</Box> </Box>
); );
}; };

View File

@ -0,0 +1,24 @@
import { useSelector } from "react-redux";
import { FollowButton } from "../../../components/common/ContentButtons/FollowButton.tsx";
import { SubscribeButton } from "../../../components/common/ContentButtons/SubscribeButton.tsx";
import { RootState } from "../../../state/store.ts";
import { ChannelParams } from "./ChannelName.tsx";
import { StyledCardColComment } from "./VideoContent-styles.tsx";
export const ChannelButtons = ({ channelName, sx }: ChannelParams) => {
const userName = useSelector((state: RootState) => state.auth.user?.name);
return (
<StyledCardColComment sx={{ alignItems: "center", ...sx }}>
{channelName !== userName && (
<>
<SubscribeButton subscriberName={channelName} />
<FollowButton
followerName={channelName}
sx={{ marginLeft: "20px" }}
/>
</>
)}
</StyledCardColComment>
);
};

View File

@ -0,0 +1,58 @@
import { Avatar, Box, SxProps, Theme, useTheme } from "@mui/material";
import {
AuthorTextComment,
StyledCardHeaderComment,
} from "./VideoContent-styles.tsx";
import { useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
export interface ChannelParams {
channelName: string;
sx?: SxProps<Theme>;
}
export const ChannelName = ({ channelName }: ChannelParams) => {
const navigate = useNavigate();
const theme = useTheme();
return (
<StyledCardHeaderComment
sx={{
"& .MuiCardHeader-content": {
overflow: "hidden",
},
}}
>
<Box
sx={{
display: "flex",
flexDirection: "row",
alignItems: "center",
cursor: "pointer",
}}
onClick={() => {
navigate(`/channel/${channelName}`);
}}
>
<Avatar
src={`/arbitrary/THUMBNAIL/${channelName}/qortal_avatar`}
alt={`${channelName}'s avatar`}
/>
<AuthorTextComment
color={
theme.palette.mode === "light"
? theme.palette.text.secondary
: "#d6e8ff"
}
sx={{
cursor: "pointer",
display: "inline",
marginLeft: "10px",
}}
>
{channelName}
</AuthorTextComment>
</Box>
</StyledCardHeaderComment>
);
};

View File

@ -1,25 +1,18 @@
import { Avatar, Box, SxProps, Theme, useTheme } from "@mui/material"; import DownloadIcon from "@mui/icons-material/Download";
import { FollowButton } from "../../../components/common/ContentButtons/FollowButton.tsx"; import { Box, SxProps, Theme, useMediaQuery } from "@mui/material";
import { useMemo } from "react";
import { LikeAndDislike } from "../../../components/common/ContentButtons/LikeAndDislike.tsx"; import { LikeAndDislike } from "../../../components/common/ContentButtons/LikeAndDislike.tsx";
import { SubscribeButton } from "../../../components/common/ContentButtons/SubscribeButton.tsx";
import { SuperLike } from "../../../components/common/ContentButtons/SuperLike.tsx"; import { SuperLike } from "../../../components/common/ContentButtons/SuperLike.tsx";
import FileElement from "../../../components/common/FileElement.tsx"; import FileElement from "../../../components/common/FileElement.tsx";
import { videoRefType } from "../../../components/common/VideoPlayer/VideoPlayer.tsx"; import {
import { titleFormatterOnSave } from "../../../constants/Misc.ts"; smallScreenSizeString,
import { useFetchSuperLikes } from "../../../hooks/useFetchSuperLikes.tsx"; titleFormatterOnSave,
import DownloadIcon from "@mui/icons-material/Download"; } from "../../../constants/Misc.ts";
import { RootState } from "../../../state/store.ts";
import { ChannelActions } from "./ChannelActions.tsx"; import { ChannelActions } from "./ChannelActions.tsx";
import { import {
AuthorTextComment,
FileAttachmentContainer, FileAttachmentContainer,
FileAttachmentFont, FileAttachmentFont,
StyledCardColComment,
StyledCardHeaderComment,
} from "./VideoContent-styles.tsx"; } from "./VideoContent-styles.tsx";
import { useNavigate } from "react-router-dom";
import { useSelector } from "react-redux";
import { useMemo, useState } from "react";
export interface VideoActionsBarProps { export interface VideoActionsBarProps {
channelName: string; channelName: string;
@ -82,11 +75,12 @@ export const VideoActionsBar = ({
return ( return (
<Box <Box
sx={{ sx={{
width: "80%",
display: "grid",
gridTemplateColumns: "1fr 1fr",
marginTop: "15px", marginTop: "15px",
display: "flex",
flexDirection: "row",
alignItems: "center", alignItems: "center",
flexWrap: "wrap",
gap: "20px",
...sx, ...sx,
}} }}
> >
@ -95,6 +89,8 @@ export const VideoActionsBar = ({
sx={{ sx={{
display: "flex", display: "flex",
flexDirection: "row", flexDirection: "row",
height: "100%",
alignItems: "center",
}} }}
> >
{videoData && ( {videoData && (
@ -110,28 +106,30 @@ export const VideoActionsBar = ({
setSuperLikeList(prev => [val, ...prev]); setSuperLikeList(prev => [val, ...prev]);
}} }}
/> />
<FileAttachmentContainer>
<FileAttachmentFont>Save to Disk</FileAttachmentFont>
<FileElement
fileInfo={{
...videoReference,
filename: saveAsFilename,
mimeType: videoData?.videoType || '"video/mp4',
}}
title={videoData?.filename || videoData?.title?.slice(0, 20)}
customStyles={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
}}
>
<DownloadIcon />
</FileElement>
</FileAttachmentContainer>
</> </>
)} )}
</Box> </Box>
{videoData && (
<FileAttachmentContainer sx={{ width: "100%", maxWidth: "340px" }}>
<FileAttachmentFont>Save Video</FileAttachmentFont>
<FileElement
fileInfo={{
...videoReference,
filename: saveAsFilename,
mimeType: videoData?.videoType || '"video/mp4',
}}
title={videoData?.filename || videoData?.title?.slice(0, 20)}
customStyles={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
}}
>
<DownloadIcon />
</FileElement>
</FileAttachmentContainer>
)}
</Box> </Box>
); );
}; };

View File

@ -1,6 +1,7 @@
import { styled } from "@mui/system"; import { styled } from "@mui/system";
import { Box, Grid, Typography, Checkbox } from "@mui/material"; import { Box, Grid, Typography, Checkbox } from "@mui/material";
import { fontSizeMedium } from "../../../constants/Misc.ts"; import { fontSizeMedium, fontSizeSmall } from "../../../constants/Misc.ts";
import { useIsMobile } from "../../../hooks/useIsMobile.ts";
export const VideoContentContainer = styled(Box)(({ theme }) => ({ export const VideoContentContainer = styled(Box)(({ theme }) => ({
display: "flex", display: "flex",
@ -8,10 +9,7 @@ export const VideoContentContainer = styled(Box)(({ theme }) => ({
alignItems: "start", alignItems: "start",
})); }));
export const VideoPlayerContainer = styled(Box)(({ theme }) => ({ export const VideoPlayerContainer = styled(Box)(({ theme }) => ({}));
width: "55vw",
marginLeft: "5%",
}));
export const VideoTitle = styled(Typography)(({ theme }) => ({ export const VideoTitle = styled(Typography)(({ theme }) => ({
fontFamily: "Raleway", fontFamily: "Raleway",
@ -57,14 +55,14 @@ export const StyledCardCol = styled(Box)({
export const StyledCardColComment = styled(Box)({ export const StyledCardColComment = styled(Box)({
display: "flex", display: "flex",
overflow: "hidden", overflow: "hidden",
flexDirection: "column", flexDirection: "row",
gap: "2px", gap: "2px",
alignItems: "flex-start", alignItems: "flex-start",
}); });
export const AuthorTextComment = styled(Typography)({ export const AuthorTextComment = styled(Typography)({
fontFamily: "Raleway, sans-serif", fontFamily: "Raleway, sans-serif",
fontSize: "20px", fontSize: fontSizeMedium,
lineHeight: "1.2", lineHeight: "1.2",
}); });
@ -73,7 +71,6 @@ export const FileAttachmentContainer = styled(Box)(({ theme }) => ({
alignItems: "center", alignItems: "center",
padding: "5px 10px", padding: "5px 10px",
border: `1px solid ${theme.palette.text.primary}`, border: `1px solid ${theme.palette.text.primary}`,
width: "350px",
height: "50px", height: "50px",
})); }));

View File

@ -1,12 +1,18 @@
import { Box, Typography } from "@mui/material"; import { Box, Typography, useMediaQuery } from "@mui/material";
import React from "react"; import React, { useEffect, useState } from "react";
import DeletedVideo from "../../../assets/img/DeletedVideo.jpg"; import DeletedVideo from "../../../assets/img/DeletedVideo.jpg";
import { CommentSection } from "../../../components/common/Comments/CommentSection.tsx"; import { CommentSection } from "../../../components/common/Comments/CommentSection.tsx";
import { SuperLikesSection } from "../../../components/common/SuperLikesList/SuperLikesSection.tsx"; import { SuperLikesSection } from "../../../components/common/SuperLikesList/SuperLikesSection.tsx";
import { DisplayHtml } from "../../../components/common/TextEditor/DisplayHtml.tsx"; import { DisplayHtml } from "../../../components/common/TextEditor/DisplayHtml.tsx";
import { VideoPlayer } from "../../../components/common/VideoPlayer/VideoPlayer.tsx"; import { VideoPlayer } from "../../../components/common/VideoPlayer/VideoPlayer.tsx";
import { minFileSize } from "../../../constants/Misc.ts"; import {
fontSizeSmall,
largeScreenSizeString,
minFileSize,
smallScreenSizeString,
} from "../../../constants/Misc.ts";
import { useIsMobile } from "../../../hooks/useIsMobile.ts";
import { formatBytes } from "../../../utils/numberFunctions.ts"; import { formatBytes } from "../../../utils/numberFunctions.ts";
import { formatDate } from "../../../utils/time.ts"; import { formatDate } from "../../../utils/time.ts";
import { VideoActionsBar } from "./VideoActionsBar.tsx"; import { VideoActionsBar } from "./VideoActionsBar.tsx";
@ -18,6 +24,7 @@ import {
VideoPlayerContainer, VideoPlayerContainer,
VideoTitle, VideoTitle,
} from "./VideoContent-styles.tsx"; } from "./VideoContent-styles.tsx";
import { useSignal } from "@preact/signals-react";
export const VideoContent = () => { export const VideoContent = () => {
const { const {
@ -39,18 +46,47 @@ export const VideoContent = () => {
superLikeList, superLikeList,
setSuperLikeList, setSuperLikeList,
} = useVideoContentState(); } = useVideoContentState();
const isScreenSmall = !useMediaQuery(`(min-width:${smallScreenSizeString})`);
const [screenWidth, setScreenWidth] = useState<number>(
window.innerWidth + 120
);
let videoWidth = 100;
const maxWidth = 95;
const pixelsPerPercent = 17.5;
const smallScreenPixels = 700;
if (!isScreenSmall)
videoWidth =
maxWidth - (screenWidth - smallScreenPixels) / pixelsPerPercent;
const minWidthPercent = 70;
if (videoWidth < minWidthPercent) videoWidth = minWidthPercent;
useEffect(() => {
window.addEventListener("resize", e => {
setScreenWidth(window.innerWidth + 120);
});
}, []);
return ( return (
<> <>
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
padding: "0px 10px 0px 5%", padding: `0px 0px 0px ${isScreenSmall ? "5px" : "2%"}`,
width: "100%",
}} }}
onClick={focusVideo} onClick={focusVideo}
> >
{videoReference ? ( {videoReference ? (
<VideoPlayerContainer> <VideoPlayerContainer
sx={{
width: `${videoWidth}%`,
marginLeft: "0%",
}}
>
<VideoPlayer <VideoPlayer
name={videoReference?.name} name={videoReference?.name}
service={videoReference?.service} service={videoReference?.service}
@ -77,64 +113,61 @@ export const VideoContent = () => {
<Box sx={{ width: "55vw", aspectRatio: "16/9" }}></Box> <Box sx={{ width: "55vw", aspectRatio: "16/9" }}></Box>
)} )}
<VideoContentContainer> <VideoContentContainer>
<VideoTitle
variant={isScreenSmall ? "h2" : "h1"}
color="textPrimary"
sx={{
textAlign: "start",
marginTop: "10px",
}}
>
{videoData?.title}
</VideoTitle>
<Box>
{videoData?.created && (
<Typography
variant="h2"
sx={{
fontSize: fontSizeSmall,
display: "inline",
}}
color={theme.palette.text.primary}
>
{formatDate(videoData.created)}
</Typography>
)}
{videoData?.fileSize > minFileSize && (
<Typography
variant="h1"
sx={{
fontSize: "90%",
display: "inline",
marginLeft: "20px",
}}
color={"green"}
>
{formatBytes(videoData.fileSize, 2, "Decimal")}
</Typography>
)}
</Box>
<VideoActionsBar <VideoActionsBar
channelName={channelName} channelName={channelName}
videoData={videoData} videoData={videoData}
setSuperLikeList={setSuperLikeList} setSuperLikeList={setSuperLikeList}
superLikeList={superLikeList} superLikeList={superLikeList}
videoReference={videoReference} videoReference={videoReference}
sx={{ width: "calc(100% - 5px)" }}
/> />
<Box <Spacer height="15px" />
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
width: "100%",
marginTop: "20px",
gap: "10px",
}}
>
<VideoTitle
variant="h1"
color="textPrimary"
sx={{
textAlign: "start",
}}
>
{videoData?.title}
</VideoTitle>
</Box>
{videoData?.fileSize > minFileSize && (
<Typography
variant="h1"
sx={{
fontSize: "90%",
}}
color={"green"}
>
{formatBytes(videoData.fileSize, 2, "Decimal")}
</Typography>
)}
{videoData?.created && (
<Typography
variant="h6"
sx={{
fontSize: "16px",
}}
color={theme.palette.text.primary}
>
{formatDate(videoData.created)}
</Typography>
)}
<Spacer height="30px" />
{videoData?.fullDescription && ( {videoData?.fullDescription && (
<Box <Box
sx={{ sx={{
background: "#333333", background: "#333333",
borderRadius: "5px", borderRadius: "5px",
padding: "5px", padding: "5px",
width: "70%", width: "95%",
cursor: !descriptionHeight cursor: !descriptionHeight
? "default" ? "default"
: isExpandedDescription : isExpandedDescription

View File

@ -7,9 +7,11 @@ import {
MenuItem, MenuItem,
OutlinedInput, OutlinedInput,
Select, Select,
useMediaQuery,
} from "@mui/material"; } from "@mui/material";
import { StatsData } from "../../../components/StatsData.tsx"; import { StatsData } from "../../../components/StatsData.tsx";
import { categories, subCategories } from "../../../constants/Categories.ts"; import { categories, subCategories } from "../../../constants/Categories.ts";
import { smallScreenSizeString } from "../../../constants/Misc.ts";
import { useSidebarState } from "./SearchSidebar-State.ts"; import { useSidebarState } from "./SearchSidebar-State.ts";
import { import {
FiltersCol, FiltersCol,
@ -38,8 +40,19 @@ export const SearchSidebar = ({ onSearch }: SearchSidebarProps) => {
filtersToDefault, filtersToDefault,
} = useSidebarState(onSearch); } = useSidebarState(onSearch);
const filtersStyle = { width: "75px", marginRight: "10px" };
const isScreenSmall = !useMediaQuery(`(min-width:600px)`);
return ( return (
<FiltersCol item xs={12} md={2} lg={2} xl={2} sm={3}> <Box
sx={{
marginLeft: "5px",
marginRight: isScreenSmall ? "5px" : "0px",
alignItems: "center",
display: "flex",
flexDirection: "column",
}}
>
<FiltersContainer> <FiltersContainer>
<StatsData /> <StatsData />
<Input <Input
@ -101,12 +114,11 @@ export const SearchSidebar = ({ onSearch }: SearchSidebarProps) => {
/> />
<FiltersSubContainer> <FiltersSubContainer>
<FormControl sx={{ width: "100%", marginTop: "30px" }}> <FormControl sx={{ width: "98%", marginTop: "30px" }}>
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
gap: "20px", gap: "20px",
alignItems: "center",
flexDirection: "column", flexDirection: "column",
}} }}
> >
@ -201,7 +213,7 @@ export const SearchSidebar = ({ onSearch }: SearchSidebarProps) => {
</FiltersSubContainer> </FiltersSubContainer>
<FiltersSubContainer> <FiltersSubContainer>
<FiltersRow> <FiltersRow>
Videos <span style={filtersStyle}>Videos</span>
<FiltersRadioButton <FiltersRadioButton
checked={filterType === "videos"} checked={filterType === "videos"}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => { onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@ -211,7 +223,7 @@ export const SearchSidebar = ({ onSearch }: SearchSidebarProps) => {
/> />
</FiltersRow> </FiltersRow>
<FiltersRow> <FiltersRow>
Playlists <span style={filtersStyle}> Playlists</span>
<FiltersRadioButton <FiltersRadioButton
checked={filterType === "playlists"} checked={filterType === "playlists"}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => { onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@ -221,12 +233,15 @@ export const SearchSidebar = ({ onSearch }: SearchSidebarProps) => {
/> />
</FiltersRow> </FiltersRow>
</FiltersSubContainer> </FiltersSubContainer>
<Button <Button
onClick={() => { onClick={() => {
filtersToDefault(); filtersToDefault();
}} }}
sx={{ sx={{
marginTop: "20px", marginTop: "20px",
width: "80%",
alignSelf: "center",
}} }}
variant="contained" variant="contained"
> >
@ -238,12 +253,14 @@ export const SearchSidebar = ({ onSearch }: SearchSidebarProps) => {
}} }}
sx={{ sx={{
marginTop: "20px", marginTop: "20px",
width: "80%",
alignSelf: "center",
}} }}
variant="contained" variant="contained"
> >
Search Search
</Button> </Button>
</FiltersContainer> </FiltersContainer>
</FiltersCol> </Box>
); );
}; };

View File

@ -9,6 +9,7 @@ import {
Autocomplete, Autocomplete,
Radio, Radio,
} from "@mui/material"; } from "@mui/material";
import { fontSizeMedium, fontSizeSmall } from "../../../constants/Misc.ts";
export const VideoContainer = styled(Grid)(({ theme }) => ({ export const VideoContainer = styled(Grid)(({ theme }) => ({
position: "relative", position: "relative",
@ -23,7 +24,7 @@ export const VideoContainer = styled(Grid)(({ theme }) => ({
export const VideoCardContainer = styled("div")(({ theme }) => ({ export const VideoCardContainer = styled("div")(({ theme }) => ({
display: "grid", display: "grid",
gridTemplateColumns: "repeat(auto-fill, minmax(250px, 1fr))", gridTemplateColumns: "repeat(auto-fill, minmax(200px, 1fr))",
gap: theme.spacing(2), gap: theme.spacing(2),
padding: "10px", padding: "10px",
width: "100%", width: "100%",
@ -31,7 +32,7 @@ export const VideoCardContainer = styled("div")(({ theme }) => ({
export const VideoCardCol = styled("div")({ export const VideoCardCol = styled("div")({
position: "relative", position: "relative",
minWidth: "250px", // Minimum width of each item minWidth: "200px", // Minimum width of each item
maxWidth: "1fr", // Maximum width, allowing the item to fill the column maxWidth: "1fr", // Maximum width, allowing the item to fill the column
// ... other styles // ... other styles
}); });
@ -132,16 +133,14 @@ export const FiltersCol = styled(Grid)(({ theme }) => ({
export const FiltersContainer = styled(Box)(({ theme }) => ({ export const FiltersContainer = styled(Box)(({ theme }) => ({
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
justifyContent: "space-between",
})); }));
export const FiltersRow = styled(Box)(({ theme }) => ({ export const FiltersRow = styled(Box)(({ theme }) => ({
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "space-between", justifyContent: "center",
width: "100%", width: "100%",
padding: "0 15px", fontSize: fontSizeSmall,
fontSize: "16px",
userSelect: "none", userSelect: "none",
})); }));
@ -161,7 +160,7 @@ export const FiltersRadioButton = styled(Radio)(({ theme }) => ({
export const FiltersSubContainer = styled(Box)(({ theme }) => ({ export const FiltersSubContainer = styled(Box)(({ theme }) => ({
display: "flex", display: "flex",
alignItems: "center", alignItems: "start",
flexDirection: "column", flexDirection: "column",
gap: "5px", gap: "5px",
})); }));

View File

@ -145,7 +145,6 @@ export const VideoList = ({ videos }: VideoListProps) => {
maxHeight: "50%", maxHeight: "50%",
}} }}
/> />
<VideoCardTitle>{videoObj?.title}</VideoCardTitle> <VideoCardTitle>{videoObj?.title}</VideoCardTitle>
<BottomParent> <BottomParent>
<NameContainer <NameContainer

View File

@ -1,10 +1,15 @@
import { TabContext, TabList, TabPanel } from "@mui/lab"; import { TabContext, TabList, TabPanel } from "@mui/lab";
import { Box, Grid, Tab } from "@mui/material"; import { Box, Grid, Tab, useMediaQuery } from "@mui/material";
import React from "react"; import React from "react";
import LazyLoad from "../../components/common/LazyLoad"; import LazyLoad from "../../components/common/LazyLoad";
import { ListSuperLikeContainer } from "../../components/common/ListSuperLikes/ListSuperLikeContainer.tsx"; import { ListSuperLikeContainer } from "../../components/common/ListSuperLikes/ListSuperLikeContainer.tsx";
import { fontSizeMedium } from "../../constants/Misc.ts"; import {
fontSizeLarge,
fontSizeMedium,
fontSizeSmall,
} from "../../constants/Misc.ts";
import { useIsMobile } from "../../hooks/useIsMobile.ts";
import { SearchSidebar } from "./Components/SearchSidebar.tsx"; import { SearchSidebar } from "./Components/SearchSidebar.tsx";
import { FiltersCol, VideoManagerRow } from "./Components/VideoList-styles.tsx"; import { FiltersCol, VideoManagerRow } from "./Components/VideoList-styles.tsx";
import VideoList from "./Components/VideoList.tsx"; import VideoList from "./Components/VideoList.tsx";
@ -29,79 +34,77 @@ export const Home = ({ mode }: HomeProps) => {
paddingLeft: "0px", paddingLeft: "0px",
paddingRight: "0px", paddingRight: "0px",
}; };
const isScreenSmall = !useMediaQuery("(min-width:600px)");
const isScreenLarge = useMediaQuery("(min-width:1200px)");
const tabSX = {
fontSize: isScreenSmall ? fontSizeSmall : fontSizeLarge,
paddingLeft: "0px",
paddingRight: "0px",
};
const homeBaseSX = { display: "grid", width: "100%" };
const bigGridSX = { gridTemplateColumns: "200px auto 250px" };
const mediumGridSX = { gridTemplateColumns: "200px auto" };
const smallGridSX = { gridTemplateColumns: "100%", gap: "20px" };
let homeColumns: object;
if (isScreenLarge) homeColumns = bigGridSX;
else if (!isScreenSmall) homeColumns = mediumGridSX;
else homeColumns = smallGridSX;
return ( return (
<> <>
<Grid container sx={{ width: "100%" }}> <Box sx={{ ...homeBaseSX, ...homeColumns }}>
<SearchSidebar onSearch={getVideosHandler} /> <SearchSidebar onSearch={getVideosHandler} />
<Grid item xs={12} md={10} lg={7} xl={8} sm={9}>
<VideoManagerRow> <Box
<Box sx={{
sx={{ width: "100%",
width: "100%", display: "flex",
display: "flex", flexDirection: "column",
flexDirection: "column", alignItems: "center",
alignItems: "center", }}
marginTop: "20px", >
}} <TabContext value={tabValue}>
<TabList
onChange={changeTab}
textColor={"secondary"}
indicatorColor={"secondary"}
centered={false}
> >
<SubtitleContainer <Tab label="All" value={"all"} sx={tabSX} />
sx={{ <Tab label="Subscriptions" value={"subscriptions"} sx={tabSX} />
justifyContent: "flex-start", </TabList>
paddingLeft: "15px", <TabPanel value={"all"} sx={tabPaneSX}>
width: "100%", <VideoList videos={videos} />
maxWidth: "1400px", <LazyLoad
}} onLoadMore={getVideosHandler}
></SubtitleContainer> isLoading={isLoading}
<TabContext value={tabValue}> ></LazyLoad>
<TabList </TabPanel>
onChange={changeTab} <TabPanel value={"subscriptions"} sx={tabPaneSX}>
textColor={"secondary"} {filteredSubscriptionList.length > 0 ? (
indicatorColor={"secondary"} <>
>
<Tab
label="All Videos"
value={"all"}
sx={{ fontSize: fontSizeMedium }}
/>
<Tab
label="Subscriptions"
value={"subscriptions"}
sx={{ fontSize: fontSizeMedium }}
/>
</TabList>
<TabPanel value={"all"} sx={tabPaneSX}>
<VideoList videos={videos} /> <VideoList videos={videos} />
<LazyLoad <LazyLoad
onLoadMore={getVideosHandler} onLoadMore={getVideosHandler}
isLoading={isLoading} isLoading={isLoading}
></LazyLoad> ></LazyLoad>
</TabPanel> </>
<TabPanel value={"subscriptions"} sx={tabPaneSX}> ) : (
{filteredSubscriptionList.length > 0 ? ( !isLoading && (
<> <div style={{ textAlign: "center" }}>
<VideoList videos={videos} /> You have no subscriptions
<LazyLoad </div>
onLoadMore={getVideosHandler} )
isLoading={isLoading} )}
></LazyLoad> </TabPanel>
</> </TabContext>
) : !isLoading ? ( </Box>
<div style={{ textAlign: "center" }}>
You have no subscriptions <ListSuperLikeContainer />
</div> </Box>
) : (
<></>
)}
</TabPanel>
</TabContext>
</Box>
</VideoManagerRow>
</Grid>
<FiltersCol item xs={0} lg={3} xl={2}>
<ListSuperLikeContainer />
</FiltersCol>
</Grid>
</> </>
); );
}; };

View File

@ -19,6 +19,8 @@ interface settingsState {
subscriptionListFilter: SubscriptionListFilterType; subscriptionListFilter: SubscriptionListFilterType;
showStats: boolean; showStats: boolean;
volume: number; volume: number;
mutedVolume: number;
isMuted: boolean;
} }
const initialState: settingsState = { const initialState: settingsState = {
@ -30,6 +32,8 @@ const initialState: settingsState = {
subscriptionListFilter: "currentNameOnly", subscriptionListFilter: "currentNameOnly",
showStats: true, showStats: true,
volume: 0.5, volume: 0.5,
mutedVolume: 0,
isMuted: false,
}; };
export const persistSlice = createSlice({ export const persistSlice = createSlice({
@ -77,6 +81,12 @@ export const persistSlice = createSlice({
setVolumeSetting: (state, action: PayloadAction<number>) => { setVolumeSetting: (state, action: PayloadAction<number>) => {
state.volume = action.payload; state.volume = action.payload;
}, },
setMutedVolumeSetting: (state, action: PayloadAction<number>) => {
state.mutedVolume = action.payload;
},
setIsMuted: (state, action: PayloadAction<boolean>) => {
state.isMuted = action.payload;
},
}, },
}); });
@ -89,6 +99,8 @@ export const {
changeFilterType, changeFilterType,
resetSubscriptions, resetSubscriptions,
setVolumeSetting, setVolumeSetting,
setMutedVolumeSetting,
setIsMuted,
} = persistSlice.actions; } = persistSlice.actions;
export default persistSlice.reducer; export default persistSlice.reducer;