mirror of
https://github.com/Qortal/q-tube.git
synced 2025-02-11 17:55:51 +00:00
Merge pull request #55 from QortalSeth/main
Updates to VideoPlayer controls
This commit is contained in:
commit
84c07b07cd
20
package-lock.json
generated
20
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "qtube",
|
"name": "qtube",
|
||||||
"version": "2.0.0",
|
"version": "2.1.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "qtube",
|
"name": "qtube",
|
||||||
"version": "2.0.0",
|
"version": "2.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.10.6",
|
"@emotion/react": "^11.10.6",
|
||||||
"@emotion/styled": "^11.10.6",
|
"@emotion/styled": "^11.10.6",
|
||||||
@ -23,6 +23,7 @@
|
|||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
|
"react-idle-timer": "^5.7.2",
|
||||||
"react-intersection-observer": "^9.4.3",
|
"react-intersection-observer": "^9.4.3",
|
||||||
"react-quill": "^2.0.0",
|
"react-quill": "^2.0.0",
|
||||||
"react-redux": "^8.0.5",
|
"react-redux": "^8.0.5",
|
||||||
@ -3958,6 +3959,15 @@
|
|||||||
"react": ">= 16.8 || 18.0.0"
|
"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": {
|
"node_modules/react-intersection-observer": {
|
||||||
"version": "9.5.0",
|
"version": "9.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.5.0.tgz",
|
||||||
@ -7321,6 +7331,12 @@
|
|||||||
"prop-types": "^15.8.1"
|
"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": {
|
"react-intersection-observer": {
|
||||||
"version": "9.5.0",
|
"version": "9.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.5.0.tgz",
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
|
"react-idle-timer": "^5.7.2",
|
||||||
"react-intersection-observer": "^9.4.3",
|
"react-intersection-observer": "^9.4.3",
|
||||||
"react-quill": "^2.0.0",
|
"react-quill": "^2.0.0",
|
||||||
"react-redux": "^8.0.5",
|
"react-redux": "^8.0.5",
|
||||||
|
@ -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>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
@ -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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -5,16 +5,8 @@ import { useEffect } from "react";
|
|||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { Key } from "ts-key-enum";
|
import { Key } from "ts-key-enum";
|
||||||
import { useIsMobile } from "../../../../hooks/useIsMobile.ts";
|
|
||||||
import { setVideoPlaying } from "../../../../state/features/globalSlice.ts";
|
import { setVideoPlaying } from "../../../../state/features/globalSlice.ts";
|
||||||
import {
|
import { RootState } from "../../../../state/store.ts";
|
||||||
setIsMuted,
|
|
||||||
setMutedVolumeSetting,
|
|
||||||
setReduxPlaybackRate,
|
|
||||||
setStretchVideoSetting,
|
|
||||||
setVolumeSetting,
|
|
||||||
} from "../../../../state/features/persistSlice.ts";
|
|
||||||
import { RootState, store } from "../../../../state/store.ts";
|
|
||||||
import { useVideoPlayerState } from "../VideoPlayer-State.ts";
|
import { useVideoPlayerState } from "../VideoPlayer-State.ts";
|
||||||
import { VideoPlayerProps } from "../VideoPlayer.tsx";
|
import { VideoPlayerProps } from "../VideoPlayer.tsx";
|
||||||
|
|
||||||
@ -38,6 +30,7 @@ export const useVideoControlsState = (
|
|||||||
progress,
|
progress,
|
||||||
videoObjectFit,
|
videoObjectFit,
|
||||||
canPlay,
|
canPlay,
|
||||||
|
containerRef,
|
||||||
} = videoPlayerState;
|
} = videoPlayerState;
|
||||||
const { identifier, autoPlay } = props;
|
const { identifier, autoPlay } = props;
|
||||||
|
|
||||||
@ -78,16 +71,15 @@ export const useVideoControlsState = (
|
|||||||
const isFullscreen = useSignal(false);
|
const isFullscreen = useSignal(false);
|
||||||
|
|
||||||
const enterFullscreen = () => {
|
const enterFullscreen = () => {
|
||||||
if (!videoRef.current) return;
|
if (!containerRef.current) return;
|
||||||
if (videoRef.current.requestFullscreen) {
|
|
||||||
videoRef.current.requestFullscreen();
|
if (containerRef.current.requestFullscreen && !isFullscreen.value) {
|
||||||
|
containerRef.current.requestFullscreen();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const exitFullscreen = () => {
|
const exitFullscreen = () => {
|
||||||
if (document.exitFullscreen) {
|
if (isFullscreen.value) document.exitFullscreen();
|
||||||
document.exitFullscreen();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleFullscreen = () => {
|
const toggleFullscreen = () => {
|
||||||
@ -218,14 +210,20 @@ export const useVideoControlsState = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleStretchVideoSetting = () => {
|
const setStretchVideoSetting = (value: "contain" | "fill") => {
|
||||||
const newStretchVideoSetting =
|
videoObjectFit.value = value;
|
||||||
persistSelector.stretchVideoSetting === "contain" ? "fill" : "contain";
|
|
||||||
|
|
||||||
videoObjectFit.value = newStretchVideoSetting;
|
|
||||||
};
|
};
|
||||||
const keyboardShortcutsDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
|
||||||
|
const toggleStretchVideoSetting = () => {
|
||||||
|
videoObjectFit.value =
|
||||||
|
videoObjectFit.value === "contain" ? "fill" : "contain";
|
||||||
|
};
|
||||||
|
|
||||||
|
const keyboardShortcuts = (
|
||||||
|
e: KeyboardEvent | React.KeyboardEvent<HTMLDivElement>
|
||||||
|
) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
// console.log("hotkey is: ", '"' + e.key + '"');
|
||||||
|
|
||||||
switch (e.key) {
|
switch (e.key) {
|
||||||
case "o":
|
case "o":
|
||||||
@ -276,13 +274,6 @@ export const useVideoControlsState = (
|
|||||||
case Key.ArrowUp:
|
case Key.ArrowUp:
|
||||||
changeVolume(0.05);
|
changeVolume(0.05);
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const keyboardShortcutsUp = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
switch (e.key) {
|
|
||||||
case " ":
|
case " ":
|
||||||
togglePlay();
|
togglePlay();
|
||||||
break;
|
break;
|
||||||
@ -291,12 +282,20 @@ export const useVideoControlsState = (
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "f":
|
case "f":
|
||||||
enterFullscreen();
|
toggleFullscreen();
|
||||||
break;
|
break;
|
||||||
case Key.Escape:
|
case Key.Escape:
|
||||||
exitFullscreen();
|
exitFullscreen();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "r":
|
||||||
|
reloadVideo();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "p":
|
||||||
|
togglePictureInPicture();
|
||||||
|
break;
|
||||||
|
|
||||||
case "0":
|
case "0":
|
||||||
setProgressAbsolute(0);
|
setProgressAbsolute(0);
|
||||||
break;
|
break;
|
||||||
@ -360,11 +359,12 @@ export const useVideoControlsState = (
|
|||||||
increaseSpeed,
|
increaseSpeed,
|
||||||
togglePictureInPicture,
|
togglePictureInPicture,
|
||||||
toggleFullscreen,
|
toggleFullscreen,
|
||||||
keyboardShortcutsUp,
|
keyboardShortcuts,
|
||||||
keyboardShortcutsDown,
|
|
||||||
handleCanPlay,
|
handleCanPlay,
|
||||||
toggleMute,
|
toggleMute,
|
||||||
showControlsFullScreen,
|
showControlsFullScreen,
|
||||||
setPlaying,
|
setPlaying,
|
||||||
|
isFullscreen,
|
||||||
|
setStretchVideoSetting,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -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 {
|
import {
|
||||||
Fullscreen,
|
Fullscreen,
|
||||||
Pause,
|
Pause,
|
||||||
@ -7,142 +12,190 @@ import {
|
|||||||
VolumeOff,
|
VolumeOff,
|
||||||
VolumeUp,
|
VolumeUp,
|
||||||
} from "@mui/icons-material";
|
} 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";
|
import { useSignalEffect } from "@preact/signals-react";
|
||||||
|
|
||||||
export const VideoControls = () => {
|
export const PlayButton = () => {
|
||||||
const {
|
const { togglePlay, playing } = useVideoContext();
|
||||||
reloadVideo,
|
return (
|
||||||
togglePlay,
|
<CustomFontTooltip title="Pause/Play (Spacebar)" placement="top" arrow>
|
||||||
onVolumeChange,
|
<IconButton
|
||||||
increaseSpeed,
|
sx={{
|
||||||
togglePictureInPicture,
|
color: "white",
|
||||||
toggleFullscreen,
|
}}
|
||||||
toggleMute,
|
onClick={() => togglePlay()}
|
||||||
onProgressChange,
|
>
|
||||||
toggleRef,
|
{playing.value ? <Pause /> : <PlayArrow />}
|
||||||
from,
|
</IconButton>
|
||||||
videoRef,
|
</CustomFontTooltip>
|
||||||
canPlay,
|
);
|
||||||
isMuted,
|
};
|
||||||
playbackRate,
|
|
||||||
playing,
|
|
||||||
progress,
|
|
||||||
volume,
|
|
||||||
showControlsFullScreen,
|
|
||||||
} = useVideoContext();
|
|
||||||
|
|
||||||
const isScreenSmall = !useMediaQuery(`(min-width:580px)`);
|
export const ReloadButton = () => {
|
||||||
const showMobileControls = isScreenSmall && canPlay.value;
|
const { reloadVideo } = useVideoContext();
|
||||||
|
return (
|
||||||
|
<CustomFontTooltip title="Reload Video (R)" placement="top" arrow>
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
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={{
|
||||||
|
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 (
|
return (
|
||||||
<ControlsContainer
|
<CustomFontTooltip
|
||||||
style={{ bottom: from === "create" ? "15px" : 0, padding: "0px" }}
|
title="Seek video in 10% increments (0-9)"
|
||||||
display={showControlsFullScreen.value ? "flex" : "none"}
|
placement="top"
|
||||||
|
arrow
|
||||||
>
|
>
|
||||||
{showMobileControls ? (
|
<Typography
|
||||||
<MobileControls />
|
sx={{
|
||||||
) : canPlay.value ? (
|
fontSize: isScreenSmall ? fontSizeExSmall : fontSizeSmall,
|
||||||
<>
|
color: "white",
|
||||||
<IconButton
|
visibility: !videoRef.current?.duration ? "hidden" : "visible",
|
||||||
sx={{
|
whiteSpace: "nowrap",
|
||||||
color: "rgba(255, 255, 255, 0.7)",
|
}}
|
||||||
}}
|
>
|
||||||
onClick={() => togglePlay()}
|
{videoRef.current?.duration ? formatTime(progress.value) : ""}
|
||||||
>
|
{" / "}
|
||||||
{playing.value ? <Pause /> : <PlayArrow />}
|
{videoRef.current?.duration
|
||||||
</IconButton>
|
? formatTime(videoRef.current?.duration)
|
||||||
<IconButton
|
: ""}
|
||||||
sx={{
|
</Typography>
|
||||||
color: "rgba(255, 255, 255, 0.7)",
|
</CustomFontTooltip>
|
||||||
marginLeft: "15px",
|
);
|
||||||
}}
|
};
|
||||||
onClick={reloadVideo}
|
|
||||||
>
|
|
||||||
<Refresh />
|
|
||||||
</IconButton>
|
|
||||||
<Slider
|
|
||||||
value={progress.value}
|
|
||||||
onChange={onProgressChange}
|
|
||||||
min={0}
|
|
||||||
max={videoRef.current?.duration || 100}
|
|
||||||
sx={{ flexGrow: 1, mx: 2 }}
|
|
||||||
/>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontSize: "14px",
|
|
||||||
marginRight: "5px",
|
|
||||||
color: "rgba(255, 255, 255, 0.7)",
|
|
||||||
visibility:
|
|
||||||
!videoRef.current?.duration || !progress.value
|
|
||||||
? "hidden"
|
|
||||||
: "visible",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{progress.value &&
|
|
||||||
videoRef.current?.duration &&
|
|
||||||
formatTime(progress.value)}
|
|
||||||
/
|
|
||||||
{progress.value &&
|
|
||||||
videoRef.current?.duration &&
|
|
||||||
formatTime(videoRef.current?.duration)}
|
|
||||||
</Typography>
|
|
||||||
<IconButton
|
|
||||||
sx={{
|
|
||||||
color: "rgba(255, 255, 255, 0.7)",
|
|
||||||
marginRight: "10px",
|
|
||||||
}}
|
|
||||||
onClick={toggleMute}
|
|
||||||
>
|
|
||||||
{isMuted.value ? <VolumeOff /> : <VolumeUp />}
|
|
||||||
</IconButton>
|
|
||||||
<Slider
|
|
||||||
value={volume.value}
|
|
||||||
onChange={onVolumeChange}
|
|
||||||
min={0}
|
|
||||||
max={1}
|
|
||||||
step={0.01}
|
|
||||||
sx={{
|
|
||||||
maxWidth: "100px",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
sx={{
|
|
||||||
color: "rgba(255, 255, 255, 0.7)",
|
|
||||||
fontSize: "14px",
|
|
||||||
marginLeft: "5px",
|
|
||||||
}}
|
|
||||||
onClick={e => increaseSpeed()}
|
|
||||||
>
|
|
||||||
Speed: {playbackRate}x
|
|
||||||
</IconButton>
|
|
||||||
|
|
||||||
|
export const VolumeButton = () => {
|
||||||
|
const { isMuted, toggleMute } = useVideoContext();
|
||||||
|
return (
|
||||||
|
<CustomFontTooltip
|
||||||
|
title="Toggle Mute (M), Raise (UP), Lower (DOWN)"
|
||||||
|
placement="top"
|
||||||
|
arrow
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
color: "white",
|
||||||
|
}}
|
||||||
|
onClick={toggleMute}
|
||||||
|
>
|
||||||
|
{isMuted.value ? <VolumeOff /> : <VolumeUp />}
|
||||||
|
</IconButton>
|
||||||
|
</CustomFontTooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const VolumeSlider = () => {
|
||||||
|
const { volume, onVolumeChange } = useVideoContext();
|
||||||
|
return (
|
||||||
|
<Slider
|
||||||
|
value={volume.value}
|
||||||
|
onChange={onVolumeChange}
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
step={0.01}
|
||||||
|
sx={{
|
||||||
|
width: "100px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PlaybackRate = () => {
|
||||||
|
const { playbackRate, increaseSpeed, isScreenSmall } = useVideoContext();
|
||||||
|
return (
|
||||||
|
<CustomFontTooltip
|
||||||
|
title="Video Speed. Increase (+ or >), Decrease (- or <)"
|
||||||
|
placement="top"
|
||||||
|
arrow
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
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
|
<IconButton
|
||||||
sx={{
|
sx={{
|
||||||
color: "rgba(255, 255, 255, 0.7)",
|
color: "white",
|
||||||
marginLeft: "15px",
|
|
||||||
}}
|
}}
|
||||||
ref={toggleRef}
|
ref={toggleRef}
|
||||||
onClick={togglePictureInPicture}
|
onClick={togglePictureInPicture}
|
||||||
>
|
>
|
||||||
<PictureInPicture />
|
<PictureInPicture />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
</CustomFontTooltip>
|
||||||
sx={{
|
)}
|
||||||
color: "rgba(255, 255, 255, 0.7)",
|
</>
|
||||||
}}
|
);
|
||||||
onClick={toggleFullscreen}
|
};
|
||||||
>
|
|
||||||
<Fullscreen />
|
export const FullscreenButton = () => {
|
||||||
</IconButton>
|
const { toggleFullscreen } = useVideoContext();
|
||||||
</>
|
return (
|
||||||
) : null}
|
<CustomFontTooltip title="Toggle Fullscreen (F)" placement="top" arrow>
|
||||||
</ControlsContainer>
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
color: "white",
|
||||||
|
paddingRight: "0px",
|
||||||
|
}}
|
||||||
|
onClick={() => toggleFullscreen()}
|
||||||
|
>
|
||||||
|
<Fullscreen />
|
||||||
|
</IconButton>
|
||||||
|
</CustomFontTooltip>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
@ -1,3 +1,4 @@
|
|||||||
|
import { useMediaQuery } from "@mui/material";
|
||||||
import {
|
import {
|
||||||
useSignal,
|
useSignal,
|
||||||
useSignalEffect,
|
useSignalEffect,
|
||||||
@ -12,6 +13,7 @@ import React, {
|
|||||||
} from "react";
|
} from "react";
|
||||||
|
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { smallVideoSize } from "../../../constants/Misc.ts";
|
||||||
import { setVideoPlaying } from "../../../state/features/globalSlice.ts";
|
import { setVideoPlaying } from "../../../state/features/globalSlice.ts";
|
||||||
import {
|
import {
|
||||||
setIsMuted,
|
setIsMuted,
|
||||||
@ -292,6 +294,7 @@ export const useVideoPlayerState = (props: VideoPlayerProps, ref: any) => {
|
|||||||
anchorEl.value = null;
|
anchorEl.value = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isScreenSmall = !useMediaQuery(smallVideoSize);
|
||||||
return {
|
return {
|
||||||
containerRef,
|
containerRef,
|
||||||
resourceStatus,
|
resourceStatus,
|
||||||
@ -315,5 +318,6 @@ export const useVideoPlayerState = (props: VideoPlayerProps, ref: any) => {
|
|||||||
playbackRate,
|
playbackRate,
|
||||||
anchorEl,
|
anchorEl,
|
||||||
videoObjectFit,
|
videoObjectFit,
|
||||||
|
isScreenSmall,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -20,12 +20,9 @@ export const VideoElement = styled("video")(({ theme }) => ({
|
|||||||
}));
|
}));
|
||||||
//1075 x 604
|
//1075 x 604
|
||||||
export const ControlsContainer = styled(Box)`
|
export const ControlsContainer = styled(Box)`
|
||||||
position: absolute;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.6);
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
`;
|
`;
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import CSS from "csstype";
|
import CSS from "csstype";
|
||||||
import { forwardRef } from "react";
|
import { forwardRef } from "react";
|
||||||
|
import useIdleTimeout from "../../../hooks/useIdleTimeout.ts";
|
||||||
import { LoadingVideo } from "./Components/LoadingVideo.tsx";
|
import { LoadingVideo } from "./Components/LoadingVideo.tsx";
|
||||||
import { useContextData, VideoContext } from "./Components/VideoContext.ts";
|
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";
|
import { VideoContainer, VideoElement } from "./VideoPlayer-styles.ts";
|
||||||
|
|
||||||
export interface VideoStyles {
|
export interface VideoStyles {
|
||||||
@ -37,8 +38,7 @@ export const VideoPlayer = forwardRef<videoRefType, VideoPlayerProps>(
|
|||||||
const contextData = useContextData(props, ref);
|
const contextData = useContextData(props, ref);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
keyboardShortcutsUp,
|
keyboardShortcuts,
|
||||||
keyboardShortcutsDown,
|
|
||||||
from,
|
from,
|
||||||
videoStyles,
|
videoStyles,
|
||||||
containerRef,
|
containerRef,
|
||||||
@ -55,18 +55,35 @@ export const VideoPlayer = forwardRef<videoRefType, VideoPlayerProps>(
|
|||||||
startPlay,
|
startPlay,
|
||||||
videoObjectFit,
|
videoObjectFit,
|
||||||
showControlsFullScreen,
|
showControlsFullScreen,
|
||||||
|
isFullscreen,
|
||||||
} = contextData;
|
} = 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 (
|
return (
|
||||||
<VideoContext.Provider value={contextData}>
|
<VideoContext.Provider value={contextData}>
|
||||||
<VideoContainer
|
<VideoContainer
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onKeyUp={keyboardShortcutsUp}
|
onKeyDown={keyboardShortcuts}
|
||||||
onKeyDown={keyboardShortcutsDown}
|
|
||||||
style={{
|
style={{
|
||||||
padding: from === "create" ? "8px" : 0,
|
padding: from === "create" ? "8px" : 0,
|
||||||
...videoStyles?.videoContainer,
|
...videoStyles?.videoContainer,
|
||||||
}}
|
}}
|
||||||
|
onMouseEnter={e => {
|
||||||
|
showControlsFullScreen.value = true;
|
||||||
|
}}
|
||||||
|
onMouseLeave={e => {
|
||||||
|
showControlsFullScreen.value = false;
|
||||||
|
}}
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
>
|
>
|
||||||
<LoadingVideo />
|
<LoadingVideo />
|
||||||
@ -83,23 +100,17 @@ export const VideoPlayer = forwardRef<videoRefType, VideoPlayerProps>(
|
|||||||
onEnded={handleEnded}
|
onEnded={handleEnded}
|
||||||
// onLoadedMetadata={handleLoadedMetadata}
|
// onLoadedMetadata={handleLoadedMetadata}
|
||||||
onCanPlay={handleCanPlay}
|
onCanPlay={handleCanPlay}
|
||||||
onMouseEnter={e => {
|
|
||||||
showControlsFullScreen.value = true;
|
|
||||||
}}
|
|
||||||
onMouseLeave={e => {
|
|
||||||
showControlsFullScreen.value = false;
|
|
||||||
}}
|
|
||||||
preload="metadata"
|
preload="metadata"
|
||||||
style={
|
style={{
|
||||||
startPlay.value
|
...videoStyles?.video,
|
||||||
? {
|
objectFit: isFullscreen ? "fill" : videoObjectFit.value,
|
||||||
...videoStyles?.video,
|
height:
|
||||||
objectFit: videoObjectFit.value,
|
isFullscreen.value && showControlsFullScreen.value
|
||||||
}
|
? "calc(100vh - 40px)"
|
||||||
: { height: "100%", ...videoStyles }
|
: "100%",
|
||||||
}
|
}}
|
||||||
/>
|
/>
|
||||||
<VideoControls />
|
{showControls && <VideoControlsBar />}
|
||||||
</VideoContainer>
|
</VideoContainer>
|
||||||
</VideoContext.Provider>
|
</VideoContext.Provider>
|
||||||
);
|
);
|
||||||
|
@ -21,5 +21,6 @@ const largeScreenSize = 1400 - newUIWidthDiff;
|
|||||||
export const smallScreenSizeString = `${smallScreenSize}px`;
|
export const smallScreenSizeString = `${smallScreenSize}px`;
|
||||||
export const largeScreenSizeString = `${largeScreenSize}px`;
|
export const largeScreenSizeString = `${largeScreenSize}px`;
|
||||||
|
|
||||||
|
export const smallVideoSize = `(min-width:720px)`;
|
||||||
export const headerIconSize = "40px";
|
export const headerIconSize = "40px";
|
||||||
export const menuIconSize = "28px";
|
export const menuIconSize = "28px";
|
||||||
|
14
src/hooks/useIdleTimeout.ts
Normal file
14
src/hooks/useIdleTimeout.ts
Normal 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;
|
@ -11,6 +11,7 @@ import {
|
|||||||
largeScreenSizeString,
|
largeScreenSizeString,
|
||||||
minFileSize,
|
minFileSize,
|
||||||
smallScreenSizeString,
|
smallScreenSizeString,
|
||||||
|
smallVideoSize,
|
||||||
} from "../../../constants/Misc.ts";
|
} from "../../../constants/Misc.ts";
|
||||||
import { useIsMobile } from "../../../hooks/useIsMobile.ts";
|
import { useIsMobile } from "../../../hooks/useIsMobile.ts";
|
||||||
import { formatBytes } from "../../../utils/numberFunctions.ts";
|
import { formatBytes } from "../../../utils/numberFunctions.ts";
|
||||||
@ -47,7 +48,7 @@ export const VideoContent = () => {
|
|||||||
setSuperLikeList,
|
setSuperLikeList,
|
||||||
} = useVideoContentState();
|
} = useVideoContentState();
|
||||||
|
|
||||||
const isScreenSmall = !useMediaQuery(`(min-width:${smallScreenSizeString})`);
|
const isScreenSmall = !useMediaQuery(smallVideoSize);
|
||||||
const [screenWidth, setScreenWidth] = useState<number>(
|
const [screenWidth, setScreenWidth] = useState<number>(
|
||||||
window.innerWidth + 120
|
window.innerWidth + 120
|
||||||
);
|
);
|
||||||
@ -75,7 +76,7 @@ export const VideoContent = () => {
|
|||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
padding: `0px 0px 0px ${isScreenSmall ? "5px" : "2%"}`,
|
padding: `0px 0px 0px ${isScreenSmall ? "0px" : "2%"}`,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
}}
|
}}
|
||||||
onClick={focusVideo}
|
onClick={focusVideo}
|
||||||
@ -112,7 +113,9 @@ export const VideoContent = () => {
|
|||||||
) : (
|
) : (
|
||||||
<Box sx={{ width: "55vw", aspectRatio: "16/9" }}></Box>
|
<Box sx={{ width: "55vw", aspectRatio: "16/9" }}></Box>
|
||||||
)}
|
)}
|
||||||
<VideoContentContainer>
|
<VideoContentContainer
|
||||||
|
sx={{ paddingLeft: isScreenSmall ? "5px" : "0px" }}
|
||||||
|
>
|
||||||
<VideoTitle
|
<VideoTitle
|
||||||
variant={isScreenSmall ? "h2" : "h1"}
|
variant={isScreenSmall ? "h2" : "h1"}
|
||||||
color="textPrimary"
|
color="textPrimary"
|
||||||
|
@ -1,20 +1,13 @@
|
|||||||
import { TabContext, TabList, TabPanel } from "@mui/lab";
|
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 React from "react";
|
||||||
import LazyLoad from "../../components/common/LazyLoad";
|
import LazyLoad from "../../components/common/LazyLoad";
|
||||||
import { ListSuperLikeContainer } from "../../components/common/ListSuperLikes/ListSuperLikeContainer.tsx";
|
import { ListSuperLikeContainer } from "../../components/common/ListSuperLikes/ListSuperLikeContainer.tsx";
|
||||||
import {
|
import { fontSizeLarge, fontSizeSmall } from "../../constants/Misc.ts";
|
||||||
fontSizeLarge,
|
|
||||||
fontSizeMedium,
|
|
||||||
fontSizeSmall,
|
|
||||||
} from "../../constants/Misc.ts";
|
|
||||||
import { useIsMobile } from "../../hooks/useIsMobile.ts";
|
|
||||||
import { SearchSidebar } from "./Components/SearchSidebar.tsx";
|
import { SearchSidebar } from "./Components/SearchSidebar.tsx";
|
||||||
import { FiltersCol, VideoManagerRow } from "./Components/VideoList-styles.tsx";
|
|
||||||
import VideoList from "./Components/VideoList.tsx";
|
import VideoList from "./Components/VideoList.tsx";
|
||||||
import { useHomeState } from "./Home-State.ts";
|
import { useHomeState } from "./Home-State.ts";
|
||||||
import { SubtitleContainer } from "./Home-styles";
|
|
||||||
|
|
||||||
interface HomeProps {
|
interface HomeProps {
|
||||||
mode?: string;
|
mode?: string;
|
||||||
|
23
src/utils/CustomFontTooltip.tsx
Normal file
23
src/utils/CustomFontTooltip.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
@ -1,8 +1,9 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from "vite";
|
||||||
import react from '@vitejs/plugin-react'
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
base: ""
|
server: { port: 3000 },
|
||||||
})
|
base: "",
|
||||||
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user