diff --git a/src/components/cards/CommentCard.tsx b/src/components/cards/CommentCard.tsx index 815e6ca..8faba1b 100644 --- a/src/components/cards/CommentCard.tsx +++ b/src/components/cards/CommentCard.tsx @@ -1,22 +1,51 @@ -import { ActionIcon, Box, Button, CopyButton, Grid, Group, Paper, Stack, Tooltip } from "@mantine/core"; +import { ActionIcon, Box, Button, Collapse, CopyButton, Grid, Group, Loader, Paper, Stack, Tooltip } from "@mantine/core"; import { Comment } from "../../api/types/comment"; import { MarkdownText } from "../ui/MarkdownText"; import { ChannelCard } from "./ChannelCard"; import { VotingCard } from "./VotingCard"; -import { IconArrowDown, IconCopy, IconPencil, IconPinned, IconTableImport, IconTableOff } from "@tabler/icons-react"; +import { IconArrowDown, IconArrowUp, IconCopy, IconPencil, IconPinned, IconTableImport, IconTableOff } from "@tabler/icons-react"; import { parseChapters } from "../../utils/parseChapters"; -import { useContext } from "react"; +import { useContext, useState } from "react"; import { VideoPlayerContext } from "../../api/player/VideoPlayerContext"; import { parseFormattedText, textPartsToString } from "../../utils/parseFormattedText"; import { DateCard } from "./DateCard"; import { TimestampRegex } from "../../utils/timestamp"; +import { useDisclosure } from "@mantine/hooks"; +import { APIContext } from "../../api/provider/APIController"; export const CommentCard = ({ comment }: { comment: Comment, }) => { + const { api } = useContext(APIContext); const { activeChapters, setActiveChapters } = useContext(VideoPlayerContext); + const [opened, { toggle }] = useDisclosure(false); + const [isLoading, setLoading] = useState(true); + const [error, setError] = useState(); + const [replies, setReplies] = useState([]); + + const onExpand = async () => { + toggle(); + + // dont fetch when closing + if(opened) return; + + // dont fetch if fetched + if(replies.length) return; + + setLoading(true); + setError(null); + setReplies([]); + + try { + + } catch(e) { + setError(e); + } finally { + setLoading(false); + } + }; const hasChapters = comment.content.match(TimestampRegex)?.length > 1; const isChaptersSource = activeChapters.type == "comment" && activeChapters.id == comment.id; @@ -62,7 +91,8 @@ export const CommentCard = ({ @@ -103,6 +133,9 @@ export const CommentCard = ({ + + {isLoading && } + ); diff --git a/src/components/options/comps/Flavor.tsx b/src/components/options/comps/Flavor.tsx new file mode 100644 index 0000000..e8627bf --- /dev/null +++ b/src/components/options/comps/Flavor.tsx @@ -0,0 +1,16 @@ +import { useContext } from "react"; +import { TabsContext } from "../../tabs/TabsContext"; +import { SegmentedControl } from "@mantine/core"; +import { UIFlavor } from "../../tabs/TabTypes"; + +export const FlavorOption = () => { + const { flavor, setFlavor } = useContext(TabsContext); + + return ( + setFlavor(v as UIFlavor)} + /> + ) +}; diff --git a/src/components/options/views/MainView.tsx b/src/components/options/views/MainView.tsx index bffaa13..cf4614e 100644 --- a/src/components/options/views/MainView.tsx +++ b/src/components/options/views/MainView.tsx @@ -9,6 +9,7 @@ import { DebuggingSection } from "../comps/DebuggingSection"; import { OpenWithButton } from "../links/OpenWithButton"; import { LoopVideo } from "../comps/LoopVideo"; import { ThemeSection } from "../comps/ThemeSection"; +import { FlavorOption } from "../comps/Flavor"; export const OptionsMainView = () => { const { videoInfo } = useContext(VideoPlayerContext); @@ -31,6 +32,7 @@ export const OptionsMainView = () => { {loaded && } + diff --git a/src/components/player/controls/LoopButton.tsx b/src/components/player/controls/LoopButton.tsx new file mode 100644 index 0000000..dd77a1b --- /dev/null +++ b/src/components/player/controls/LoopButton.tsx @@ -0,0 +1,28 @@ +import { ActionIcon, Tooltip } from "@mantine/core"; +import { useFullscreen } from "@mantine/hooks"; +import { IconMaximize, IconMinimize, IconRepeat, IconRepeatOff } from "@tabler/icons-react"; +import { useContext, useState } from "react"; +import { VideoPlayerContext } from "../../../api/player/VideoPlayerContext"; + +export const LoopButton = () => { + const { videoElement } = useContext(VideoPlayerContext); + const [loop, setLoop] = useState(videoElement.loop); + + return ( + + { + videoElement.loop = !videoElement.loop; + setLoop(!videoElement.loop); + }} + variant="light" + > + {loop ? ( + + ) : ( + + )} + + + ); +}; diff --git a/src/components/player/layout/LayoutTop.tsx b/src/components/player/layout/LayoutTop.tsx index b0405fa..5b3a8cd 100644 --- a/src/components/player/layout/LayoutTop.tsx +++ b/src/components/player/layout/LayoutTop.tsx @@ -13,8 +13,8 @@ export const PlayerLayoutTop = () => { p="xs" onClick={(e) => e.stopPropagation()} > - - + + {playState == "loading" && ( )} diff --git a/src/components/player/music/MusicControls.tsx b/src/components/player/music/MusicControls.tsx new file mode 100644 index 0000000..2210b31 --- /dev/null +++ b/src/components/player/music/MusicControls.tsx @@ -0,0 +1,28 @@ +import { Group, Paper, Stack } from "@mantine/core" +import { PlayPauseButton } from "../controls/PlayPauseButton" +import { VolumeControls } from "../controls/VolumeControls" +import { PlayerTimestamp } from "../controls/PlayerTimestamp" +import { ProgressBar } from "../bar/ProgressBar" +import { LoopButton } from "../controls/LoopButton" +import { PlayerLayoutTop } from "../layout/LayoutTop" + +export const MusicControls = () => { + return ( + + + + + + + + + + + + + + + + + ) +} diff --git a/src/components/player/music/MusicPlayer.tsx b/src/components/player/music/MusicPlayer.tsx new file mode 100644 index 0000000..f2fee62 --- /dev/null +++ b/src/components/player/music/MusicPlayer.tsx @@ -0,0 +1,62 @@ +import { useContext, useEffect, useRef, useState } from "react"; +import { VideoPlayerContext } from "../../../api/player/VideoPlayerContext" +import { Box, Stack, Transition } from "@mantine/core"; +import { useDebouncedCallback, useHotkeys, useHover, useMergedRef } from "@mantine/hooks"; +import { usePreference } from "../../../api/pref/Preferences"; +import { MusicControls } from "./MusicControls"; +import { useIsMobile } from "../../../hooks/useIsMobile"; + +export const MusicPlayer = () => { + const { + videoElement, + seekToChapterOffset, + seekTo, + togglePlay, + playState, + muted, + setMuted, + autoplayDate, + } = useContext(VideoPlayerContext); + const videoContainerRef = useRef(null); + + useEffect(() => { + videoContainerRef.current?.appendChild(videoElement); + if(videoElement.paused && videoElement.currentSrc && videoElement.currentTime) + videoElement.play(); + return () => { + videoElement.pause(); + }; + }, [videoElement, videoContainerRef.current]); + + useHotkeys([ + ["ArrowLeft", () => seekTo(videoElement.currentTime - 5)], + ["ArrowRight", () => seekTo(videoElement.currentTime + 5)], + ["Shift + ArrowRight", () => seekToChapterOffset(1)], + ["Shift + ArrowLeft", () => seekToChapterOffset(-1)], + ["J", () => seekTo(videoElement.currentTime - 10)], + ["L", () => seekTo(videoElement.currentTime + 10)], + ["K", () => togglePlay()], + ["Space", () => togglePlay()], + ["m", () => setMuted(!muted)], + ["0", () => seekTo(0)], + ]); + return ( + + + + + ); +}; diff --git a/src/hooks/useIsMobile.tsx b/src/hooks/useIsMobile.tsx index 91b7584..898199b 100644 --- a/src/hooks/useIsMobile.tsx +++ b/src/hooks/useIsMobile.tsx @@ -9,7 +9,7 @@ export const useIsMobile = () => { export const MobileContext = createContext(false); export const MobileProvider = ({ children }: React.PropsWithChildren) => { const { breakpoints } = useMantineTheme(); - let value = useMediaQuery(`(min-width: ${breakpoints.sm})`, true); + let value = !useMediaQuery(`(min-width: ${breakpoints.sm})`, true); return ( { - - + + - - + + diff --git a/src/site/layouts/LayoutDesktopMusic.tsx b/src/site/layouts/LayoutDesktopMusic.tsx new file mode 100644 index 0000000..a9fedb9 --- /dev/null +++ b/src/site/layouts/LayoutDesktopMusic.tsx @@ -0,0 +1,14 @@ +import { Box, Stack } from "@mantine/core"; +import { MusicPlayer } from "../../components/player/music/MusicPlayer"; +import { TabsRenderer } from "../../components/tabs/TabsRenderer"; + +export const LayoutDesktopMusic = () => { + return ( + + + + + + + ) +}; diff --git a/src/site/pages/WatchPage.tsx b/src/site/pages/WatchPage.tsx index 527ed8f..a2ecf42 100644 --- a/src/site/pages/WatchPage.tsx +++ b/src/site/pages/WatchPage.tsx @@ -11,6 +11,7 @@ import { useIsMobile } from "../../hooks/useIsMobile"; import { LayoutDesktopVideo } from "../layouts/LayoutDesktopVideo"; import { LayoutMobileVideo } from "../layouts/LayoutMobileVideo"; import { UIFlavor } from "../../components/tabs/TabTypes"; +import { LayoutDesktopMusic } from "../layouts/LayoutDesktopMusic"; export const WatchPage = () => { const { flavor } = useContext(TabsContext); @@ -20,6 +21,7 @@ export const WatchPage = () => { ({ "d:video": , "m:video": , + "d:music": , } as Record<`${"m" | "d"}:${UIFlavor}`, React.ReactNode>)[`${isMobile ? "m" : "d"}:${flavor}`] ); };