diff --git a/package.json b/package.json index 90535ef..0e9e6af 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "qtube", "private": true, - "version": "2.0.0", + "version": "2.1.0", "type": "module", "scripts": { "dev": "vite", diff --git a/src/components/Playlists/Playlists.tsx b/src/components/Playlists/Playlists.tsx index 0bf6ddc..d514927 100644 --- a/src/components/Playlists/Playlists.tsx +++ b/src/components/Playlists/Playlists.tsx @@ -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 ( {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}`) }} diff --git a/src/components/Publish/PublishVideo/PublishVideo.tsx b/src/components/Publish/PublishVideo/PublishVideo.tsx index a62642f..372294e 100644 --- a/src/components/Publish/PublishVideo/PublishVideo.tsx +++ b/src/components/Publish/PublishVideo/PublishVideo.tsx @@ -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 => new Promise((resolve, reject) => { @@ -82,13 +83,14 @@ export const toBase64 = (file: File): Promise => 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 : ( } + startIcon={ + + } onClick={() => { setIsOpen(true); }} > - add video + Video )} > diff --git a/src/components/common/Comments/Comments-styles.tsx b/src/components/common/Comments/Comments-styles.tsx index 9cda045..34396e2 100644 --- a/src/components/common/Comments/Comments-styles.tsx +++ b/src/components/common/Comments/Comments-styles.tsx @@ -159,7 +159,7 @@ export const BlockIconContainer = styled(Box)({ }); export const CommentsContainer = styled(Box)({ - width: "70%", + width: "90%", maxWidth: "1000px", display: "flex", flexDirection: "column", diff --git a/src/components/common/ContentButtons/LikeAndDislike.tsx b/src/components/common/ContentButtons/LikeAndDislike.tsx index ebf8912..29e97c7 100644 --- a/src/components/common/ContentButtons/LikeAndDislike.tsx +++ b/src/components/common/ContentButtons/LikeAndDislike.tsx @@ -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", }} > diff --git a/src/components/common/ContentButtons/SuperLike.tsx b/src/components/common/ContentButtons/SuperLike.tsx index 7e2c4db..60d2b17 100644 --- a/src/components/common/ContentButtons/SuperLike.tsx +++ b/src/components/common/ContentButtons/SuperLike.tsx @@ -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 }); diff --git a/src/components/common/DownloadTaskManager.tsx b/src/components/common/DownloadTaskManager.tsx index 6ea28d6..c483b2d 100644 --- a/src/components/common/DownloadTaskManager.tsx +++ b/src/components/common/DownloadTaskManager.tsx @@ -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(null); - - const [openDownload, setOpenDownload] = useState(false); - + const hashMapVideos = useSelector( + (state: RootState) => state.video.hashMapVideos + ); const handleClick = (event?: React.MouseEvent) => { const target = event?.currentTarget as unknown as HTMLButtonElement | null; setAnchorEl(target); @@ -49,155 +48,159 @@ 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 - ) - return null - - - let downloadInProgress = false - if(Object.keys(downloads).find((key)=> (downloads[key]?.status?.status !== 'READY' && downloads[key]?.status?.status !== 'DOWNLOADED'))){ - downloadInProgress = true + Object.keys(downloads).find( + key => + downloads[key]?.status?.status !== "READY" && + downloads[key]?.status?.status !== "DOWNLOADED" + ) + ) { + downloadInProgress = true; } return ( - { - handleClick(e); - setOpenDownload(true); - }}> - {downloadInProgress ? ( - - ) : ( - - )} - - - - { + handleClick(e); + setOpenDownload(true); + }} + > + {downloadInProgress ? ( + + ) : ( + + )} + + + + - - {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 ( - { - const id = downloadObj?.properties?.jsonId - if (!id) return - - navigate( - `/video/${downloadObj?.properties?.user}/${id}` - ) - }} - > - - - {service === 'VIDEO' && ( - - )} - + {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; - - - - - {`${progress?.toFixed(0)}%`}{' '} - {status && status === 'REFETCHING' && '- refetching'} - {status && status === 'DOWNLOADED' && '- building'} - - - { + const userName = downloadObj?.name; + const identifier = downloadObj?.identifier; + + if (identifier && userName) + navigate(`/video/${userName}/${identifier}_metadata`); + }} + > + + + {service === "VIDEO" && ( + + )} + + + + - {downloadObj?.identifier} - - - ) - })} - - - + /> + + + {`${progress?.toFixed(0)}%`}{" "} + {status && status === "REFETCHING" && "- refetching"} + {status && status === "DOWNLOADED" && "- building"} + + + + {videoTitle || downloadObj?.identifier} + + + ); + })} + + - - ) -} + ); +}; diff --git a/src/components/common/ListSuperLikes/ListSuperLikeContainer.tsx b/src/components/common/ListSuperLikes/ListSuperLikeContainer.tsx index 4a7cc53..3afd50c 100644 --- a/src/components/common/ListSuperLikes/ListSuperLikeContainer.tsx +++ b/src/components/common/ListSuperLikes/ListSuperLikeContainer.tsx @@ -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 ( - + const isScreenLarge = useMediaQuery("(min-width:1200px)"); + const superlikeListComponent = ( + <> { Recent Super likes + > + ); + + // @ts-ignore + return ( + + {isScreenLarge ? ( + <>{superlikeListComponent}> + ) : ( + + + + + + } + > + {superlikeListComponent} + + )} ); }; diff --git a/src/components/common/Notifications/Notifications.tsx b/src/components/common/Notifications/Notifications.tsx index 4568f8e..654fa3e 100644 --- a/src/components/common/Notifications/Notifications.tsx +++ b/src/components/common/Notifications/Notifications.tsx @@ -64,7 +64,6 @@ export function extractIdValue(metadescription) { } export const Notifications = () => { - const dispatch = useDispatch(); const [anchorElNotification, setAnchorElNotification] = useState(null); const [notifications, setNotifications] = useState([]); @@ -238,7 +237,7 @@ export const Notifications = () => { badgeContent={notificationBadgeLength} color="primary" sx={{ - margin: "0px 12px", + margin: "0px", }} > { vertical: "bottom", horizontal: "left", }} + sx={{ marginTop: "12px" }} > ; + showExpandIcon?: boolean; + popoverProps?: PopoverProps; +} + +export type PopMenuRefType = { + closePopover: () => void; +}; + +export const PopMenu = forwardRef< + PopMenuRefType, + PropsWithChildren +>( + ( + { + containerSX, + popoverProps, + MenuHeader, + showExpandIcon = true, + children, + }: PropsWithChildren, + ref + ) => { + const [isOpenUserDropdown, setIsOpenUserDropdown] = + useState(false); + const [anchorEl, setAnchorEl] = useState(null); + + const openPopover = (event: React.MouseEvent) => { + const target = event.currentTarget as unknown as HTMLButtonElement | null; + + setAnchorEl(target); + setIsOpenUserDropdown(true); + }; + + const closePopover = () => { + setAnchorEl(null); + setIsOpenUserDropdown(false); + }; + + useImperativeHandle(ref, () => ({ + closePopover: () => closePopover(), + })); + + return ( + <> + + {MenuHeader} + {showExpandIcon && } + + + {children} + + > + ); + } +); diff --git a/src/components/common/SuperLikesList/Comment.tsx b/src/components/common/SuperLikesList/Comment.tsx index b244f6d..4004248 100644 --- a/src/components/common/SuperLikesList/Comment.tsx +++ b/src/components/common/SuperLikesList/Comment.tsx @@ -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(false); const [isEditing, setIsEditing] = useState(false); @@ -199,7 +204,7 @@ export const CommentCard = ({ children, setCurrentEdit, isReply, - amount + amount, }: any) => { const [avatarUrl, setAvatarUrl] = React.useState(""); 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 ( - - - - - - {name} - {!isReply && ( - - )} - {amount && ( - - {parseFloat(amount)?.toFixed(2)} QORT - - )} - + + + + {name} - - + + {!isReply && ( + + )} + {amount && ( + + {parseFloat(amount)?.toFixed(2)} QORT + + )} + + {message} diff --git a/src/components/common/SuperLikesList/Comments-styles.tsx b/src/components/common/SuperLikesList/Comments-styles.tsx index 333221a..c5ae43f 100644 --- a/src/components/common/SuperLikesList/Comments-styles.tsx +++ b/src/components/common/SuperLikesList/Comments-styles.tsx @@ -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", diff --git a/src/components/common/VideoPlayer/Components/MobileControls.tsx b/src/components/common/VideoPlayer/Components/MobileControls.tsx index 536fed7..d5c8a39 100644 --- a/src/components/common/VideoPlayer/Components/MobileControls.tsx +++ b/src/components/common/VideoPlayer/Components/MobileControls.tsx @@ -42,7 +42,6 @@ export const MobileControls = () => { @@ -55,11 +54,26 @@ export const MobileControls = () => { max={videoRef.current?.duration || 100} sx={{ flexGrow: 1, mx: 2 }} /> + increaseSpeed()} + > + {playbackRate}x + + + + @@ -85,22 +99,10 @@ export const MobileControls = () => { step={0.01} /> - increaseSpeed()}> - - Speed: {playbackRate}x - - + - - - > ); diff --git a/src/components/common/VideoPlayer/Components/VideoControls-State.ts b/src/components/common/VideoPlayer/Components/VideoControls-State.ts index 78c7f85..3ba70d1 100644 --- a/src/components/common/VideoPlayer/Components/VideoControls-State.ts +++ b/src/components/common/VideoPlayer/Components/VideoControls-State.ts @@ -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) => { 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 { diff --git a/src/components/common/VideoPlayer/Components/VideoControls.tsx b/src/components/common/VideoPlayer/Components/VideoControls.tsx index 03e3283..93e620b 100644 --- a/src/components/common/VideoPlayer/Components/VideoControls.tsx +++ b/src/components/common/VideoPlayer/Components/VideoControls.tsx @@ -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 ( { 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( 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, diff --git a/src/components/common/VideoPlayer/VideoPlayer.tsx b/src/components/common/VideoPlayer/VideoPlayer.tsx index 3b81d5a..11d42cc 100644 --- a/src/components/common/VideoPlayer/VideoPlayer.tsx +++ b/src/components/common/VideoPlayer/VideoPlayer.tsx @@ -55,7 +55,6 @@ export const VideoPlayer = forwardRef( startPlay, videoObjectFit, showControlsFullScreen, - duration, } = contextData; return ( diff --git a/src/components/layout/Navbar/Components/PublishMenu.tsx b/src/components/layout/Navbar/Components/PublishMenu.tsx new file mode 100644 index 0000000..9617114 --- /dev/null +++ b/src/components/layout/Navbar/Components/PublishMenu.tsx @@ -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(null); + + const isScreenSmall = !useMediaQuery(`(min-width:600px)`); + return ( + <> + {isDisplayed && ( + + {!isScreenSmall && ( + Publish + )} + + > + } + ref={popMenuRef} + > + + + + + + } + onClick={() => { + dispatch(setEditPlaylist({ mode: "new" })); + }} + > + Playlist + + + + )} + > + ); +}; diff --git a/src/components/layout/Navbar/Components/QtubeLogo.tsx b/src/components/layout/Navbar/Components/QtubeLogo.tsx new file mode 100644 index 0000000..83a91a7 --- /dev/null +++ b/src/components/layout/Navbar/Components/QtubeLogo.tsx @@ -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 ( + + { + navigate("/"); + dispatch(setIsFiltering(false)); + dispatch(setFilterValue("")); + dispatch(addFilteredVideos([])); + }} + > + + + + ); +}; diff --git a/src/components/layout/Navbar/Components/UserMenu.tsx b/src/components/layout/Navbar/Components/UserMenu.tsx new file mode 100644 index 0000000..2bcd97d --- /dev/null +++ b/src/components/layout/Navbar/Components/UserMenu.tsx @@ -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(false); + const popMenuRef = useRef(null); + const navigate = useNavigate(); + + const handleMyChannelLink = () => { + navigate(`/channel/${userName}`); + }; + + const onCloseBlockedNames = () => { + setIsOpenBlockedNamesModal(false); + }; + + return ( + <> + {isShowMenu && ( + <> + + {!isScreenSmall && {userName}} + {!userAvatar ? ( + + ) : ( + + )} + + } + > + { + handleMyChannelLink(); + popMenuRef.current.closePopover(); + }} + > + {!userAvatar ? ( + + ) : ( + + )} + {userName} + + { + setIsOpenBlockedNamesModal(true); + popMenuRef.current.closePopover(); + }} + > + + Blocked Names + + + {isOpenBlockedNamesModal && ( + + )} + > + )} + > + ); +}; diff --git a/src/components/layout/Navbar/Navbar-styles.tsx b/src/components/layout/Navbar/Navbar-styles.tsx index 8171e10..b2f5eb6 100644 --- a/src/components/layout/Navbar/Navbar-styles.tsx +++ b/src/components/layout/Navbar/Navbar-styles.tsx @@ -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%", }); diff --git a/src/components/layout/Navbar/Navbar.tsx b/src/components/layout/Navbar/Navbar.tsx index 40f3d94..ac0e9df 100644 --- a/src/components/layout/Navbar/Navbar.tsx +++ b/src/components/layout/Navbar/Navbar.tsx @@ -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 = ({ - isAuthenticated, - userName, - userAvatar, - authenticate, - setTheme, -}) => { - const windowSize = useWindowSize(); - const searchValRef = useRef(""); - const inputRef = useRef(null); - const theme = useTheme(); - const dispatch = useDispatch(); - const navigate = useNavigate(); - const [anchorEl, setAnchorEl] = React.useState( - null - ); - const [openUserDropdown, setOpenUserDropdown] = useState(false); - const [isOpenBlockedNamesModal, setIsOpenBlockedNamesModal] = - useState(false); - - const [anchorElNotification, setAnchorElNotification] = - React.useState(null); - const filterValue = useSelector( - (state: RootState) => state.video.filterValue - ); - - const handleClick = (event: React.MouseEvent) => { - 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 = ({ isAuthenticated, userName, userAvatar }) => { + const isScreenSmall = !useMediaQuery(`(min-width:600px)`); + const isSecure = isAuthenticated && !!userName; + const gapSize = 10; return ( - - { - navigate("/"); - dispatch(setIsFiltering(false)); - dispatch(setFilterValue("")); - dispatch(addFilteredVideos([])); - searchValRef.current = ""; - if (!inputRef.current) return; - inputRef.current.value = ""; - }} - > - - - - + - - { - 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", - }} - /> + {isSecure && } - { - 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)); - }} - /> - { - dispatch(setIsFiltering(false)); - dispatch(setFilterValue("")); - dispatch(addFilteredVideos([])); - searchValRef.current = ""; - if (!inputRef.current) return; - inputRef.current.value = ""; - }} - /> - - - {isAuthenticated && userName && } - - - {isAuthenticated && userName && ( - <> - { - handleClick(e); - setOpenUserDropdown(true); - }} - > - {userName} - {!userAvatar ? ( - - ) : ( - - )} - - - > - )} - - {isAuthenticated && userName && ( - <> - - } - onClick={() => { - dispatch(setEditPlaylist({ mode: "new" })); - }} - > - create playlist - - > - )} - - - - { - handleMyChannelLink(); - handleCloseUserDropdown(); - }} - > - {!userAvatar ? ( - - ) : ( - - )} - My Channel - - { - setIsOpenBlockedNamesModal(true); - handleCloseUserDropdown(); - }} - > - - Blocked Names - - - {isOpenBlockedNamesModal && ( - + - )} + + ); diff --git a/src/constants/Misc.ts b/src/constants/Misc.ts index c1e35d5..d581dd9 100644 --- a/src/constants/Misc.ts +++ b/src/constants/Misc.ts @@ -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"; diff --git a/src/pages/ContentPages/PlaylistContent/PlaylistContent.tsx b/src/pages/ContentPages/PlaylistContent/PlaylistContent.tsx index a059cf2..af587bd 100644 --- a/src/pages/ContentPages/PlaylistContent/PlaylistContent.tsx +++ b/src/pages/ContentPages/PlaylistContent/PlaylistContent.tsx @@ -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 ? ( { display: "flex", alignItems: "center", flexDirection: "column", - padding: "0px 10px", - marginLeft: "5%", + padding: "0px", + marginLeft: "2%", }} onClick={focusVideo} > - <> + {videoReference && ( - {videoReference && ( - - - - )} - {playlistData && ( - - )} + - - - - - {videoData?.title} - - - - {videoData?.created && ( - - {formatDate(videoData.created)} - - )} - - - {videoData?.fullDescription && ( - - {descriptionHeight && !isExpandedDescription && ( - { - if (isExpandedDescription) return; - setIsExpandedDescription(true); - }} - /> - )} - - {videoData?.htmlDescription ? ( - - ) : ( - - {videoData?.fullDescription} - - )} - - {descriptionHeight >= descriptionThreshold && ( - { - setIsExpandedDescription(prev => !prev); - }} - sx={{ - fontWeight: "bold", - fontSize: "16px", - cursor: "pointer", - paddingLeft: "15px", - paddingTop: "15px", - }} - > - {isExpandedDescription ? "Show less" : "...more"} - - )} - - )} - > - {videoData?.id && videoData?.user && ( - )} - {videoData?.id && channelName && ( - )} + + + + {videoData?.title} + + + + + {videoData?.created && ( + + {formatDate(videoData.created)} + + )} + {videoData?.fileSize > minFileSize && ( + + {formatBytes(videoData.fileSize, 2, "Decimal")} + + )} + + + {videoData?.fullDescription && ( + + {descriptionHeight && !isExpandedDescription && ( + { + if (isExpandedDescription) return; + setIsExpandedDescription(true); + }} + /> + )} + + {videoData?.htmlDescription ? ( + + ) : ( + + {videoData?.fullDescription} + + )} + + {descriptionHeight >= descriptionThreshold && ( + { + setIsExpandedDescription(prev => !prev); + }} + sx={{ + fontWeight: "bold", + fontSize: "16px", + cursor: "pointer", + paddingLeft: "15px", + paddingTop: "15px", + }} + > + {isExpandedDescription ? "Show less" : "...more"} + + )} + + )} + {videoData?.id && videoData?.user && ( + + )} + {videoData?.id && channelName && ( + + )} ); }; diff --git a/src/pages/ContentPages/VideoContent/ChannelActions.tsx b/src/pages/ContentPages/VideoContent/ChannelActions.tsx index 3bd2afa..d8b4a1b 100644 --- a/src/pages/ContentPages/VideoContent/ChannelActions.tsx +++ b/src/pages/ContentPages/VideoContent/ChannelActions.tsx @@ -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 = { + display: "flex", + flexDirection: "row", + flexWrap: "wrap", + rowGap: "10px", + columnGap: "20px", + }; return ( - - - { - navigate(`/channel/${channelName}`); - }} - > - - - - { - navigate(`/channel/${channelName}`); - }} - > - {channelName} - {channelName !== userName && ( - <> - - - > - )} - - - + + + ); }; diff --git a/src/pages/ContentPages/VideoContent/ChannelButtons.tsx b/src/pages/ContentPages/VideoContent/ChannelButtons.tsx new file mode 100644 index 0000000..40d021b --- /dev/null +++ b/src/pages/ContentPages/VideoContent/ChannelButtons.tsx @@ -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 ( + + {channelName !== userName && ( + <> + + + > + )} + + ); +}; diff --git a/src/pages/ContentPages/VideoContent/ChannelName.tsx b/src/pages/ContentPages/VideoContent/ChannelName.tsx new file mode 100644 index 0000000..6c71721 --- /dev/null +++ b/src/pages/ContentPages/VideoContent/ChannelName.tsx @@ -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; +} + +export const ChannelName = ({ channelName }: ChannelParams) => { + const navigate = useNavigate(); + const theme = useTheme(); + + return ( + + { + navigate(`/channel/${channelName}`); + }} + > + + + {channelName} + + + + ); +}; diff --git a/src/pages/ContentPages/VideoContent/VideoActionsBar.tsx b/src/pages/ContentPages/VideoContent/VideoActionsBar.tsx index 8fd2331..c179ba6 100644 --- a/src/pages/ContentPages/VideoContent/VideoActionsBar.tsx +++ b/src/pages/ContentPages/VideoContent/VideoActionsBar.tsx @@ -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 ( @@ -95,6 +89,8 @@ export const VideoActionsBar = ({ sx={{ display: "flex", flexDirection: "row", + height: "100%", + alignItems: "center", }} > {videoData && ( @@ -110,28 +106,30 @@ export const VideoActionsBar = ({ setSuperLikeList(prev => [val, ...prev]); }} /> - - - Save to Disk - - - - > )} + + {videoData && ( + + Save Video + + + + + )} ); }; diff --git a/src/pages/ContentPages/VideoContent/VideoContent-styles.tsx b/src/pages/ContentPages/VideoContent/VideoContent-styles.tsx index 9fda806..c86b00b 100644 --- a/src/pages/ContentPages/VideoContent/VideoContent-styles.tsx +++ b/src/pages/ContentPages/VideoContent/VideoContent-styles.tsx @@ -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", })); diff --git a/src/pages/ContentPages/VideoContent/VideoContent.tsx b/src/pages/ContentPages/VideoContent/VideoContent.tsx index a7f0f86..e2ac061 100644 --- a/src/pages/ContentPages/VideoContent/VideoContent.tsx +++ b/src/pages/ContentPages/VideoContent/VideoContent.tsx @@ -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( + 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 ( <> {videoReference ? ( - + { )} + + {videoData?.title} + + + {videoData?.created && ( + + {formatDate(videoData.created)} + + )} + + {videoData?.fileSize > minFileSize && ( + + {formatBytes(videoData.fileSize, 2, "Decimal")} + + )} + - - - {videoData?.title} - - - {videoData?.fileSize > minFileSize && ( - - {formatBytes(videoData.fileSize, 2, "Decimal")} - - )} - {videoData?.created && ( - - {formatDate(videoData.created)} - - )} - + {videoData?.fullDescription && ( { filtersToDefault, } = useSidebarState(onSearch); + const filtersStyle = { width: "75px", marginRight: "10px" }; + const isScreenSmall = !useMediaQuery(`(min-width:600px)`); + return ( - + { /> - + @@ -201,7 +213,7 @@ export const SearchSidebar = ({ onSearch }: SearchSidebarProps) => { - Videos + Videos ) => { @@ -211,7 +223,7 @@ export const SearchSidebar = ({ onSearch }: SearchSidebarProps) => { /> - Playlists + Playlists ) => { @@ -221,12 +233,15 @@ export const SearchSidebar = ({ onSearch }: SearchSidebarProps) => { /> + { 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 - + ); }; diff --git a/src/pages/Home/Components/VideoList-styles.tsx b/src/pages/Home/Components/VideoList-styles.tsx index 3a0e48d..eb82aa2 100644 --- a/src/pages/Home/Components/VideoList-styles.tsx +++ b/src/pages/Home/Components/VideoList-styles.tsx @@ -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", })); diff --git a/src/pages/Home/Components/VideoList.tsx b/src/pages/Home/Components/VideoList.tsx index 725fadd..5e34386 100644 --- a/src/pages/Home/Components/VideoList.tsx +++ b/src/pages/Home/Components/VideoList.tsx @@ -145,7 +145,6 @@ export const VideoList = ({ videos }: VideoListProps) => { maxHeight: "50%", }} /> - {videoObj?.title} { 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 ( <> - + - - - + + - - - - - - - + + + + + + + + + {filteredSubscriptionList.length > 0 ? ( + <> - - - {filteredSubscriptionList.length > 0 ? ( - <> - - - > - ) : !isLoading ? ( - - You have no subscriptions - - ) : ( - <>> - )} - - - - - - - - - + > + ) : ( + !isLoading && ( + + You have no subscriptions + + ) + )} + + + + + + > ); }; diff --git a/src/state/features/persistSlice.ts b/src/state/features/persistSlice.ts index 6c223ee..c2e2c6f 100644 --- a/src/state/features/persistSlice.ts +++ b/src/state/features/persistSlice.ts @@ -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) => { state.volume = action.payload; }, + setMutedVolumeSetting: (state, action: PayloadAction) => { + state.mutedVolume = action.payload; + }, + setIsMuted: (state, action: PayloadAction) => { + state.isMuted = action.payload; + }, }, }); @@ -89,6 +99,8 @@ export const { changeFilterType, resetSubscriptions, setVolumeSetting, + setMutedVolumeSetting, + setIsMuted, } = persistSlice.actions; export default persistSlice.reducer;