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:
commit
29de8a8a9f
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "qtube",
|
||||
"private": true,
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
@ -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}`)
|
||||
}}
|
||||
|
@ -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>
|
||||
)}
|
||||
</>
|
||||
|
@ -159,7 +159,7 @@ export const BlockIconContainer = styled(Box)({
|
||||
});
|
||||
|
||||
export const CommentsContainer = styled(Box)({
|
||||
width: "70%",
|
||||
width: "90%",
|
||||
maxWidth: "1000px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
|
@ -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",
|
||||
}}
|
||||
>
|
||||
|
@ -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 });
|
||||
|
||||
|
@ -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>
|
||||
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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
|
||||
|
77
src/components/common/PopMenu.tsx
Normal file
77
src/components/common/PopMenu.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
@ -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>
|
||||
|
@ -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",
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -55,7 +55,6 @@ export const VideoPlayer = forwardRef<videoRefType, VideoPlayerProps>(
|
||||
startPlay,
|
||||
videoObjectFit,
|
||||
showControlsFullScreen,
|
||||
duration,
|
||||
} = contextData;
|
||||
|
||||
return (
|
||||
|
73
src/components/layout/Navbar/Components/PublishMenu.tsx
Normal file
73
src/components/layout/Navbar/Components/PublishMenu.tsx
Normal 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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
40
src/components/layout/Navbar/Components/QtubeLogo.tsx
Normal file
40
src/components/layout/Navbar/Components/QtubeLogo.tsx
Normal 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>
|
||||
);
|
||||
};
|
124
src/components/layout/Navbar/Components/UserMenu.tsx
Normal file
124
src/components/layout/Navbar/Components/UserMenu.tsx
Normal 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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
@ -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%",
|
||||
});
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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";
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
24
src/pages/ContentPages/VideoContent/ChannelButtons.tsx
Normal file
24
src/pages/ContentPages/VideoContent/ChannelButtons.tsx
Normal 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>
|
||||
);
|
||||
};
|
58
src/pages/ContentPages/VideoContent/ChannelName.tsx
Normal file
58
src/pages/ContentPages/VideoContent/ChannelName.tsx
Normal 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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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",
|
||||
}));
|
||||
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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",
|
||||
}));
|
||||
|
@ -145,7 +145,6 @@ export const VideoList = ({ videos }: VideoListProps) => {
|
||||
maxHeight: "50%",
|
||||
}}
|
||||
/>
|
||||
|
||||
<VideoCardTitle>{videoObj?.title}</VideoCardTitle>
|
||||
<BottomParent>
|
||||
<NameContainer
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user