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",
"private": true,
"version": "2.0.0",
"version": "2.1.0",
"type": "module",
"scripts": {
"dev": "vite",

View File

@ -1,10 +1,11 @@
import React from "react";
import { smallScreenSizeString } from "../../constants/Misc.ts";
import { CardContentContainerComment } from "../common/Comments/Comments-styles";
import {
CrowdfundSubTitle,
CrowdfundSubTitleRow,
} 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";
export const Playlists = ({
@ -13,7 +14,8 @@ export const Playlists = ({
onClick,
}) => {
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 (
<Box
@ -21,7 +23,7 @@ export const Playlists = ({
display: "flex",
flexDirection: "column",
width: "100%",
maxHeight: "30.94vw",
height: isScreenSmall ? "200px" : videoPlayerHeight,
}}
>
<CardContentContainerComment
@ -32,7 +34,7 @@ export const Playlists = ({
}}
>
{playlistData?.videos?.map((vid, index) => {
const isCurrentVidPlayling =
const isCurrentVidPlaying =
vid?.identifier === currentVideoIdentifier;
return (
@ -42,15 +44,15 @@ export const Playlists = ({
display: "flex",
gap: "10px",
width: "100%",
background: isCurrentVidPlayling && theme.palette.primary.main,
background: isCurrentVidPlaying && theme.palette.primary.main,
alignItems: "center",
padding: "10px",
borderRadius: "5px",
cursor: isCurrentVidPlayling ? "default" : "pointer",
cursor: isCurrentVidPlaying ? "default" : "pointer",
userSelect: "none",
}}
onClick={() => {
if (isCurrentVidPlayling) return;
if (isCurrentVidPlaying) return;
onClick(vid.name, vid.identifier);
// navigate(`/video/${vid.name}/${vid.identifier}`)
}}

View File

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

View File

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

View File

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

View File

@ -1,32 +1,29 @@
import React, { useEffect, useState } from "react";
import ThumbUpIcon from "@mui/icons-material/ThumbUp";
import {
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
FormControl,
Input,
InputAdornment,
InputLabel,
MenuItem,
Modal,
Select,
Tooltip,
} from "@mui/material";
import qortImg from "../../../assets/img/qort.png";
import { MultiplePublish } from "../../Publish/MultiplePublish/MultiplePublishAll.tsx";
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { setNotification } from "../../../state/features/notificationsSlice.ts";
import ShortUniqueId from "short-unique-id";
import qortImg from "../../../assets/img/qort.png";
import {
objectToBase64,
objectToFile,
} from "../../../utils/PublishFormatter.ts";
FOR,
FOR_SUPER_LIKE,
SUPER_LIKE_BASE,
} from "../../../constants/Identifiers.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 {
CrowdfundActionButton,
CrowdfundActionButtonRow,
@ -34,16 +31,7 @@ import {
NewCrowdfundTitle,
Spacer,
} from "../../Publish/PublishVideo/PublishVideo-styles.tsx";
import { utf8ToBase64 } from "../SuperLikesList/CommentEditor.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";
import { CommentInput } from "../Comments/Comments-styles.tsx";
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 {
Accordion,
AccordionDetails,
@ -11,29 +11,28 @@ import {
ListItemIcon,
Popover,
Typography,
useTheme
} from '@mui/material'
import { Movie } from '@mui/icons-material'
import { useSelector } from 'react-redux'
import { RootState } from '../../state/store'
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
import { useLocation, useNavigate } from 'react-router-dom'
import { DownloadingLight } from '../../assets/svgs/DownloadingLight'
import { DownloadedLight } from '../../assets/svgs/DownloadedLight'
useTheme,
} from "@mui/material";
import { Movie } from "@mui/icons-material";
import { useSelector } from "react-redux";
import { RootState } from "../../state/store";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import { useLocation, useNavigate } from "react-router-dom";
import { DownloadingLight } from "../../assets/svgs/DownloadingLight";
import { DownloadedLight } from "../../assets/svgs/DownloadedLight";
export const DownloadTaskManager: React.FC = () => {
const { downloads } = useSelector((state: RootState) => state.global)
const location = useLocation()
const theme = useTheme()
const [visible, setVisible] = useState(false)
const [hidden, setHidden] = useState(true)
const navigate = useNavigate()
const { downloads } = useSelector((state: RootState) => state.global);
const theme = useTheme();
const [visible, setVisible] = useState(false);
const [hidden, setHidden] = useState(true);
const navigate = useNavigate();
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
const [openDownload, setOpenDownload] = useState<boolean>(false);
const hashMapVideos = useSelector(
(state: RootState) => state.video.hashMapVideos
);
const handleClick = (event?: React.MouseEvent<HTMLDivElement>) => {
const target = event?.currentTarget as unknown as HTMLButtonElement | null;
setAnchorEl(target);
@ -49,44 +48,49 @@ export const DownloadTaskManager: React.FC = () => {
if (visible) {
setTimeout(() => {
setHidden(true)
setVisible(false)
}, 3000)
setHidden(true);
setVisible(false);
}, 3000);
}
}, [visible])
}, [visible]);
useEffect(() => {
if (Object.keys(downloads).length === 0) return
setVisible(true)
setHidden(false)
}, [downloads])
if (Object.keys(downloads).length === 0) return;
setVisible(true);
setHidden(false);
}, [downloads]);
if (!downloads || Object.keys(downloads).length === 0) return null;
let downloadInProgress = false;
if (
!downloads ||
Object.keys(downloads).length === 0
Object.keys(downloads).find(
key =>
downloads[key]?.status?.status !== "READY" &&
downloads[key]?.status?.status !== "DOWNLOADED"
)
return null
let downloadInProgress = false
if(Object.keys(downloads).find((key)=> (downloads[key]?.status?.status !== 'READY' && downloads[key]?.status?.status !== 'DOWNLOADED'))){
downloadInProgress = true
) {
downloadInProgress = true;
}
return (
<Box>
<Button onClick={(e: any) => {
<Button
sx={{ padding: "0px 0px", minWidth: "0px" }}
onClick={(e: any) => {
handleClick(e);
setOpenDownload(true);
}}>
}}
>
{downloadInProgress ? (
<DownloadingLight height='24px' width='24px' className='download-icon' />
<DownloadingLight
height="24px"
width="24px"
className="download-icon"
/>
) : (
<DownloadedLight height='24px' width='24px' />
<DownloadedLight height="24px" width="24px" />
)}
</Button>
<Popover
@ -96,108 +100,107 @@ export const DownloadTaskManager: React.FC = () => {
onClose={handleCloseDownload}
anchorOrigin={{
vertical: "bottom",
horizontal: "left"
horizontal: "left",
}}
sx={{ marginTop: "12px" }}
>
<List
sx={{
maxHeight: '50vh',
overflow: 'auto',
width: '250px',
gap: '5px',
display: 'flex',
flexDirection: 'column',
maxHeight: "50vh",
overflow: "auto",
width: "100%",
maxWidth: "400px",
gap: "5px",
display: "flex",
flexDirection: "column",
backgroundColor: "#555555",
}}
>
{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
{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;
const id =
downloadObj?.identifier + "_metadata-" + downloadObj?.name;
const videoTitle = hashMapVideos[id]?.title;
return (
<ListItem
key={downloadObj?.identifier}
sx={{
display: 'flex',
flexDirection: 'column',
width: '100%',
justifyContent: 'center',
display: "flex",
flexDirection: "column",
width: "100%",
justifyContent: "center",
background: theme.palette.primary.main,
color: theme.palette.text.primary,
cursor: 'pointer',
padding: '2px',
cursor: "pointer",
padding: "2px",
}}
onClick={() => {
const id = downloadObj?.properties?.jsonId
if (!id) return
const userName = downloadObj?.name;
const identifier = downloadObj?.identifier;
navigate(
`/video/${downloadObj?.properties?.user}/${id}`
)
if (identifier && userName)
navigate(`/video/${userName}/${identifier}_metadata`);
}}
>
<Box
sx={{
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between'
width: "100%",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
}}
>
<ListItemIcon>
{service === 'VIDEO' && (
{service === "VIDEO" && (
<Movie sx={{ color: theme.palette.text.primary }} />
)}
</ListItemIcon>
<Box
sx={{ width: '100px', marginLeft: 1, marginRight: 1 }}
>
<Box sx={{ width: "100px", marginLeft: 1, marginRight: 1 }}>
<LinearProgress
variant="determinate"
value={progress}
sx={{
borderRadius: '5px',
color: theme.palette.secondary.main
borderRadius: "5px",
color: theme.palette.secondary.main,
}}
/>
</Box>
<Typography
sx={{
fontFamily: 'Arial',
color: theme.palette.text.primary
fontFamily: "Arial",
color: theme.palette.text.primary,
}}
variant="caption"
>
{`${progress?.toFixed(0)}%`}{' '}
{status && status === 'REFETCHING' && '- refetching'}
{status && status === 'DOWNLOADED' && '- building'}
{`${progress?.toFixed(0)}%`}{" "}
{status && status === "REFETCHING" && "- refetching"}
{status && status === "DOWNLOADED" && "- building"}
</Typography>
</Box>
<Typography
sx={{
fontSize: '10px',
width: '100%',
textAlign: 'end',
fontFamily: 'Arial',
fontSize: "10px",
width: "100%",
textAlign: "start",
fontFamily: "Arial",
color: theme.palette.text.primary,
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: "ellipsis",
whiteSpace: "nowrap",
overflow: "hidden",
}}
>
{downloadObj?.identifier}
{videoTitle || downloadObj?.identifier}
</Typography>
</ListItem>
)
);
})}
</List>
</Popover>
</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 { PopMenu } from "../PopMenu.tsx";
import ListSuperLikes from "./ListSuperLikes";
import { useSelector } from "react-redux";
import { RootState } from "../../../state/store";
import ThumbUpIcon from "@mui/icons-material/ThumbUp";
export const ListSuperLikeContainer = () => {
const superlikelist = useSelector(
(state: RootState) => state.global.superlikelistAll
);
return (
<Box>
const isScreenLarge = useMediaQuery("(min-width:1200px)");
const superlikeListComponent = (
<>
<Typography
sx={{
fontSize: "18px",
@ -20,6 +22,53 @@ export const ListSuperLikeContainer = () => {
Recent Super likes
</Typography>
<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>
);
};

View File

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

View File

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

View File

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

View File

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

View File

@ -7,12 +7,14 @@ import {
VolumeOff,
VolumeUp,
} 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 { ControlsContainer } from "../VideoPlayer-styles.ts";
import { MobileControls } from "./MobileControls.tsx";
import { useVideoContext } from "./VideoContext.ts";
import { useSignalEffect } from "@preact/signals-react";
export const VideoControls = () => {
const {
@ -28,7 +30,6 @@ export const VideoControls = () => {
from,
videoRef,
canPlay,
isMobileView,
isMuted,
playbackRate,
playing,
@ -37,8 +38,8 @@ export const VideoControls = () => {
showControlsFullScreen,
} = useVideoContext();
const showMobileControls =
isMobileView.value && canPlay.value && showControlsFullScreen.value;
const isScreenSmall = !useMediaQuery(`(min-width:580px)`);
const showMobileControls = isScreenSmall && canPlay.value;
return (
<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, {
useContext,
useEffect,
@ -9,7 +13,14 @@ import React, {
import { useDispatch, useSelector } from "react-redux";
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 { MyContext } from "../../../wrappers/DownloadWrapper.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 playing = useSignal(false);
const isMuted = useSignal(false);
const progress = useSignal(0);
const isLoading = useSignal(false);
const canPlay = useSignal(false);
const startPlay = useSignal(false);
const isMobileView = useSignal(false);
const isMuted = useSignal(persistSelector.isMuted);
const volume = useSignal(persistSelector.volume);
const mutedVolume = useSignal(persistSelector.volume);
const mutedVolume = useSignal(persistSelector.mutedVolume);
const playbackRate = useSignal(persistSelector.playbackRate);
const anchorEl = useSignal(null);
const videoObjectFit = useSignal<StretchVideoType>(
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 {
name,
identifier,
@ -279,7 +310,6 @@ export const useVideoPlayerState = (props: VideoPlayerProps, ref: any) => {
isLoading,
canPlay,
startPlay,
isMobileView,
volume,
mutedVolume,
playbackRate,

View File

@ -55,7 +55,6 @@ export const VideoPlayer = forwardRef<videoRefType, VideoPlayerProps>(
startPlay,
videoObjectFit,
showControlsFullScreen,
duration,
} = contextData;
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 { LightModeSVG } from "../../../assets/svgs/LightModeSVG";
import { DarkModeSVG } from "../../../assets/svgs/DarkModeSVG";
import { fontSizeSmall } from "../../../constants/Misc.ts";
export const CustomAppBar = styled(AppBar)(({ theme }) => ({
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
justifyContent: "start",
alignItems: "center",
width: "100%",
padding: "5px 16px",
padding: "5px 16px 5px 5px",
backgroundImage: "none",
borderBottom: `1px solid ${theme.palette.primary.light}`,
backgroundColor: theme.palette.background.default,
[theme.breakpoints.only("xs")]: {
gap: "15px",
},
height: "50px",
}));
export const LogoContainer = styled("div")({
@ -85,16 +83,15 @@ export const DropdownText = styled(Typography)(({ theme }) => ({
export const NavbarName = styled(Typography)(({ theme }) => ({
fontFamily: "Raleway",
fontSize: "18px",
fontSize: fontSizeSmall,
color: theme.palette.text.primary,
margin: "0 10px",
marginRight: "10px",
}));
export const ThemeSelectRow = styled(Box)({
display: "flex",
alignItems: "center",
gap: "5px",
flexBasis: 0,
gap: "10px",
height: "100%",
});

View File

@ -1,43 +1,12 @@
import React, { useState, useRef } from "react";
import { Box, Button, Input, Popover, useTheme } from "@mui/material";
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 { Box, useMediaQuery } from "@mui/material";
import React from "react";
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 { PublishMenu } from "./Components/PublishMenu.tsx";
import { QtubeLogo } from "./Components/QtubeLogo.tsx";
import { UserMenu } from "./Components/UserMenu.tsx";
import { CustomAppBar } from "./Navbar-styles";
interface Props {
isAuthenticated: boolean;
userName: string | null;
@ -46,296 +15,39 @@ interface Props {
setTheme: (val: string) => void;
}
const NavBar: React.FC<Props> = ({
isAuthenticated,
userName,
userAvatar,
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);
};
const NavBar: React.FC<Props> = ({ isAuthenticated, userName, userAvatar }) => {
const isScreenSmall = !useMediaQuery(`(min-width:600px)`);
const isSecure = isAuthenticated && !!userName;
const gapSize = 10;
return (
<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
sx={{
display: "flex",
alignItems: "center",
gap: "10px",
}}
>
<Popover
id={idNotification}
open={openPopover}
anchorEl={anchorElNotification}
onClose={closeNotificationPopover}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
width: "100%",
justifyContent: "space-between",
gap: `${gapSize}px`,
}}
>
<QtubeLogo />
<Box
sx={{
display: "flex",
gap: `${isScreenSmall ? gapSize : gapSize * 2}px`,
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
sx={{
cursor: "pointer",
}}
onClick={() => {
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 />}
{isSecure && <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"
<UserMenu
isShowMenu={isSecure}
userAvatar={userAvatar}
userName={userName}
/>
) : (
<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>
</CustomAppBar>
);

View File

@ -14,3 +14,12 @@ export const fontSizeExLarge = "150%";
export const maxCommentLength = 10_000;
export const minFileSize = 1_000;
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 { CommentSection } from "../../../components/common/Comments/CommentSection.tsx";
import { SuperLikesSection } from "../../../components/common/SuperLikesList/SuperLikesSection.tsx";
import { DisplayHtml } from "../../../components/common/TextEditor/DisplayHtml.tsx";
import { VideoPlayer } from "../../../components/common/VideoPlayer/VideoPlayer.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 { VideoActionsBar } from "../VideoContent/VideoActionsBar.tsx";
import { usePlaylistContentState } from "./PlaylistContent-State.ts";
@ -40,6 +46,8 @@ export const PlaylistContent = () => {
loadingSuperLikes,
} = usePlaylistContentState();
const isScreenSmall = !useMediaQuery(`(min-width:700px)`);
return videoData && videoData?.videos?.length === 0 ? (
<Box
sx={{
@ -55,23 +63,17 @@ export const PlaylistContent = () => {
display: "flex",
alignItems: "center",
flexDirection: "column",
padding: "0px 10px",
marginLeft: "5%",
padding: "0px",
marginLeft: "2%",
}}
onClick={focusVideo}
>
<VideoPlayerContainer
sx={{
marginBottom: "30px",
}}
>
<>
<Box
sx={{
width: "100%",
display: "grid",
gridTemplateColumns: "55vw 35vw",
width: "100vw",
gap: "3vw",
gridTemplateColumns: isScreenSmall ? "1fr" : "60vw auto",
gap: "20px",
}}
>
{videoReference && (
@ -105,8 +107,7 @@ export const PlaylistContent = () => {
onClick={getVideoData}
/>
)}
</Box>
</VideoPlayerContainer>
<VideoActionsBar
channelName={channelName}
videoData={videoData}
@ -136,18 +137,36 @@ export const PlaylistContent = () => {
</VideoTitle>
</Box>
<Box
sx={{
display: "flex",
width: "100%",
gap: "20px",
}}
>
{videoData?.created && (
<Typography
variant="h6"
variant="h2"
sx={{
fontSize: "16px",
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
@ -155,20 +174,16 @@ export const PlaylistContent = () => {
background: "#333333",
borderRadius: "5px",
padding: "5px",
width: "100%",
width: "95%",
alignSelf: "flex-start",
cursor: !descriptionHeight
? "default"
: isExpandedDescription
? "default"
: "pointer",
position: "relative",
}}
className={
!descriptionHeight
? ""
: isExpandedDescription
? ""
: "hover-click"
!descriptionHeight ? "" : isExpandedDescription ? "" : "hover-click"
}
>
{descriptionHeight && !isExpandedDescription && (
@ -230,7 +245,6 @@ export const PlaylistContent = () => {
)}
</Box>
)}
</>
{videoData?.id && videoData?.user && (
<SuperLikesSection
loadingSuperLikes={loadingSuperLikes}
@ -245,7 +259,6 @@ export const PlaylistContent = () => {
postName={channelName || ""}
/>
)}
</VideoPlayerContainer>
</Box>
);
};

View File

@ -1,75 +1,22 @@
import { Avatar, Box, useTheme } from "@mui/material";
import { FollowButton } from "../../../components/common/ContentButtons/FollowButton.tsx";
import { SubscribeButton } from "../../../components/common/ContentButtons/SubscribeButton.tsx";
import { RootState } from "../../../state/store.ts";
import {
AuthorTextComment,
StyledCardColComment,
StyledCardHeaderComment,
} from "./VideoContent-styles.tsx";
import { useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import { Box, SxProps, Theme, useMediaQuery } from "@mui/material";
import { smallScreenSizeString } from "../../../constants/Misc.ts";
import { ChannelButtons } from "./ChannelButtons.tsx";
import { ChannelName, ChannelParams } from "./ChannelName.tsx";
import { Spacer } from "./VideoContent-styles.tsx";
export interface ChannelActionsParams {
channelName: string;
}
export const ChannelActions = ({ channelName }: ChannelActionsParams) => {
const navigate = useNavigate();
const theme = useTheme();
const userName = useSelector((state: RootState) => state.auth.user?.name);
export const ChannelActions = ({ channelName, sx }: ChannelParams) => {
const boxSX: SxProps<Theme> = {
display: "flex",
flexDirection: "row",
flexWrap: "wrap",
rowGap: "10px",
columnGap: "20px",
};
return (
<Box>
<StyledCardHeaderComment
sx={{
"& .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 sx={{ ...boxSX, ...sx }}>
<ChannelName channelName={channelName} />
<ChannelButtons channelName={channelName} />
</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 { FollowButton } from "../../../components/common/ContentButtons/FollowButton.tsx";
import DownloadIcon from "@mui/icons-material/Download";
import { Box, SxProps, Theme, useMediaQuery } from "@mui/material";
import { useMemo } from "react";
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 FileElement from "../../../components/common/FileElement.tsx";
import { videoRefType } from "../../../components/common/VideoPlayer/VideoPlayer.tsx";
import { titleFormatterOnSave } from "../../../constants/Misc.ts";
import { useFetchSuperLikes } from "../../../hooks/useFetchSuperLikes.tsx";
import DownloadIcon from "@mui/icons-material/Download";
import { RootState } from "../../../state/store.ts";
import {
smallScreenSizeString,
titleFormatterOnSave,
} from "../../../constants/Misc.ts";
import { ChannelActions } from "./ChannelActions.tsx";
import {
AuthorTextComment,
FileAttachmentContainer,
FileAttachmentFont,
StyledCardColComment,
StyledCardHeaderComment,
} from "./VideoContent-styles.tsx";
import { useNavigate } from "react-router-dom";
import { useSelector } from "react-redux";
import { useMemo, useState } from "react";
export interface VideoActionsBarProps {
channelName: string;
@ -82,11 +75,12 @@ export const VideoActionsBar = ({
return (
<Box
sx={{
width: "80%",
display: "grid",
gridTemplateColumns: "1fr 1fr",
marginTop: "15px",
display: "flex",
flexDirection: "row",
alignItems: "center",
flexWrap: "wrap",
gap: "20px",
...sx,
}}
>
@ -95,6 +89,8 @@ export const VideoActionsBar = ({
sx={{
display: "flex",
flexDirection: "row",
height: "100%",
alignItems: "center",
}}
>
{videoData && (
@ -110,9 +106,13 @@ export const VideoActionsBar = ({
setSuperLikeList(prev => [val, ...prev]);
}}
/>
</>
)}
</Box>
<FileAttachmentContainer>
<FileAttachmentFont>Save to Disk</FileAttachmentFont>
{videoData && (
<FileAttachmentContainer sx={{ width: "100%", maxWidth: "340px" }}>
<FileAttachmentFont>Save Video</FileAttachmentFont>
<FileElement
fileInfo={{
...videoReference,
@ -129,9 +129,7 @@ export const VideoActionsBar = ({
<DownloadIcon />
</FileElement>
</FileAttachmentContainer>
</>
)}
</Box>
</Box>
);
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,15 @@
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 LazyLoad from "../../components/common/LazyLoad";
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 { FiltersCol, VideoManagerRow } from "./Components/VideoList-styles.tsx";
import VideoList from "./Components/VideoList.tsx";
@ -29,46 +34,47 @@ export const Home = ({ mode }: HomeProps) => {
paddingLeft: "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 (
<>
<Grid container sx={{ width: "100%" }}>
<Box sx={{ ...homeBaseSX, ...homeColumns }}>
<SearchSidebar onSearch={getVideosHandler} />
<Grid item xs={12} md={10} lg={7} xl={8} sm={9}>
<VideoManagerRow>
<Box
sx={{
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
marginTop: "20px",
}}
>
<SubtitleContainer
sx={{
justifyContent: "flex-start",
paddingLeft: "15px",
width: "100%",
maxWidth: "1400px",
}}
></SubtitleContainer>
<TabContext value={tabValue}>
<TabList
onChange={changeTab}
textColor={"secondary"}
indicatorColor={"secondary"}
centered={false}
>
<Tab
label="All Videos"
value={"all"}
sx={{ fontSize: fontSizeMedium }}
/>
<Tab
label="Subscriptions"
value={"subscriptions"}
sx={{ fontSize: fontSizeMedium }}
/>
<Tab label="All" value={"all"} sx={tabSX} />
<Tab label="Subscriptions" value={"subscriptions"} sx={tabSX} />
</TabList>
<TabPanel value={"all"} sx={tabPaneSX}>
<VideoList videos={videos} />
@ -86,22 +92,19 @@ export const Home = ({ mode }: HomeProps) => {
isLoading={isLoading}
></LazyLoad>
</>
) : !isLoading ? (
) : (
!isLoading && (
<div style={{ textAlign: "center" }}>
You have no subscriptions
</div>
) : (
<></>
)
)}
</TabPanel>
</TabContext>
</Box>
</VideoManagerRow>
</Grid>
<FiltersCol item xs={0} lg={3} xl={2}>
<ListSuperLikeContainer />
</FiltersCol>
</Grid>
</Box>
</>
);
};

View File

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