1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-24 12:26:40 +07:00

play video in embed instead of new window on desktops

This commit is contained in:
Fedor Katurov 2025-02-26 16:48:54 +07:00
parent 5056047546
commit 06cf7050a9
8 changed files with 184 additions and 44 deletions

View file

@ -36,6 +36,7 @@
"react-lazyload": "^3.2.0",
"react-masonry-css": "^1.0.16",
"react-popper": "^2.2.3",
"react-resize-detector": "^12.0.2",
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2",
"react-sticky-box": "^1.0.2",

View file

@ -0,0 +1,28 @@
import classNames from 'classnames';
import { useResizeDetector } from 'react-resize-detector';
import styles from './styles.module.scss';
interface Props {
id: string;
title: string;
className?: string;
}
export const CommentVideoFrame = ({ id, title, className }: Props) => {
const { ref, width = 0, height = 0 } = useResizeDetector();
return (
<div className={classNames(styles.wrap, className)} ref={ref}>
<iframe
width={width}
height={height}
src={`https://www.youtube.com/embed/${id}?autoplay=1`}
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
frameBorder="0"
allowFullScreen
title={title}
/>
</div>
);
};

View file

@ -0,0 +1,8 @@
@import '~/styles/variables';
.wrap {
width: 100%;
aspect-ratio: calc(16 / 9);
overflow: hidden;
border-radius: $radius;
}

View file

@ -1,15 +1,21 @@
import { FC, memo, useMemo } from 'react';
import { FC, memo, useCallback, useMemo } from 'react';
import { Icon } from '~/components/common/Icon';
import { ICommentBlockProps } from '~/constants/comment';
import { useWindowSize } from '~/hooks/dom/useWindowSize';
import { useYoutubeMetadata } from '~/hooks/metadata/useYoutubeMetadata';
import { getYoutubeThumb } from '~/utils/dom';
import { useVideoPlayer } from '~/utils/providers/VideoPlayerProvider';
import { CommentVideoFrame } from './components/CommentVideoFrame';
import styles from './styles.module.scss';
type Props = ICommentBlockProps & {};
const CommentEmbedBlock: FC<Props> = memo(({ block }) => {
const { isTablet } = useWindowSize();
const { url, setUrl } = useVideoPlayer();
const id = useMemo(() => {
const match = block.content.match(
/https?:\/\/(?:www\.)?(?:youtube\.com|youtu\.be)\/(?:watch)?(?:\?v=)?([\w\-=]+)/,
@ -18,7 +24,7 @@ const CommentEmbedBlock: FC<Props> = memo(({ block }) => {
return (match && match[1]) || '';
}, [block.content]);
const url = useMemo(() => `https://youtube.com/watch?v=${id}`, [id]);
const address = `https://youtube.com/watch?v=${id}`;
const preview = useMemo(
() => getYoutubeThumb(block.content),
@ -28,11 +34,35 @@ const CommentEmbedBlock: FC<Props> = memo(({ block }) => {
const metadata = useYoutubeMetadata(id);
const title = metadata?.metadata?.title || '';
const onClick = useCallback(() => {
if (isTablet) {
window.open(address, '_blank');
return;
}
setUrl(address);
}, [isTablet, setUrl, address]);
const closeVideo = useCallback(() => setUrl(''), [setUrl]);
return (
<div className={styles.embed}>
<a href={url} target="_blank" rel="noreferrer" />
<div className={styles.preview}>
{url === address ? (
<div className={styles.video}>
<div className={styles.close} onClick={closeVideo}>
<Icon icon="close" />
</div>
<div className={styles.animation}>
<CommentVideoFrame id={id} title={title} />
</div>
</div>
) : (
<div
className={styles.preview}
role="button"
onClick={onClick}
tabIndex={-1}
>
<div style={{ backgroundImage: `url("${preview}")` }}>
<div className={styles.backdrop}>
<div className={styles.play}>
@ -43,6 +73,7 @@ const CommentEmbedBlock: FC<Props> = memo(({ block }) => {
</div>
</div>
</div>
)}
</div>
);
});

View file

@ -1,8 +1,8 @@
@import 'src/styles/variables';
.embed {
padding: 0 $gap;
height: $comment_height;
padding: 0 0;
min-height: $comment_height;
width: 100%;
box-sizing: border-box;
background: 50% 50% no-repeat;
@ -69,6 +69,7 @@
justify-content: stretch;
box-sizing: border-box;
z-index: 2;
cursor: pointer;
& > div {
width: 100%;
@ -98,3 +99,47 @@
overflow: hidden;
text-overflow: ellipsis;
}
@keyframes appear {
0% {
grid-template-columns: 0fr;
opacity: 0;
}
50% {
grid-template-columns: 1fr;
}
100% {
opacity: 1;
}
}
.video {
width: 100%;
position: relative;
padding: $gap / 2;
}
.close {
display: flex;
align-items: center;
justify-content: center;
background: var(--color_danger);
width: 64px;
height: 24px;
position: absolute;
bottom: calc(100% - #{$gap / 2});
right: 24px;
border-radius: $radius $radius 0 0;
z-index: 10;
cursor: pointer;
}
.animation {
background-color: var(--content_bg_darker);
display: grid;
animation: appear 0.5s forwards;
width: 100%;
border-radius: $radius;
}

View file

@ -15,6 +15,7 @@ import { useCommentContext } from '~/utils/context/CommentContextProvider';
import { useNodeContext } from '~/utils/context/NodeContextProvider';
import { useUserContext } from '~/utils/context/UserContextProvider';
import { canEditComment, canLikeComment } from '~/utils/node';
import { VideoPlayerProvider } from '~/utils/providers/VideoPlayerProvider';
import styles from './styles.module.scss';
@ -84,6 +85,7 @@ const NodeComments: FC<Props> = observer(({ order }) => {
}, [isLoading]);
return (
<VideoPlayerProvider>
<div className={styles.wrap}>
{order === 'DESC' && more}
@ -116,6 +118,7 @@ const NodeComments: FC<Props> = observer(({ order }) => {
{order === 'ASC' && more}
</div>
</VideoPlayerProvider>
);
});

View file

@ -0,0 +1,17 @@
import { createContext, ReactNode, useContext, useState } from 'react';
const Context = createContext({
url: '',
setUrl: (val: string) => {},
});
/** Provides context for comment video playing */
export const VideoPlayerProvider = ({ children }: { children: ReactNode }) => {
const [url, setUrl] = useState('');
return (
<Context.Provider value={{ url, setUrl }}>{children}</Context.Provider>
);
};
export const useVideoPlayer = () => useContext(Context);

View file

@ -2626,6 +2626,13 @@ react-popper@^2.2.3:
react-fast-compare "^3.0.1"
warning "^4.0.2"
react-resize-detector@^12.0.2:
version "12.0.2"
resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-12.0.2.tgz#5e65f906a85835d246de57dbf608bf22ab333cad"
integrity sha512-aAI4WxWAysWLhA8wKDpsS+PnnxQ0lWCkTlk2t+2ijalWvoSa7vPxmcKRLURkH+PU84QE4KP4dO58oVP3ypWkKA==
dependencies:
lodash "^4.17.21"
react-router-dom@^5.1.2:
version "5.3.0"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.0.tgz#da1bfb535a0e89a712a93b97dd76f47ad1f32363"