update playlist content

This commit is contained in:
2025-07-15 09:59:09 +03:00
parent 65fb4245a0
commit e5f3f1e7b3
23 changed files with 434 additions and 381 deletions

8
package-lock.json generated
View File

@@ -21,7 +21,7 @@
"localforage": "^1.10.0",
"mediainfo.js": "^0.3.5",
"moment": "^2.30.1",
"qapp-core": "^1.0.41",
"qapp-core": "^1.0.43",
"quill": "^2.0.2",
"quill-image-resize-module-react": "^3.0.0",
"react": "^19.0.0",
@@ -5062,9 +5062,9 @@
}
},
"node_modules/qapp-core": {
"version": "1.0.41",
"resolved": "https://registry.npmjs.org/qapp-core/-/qapp-core-1.0.41.tgz",
"integrity": "sha512-3j10QXhLIRVg7T6RF67B4b29q+C6wnBkPSILdveCOjZeGI4gsrE6Olf8RO3kytNCdgbj4/EuXiE6kC2D5oi9KA==",
"version": "1.0.43",
"resolved": "https://registry.npmjs.org/qapp-core/-/qapp-core-1.0.43.tgz",
"integrity": "sha512-Ts5E5+PGnKp1jybxxtDA3zq8FVl3Dr2vRSs7xZHG9DW0bXtb57/qc9joERlXyJzcDowLzmaEraisMtehgwn/GA==",
"license": "MIT",
"dependencies": {
"@tanstack/react-virtual": "^3.13.2",

View File

@@ -23,7 +23,7 @@
"localforage": "^1.10.0",
"mediainfo.js": "^0.3.5",
"moment": "^2.30.1",
"qapp-core": "^1.0.41",
"qapp-core": "^1.0.43",
"quill": "^2.0.2",
"quill-image-resize-module-react": "^3.0.0",
"react": "^19.0.0",

View File

@@ -54,12 +54,15 @@ const Layout = () => {
},
'::-webkit-scrollbar-thumb': {
backgroundColor: '#90CAF9',
backgroundColor: 'rgba(63, 67, 80, 0.24)',
borderRadius: '8px',
backgroundClip: 'content-box',
border: '4px solid transparent',
transition: '0.3s background-color',
},
'::-webkit-scrollbar-thumb:hover': {
backgroundColor: 'rgba(63, 67, 80, 0.50)',
},
}}
>
<Outlet />

View File

