3
0
mirror of https://github.com/Qortal/q-tube.git synced 2025-02-11 09:45:51 +00:00

Updates to VideoPlayer controls

All controls and hotkeys work when the VideoPlayer is fullscreen

Controls are below video instead of inside of it

Controls have tooltips showing what they do and their hotkeys

Each control is a separate component that is used in both mobile and normal controls

Video Progress slider is above controls to save horizontal space

Controls will disappear when fullscreen if mouse leaves video player, or after 5 seconds of inactivity

Default port in vite.config.ts set to 3000 for simplicity.
This commit is contained in:
Qortal Dev 2024-12-20 10:21:58 -07:00
parent 29de8a8a9f
commit 53eaad448b
16 changed files with 440 additions and 304 deletions

20
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "qtube",
"version": "2.0.0",
"version": "2.1.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "qtube",
"version": "2.0.0",
"version": "2.1.0",
"dependencies": {
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
@ -23,6 +23,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-idle-timer": "^5.7.2",
"react-intersection-observer": "^9.4.3",
"react-quill": "^2.0.0",
"react-redux": "^8.0.5",
@ -3958,6 +3959,15 @@
"react": ">= 16.8 || 18.0.0"
}
},
"node_modules/react-idle-timer": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/react-idle-timer/-/react-idle-timer-5.7.2.tgz",
"integrity": "sha512-+BaPfc7XEUU5JFkwZCx6fO1bLVK+RBlFH+iY4X34urvIzZiZINP6v2orePx3E6pAztJGE7t4DzvL7if2SL/0GQ==",
"peerDependencies": {
"react": ">=16",
"react-dom": ">=16"
}
},
"node_modules/react-intersection-observer": {
"version": "9.5.0",
"resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.5.0.tgz",
@ -7321,6 +7331,12 @@
"prop-types": "^15.8.1"
}
},
"react-idle-timer": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/react-idle-timer/-/react-idle-timer-5.7.2.tgz",
"integrity": "sha512-+BaPfc7XEUU5JFkwZCx6fO1bLVK+RBlFH+iY4X34urvIzZiZINP6v2orePx3E6pAztJGE7t4DzvL7if2SL/0GQ==",
"requires": {}
},
"react-intersection-observer": {
"version": "9.5.0",
"resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.5.0.tgz",

View File

@ -25,6 +25,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-idle-timer": "^5.7.2",
"react-intersection-observer": "^9.4.3",
"react-quill": "^2.0.0",
"react-redux": "^8.0.5",

View File

@ -1,109 +0,0 @@
import {
Fullscreen,
MoreVert as MoreIcon,
Pause,
PictureInPicture,
PlayArrow,
Refresh,
VolumeUp,
} from "@mui/icons-material";
import { IconButton, Menu, MenuItem, Slider, Typography } from "@mui/material";
import { useVideoContext } from "./VideoContext.ts";
export const MobileControls = () => {
const {
togglePlay,
reloadVideo,
onProgressChange,
videoRef,
handleMenuOpen,
handleMenuClose,
onVolumeChange,
increaseSpeed,
togglePictureInPicture,
toggleFullscreen,
playing,
progress,
anchorEl,
volume,
playbackRate,
} = useVideoContext();
return (
<>
<IconButton
sx={{
color: "rgba(255, 255, 255, 0.7)",
}}
onClick={() => togglePlay()}
>
{playing.value ? <Pause /> : <PlayArrow />}
</IconButton>
<IconButton
sx={{
color: "rgba(255, 255, 255, 0.7)",
}}
onClick={reloadVideo}
>
<Refresh />
</IconButton>
<Slider
value={progress.value}
onChange={onProgressChange}
min={0}
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>
<Menu
id="simple-menu"
anchorEl={anchorEl.value}
keepMounted
open={Boolean(anchorEl.value)}
onClose={handleMenuClose}
PaperProps={{
style: {
width: "250px",
},
}}
>
<MenuItem>
<VolumeUp />
<Slider
value={volume.value}
onChange={onVolumeChange}
min={0}
max={1}
step={0.01}
/>
</MenuItem>
<MenuItem onClick={togglePictureInPicture}>
<PictureInPicture />
</MenuItem>
</Menu>
</>
);
};

View File

@ -0,0 +1,66 @@
import { MoreVert as MoreIcon } from "@mui/icons-material";
import { Box, IconButton, Menu, MenuItem } from "@mui/material";
import { useVideoContext } from "./VideoContext.ts";
import {
FullscreenButton,
PictureInPictureButton,
PlaybackRate,
PlayButton,
ProgressSlider,
ReloadButton,
VideoTime,
VolumeButton,
VolumeSlider,
} from "./VideoControls.tsx";
export const MobileControlsBar = () => {
const { handleMenuOpen, handleMenuClose, anchorEl } = useVideoContext();
const controlGroupSX = { display: "flex", gap: "5px", alignItems: "center" };
return (
<>
<Box sx={controlGroupSX}>
<PlayButton />
<ReloadButton />
<ProgressSlider />
<VideoTime />
</Box>
<Box sx={controlGroupSX}>
<PlaybackRate />
<FullscreenButton />
<IconButton
edge="end"
color="inherit"
aria-label="menu"
onClick={handleMenuOpen}
sx={{ minWidth: "30px", paddingLeft: "0px" }}
>
<MoreIcon />
</IconButton>
</Box>
<Menu
id="simple-menu"
anchorEl={anchorEl.value}
keepMounted
open={Boolean(anchorEl.value)}
onClose={handleMenuClose}
PaperProps={{
style: {
width: "250px",
},
}}
>
<MenuItem>
<VolumeButton />
<VolumeSlider />
</MenuItem>
<MenuItem>
<PictureInPictureButton />
</MenuItem>
</Menu>
</>
);
};

View File

@ -5,16 +5,8 @@ import { useEffect } from "react";
import ReactDOM from "react-dom";
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,
} from "../../../../state/features/persistSlice.ts";
import { RootState, store } from "../../../../state/store.ts";
import { RootState } from "../../../../state/store.ts";
import { useVideoPlayerState } from "../VideoPlayer-State.ts";
import { VideoPlayerProps } from "../VideoPlayer.tsx";
@ -38,6 +30,7 @@ export const useVideoControlsState = (
progress,
videoObjectFit,
canPlay,
containerRef,
} = videoPlayerState;
const { identifier, autoPlay } = props;
@ -78,16 +71,15 @@ export const useVideoControlsState = (
const isFullscreen = useSignal(false);
const enterFullscreen = () => {
if (!videoRef.current) return;
if (videoRef.current.requestFullscreen) {
videoRef.current.requestFullscreen();
if (!containerRef.current) return;
if (containerRef.current.requestFullscreen && !isFullscreen.value) {
containerRef.current.requestFullscreen();
}
};
const exitFullscreen = () => {
if (document.exitFullscreen) {
document.exitFullscreen();
}
if (isFullscreen.value) document.exitFullscreen();
};
const toggleFullscreen = () => {
@ -218,14 +210,20 @@ export const useVideoControlsState = (
}
};
const toggleStretchVideoSetting = () => {
const newStretchVideoSetting =
persistSelector.stretchVideoSetting === "contain" ? "fill" : "contain";
videoObjectFit.value = newStretchVideoSetting;
const setStretchVideoSetting = (value: "contain" | "fill") => {
videoObjectFit.value = value;
};
const keyboardShortcutsDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
const toggleStretchVideoSetting = () => {
videoObjectFit.value =
videoObjectFit.value === "contain" ? "fill" : "contain";
};
const keyboardShortcuts = (
e: KeyboardEvent | React.KeyboardEvent<HTMLDivElement>
) => {
e.preventDefault();
// console.log("hotkey is: ", '"' + e.key + '"');
switch (e.key) {
case "o":
@ -276,13 +274,6 @@ export const useVideoControlsState = (
case Key.ArrowUp:
changeVolume(0.05);
break;
}
};
const keyboardShortcutsUp = (e: React.KeyboardEvent<HTMLDivElement>) => {
e.preventDefault();
switch (e.key) {
case " ":
togglePlay();
break;
@ -291,12 +282,20 @@ export const useVideoControlsState = (
break;
case "f":
enterFullscreen();
toggleFullscreen();
break;
case Key.Escape:
exitFullscreen();
break;
case "r":
reloadVideo();
break;
case "p":
togglePictureInPicture();
break;
case "0":
setProgressAbsolute(0);
break;
@ -360,11 +359,12 @@ export const useVideoControlsState = (
increaseSpeed,
togglePictureInPicture,
toggleFullscreen,
keyboardShortcutsUp,
keyboardShortcutsDown,
keyboardShortcuts,
handleCanPlay,
toggleMute,
showControlsFullScreen,
setPlaying,
isFullscreen,
setStretchVideoSetting,
};
};

View File

@ -1,3 +1,8 @@
import { IconButton, Slider, Typography } from "@mui/material";
import { fontSizeExSmall, fontSizeSmall } from "../../../../constants/Misc.ts";
import { CustomFontTooltip } from "../../../../utils/CustomFontTooltip.tsx";
import { formatTime } from "../../../../utils/numberFunctions.ts";
import { useVideoContext } from "./VideoContext.ts";
import {
Fullscreen,
Pause,
@ -7,101 +12,118 @@ import {
VolumeOff,
VolumeUp,
} from "@mui/icons-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 {
reloadVideo,
togglePlay,
onVolumeChange,
increaseSpeed,
togglePictureInPicture,
toggleFullscreen,
toggleMute,
onProgressChange,
toggleRef,
from,
videoRef,
canPlay,
isMuted,
playbackRate,
playing,
progress,
volume,
showControlsFullScreen,
} = useVideoContext();
const isScreenSmall = !useMediaQuery(`(min-width:580px)`);
const showMobileControls = isScreenSmall && canPlay.value;
export const PlayButton = () => {
const { togglePlay, playing } = useVideoContext();
return (
<ControlsContainer
style={{ bottom: from === "create" ? "15px" : 0, padding: "0px" }}
display={showControlsFullScreen.value ? "flex" : "none"}
>
{showMobileControls ? (
<MobileControls />
) : canPlay.value ? (
<>
<CustomFontTooltip title="Pause/Play (Spacebar)" placement="top" arrow>
<IconButton
sx={{
color: "rgba(255, 255, 255, 0.7)",
color: "white",
}}
onClick={() => togglePlay()}
>
{playing.value ? <Pause /> : <PlayArrow />}
</IconButton>
</CustomFontTooltip>
);
};
export const ReloadButton = () => {
const { reloadVideo } = useVideoContext();
return (
<CustomFontTooltip title="Reload Video (R)" placement="top" arrow>
<IconButton
sx={{
color: "rgba(255, 255, 255, 0.7)",
marginLeft: "15px",
color: "white",
}}
onClick={reloadVideo}
>
<Refresh />
</IconButton>
</CustomFontTooltip>
);
};
export const ProgressSlider = () => {
const { progress, onProgressChange, videoRef } = useVideoContext();
const sliderThumbSize = "16px";
return (
<Slider
value={progress.value}
onChange={onProgressChange}
min={0}
max={videoRef.current?.duration || 100}
sx={{ flexGrow: 1, mx: 2 }}
sx={{
position: "absolute",
bottom: "40px",
color: "#00abff",
padding: "0px",
// prevents the slider from jumping up 20px in certain mobile conditions
"@media (pointer: coarse)": { padding: "0px" },
"& .MuiSlider-thumb": {
backgroundColor: "#fff",
width: sliderThumbSize,
height: sliderThumbSize,
},
"& .MuiSlider-rail": { opacity: 0.5 },
}}
/>
);
};
export const VideoTime = () => {
const { videoRef, progress, isScreenSmall } = useVideoContext();
return (
<CustomFontTooltip
title="Seek video in 10% increments (0-9)"
placement="top"
arrow
>
<Typography
sx={{
fontSize: "14px",
marginRight: "5px",
color: "rgba(255, 255, 255, 0.7)",
visibility:
!videoRef.current?.duration || !progress.value
? "hidden"
: "visible",
fontSize: isScreenSmall ? fontSizeExSmall : fontSizeSmall,
color: "white",
visibility: !videoRef.current?.duration ? "hidden" : "visible",
whiteSpace: "nowrap",
}}
>
{progress.value &&
videoRef.current?.duration &&
formatTime(progress.value)}
/
{progress.value &&
videoRef.current?.duration &&
formatTime(videoRef.current?.duration)}
{videoRef.current?.duration ? formatTime(progress.value) : ""}
{" / "}
{videoRef.current?.duration
? formatTime(videoRef.current?.duration)
: ""}
</Typography>
</CustomFontTooltip>
);
};
export const VolumeButton = () => {
const { isMuted, toggleMute } = useVideoContext();
return (
<CustomFontTooltip
title="Toggle Mute (M), Raise (UP), Lower (DOWN)"
placement="top"
arrow
>
<IconButton
sx={{
color: "rgba(255, 255, 255, 0.7)",
marginRight: "10px",
color: "white",
}}
onClick={toggleMute}
>
{isMuted.value ? <VolumeOff /> : <VolumeUp />}
</IconButton>
</CustomFontTooltip>
);
};
export const VolumeSlider = () => {
const { volume, onVolumeChange } = useVideoContext();
return (
<Slider
value={volume.value}
onChange={onVolumeChange}
@ -109,40 +131,71 @@ export const VideoControls = () => {
max={1}
step={0.01}
sx={{
maxWidth: "100px",
width: "100px",
}}
/>
<IconButton
sx={{
color: "rgba(255, 255, 255, 0.7)",
fontSize: "14px",
marginLeft: "5px",
}}
onClick={e => increaseSpeed()}
>
Speed: {playbackRate}x
</IconButton>
);
};
export const PlaybackRate = () => {
const { playbackRate, increaseSpeed, isScreenSmall } = useVideoContext();
return (
<CustomFontTooltip
title="Video Speed. Increase (+ or >), Decrease (- or <)"
placement="top"
arrow
>
<IconButton
sx={{
color: "rgba(255, 255, 255, 0.7)",
marginLeft: "15px",
color: "white",
fontSize: isScreenSmall ? fontSizeExSmall : fontSizeSmall,
paddingTop: "0px",
paddingBottom: "0px",
}}
onClick={() => increaseSpeed()}
>
<span style={{ display: "flex", alignItems: "center", height: "40px" }}>
{playbackRate}x
</span>
</IconButton>
</CustomFontTooltip>
);
};
export const PictureInPictureButton = () => {
const { isFullscreen, toggleRef, togglePictureInPicture } = useVideoContext();
return (
<>
{!isFullscreen.value && (
<CustomFontTooltip title="Picture in Picture (P)" placement="top" arrow>
<IconButton
sx={{
color: "white",
}}
ref={toggleRef}
onClick={togglePictureInPicture}
>
<PictureInPicture />
</IconButton>
</CustomFontTooltip>
)}
</>
);
};
export const FullscreenButton = () => {
const { toggleFullscreen } = useVideoContext();
return (
<CustomFontTooltip title="Toggle Fullscreen (F)" placement="top" arrow>
<IconButton
sx={{
color: "rgba(255, 255, 255, 0.7)",
color: "white",
paddingRight: "0px",
}}
onClick={toggleFullscreen}
onClick={() => toggleFullscreen()}
>
<Fullscreen />
</IconButton>
</>
) : null}
</ControlsContainer>
</CustomFontTooltip>
);
};

View File

@ -0,0 +1,62 @@
import { Box } from "@mui/material";
import { ControlsContainer } from "../VideoPlayer-styles.ts";
import { MobileControlsBar } from "./MobileControlsBar.tsx";
import { useVideoContext } from "./VideoContext.ts";
import {
FullscreenButton,
PictureInPictureButton,
PlaybackRate,
PlayButton,
ProgressSlider,
ReloadButton,
VideoTime,
VolumeButton,
VolumeSlider,
} from "./VideoControls.tsx";
import { useSignalEffect } from "@preact/signals-react";
export const VideoControlsBar = () => {
const { from, canPlay, showControlsFullScreen, isScreenSmall, progress } =
useVideoContext();
const showMobileControls = isScreenSmall && canPlay.value;
const controlsHeight = "40px";
const controlGroupSX = {
display: "flex",
gap: "5px",
alignItems: "center",
height: controlsHeight,
};
return (
<ControlsContainer
style={{
padding: "0px",
height: controlsHeight,
}}
>
{showMobileControls ? (
<MobileControlsBar />
) : canPlay.value ? (
<>
<Box sx={controlGroupSX}>
<PlayButton />
<ReloadButton />
<ProgressSlider />
<VolumeButton />
<VolumeSlider />
<VideoTime />
</Box>
<Box sx={controlGroupSX}>
<PlaybackRate />
<PictureInPictureButton />
<FullscreenButton />
</Box>
</>
) : null}
</ControlsContainer>
);
};

View File

@ -1,3 +1,4 @@
import { useMediaQuery } from "@mui/material";
import {
useSignal,
useSignalEffect,
@ -12,6 +13,7 @@ import React, {
} from "react";
import { useDispatch, useSelector } from "react-redux";
import { smallVideoSize } from "../../../constants/Misc.ts";
import { setVideoPlaying } from "../../../state/features/globalSlice.ts";
import {
setIsMuted,
@ -292,6 +294,7 @@ export const useVideoPlayerState = (props: VideoPlayerProps, ref: any) => {
anchorEl.value = null;
};
const isScreenSmall = !useMediaQuery(smallVideoSize);
return {
containerRef,
resourceStatus,
@ -315,5 +318,6 @@ export const useVideoPlayerState = (props: VideoPlayerProps, ref: any) => {
playbackRate,
anchorEl,
videoObjectFit,
isScreenSmall,
};
};

View File

@ -20,12 +20,9 @@ export const VideoElement = styled("video")(({ theme }) => ({
}));
//1075 x 604
export const ControlsContainer = styled(Box)`
position: absolute;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.6);
`;

View File

@ -1,8 +1,9 @@
import CSS from "csstype";
import { forwardRef } from "react";
import useIdleTimeout from "../../../hooks/useIdleTimeout.ts";
import { LoadingVideo } from "./Components/LoadingVideo.tsx";
import { useContextData, VideoContext } from "./Components/VideoContext.ts";
import { VideoControls } from "./Components/VideoControls.tsx";
import { VideoControlsBar } from "./Components/VideoControlsBar.tsx";
import { VideoContainer, VideoElement } from "./VideoPlayer-styles.ts";
export interface VideoStyles {
@ -37,8 +38,7 @@ export const VideoPlayer = forwardRef<videoRefType, VideoPlayerProps>(
const contextData = useContextData(props, ref);
const {
keyboardShortcutsUp,
keyboardShortcutsDown,
keyboardShortcuts,
from,
videoStyles,
containerRef,
@ -55,18 +55,35 @@ export const VideoPlayer = forwardRef<videoRefType, VideoPlayerProps>(
startPlay,
videoObjectFit,
showControlsFullScreen,
isFullscreen,
} = contextData;
const showControls =
!isFullscreen.value ||
(isFullscreen.value && showControlsFullScreen.value);
const idleTime = 5000; // Time in milliseconds
useIdleTimeout({
onIdle: () => (showControlsFullScreen.value = false),
onActive: () => (showControlsFullScreen.value = true),
idleTime,
});
return (
<VideoContext.Provider value={contextData}>
<VideoContainer
tabIndex={0}
onKeyUp={keyboardShortcutsUp}
onKeyDown={keyboardShortcutsDown}
onKeyDown={keyboardShortcuts}
style={{
padding: from === "create" ? "8px" : 0,
...videoStyles?.videoContainer,
}}
onMouseEnter={e => {
showControlsFullScreen.value = true;
}}
onMouseLeave={e => {
showControlsFullScreen.value = false;
}}
ref={containerRef}
>
<LoadingVideo />
@ -83,23 +100,17 @@ export const VideoPlayer = forwardRef<videoRefType, VideoPlayerProps>(
onEnded={handleEnded}
// onLoadedMetadata={handleLoadedMetadata}
onCanPlay={handleCanPlay}
onMouseEnter={e => {
showControlsFullScreen.value = true;
}}
onMouseLeave={e => {
showControlsFullScreen.value = false;
}}
preload="metadata"
style={
startPlay.value
? {
style={{
...videoStyles?.video,
objectFit: videoObjectFit.value,
}
: { height: "100%", ...videoStyles }
}
objectFit: isFullscreen ? "fill" : videoObjectFit.value,
height:
isFullscreen.value && showControlsFullScreen.value
? "calc(100vh - 40px)"
: "100%",
}}
/>
<VideoControls />
{showControls && <VideoControlsBar />}
</VideoContainer>
</VideoContext.Provider>
);

View File

@ -21,5 +21,6 @@ const largeScreenSize = 1400 - newUIWidthDiff;
export const smallScreenSizeString = `${smallScreenSize}px`;
export const largeScreenSizeString = `${largeScreenSize}px`;
export const smallVideoSize = `(min-width:720px)`;
export const headerIconSize = "40px";
export const menuIconSize = "28px";

View File

@ -0,0 +1,14 @@
import { useContext, useState } from "react";
import { useIdleTimer } from "react-idle-timer";
const useIdleTimeout = ({ onIdle, onActive, idleTime = 10_000 }) => {
const idleTimer = useIdleTimer({
timeout: idleTime,
onIdle: onIdle,
onActive: onActive,
});
return {
idleTimer,
};
};
export default useIdleTimeout;

View File

@ -11,6 +11,7 @@ import {
largeScreenSizeString,
minFileSize,
smallScreenSizeString,
smallVideoSize,
} from "../../../constants/Misc.ts";
import { useIsMobile } from "../../../hooks/useIsMobile.ts";
import { formatBytes } from "../../../utils/numberFunctions.ts";
@ -47,7 +48,7 @@ export const VideoContent = () => {
setSuperLikeList,
} = useVideoContentState();
const isScreenSmall = !useMediaQuery(`(min-width:${smallScreenSizeString})`);
const isScreenSmall = !useMediaQuery(smallVideoSize);
const [screenWidth, setScreenWidth] = useState<number>(
window.innerWidth + 120
);
@ -75,7 +76,7 @@ export const VideoContent = () => {
sx={{
display: "flex",
flexDirection: "column",
padding: `0px 0px 0px ${isScreenSmall ? "5px" : "2%"}`,
padding: `0px 0px 0px ${isScreenSmall ? "0px" : "2%"}`,
width: "100%",
}}
onClick={focusVideo}
@ -112,7 +113,9 @@ export const VideoContent = () => {
) : (
<Box sx={{ width: "55vw", aspectRatio: "16/9" }}></Box>
)}
<VideoContentContainer>
<VideoContentContainer
sx={{ paddingLeft: isScreenSmall ? "5px" : "0px" }}
>
<VideoTitle
variant={isScreenSmall ? "h2" : "h1"}
color="textPrimary"

View File

@ -1,20 +1,13 @@
import { TabContext, TabList, TabPanel } from "@mui/lab";
import { Box, Grid, Tab, useMediaQuery } from "@mui/material";
import { Box, Tab, useMediaQuery } from "@mui/material";
import React from "react";
import LazyLoad from "../../components/common/LazyLoad";
import { ListSuperLikeContainer } from "../../components/common/ListSuperLikes/ListSuperLikeContainer.tsx";
import {
fontSizeLarge,
fontSizeMedium,
fontSizeSmall,
} from "../../constants/Misc.ts";
import { useIsMobile } from "../../hooks/useIsMobile.ts";
import { fontSizeLarge, fontSizeSmall } from "../../constants/Misc.ts";
import { SearchSidebar } from "./Components/SearchSidebar.tsx";
import { FiltersCol, VideoManagerRow } from "./Components/VideoList-styles.tsx";
import VideoList from "./Components/VideoList.tsx";
import { useHomeState } from "./Home-State.ts";
import { SubtitleContainer } from "./Home-styles";
interface HomeProps {
mode?: string;

View File

@ -0,0 +1,23 @@
import { Box, Tooltip, TooltipProps } from "@mui/material";
import { PropsWithChildren } from "react";
import { fontSizeSmall } from "../constants/Misc";
export interface CustomFontTooltipProps extends TooltipProps {
fontSize?: string;
}
export const CustomFontTooltip = ({
fontSize,
title,
children,
...props
}: PropsWithChildren<CustomFontTooltipProps>) => {
if (!fontSize) fontSize = "160%";
const text = <Box sx={{ fontSize: fontSize }}>{title}</Box>;
// put controls into individual components
return (
<Tooltip title={text} {...props} sx={{ display: "contents", ...props.sx }}>
<div>{children}</div>
</Tooltip>
);
};

View File

@ -1,8 +1,9 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
base: ""
})
server: { port: 3000 },
base: "",
});