@@ -5,9 +5,9 @@ import {
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import React from "react";
import { CardContentContainerComment } from "../common/Comments/Comments-styles";
} from '@mui/material';
import React from 'react';
import { CardContentContainerComment } from '../common/Comments/Comments-styles';
interface PlaylistsProps {
playlistData;
@@ -23,23 +23,25 @@ export const Playlists = ({
}: PlaylistsProps) => {
const theme = useTheme();
const isScreenSmall = !useMediaQuery(`(min-width:700px)`);
const PlaylistsHeight = "36vw"; // This is videoplayer width * 9/16 (inverse of aspect ratio)
const PlaylistsHeight = '36vw'; // This is videoplayer width * 9/16 (inverse of aspect ratio)
return (
<Box
sx={{
width: "100%",
height: isScreenSmall ? "200px" : PlaylistsHeight,
// width: "100%",
...sx,
display: "flex",
flexDirection: "column",
display: 'flex',
flexDirection: 'column',
width: '100%',
height: '100%',
}}
>
<CardContentContainerComment
sx={{
marginTop: "0px",
height: "100%",
overflow: "auto",
marginTop: '0px',
height: '100%',
gap: '3px',
padding: '3px',
}}
>
{playlistData?.videos?.map((vid, index) => {
@@ -50,15 +52,18 @@ export const Playlists = ({
<Box
key={vid?.identifier}
sx={{
display: "flex",
gap: "10px",
width: "100%",
display: 'flex',
gap: '10px',
width: '100%',
background: isCurrentVidPlaying && theme.palette.primary.main,
alignItems: "center",
padding: "10px",
borderRadius: "5px",
cursor: isCurrentVidPlaying ? "default" : "pointer",
userSelect: "none",
color:
isCurrentVidPlaying && theme.palette.primary.contrastText,
alignItems: 'center',
padding: '10px',
borderRadius: '5px',
cursor: isCurrentVidPlaying ? 'default' : 'pointer',
userSelect: 'none',
border: '1px solid rgba(255, 255, 255, 0.23)',
}}
onClick={() => {
if (isCurrentVidPlaying) return;
@@ -68,16 +73,16 @@ export const Playlists = ({
>
<Typography
sx={{
fontSize: "18px",
fontWeight: "bold",
fontSize: '18px',
fontWeight: 'bold',
}}
>
{index + 1}
</Typography>
<Typography
sx={{
fontSize: "18px",
wordBreak: "break-word",
fontSize: '18px',
wordBreak: 'break-word',
}}
>
{vid?.metadata?.title}

View File

@@ -9,6 +9,7 @@ interface ResponsiveImageProps {
alt?: string;
className?: string;
style?: CSSProperties;
fill?: boolean;
}
const ResponsiveImage: React.FC<ResponsiveImageProps> = ({
@@ -18,6 +19,7 @@ const ResponsiveImage: React.FC<ResponsiveImageProps> = ({
alt,
className,
style,
fill,
}) => {
const [loading, setLoading] = useState(true);
@@ -46,8 +48,7 @@ const ResponsiveImage: React.FC<ResponsiveImageProps> = ({
variant="rectangular"
style={{
width: '100%',
height: 0,
paddingBottom: `${(height / width) * 100}%`,
height: '100%',
objectFit: 'contain',
visibility: loading ? 'visible' : 'hidden',
borderRadius: '8px',
@@ -57,13 +58,13 @@ const ResponsiveImage: React.FC<ResponsiveImageProps> = ({
<img
onLoad={() => setLoading(false)}
src={!src && !loading ? DeletedVideo : src || null}
src={!src && !loading ? DeletedVideo : src || ''}
style={{
width: '100%',
height: '100%',
borderRadius: '8px',
display: loading ? 'none' : 'unset',
objectFit: 'contain',
objectFit: fill ? 'fill' : 'contain',
maskImage: 'radial-gradient(circle, black 95%, transparent 100%)',
maskMode: 'alpha',
}}

View File

@@ -2,6 +2,8 @@ import {
Avatar,
Box,
Button,
ButtonBase,
Collapse,
Dialog,
DialogActions,
DialogContent,
@@ -15,12 +17,15 @@ import {
CardContentContainerComment,
CommentActionButtonRow,
CommentDateText,
CreatedTextComment,
EditReplyButton,
StyledCardComment,
} from './Comments-styles';
import { StyledCardHeaderComment } from './Comments-styles';
import { StyledCardColComment } from './Comments-styles';
import { AuthorTextComment } from './Comments-styles';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import {
StyledCardContentComment,
LoadMoreCommentsButton as CommentActionButton,
@@ -28,7 +33,7 @@ import {
import Portal from '../Portal';
import { formatDate } from '../../../utils/time';
import { createAvatarLink, useAuth } from 'qapp-core';
import { createAvatarLink, Spacer, useAuth } from 'qapp-core';
interface CommentProps {
comment: any;
postId: string;
@@ -46,7 +51,7 @@ export const Comment = ({
const { name } = useAuth();
const [currentEdit, setCurrentEdit] = useState<any>(null);
const theme = useTheme();
const [isOpenReplies, setIsOpenReplies] = useState(false);
const handleSubmit = useCallback((comment: any, isEdit?: boolean) => {
onSubmit(comment, isEdit);
setCurrentEdit(null);
@@ -97,12 +102,37 @@ export const Comment = ({
</Dialog>
</Portal>
)}
<CommentCard
name={comment?.name}
message={comment?.message}
replies={comment?.replies || []}
setCurrentEdit={setCurrentEdit}
created={comment?.created}
isOpenReplies={isOpenReplies}
>
<Box
sx={{
display: 'flex',
width: '100%',
flexDirection: 'column',
alignItems: 'flex-start',
}}
>
{isReplying && (
<CommentEditor
onSubmit={handleSubmit}
postId={postId}
postName={postName}
isReply
commentId={comment.identifier}
onCloseReply={() => {
setIsReplying(false);
setIsEditing(false);
}}
/>
)}
</Box>
<Box
sx={{
display: 'flex',
@@ -112,36 +142,23 @@ export const Comment = ({
justifyContent: 'space-between',
}}
>
{comment?.created && (
<Typography
variant="h6"
sx={{
fontSize: '12px',
marginLeft: '5px',
}}
color={theme.palette.text.primary}
>
{formatDate(+comment?.created)}
</Typography>
)}
<CommentActionButtonRow>
<CommentActionButton
size="small"
variant="contained"
onClick={() => setIsReplying(true)}
>
reply
</CommentActionButton>
{name === comment?.name && (
<CommentActionButton
size="small"
variant="contained"
onClick={() => setCurrentEdit(comment)}
>
edit
</CommentActionButton>
<CommentActionButtonRow
sx={{
gap: '20px',
}}
>
{!isReplying && (
<ButtonBase onClick={() => setIsReplying(true)}>
<Typography>Reply</Typography>
</ButtonBase>
)}
{isReplying && (
{/* {name === comment?.name && (
<ButtonBase onClick={() => setCurrentEdit(comment)}>
<Typography>Edit</Typography>
</ButtonBase>
)} */}
{/* {isReplying && (
<CommentActionButton
size="small"
variant="contained"
@@ -152,29 +169,33 @@ export const Comment = ({
>
close
</CommentActionButton>
)} */}
{comment?.replies && comment?.replies?.length > 0 && (
<ButtonBase onClick={() => setIsOpenReplies((prev) => !prev)}>
{isOpenReplies ? (
<ExpandLessIcon
sx={{
color: 'primary.dark',
}}
/>
) : (
<ExpandMoreIcon
sx={{
color: 'primary.dark',
}}
/>
)}
<Typography color="primary.dark">
{isOpenReplies
? ` Hide all replies (${comment?.replies?.length})`
: ` View all replies (${comment?.replies?.length})`}
</Typography>
</ButtonBase>
)}
</CommentActionButtonRow>
</Box>
</CommentCard>
<Box
sx={{
display: 'flex',
width: '100%',
flexDirection: 'column',
alignItems: 'center',
}}
>
{isReplying && (
<CommentEditor
onSubmit={handleSubmit}
postId={postId}
postName={postName}
isReply
commentId={comment.identifier}
/>
)}
</Box>
</Box>
);
};
@@ -186,9 +207,11 @@ export const CommentCard = ({
replies,
children,
setCurrentEdit,
isOpenReplies,
isReply,
}: any) => {
const { name: username } = useAuth();
const avatarUrl = createAvatarLink(username);
const avatarUrl = createAvatarLink(name);
return (
<CardContentContainerComment>
@@ -203,72 +226,91 @@ export const CommentCard = ({
<Avatar
src={avatarUrl}
alt={`${name}'s avatar`}
sx={{ width: '35px', height: '35px' }}
sx={{
width: isReply ? '30px' : '40px',
height: isReply ? '30px' : '40px',
marginRight: '5px',
}}
/>
</Box>
<StyledCardColComment>
<AuthorTextComment>{name}</AuthorTextComment>
</StyledCardColComment>
<Box
sx={{
width: '100%',
}}
>
<StyledCardColComment
sx={{
flexDirection: 'row',
gap: '10px',
}}
>
<AuthorTextComment>{name}</AuthorTextComment>
<CreatedTextComment>{formatDate(+created)}</CreatedTextComment>
</StyledCardColComment>
<Spacer height="10px" />
<StyledCardContentComment>
<StyledCardComment>{message}</StyledCardComment>
</StyledCardContentComment>
{children}
</Box>
</StyledCardHeaderComment>
<StyledCardContentComment>
<StyledCardComment>{message}</StyledCardComment>
</StyledCardContentComment>
<Box
sx={{
paddingLeft: '15px',
display: 'flex',
flexDirection: 'column',
}}
>
{replies?.map((reply: any) => {
return (
<Box
key={reply?.identifier}
id={reply?.identifier}
sx={{
display: 'flex',
border: '1px solid grey',
borderRadius: '10px',
marginTop: '8px',
}}
>
<CommentCard
name={reply?.name}
message={reply?.message}
setCurrentEdit={setCurrentEdit}
<Collapse in={isOpenReplies} timeout="auto" unmountOnExit>
<Box
sx={{
paddingLeft: '50px',
paddingTop: '10px',
display: 'flex',
flexDirection: 'column',
width: '100%',
}}
>
{replies?.map((reply: any) => {
return (
<Box
key={reply?.identifier}
id={reply?.identifier}
sx={{
display: 'flex',
// border: '1px solid grey',
borderRadius: '10px',
marginTop: '8px',
width: '100%',
}}
>
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: '5px',
justifyContent: 'space-between',
}}
<CommentCard
name={reply?.name}
message={reply?.message}
setCurrentEdit={setCurrentEdit}
created={reply?.created}
isReply
>
{reply?.created && (
<CommentDateText>
{formatDate(+reply?.created)}
</CommentDateText>
)}
{username === reply?.name ? (
<EditReplyButton
size="small"
variant="contained"
onClick={() => setCurrentEdit(reply)}
sx={{}}
>
edit
</EditReplyButton>
) : (
<Box />
)}
</Box>
</CommentCard>
</Box>
);
})}
</Box>
{children}
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: '5px',
justifyContent: 'space-between',
}}
>
{/* {username === reply?.name ? (
<EditReplyButton
size="small"
variant="contained"
onClick={() => setCurrentEdit(reply)}
sx={{}}
>
edit
</EditReplyButton>
) : (
<Box />
)} */}
</Box>
</CommentCard>
</Box>
);
})}
</Box>
</Collapse>
</CardContentContainerComment>
);
};

View File

@@ -89,6 +89,7 @@ interface CommentEditorProps {
commentId?: string;
isEdit?: boolean;
commentMessage?: string;
onCloseReply?: () => void;
}
function utf8ToBase64(inputString: string): string {
@@ -111,6 +112,7 @@ export const CommentEditor = ({
commentId,
isEdit,
commentMessage,
onCloseReply,
}: CommentEditorProps) => {
const [value, setValue] = useState<string>('');
const { name, address } = useAuth();
@@ -215,6 +217,8 @@ export const CommentEditor = ({
}
};
console.log('onCloseReply', onCloseReply);
return (
<CommentInputContainer>
<CommentInput
@@ -239,10 +243,21 @@ export const CommentEditor = ({
justifyContent: 'flex-end',
display: 'flex',
gap: '20px',
visibility: value || isFocused ? 'visible' : 'hidden',
visibility: isReply
? 'visible'
: value || isFocused
? 'visible'
: 'hidden',
}}
>
<Button onClick={() => setValue('')} variant="text">
<Button
onClick={(e) => {
setValue('');
if (!onCloseReply) return;
onCloseReply();
}}
variant="text"
>
Cancel
</Button>
<SubmitCommentButton

View File

@@ -25,10 +25,10 @@ export const CardContentContainer = styled(Box)(({ theme }) => ({
}));
export const CardContentContainerComment = styled(Box)(({ theme }) => ({
backgroundColor: theme.palette.mode === 'light' ? '#a9d9d038' : '#c3abe414',
border: `1px solid ${theme.palette.primary.main}`,
// backgroundColor: theme.palette.mode === 'light' ? '#a9d9d038' : '#c3abe414',
// border: `1px solid ${theme.palette.primary.main}`,
margin: '0px',
padding: '8px 15px',
// padding: '8px 15px',
borderRadius: '8px',
width: '100%',
display: 'flex',
@@ -45,10 +45,10 @@ export const StyledCardHeader = styled(Box)({
export const StyledCardHeaderComment = styled(Box)({
display: 'flex',
alignItems: 'center',
alignItems: 'flex-start',
justifyContent: 'flex-start',
gap: '7px',
padding: '9px 7px',
padding: '0px 7px 9px 0px',
});
export const StyledCardCol = styled(Box)({
@@ -83,13 +83,13 @@ export const StyledCardContentComment = styled(Box)({
flexDirection: 'column',
alignItems: 'flex-start',
justifyContent: 'flex-start',
padding: '5px 10px',
// padding: '5px 10px',
gap: '10px',
});
export const StyledCardComment = styled(Typography)(({ theme }) => ({
letterSpacing: 0,
fontWeight: 400,
fontWeight: 300,
color: theme.palette.text.primary,
fontSize: '19px',
wordBreak: 'break-word',
@@ -112,7 +112,14 @@ export const AuthorText = styled(Typography)({
export const AuthorTextComment = styled(Typography)(({ theme }) => ({
fontSize: '17px',
letterSpacing: '0.3px',
fontWeight: 400,
fontWeight: 500,
color: theme.palette.text.primary,
userSelect: 'none',
}));
export const CreatedTextComment = styled(Typography)(({ theme }) => ({
fontSize: '17px',
letterSpacing: '0.3px',
fontWeight: 300,
color: theme.palette.text.primary,
userSelect: 'none',
}));
@@ -231,7 +238,7 @@ export const CommentInputContainer = styled(Box)({
maxWidth: '1000px',
borderRadius: '8px',
gap: '10px',
alignItems: 'center',
alignItems: 'flex-start',
});
export const CommentInput = styled(TextField)(({ theme }) => ({

View File

@@ -203,7 +203,11 @@ export const SuperLike = ({
}}
>
<CustomTooltip title="Super Like" placement="top">
<Box>
<Box
sx={{
display: 'flex',
}}
>
<ThumbUpIcon
style={{
color: 'gold',
@@ -217,6 +221,7 @@ export const SuperLike = ({
alignItems: 'center',
justifyContent: 'center',
userSelect: 'none',
marginLeft: '5px',
}}
>
<span style={{ marginRight: '10px', paddingBottom: '4px' }}>

View File

@@ -142,15 +142,19 @@ export const Comment = ({
</Typography>
)}
<CommentActionButtonRow>
<CommentActionButton
size="small"
variant="contained"
onClick={() => setIsReplying(true)}
>
reply
</CommentActionButton>
{!isReplying && (
<CommentActionButton
size="small"
variant="contained"
onClick={() => setIsReplying(true)}
>
reply
</CommentActionButton>
)}
{username === comment?.name && hasHash && (
<CommentActionButton
color="info"
size="small"
variant="contained"
onClick={() => setCurrentEdit(comment)}
@@ -161,7 +165,8 @@ export const Comment = ({
{isReplying && (
<CommentActionButton
size="small"
variant="contained"
variant="text"
color="info"
onClick={() => {
setIsReplying(false);
setIsEditing(false);
@@ -328,6 +333,7 @@ export const CommentCard = ({
{username === reply?.name ? (
<EditReplyButton
size="small"
color="info"
variant="contained"
onClick={() => setCurrentEdit(reply)}
sx={{}}

View File

@@ -277,7 +277,11 @@ export const CommentEditor = ({
onChange={(e) => setValue(e.target.value)}
/>
<SubmitCommentButton variant="contained" onClick={handleSubmit}>
<SubmitCommentButton
color="info"
variant="contained"
onClick={handleSubmit}
>
{isReply ? 'Submit reply' : isEdit ? 'Edit' : 'Submit comment'}
</SubmitCommentButton>
</CommentInputContainer>

View File

@@ -191,16 +191,12 @@ export const LoadMoreCommentsButtonRow = styled(Box)({
export const EditReplyButton = styled(Button)(({ theme }) => ({
width: '30px',
alignSelf: 'flex-end',
background: theme.palette.primary.light,
color: '#ffffff',
}));
export const LoadMoreCommentsButton = styled(Button)(({ theme }) => ({
fontWeight: 400,
letterSpacing: '0.2px',
fontSize: '15px',
backgroundColor: theme.palette.primary.main,
color: '#ffffff',
}));
export const CommentActionButtonRow = styled(Box)({
@@ -266,7 +262,5 @@ export const SubmitCommentButton = styled(Button)(({ theme }) => ({
fontWeight: 400,
letterSpacing: '0.2px',
fontSize: '15px',
backgroundColor: theme.palette.primary.main,
color: '#ffffff',
width: '75%',
}));

View File

@@ -26,6 +26,7 @@ export interface VideoPlayerProps {
style?: CSS.Properties;
duration?: number;
filename: string;
parentStyles?: CSS.Properties;
}
export const VideoPlayer = ({ ...props }: VideoPlayerProps) => {
@@ -34,9 +35,13 @@ export const VideoPlayer = ({ ...props }: VideoPlayerProps) => {
return (
<Box
sx={{
width: '100%',
// width: '100%',
height: '70vh',
background: 'black',
width: '100%',
display: 'flex',
flexDirection: 'column',
...(props?.parentStyles || {}),
}}
>
<QappVideoPlayer

View File

@@ -1,19 +1,19 @@
export const useTestIdentifiers = false;
export const QTUBE_VIDEO_BASE = useTestIdentifiers
? 'MYTEST2_vid_'
? '2MYTEST_vid_'
: 'qtube_vid_';
export const QTUBE_PLAYLIST_BASE = useTestIdentifiers
? 'MYTEST2_playlist_'
? '2MYTEST_playlist_'
: 'qtube_playlist_';
export const SUPER_LIKE_BASE = useTestIdentifiers
? 'MYTEST2_superlike_'
? '2MYTEST_superlike_'
: 'qtube_superlike_';
export const LIKE_BASE = useTestIdentifiers ? 'MYTEST2_like_' : 'qtube_like_';
export const LIKE_BASE = useTestIdentifiers ? '2MYTEST_like_' : 'qtube_like_';
export const COMMENT_BASE = useTestIdentifiers
? 'qc_v1_MYTEST2_'
? '2qc_v1_MYTEST_'
: 'qc_v1_qtube_';
export const FOR = useTestIdentifiers ? 'FORTEST52' : 'FOR0962';
export const FOR_SUPER_LIKE = useTestIdentifiers ? 'MYTEST2_sl' : `qtube_sl`;
export const FOR_LIKE = useTestIdentifiers ? 'MYTEST2_like' : `qtube_like`;
export const FOR = useTestIdentifiers ? '2FORTEST5' : 'FOR096';
export const FOR_SUPER_LIKE = useTestIdentifiers ? '2MYTEST_sl' : `qtube_sl`;
export const FOR_LIKE = useTestIdentifiers ? '2MYTEST_like' : `qtube_like`;

View File

@@ -1,7 +1,7 @@
/* Roboto Thin 100 */
@font-face {
font-family: 'Roboto';
src: url('.styles/fonts/Roboto-Thin.ttf') format('truetype');
src: url('./styles/fonts/Roboto-Thin.ttf') format('truetype');
font-weight: 100;
font-style: normal;
}

View File

@@ -61,49 +61,88 @@ export const PlaylistContent = () => {
alignItems: 'center',
flexDirection: 'column',
padding: '0px',
marginLeft: '2%',
width: '100%',
}}
>
<VideoPlayerContainer
sx={{
width: isScreenSmall ? '100%' : '60%',
alignSelf: 'start',
paddingRight: isScreenSmall ? '10px' : '0px',
marginBottom: '20px',
height: '70vh',
flexDirection: 'row',
width: '100%',
gap: '10px',
}}
>
{videoReference && (
<VideoPlayer
name={videoReference?.name}
service={videoReference?.service}
identifier={videoReference?.identifier}
user={channelName}
jsonId={id}
poster={videoCover || ''}
nextVideo={nextVideo}
onEnd={onEndVideo}
autoPlay={doAutoPlay}
videoStyles={{
video: { aspectRatio: '16 / 9' },
}}
duration={videoData?.duration}
filename={videoData?.filename}
/>
)}
{playlistData && (
<Playlists
playlistData={playlistData}
currentVideoIdentifier={videoData?.id}
onClick={(name, identifier) => {
setVideoMetadataResource({
name,
identifier,
service: 'DOCUMENT',
});
}}
sx={playlistsSX}
/>
)}
<Box
sx={{
display: 'flex',
flexGrow: 1,
flexBasis: '75%',
}}
>
{videoReference && (
<VideoPlayer
name={videoReference?.name}
service={videoReference?.service}
identifier={videoReference?.identifier}
user={channelName}
jsonId={id}
poster={videoCover || ''}
nextVideo={nextVideo}
onEnd={onEndVideo}
autoPlay={doAutoPlay}
videoStyles={{
video: { aspectRatio: '16 / 9' },
}}
duration={videoData?.duration}
filename={videoData?.filename}
/>
)}
</Box>
<Box
sx={{
display: 'flex',
flexGrow: 1,
height: '100%',
overflow: 'auto',
'::-webkit-scrollbar-track': {
backgroundColor: 'transparent',
},
'::-webkit-scrollbar': {
width: '16px',
height: '10px',
},
'::-webkit-scrollbar-thumb': {
backgroundColor: 'rgba(63, 67, 80, 0.24)',
borderRadius: '8px',
backgroundClip: 'content-box',
border: '4px solid transparent',
transition: '0.3s background-color',
},
'::-webkit-scrollbar-thumb:hover': {
backgroundColor: 'rgba(63, 67, 80, 0.50)',
},
}}
>
{playlistData && (
<Playlists
playlistData={playlistData}
currentVideoIdentifier={videoData?.id}
onClick={(name, identifier) => {
setVideoMetadataResource({
name,
identifier,
service: 'DOCUMENT',
});
}}
// sx={playlistsSX}
/>
)}
</Box>
</VideoPlayerContainer>
<VideoActionsBar
channelName={channelName}

View File

@@ -16,19 +16,16 @@ export const useVideoContentState = () => {
const { name: channelName, id } = useParams();
const [videoMetadataResource, setVideoMetadataResource] =
useState<null | QortalGetMetadata>(null);
const { resource } = usePublish(
2,
'JSON',
videoMetadataResource
? videoMetadataResource
: !id
? null
: {
identifier: id,
name: channelName,
service: 'DOCUMENT',
}
);
const metadata: QortalGetMetadata = videoMetadataResource
? videoMetadataResource
: {
identifier: id || '',
name: channelName || '',
service: 'DOCUMENT',
};
const { resource } = usePublish(2, 'JSON', metadata);
const [superLikeversion, setSuperLikeVersion] = useState<null | number>(null);
const [isExpandedDescription, setIsExpandedDescription] =
useState<boolean>(false);

View File

@@ -1,9 +1,8 @@
import { Box, Button, Divider, Typography, useMediaQuery } from '@mui/material';
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } 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 {
fontSizeSmall,
@@ -19,19 +18,17 @@ import DOMPurify from 'dompurify';
import {
Spacer,
VideoContentContainer,
VideoDescription,
VideoPlayerContainer,
VideoTitle,
} from './VideoContent-styles.tsx';
import { useScrollToTop } from '../../../hooks/useScrollToTop.tsx';
import { CrowdfundInlineContent } from '../../../components/Publish/EditPlaylist/Upload-styles.tsx';
import { convertQortalLinks } from '../../../components/common/TextEditor/utils.ts';
import { processText } from 'qapp-core';
function flattenHtml(html: string): string {
const sanitize: string = DOMPurify.sanitize(html, {
USE_PROFILES: { html: true },
});
const res = convertQortalLinks(sanitize);
const res = processText(sanitize);
return res
.replace(/<\/p>/gi, ' ') // replace end of paragraph with space
.replace(/<p[^>]*>/gi, '') // remove opening <p> tags
@@ -48,15 +45,38 @@ const CollapsibleDescription = ({
html?: any;
}) => {
const [expanded, setExpanded] = useState(false);
const [showMore, setShowMore] = useState(false);
const textRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (textRef.current) {
const el = textRef.current;
// Clone the element to measure full height
const clone = el.cloneNode(true) as HTMLElement;
clone.style.visibility = 'hidden';
clone.style.position = 'absolute';
clone.style.pointerEvents = 'none';
clone.style.WebkitLineClamp = 'none';
clone.style.display = 'block';
document.body.appendChild(clone);
const fullHeight = clone.offsetHeight;
document.body.removeChild(clone);
const clampedHeight = el.offsetHeight;
if (fullHeight > clampedHeight + 2) {
setShowMore(true);
}
}
}, [text, html]);
return (
<Box
sx={{
maxWidth: '1200px',
}}
>
<Box sx={{ maxWidth: '1200px' }}>
{text && (
<Typography
ref={textRef}
sx={{
display: '-webkit-box',
WebkitLineClamp: expanded ? 'none' : 2,
@@ -70,6 +90,7 @@ const CollapsibleDescription = ({
)}
{html && (
<Box
ref={textRef}
sx={{
display: '-webkit-box',
WebkitLineClamp: expanded ? 'none' : 2,
@@ -81,13 +102,15 @@ const CollapsibleDescription = ({
dangerouslySetInnerHTML={{ __html: flattenHtml(html) }}
/>
)}
<Button
onClick={() => setExpanded(!expanded)}
size="small"
sx={{ mt: 1, textTransform: 'none' }}
>
{expanded ? 'Show less' : 'Show more'}
</Button>
{showMore && (
<Button
onClick={() => setExpanded(!expanded)}
size="small"
sx={{ mt: 1, textTransform: 'none' }}
>
{expanded ? 'Show less' : 'Show more'}
</Button>
)}
</Box>
);
};
@@ -133,8 +156,6 @@ export const VideoContent = () => {
setScreenWidth(window.innerWidth + 120);
});
}, []);
console.log('videoData', videoData);
console.log('videoData?.fullDescription', videoData?.fullDescription);
return (
<>
@@ -233,88 +254,6 @@ export const VideoContent = () => {
) : (
<CollapsibleDescription text={videoData?.fullDescription} />
)}
{/* {videoData?.fullDescription && (
<Box
sx={{
background: '#333333',
borderRadius: '5px',
padding: '5px',
width: '95%',
cursor: !descriptionHeight
? 'default'
: isExpandedDescription
? 'default'
: 'pointer',
position: 'relative',
marginBottom: '30px',
}}
className={
!descriptionHeight
? ''
: isExpandedDescription
? ''
: 'hover-click'
}
>
{descriptionHeight && !isExpandedDescription && (
<Box
sx={{
position: 'absolute',
top: '0px',
right: '0px',
left: '0px',
bottom: '0px',
cursor: 'pointer',
}}
onClick={() => {
if (isExpandedDescription) return;
setIsExpandedDescription(true);
}}
/>
)}
<Box
ref={contentRef}
sx={{
height: !descriptionHeight
? 'auto'
: isExpandedDescription
? 'auto'
: '200px',
overflow: 'hidden',
}}
>
{videoData?.htmlDescription ? (
<DisplayHtml html={videoData?.htmlDescription} />
) : (
<VideoDescription
variant="body1"
color="textPrimary"
sx={{
cursor: 'default',
}}
>
{videoData?.fullDescription}
</VideoDescription>
)}
</Box>
{descriptionHeight >= descriptionThreshold && (
<Typography
onClick={() => {
setIsExpandedDescription((prev) => !prev);
}}
sx={{
fontWeight: 'bold',
fontSize: '16px',
cursor: 'pointer',
paddingLeft: '15px',
paddingTop: '15px',
}}
>
{isExpandedDescription ? 'Show less' : '...more'}
</Typography>
)}
</Box>
)} */}
{id && channelName && (
<>

View File

@@ -1,4 +1,4 @@
import { useCallback, useRef } from 'react';
import { RefObject, useCallback, useRef } from 'react';
import { VideoCardContainer } from './VideoList-styles.tsx';
import {

View File

@@ -100,14 +100,31 @@ export const VideoListItem = ({
);
}}
>
<ResponsiveImage
src={video?.image}
width={320}
height={180}
<div
style={{
maxHeight: '50%',
height: 480,
width: 320,
maxHeight: '50vh',
maxWidth: '100%',
position: 'relative',
overflow: 'hidden',
}}
/>
>
<ResponsiveImage
src={video?.image}
width={320}
height={180}
style={{
position: 'absolute',
inset: 0,
width: '100%',
height: '100%',
objectFit: 'cover',
zIndex: 1,
}}
fill={true}
/>
</div>
<VideoCardTitle>{video?.title}</VideoCardTitle>
<BottomParent>
<NameContainer

View File

@@ -15,8 +15,7 @@ export const VideoLoaderItem = ({ status }) => {
style={{
width: 320,
height: 180,
borderRadius: '8px',
marginTop: 10,
// borderRadius: '8px',
alignSelf: 'center',
maxWidth: '100%',
}}
@@ -24,12 +23,18 @@ export const VideoLoaderItem = ({ status }) => {
<BottomParent>
<NameContainer>
<Skeleton
variant="circular"
style={{
width: 24,
height: 24,
}}
/>
<Skeleton
variant="rectangular"
style={{
width: 200,
height: 50,
marginTop: 12,
height: 34,
alignSelf: 'center',
maxWidth: '100%',
}}
@@ -39,18 +44,8 @@ export const VideoLoaderItem = ({ status }) => {
variant="rectangular"
style={{
width: 200,
height: 24,
marginTop: 15,
alignSelf: 'center',
}}
/>
<Skeleton
variant="rectangular"
style={{
width: 200,
height: 24,
marginTop: 15,
alignSelf: 'center',
height: 15,
marginTop: '5px',
}}
/>
</BottomParent>

View File

@@ -105,7 +105,7 @@ const darkTheme = createTheme({
primary: {
// main: '#101115',
main: '#90CAF9',
dark: 'rgb(45, 92, 201)',
dark: 'rgba(66, 165, 245, 1)',
light: 'rgb(130, 185, 255)',
contrastText: 'rgba(0, 0, 0, 0.87)',
},

View File

@@ -1,33 +1,12 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { viteStaticCopy } from 'vite-plugin-static-copy';
import { fileURLToPath } from 'url';
import path from 'path';
// const __filename = fileURLToPath(import.meta.url);
// const __dirname = path.dirname(__filename);
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), viteStaticCopy({
targets: [
{
src: path.join(
__dirname,
'node_modules',
'mediainfo.js',
'dist',
'MediaInfoModule.wasm'
),
dest: '', // copies to root of /dist → served at /MediaInfoModule.wasm
},
],
}),],
plugins: [react()],
server: {
host: '0.0.0.0',
port: 5174
port: 5174,
},
base: "",
base: '',
});