diff --git a/src/App-State.ts b/src/App-Functions.ts similarity index 72% rename from src/App-State.ts rename to src/App-Functions.ts index 685e80b..cc50133 100644 --- a/src/App-State.ts +++ b/src/App-Functions.ts @@ -1,20 +1,5 @@ -import { useEffect, useState } from "react"; import { SubscriptionData } from "./components/common/ContentButtons/SubscribeButton.tsx"; -import { setFilteredSubscriptions } from "./state/features/videoSlice.ts"; import { store } from "./state/store.ts"; -import { persistStore } from "redux-persist"; - -export const useAppState = () => { - const [theme, setTheme] = useState("dark"); - const persistor = persistStore(store); - - useEffect(() => { - subscriptionListFilter(false).then(filteredList => { - store.dispatch(setFilteredSubscriptions(filteredList)); - }); - }, []); - return { persistor, theme, setTheme }; -}; export const getUserName = async () => { const account = await qortalRequest({ @@ -28,6 +13,7 @@ export const getUserName = async () => { if (nameData?.length > 0) return nameData[0].name; else return ""; }; + export const filterVideosByName = ( subscriptionList: SubscriptionData[], userName: string diff --git a/src/App.tsx b/src/App.tsx index aac7ec1..db7fc08 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,15 +1,18 @@ import { CssBaseline } from "@mui/material"; import { ThemeProvider } from "@mui/material/styles"; +import { useEffect, useState } from "react"; import { Provider } from "react-redux"; import { Route, Routes } from "react-router-dom"; +import { persistStore } from "redux-persist"; import { PersistGate } from "redux-persist/integration/react"; -import { useAppState } from "./App-State.ts"; +import { subscriptionListFilter } from "./App-Functions.ts"; import Notification from "./components/common/Notification/Notification"; import { useIframe } from "./hooks/useIframe.tsx"; import { IndividualProfile } from "./pages/ContentPages/IndividualProfile/IndividualProfile"; import { PlaylistContent } from "./pages/ContentPages/PlaylistContent/PlaylistContent"; import { VideoContent } from "./pages/ContentPages/VideoContent/VideoContent"; import { Home } from "./pages/Home/Home"; +import { setFilteredSubscriptions } from "./state/features/videoSlice.ts"; import { store } from "./state/store"; import { darkTheme, lightTheme } from "./styles/theme"; import DownloadWrapper from "./wrappers/DownloadWrapper"; @@ -18,9 +21,16 @@ import { ScrollWrapper } from "./wrappers/ScrollWrapper.tsx"; function App() { // const themeColor = window._qdnTheme - const { persistor, theme, setTheme } = useAppState(); + const persistor = persistStore(store); + const [theme, setTheme] = useState("dark"); useIframe(); + useEffect(() => { + subscriptionListFilter(false).then(filteredList => { + store.dispatch(setFilteredSubscriptions(filteredList)); + }); + }, []); + return ( diff --git a/src/components/Publish/EditVideo/EditVideo.tsx b/src/components/Publish/EditVideo/EditVideo.tsx index 05688d6..bb97d0d 100644 --- a/src/components/Publish/EditVideo/EditVideo.tsx +++ b/src/components/Publish/EditVideo/EditVideo.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from "react"; import Compressor from "compressorjs"; +import { formatBytes } from "../../../utils/numberFunctions.ts"; import { AddCoverImageButton, @@ -62,25 +63,11 @@ import { titleFormatter, videoMaxSize, } from "../../../constants/Misc.ts"; +import { Signal, useSignal } from "@preact/signals-react"; const uid = new ShortUniqueId(); const shortuid = new ShortUniqueId({ length: 5 }); -interface NewCrowdfundProps { - editId?: string; - editContent?: null | { - title: string; - user: string; - coverImage: string | null; - }; -} - -interface VideoFile { - file: File; - title: string; - description: string; - coverImage?: string; -} export const EditVideo = () => { const theme = useTheme(); const dispatch = useDispatch(); @@ -106,6 +93,10 @@ export const EditVideo = () => { useState(null); const [imageExtracts, setImageExtracts] = useState([]); + const videoDuration: Signal = useSignal([ + editVideoProperties?.duration || 0, + ]); + const { getRootProps, getInputProps } = useDropzone({ accept: { "video/*": [], @@ -293,6 +284,8 @@ export const EditVideo = () => { code: editVideoProperties.code, videoType: file?.type || "video/mp4", filename: `${alphanumericString.trim()}.${fileExtension}`, + fileSize: file?.size || 0, + duration: videoDuration.value[0], }; const metadescription = @@ -508,6 +501,8 @@ export const EditVideo = () => { onFramesExtracted(imgs)} + videoDurations={videoDuration} + index={0} /> )} diff --git a/src/components/Publish/PublishVideo/PublishVideo.tsx b/src/components/Publish/PublishVideo/PublishVideo.tsx index 1a3cda3..a62642f 100644 --- a/src/components/Publish/PublishVideo/PublishVideo.tsx +++ b/src/components/Publish/PublishVideo/PublishVideo.tsx @@ -16,7 +16,7 @@ import { useTheme, } from "@mui/material"; import Compressor from "compressorjs"; -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { useDropzone } from "react-dropzone"; import { useDispatch, useSelector } from "react-redux"; import ShortUniqueId from "short-unique-id"; @@ -38,6 +38,7 @@ import { import { setNotification } from "../../../state/features/notificationsSlice.ts"; import { RootState } from "../../../state/store.ts"; +import { formatBytes } from "../../../utils/numberFunctions.ts"; import { objectToBase64 } from "../../../utils/PublishFormatter.ts"; import { getFileName } from "../../../utils/stringFunctions.ts"; import { CardContentContainerComment } from "../../common/Comments/Comments-styles.tsx"; @@ -65,6 +66,8 @@ import { StyledButton, TimesIcon, } from "./PublishVideo-styles.tsx"; +import { signal, Signal, useSignal } from "@preact/signals-react"; +import { useSignals } from "@preact/signals-react/runtime"; export const toBase64 = (file: File): Promise => new Promise((resolve, reject) => { @@ -103,10 +106,8 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => { (state: RootState) => state.auth?.user?.address ); const [files, setFiles] = useState([]); - + const videoDurations = useSignal([]); const [isOpen, setIsOpen] = useState(false); - const [title, setTitle] = useState(""); - const [description, setDescription] = useState(""); const [coverImageForAll, setCoverImageForAll] = useState(""); const [step, setStep] = useState("videos"); @@ -136,6 +137,14 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => { useState(false); const [imageExtracts, setImageExtracts] = useState({}); + useSignals(); + const assembleVideoDurations = () => { + if (files.length === videoDurations.value.length) return; + const newArray: number[] = []; + files.map(() => newArray.push(0)); + videoDurations.value = [...newArray]; + }; + const { getRootProps, getInputProps } = useDropzone({ accept: { "video/*": [], @@ -302,6 +311,8 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => { code, videoType: file?.type || "video/mp4", filename: `${alphanumericString.trim()}.${fileExtension}`, + fileSize: file?.size || 0, + duration: videoDurations.value[i], }; const metadescription = @@ -818,11 +829,14 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => { )} {files.map((file, index) => { + assembleVideoDurations(); return ( onFramesExtracted(imgs, index)} + videoDurations={videoDurations} + index={index} /> {file?.file?.name} {!isCheckSameCoverImage && ( @@ -1159,7 +1173,7 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => { { + setInlineContent={(value: string) => { setPlaylistDescription(value); }} /> diff --git a/src/components/StatsData.tsx b/src/components/StatsData.tsx index a474ca3..10d489a 100644 --- a/src/components/StatsData.tsx +++ b/src/components/StatsData.tsx @@ -4,6 +4,12 @@ import { Grid } from "@mui/material"; import { useFetchVideos } from "../hooks/useFetchVideos.tsx"; import { useSelector } from "react-redux"; import { RootState } from "../state/store.ts"; +import { signal } from "@preact/signals-react"; + +/* eslint-disable react-refresh/only-export-components */ +export const totalVideosPublished = signal(0); +export const totalNamesPublished = signal(0); +export const videosPerNamePublished = signal(0); export const StatsData = () => { const StatsCol = styled(Grid)(({ theme }) => ({ @@ -14,44 +20,43 @@ export const StatsData = () => { backgroundColor: theme.palette.background.default, })); - const { - getVideos, - getNewVideos, - checkNewVideos, - getVideosFiltered, - getVideosCount, - } = useFetchVideos(); + const { getVideosCount } = useFetchVideos(); - const persistReducer = useSelector((state: RootState) => state.persist); - const totalVideosPublished = useSelector( - (state: RootState) => state.global.totalVideosPublished - ); - const totalNamesPublished = useSelector( - (state: RootState) => state.global.totalNamesPublished - ); - const videosPerNamePublished = useSelector( - (state: RootState) => state.global.videosPerNamePublished - ); + const showValueIfExists = (value: number) => { + return value > 0 ? "inline" : "none"; + }; + const showStats = useSelector((state: RootState) => state.persist.showStats); + const showVideoCount = showValueIfExists(totalVideosPublished.value); + const showPublisherCount = showValueIfExists(totalNamesPublished.value); + const showAverage = showValueIfExists(videosPerNamePublished.value); useEffect(() => { getVideosCount(); }, [getVideosCount]); return ( - +
Videos:{" "} - {totalVideosPublished} + + {totalVideosPublished.value} +
Publishers:{" "} - {totalNamesPublished} + + {totalNamesPublished.value} +
Average:{" "} - - {videosPerNamePublished > 0 && - Number(videosPerNamePublished).toFixed(0)} + + {Number(videosPerNamePublished.value).toFixed(0)}
diff --git a/src/components/common/ContentButtons/SubscribeButton.tsx b/src/components/common/ContentButtons/SubscribeButton.tsx index 14a3cba..136c942 100644 --- a/src/components/common/ContentButtons/SubscribeButton.tsx +++ b/src/components/common/ContentButtons/SubscribeButton.tsx @@ -1,7 +1,7 @@ import { Button, ButtonProps, Tooltip } from "@mui/material"; import { MouseEvent, useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { subscriptionListFilter } from "../../../App-State.ts"; +import { subscriptionListFilter } from "../../../App-Functions.ts"; import { RootState } from "../../../state/store.ts"; import { subscribe, diff --git a/src/components/common/FrameExtractor/FrameExtractor.tsx b/src/components/common/FrameExtractor/FrameExtractor.tsx index 04d47fa..57116ad 100644 --- a/src/components/common/FrameExtractor/FrameExtractor.tsx +++ b/src/components/common/FrameExtractor/FrameExtractor.tsx @@ -1,77 +1,92 @@ -import React, { useEffect, useRef, useState, useMemo } from 'react'; +import { Signal } from "@preact/signals-react"; +import { useSignals } from "@preact/signals-react/runtime"; +import React, { useEffect, useRef, useState, useMemo } from "react"; -export const FrameExtractor = ({ videoFile, onFramesExtracted }) => { - const videoRef = useRef(null); - const [durations, setDurations] = useState([]); - const canvasRef = useRef(null); +export interface FrameExtractorProps { + videoFile: File; + onFramesExtracted: (imgs, index?: number) => Promise; + videoDurations?: Signal; + index?: number; +} - useEffect(() => { - const video = videoRef.current; - video.addEventListener('loadedmetadata', () => { - const duration = video.duration; - if (isFinite(duration)) { - // Proceed with your logic - - console.log('duration', duration) - const section = duration / 4; - let timestamps = []; +export const FrameExtractor = ({ + videoFile, + onFramesExtracted, + videoDurations, + index, +}: FrameExtractorProps) => { + useSignals(); + const videoRef = useRef(null); + const [durations, setDurations] = useState([]); + const canvasRef = useRef(null); - for (let i = 0; i < 4; i++) { - const randomTime = Math.random() * section + i * section; - timestamps.push(randomTime); - } + useEffect(() => { + const video = videoRef.current; + video.addEventListener("loadedmetadata", () => { + const duration = video.duration; + if (isFinite(duration)) { + // Proceed with your logic - setDurations(timestamps); - } else { - onFramesExtracted([]) + const newVideoDurations = [...videoDurations.value]; + newVideoDurations[index] = duration; + videoDurations.value = [...newVideoDurations]; + + const section = duration / 4; + const timestamps = []; + + for (let i = 0; i < 4; i++) { + const randomTime = Math.random() * section + i * section; + timestamps.push(randomTime); } - }); - }, [videoFile]); - console.log({durations}) + setDurations(timestamps); + } else { + onFramesExtracted([]); + } + }); + }, [videoFile]); - useEffect(() => { - if (durations.length === 4) { - extractFrames(); - } - }, [durations]); + useEffect(() => { + if (durations.length === 4) { + extractFrames(); + } + }, [durations]); - const fileUrl = useMemo(() => { - return URL.createObjectURL(videoFile); - }, [videoFile]); + const fileUrl = useMemo(() => { + return URL.createObjectURL(videoFile); + }, [videoFile]); - const extractFrames = async () => { - const video = videoRef.current; - const canvas = canvasRef.current; - canvas.width = video.videoWidth; - canvas.height = video.videoHeight; - const context = canvas.getContext('2d'); - - let frameData = []; - - for (const time of durations) { - await new Promise((resolve) => { - video.currentTime = time; - const onSeeked = () => { - context.drawImage(video, 0, 0, canvas.width, canvas.height); - canvas.toBlob(blob => { - frameData.push(blob); - resolve(); - }, 'image/png'); - video.removeEventListener('seeked', onSeeked); - }; - video.addEventListener('seeked', onSeeked, { once: true }); - }); - } - - onFramesExtracted(frameData); - }; - + const extractFrames = async () => { + const video = videoRef.current; + const canvas = canvasRef.current; + canvas.width = video.videoWidth; + canvas.height = video.videoHeight; + const context = canvas.getContext("2d"); - return ( -
- - -
- ); + const frameData = []; + + for (const time of durations) { + await new Promise(resolve => { + video.currentTime = time; + const onSeeked = () => { + context.drawImage(video, 0, 0, canvas.width, canvas.height); + canvas.toBlob(blob => { + frameData.push(blob); + resolve(); + }, "image/png"); + video.removeEventListener("seeked", onSeeked); + }; + video.addEventListener("seeked", onSeeked, { once: true }); + }); + } + + onFramesExtracted(frameData); + }; + + return ( +
+ + +
+ ); }; diff --git a/src/components/common/VideoPlayer/Components/LoadingVideo.tsx b/src/components/common/VideoPlayer/Components/LoadingVideo.tsx index 131c1d4..3d75451 100644 --- a/src/components/common/VideoPlayer/Components/LoadingVideo.tsx +++ b/src/components/common/VideoPlayer/Components/LoadingVideo.tsx @@ -2,6 +2,7 @@ import { Box, CircularProgress, Typography } from "@mui/material"; import { setVideoPlaying } from "../../../../state/features/globalSlice.ts"; import { useDispatch } from "react-redux"; import { PlayArrow } from "@mui/icons-material"; +import { formatTime } from "../../../../utils/numberFunctions.ts"; import { useVideoContext } from "./VideoContext.ts"; export const LoadingVideo = () => { @@ -13,6 +14,7 @@ export const LoadingVideo = () => { canPlay, from, togglePlay, + duration, } = useVideoContext(); const getDownloadProgress = (current: number, total: number) => { @@ -83,36 +85,50 @@ export const LoadingVideo = () => { )} )} - {((!src && !isLoading.value) || (!startPlay.value && !canPlay.value)) && ( - { - if (from === "create") return; - dispatch(setVideoPlaying(null)); - togglePlay(); - }} - sx={{ - cursor: "pointer", - }} - > - + {duration && ( + + {formatTime(duration)} + + )} + { + if (from === "create") return; + dispatch(setVideoPlaying(null)); + + togglePlay(); }} - /> - + sx={{ + cursor: "pointer", + }} + > + + + )} ); diff --git a/src/components/common/VideoPlayer/Components/VideoControls-State.ts b/src/components/common/VideoPlayer/Components/VideoControls-State.ts index 2368473..050ab0c 100644 --- a/src/components/common/VideoPlayer/Components/VideoControls-State.ts +++ b/src/components/common/VideoPlayer/Components/VideoControls-State.ts @@ -1,5 +1,5 @@ import { useSignal } from "@preact/signals-react"; -import { useSignals } from "@preact/signals-react/runtime"; +import { useSignalEffect, useSignals } from "@preact/signals-react/runtime"; import { useEffect } from "react"; import ReactDOM from "react-dom"; @@ -35,6 +35,7 @@ export const useVideoControlsState = ( progress, videoObjectFit, canPlay, + isMobileView, } = videoPlayerState; const { identifier, autoPlay } = props; @@ -114,31 +115,6 @@ export const useVideoControlsState = ( }; }, []); - function formatTime(seconds: number): string { - seconds = Math.floor(seconds); - const minutes: number | string = Math.floor(seconds / 60); - let hours: number | string = Math.floor(minutes / 60); - - let remainingSeconds: number | string = seconds % 60; - let remainingMinutes: number | string = minutes % 60; - - if (remainingSeconds < 10) { - remainingSeconds = "0" + remainingSeconds; - } - - if (remainingMinutes < 10) { - remainingMinutes = "0" + remainingMinutes; - } - - if (hours === 0) { - hours = ""; - } else { - hours = hours + ":"; - } - - return hours + remainingMinutes + ":" + remainingSeconds; - } - const reloadVideo = async () => { if (!videoRef.current || !src) return; const currentTime = videoRef.current.currentTime; @@ -154,6 +130,7 @@ export const useVideoControlsState = ( if (firstPlay.value) setPlaying(true); // makes the video play when fully loaded firstPlay.value = false; isLoading.value = false; + updatePlaybackRate(persistSelector.playbackRate); }; const setPlaying = async (setPlay: boolean) => { @@ -380,6 +357,14 @@ export const useVideoControlsState = ( } }, [videoPlaying, identifier, src]); + useSignalEffect(() => { + console.log("canPlay is: ", canPlay.value); // makes the function execute when canPlay changes + const videoWidth = videoRef?.current?.offsetWidth; + if (videoWidth && videoWidth <= 600) { + isMobileView.value = true; + } + }); + return { reloadVideo, togglePlay, @@ -387,7 +372,6 @@ export const useVideoControlsState = ( increaseSpeed, togglePictureInPicture, toggleFullscreen, - formatTime, keyboardShortcutsUp, keyboardShortcutsDown, handleCanPlay, diff --git a/src/components/common/VideoPlayer/Components/VideoControls.tsx b/src/components/common/VideoPlayer/Components/VideoControls.tsx index 15cd00a..03e3283 100644 --- a/src/components/common/VideoPlayer/Components/VideoControls.tsx +++ b/src/components/common/VideoPlayer/Components/VideoControls.tsx @@ -8,15 +8,13 @@ import { VolumeUp, } from "@mui/icons-material"; import { IconButton, Slider, Typography } from "@mui/material"; -import { useSignalEffect, useSignals } from "@preact/signals-react/runtime"; -import { useEffect } from "react"; +import { formatTime } from "../../../../utils/numberFunctions.ts"; import { ControlsContainer } from "../VideoPlayer-styles.ts"; import { MobileControls } from "./MobileControls.tsx"; import { useVideoContext } from "./VideoContext.ts"; export const VideoControls = () => { - useSignals(); const { reloadVideo, togglePlay, @@ -24,7 +22,6 @@ export const VideoControls = () => { increaseSpeed, togglePictureInPicture, toggleFullscreen, - formatTime, toggleMute, onProgressChange, toggleRef, @@ -40,14 +37,6 @@ export const VideoControls = () => { showControlsFullScreen, } = useVideoContext(); - useSignalEffect(() => { - console.log("canPlay is: ", canPlay.value); // makes the function execute when canPlay changes - const videoWidth = videoRef?.current?.offsetWidth; - if (videoWidth && videoWidth <= 600) { - isMobileView.value = true; - } - }); - const showMobileControls = isMobileView.value && canPlay.value && showControlsFullScreen.value; diff --git a/src/components/common/VideoPlayer/VideoPlayer.tsx b/src/components/common/VideoPlayer/VideoPlayer.tsx index 7e6f8b3..3b81d5a 100644 --- a/src/components/common/VideoPlayer/VideoPlayer.tsx +++ b/src/components/common/VideoPlayer/VideoPlayer.tsx @@ -1,4 +1,3 @@ -import { useSignals } from "@preact/signals-react/runtime"; import CSS from "csstype"; import { forwardRef } from "react"; import { LoadingVideo } from "./Components/LoadingVideo.tsx"; @@ -26,6 +25,7 @@ export interface VideoPlayerProps { onEnd?: () => void; autoPlay?: boolean; style?: CSS.Properties; + duration?: number; } export type videoRefType = { @@ -34,7 +34,6 @@ export type videoRefType = { }; export const VideoPlayer = forwardRef( (props: VideoPlayerProps, ref) => { - useSignals(); const contextData = useContextData(props, ref); const { @@ -56,6 +55,7 @@ export const VideoPlayer = forwardRef( startPlay, videoObjectFit, showControlsFullScreen, + duration, } = contextData; return ( diff --git a/src/components/common/VideoPlayer/VideoPlayerGlobal.tsx b/src/components/common/VideoPlayer/VideoPlayerGlobal.tsx index 360beb7..e2cb0dd 100644 --- a/src/components/common/VideoPlayer/VideoPlayerGlobal.tsx +++ b/src/components/common/VideoPlayer/VideoPlayerGlobal.tsx @@ -12,6 +12,7 @@ import { VolumeOff, } from "@mui/icons-material"; import { styled } from "@mui/system"; +import { formatTime } from "../../../utils/numberFunctions.ts"; import { MyContext } from "../../../wrappers/DownloadWrapper.tsx"; import { useDispatch, useSelector } from "react-redux"; import { RootState } from "../../../state/store.ts"; @@ -250,31 +251,6 @@ export const VideoPlayerGlobal: React.FC = ({ }; }, []); - function formatTime(seconds: number): string { - seconds = Math.floor(seconds); - const minutes: number | string = Math.floor(seconds / 60); - let hours: number | string = Math.floor(minutes / 60); - - let remainingSeconds: number | string = seconds % 60; - let remainingMinutes: number | string = minutes % 60; - - if (remainingSeconds < 10) { - remainingSeconds = "0" + remainingSeconds; - } - - if (remainingMinutes < 10) { - remainingMinutes = "0" + remainingMinutes; - } - - if (hours === 0) { - hours = ""; - } else { - hours = hours + ":"; - } - - return hours + remainingMinutes + ":" + remainingSeconds; - } - const reloadVideo = async () => { if (!videoRef.current) return; const src = videoRef.current.src; diff --git a/src/hooks/useFetchVideos.tsx b/src/hooks/useFetchVideos.tsx index 1355101..331d83f 100644 --- a/src/hooks/useFetchVideos.tsx +++ b/src/hooks/useFetchVideos.tsx @@ -1,6 +1,11 @@ import React from "react"; import { useDispatch, useSelector } from "react-redux"; -import { subscriptionListFilter } from "../App-State.ts"; +import { subscriptionListFilter } from "../App-Functions.ts"; +import { + totalNamesPublished, + totalVideosPublished, + videosPerNamePublished, +} from "../components/StatsData.tsx"; import { addVideos, addToHashMap, @@ -11,13 +16,7 @@ import { upsertFilteredVideos, removeFromHashMap, } from "../state/features/videoSlice"; -import { - setIsLoadingGlobal, - setUserAvatarHash, - setTotalVideosPublished, - setTotalNamesPublished, - setVideosPerNamePublished, -} from "../state/features/globalSlice"; +import { setIsLoadingGlobal } from "../state/features/globalSlice"; import { RootState } from "../state/store"; import { fetchAndEvaluateVideos } from "../utils/fetchVideos"; import { RequestQueue } from "../utils/queue"; @@ -390,14 +389,11 @@ export const useFetchVideos = () => { }); const responseData = await response.json(); - const totalVideosPublished = responseData.length; + totalVideosPublished.value = responseData.length; const uniqueNames = new Set(responseData.map(video => video.name)); - const totalNamesPublished = uniqueNames.size; - const videosPerNamePublished = totalVideosPublished / totalNamesPublished; - - dispatch(setTotalVideosPublished(totalVideosPublished)); - dispatch(setTotalNamesPublished(totalNamesPublished)); - dispatch(setVideosPerNamePublished(videosPerNamePublished)); + totalNamesPublished.value = uniqueNames.size; + videosPerNamePublished.value = + totalVideosPublished.value / totalNamesPublished.value; } catch (error) { console.log({ error }); } diff --git a/src/pages/ContentPages/VideoContent/VideoContent-styles.tsx b/src/pages/ContentPages/VideoContent/VideoContent-styles.tsx index bff7089..9fda806 100644 --- a/src/pages/ContentPages/VideoContent/VideoContent-styles.tsx +++ b/src/pages/ContentPages/VideoContent/VideoContent-styles.tsx @@ -1,5 +1,6 @@ import { styled } from "@mui/system"; import { Box, Grid, Typography, Checkbox } from "@mui/material"; +import { fontSizeMedium } from "../../../constants/Misc.ts"; export const VideoContentContainer = styled(Box)(({ theme }) => ({ display: "flex", @@ -14,7 +15,7 @@ export const VideoPlayerContainer = styled(Box)(({ theme }) => ({ export const VideoTitle = styled(Typography)(({ theme }) => ({ fontFamily: "Raleway", - fontSize: "20px", + fontSize: fontSizeMedium, color: theme.palette.text.primary, wordBreak: "break-word", })); diff --git a/src/pages/ContentPages/VideoContent/VideoContent.tsx b/src/pages/ContentPages/VideoContent/VideoContent.tsx index 810b05e..fba7859 100644 --- a/src/pages/ContentPages/VideoContent/VideoContent.tsx +++ b/src/pages/ContentPages/VideoContent/VideoContent.tsx @@ -14,6 +14,7 @@ import { SUPER_LIKE_BASE, } from "../../../constants/Identifiers.ts"; import { + fontSizeMedium, minPriceSuperlike, titleFormatterOnSave, } from "../../../constants/Misc.ts"; @@ -21,6 +22,7 @@ import { useFetchSuperLikes } from "../../../hooks/useFetchSuperLikes.tsx"; import { setIsLoadingGlobal } from "../../../state/features/globalSlice.ts"; import { addToHashMap } from "../../../state/features/videoSlice.ts"; import { RootState } from "../../../state/store.ts"; +import { formatBytes } from "../../../utils/numberFunctions.ts"; import { formatDate } from "../../../utils/time.ts"; import { VideoActionsBar } from "./VideoActionsBar.tsx"; import { @@ -62,7 +64,6 @@ export const VideoContent = () => { superLikeList, setSuperLikeList, } = useVideoContentState(); - return ( <> { videoContainer: { aspectRatio: "16 / 9" }, video: { aspectRatio: "16 / 9" }, }} + duration={videoData?.duration} /> ) : isVideoLoaded ? ( @@ -128,7 +130,17 @@ export const VideoContent = () => { {videoData?.title} - + {videoData?.fileSize && ( + + {formatBytes(videoData.fileSize, 2, "Decimal")} + + )} {videoData?.created && ( { {formatDate(videoData.created)} )} - {videoData?.fullDescription && ( { videoObj = existingVideo; hasHash = true; } - // nb. this prevents showing metadata for a video which // belongs to a different user if ( @@ -147,7 +147,6 @@ export const VideoList = ({ videos }: VideoListProps) => { /> {videoObj?.title} - { @@ -237,6 +236,19 @@ export const VideoList = ({ videos }: VideoListProps) => { navigate(`/video/${videoObj?.user}/${videoObj?.id}`); }} > + {videoObj?.duration && ( + + + {formatTime(videoObj.duration)} + + + )} - publishNames: string[] | null - videoPlaying: any | null - superlikelistAll: any[] - totalVideosPublished: number - totalNamesPublished: number - videosPerNamePublished: number + isLoadingGlobal: boolean; + downloads: any; + userAvatarHash: Record; + publishNames: string[] | null; + videoPlaying: any | null; + superlikelistAll: any[]; } const initialState: GlobalState = { isLoadingGlobal: false, @@ -19,56 +15,44 @@ const initialState: GlobalState = { publishNames: null, videoPlaying: null, superlikelistAll: [], - totalVideosPublished: null, - totalNamesPublished: null, - videosPerNamePublished: null -} +}; export const globalSlice = createSlice({ - name: 'global', + name: "global", initialState, reducers: { setIsLoadingGlobal: (state, action) => { - state.isLoadingGlobal = action.payload + state.isLoadingGlobal = action.payload; }, setAddToDownloads: (state, action) => { - const download = action.payload - state.downloads[download.identifier] = download + const download = action.payload; + state.downloads[download.identifier] = download; }, updateDownloads: (state, action) => { - const { identifier } = action.payload - const download = action.payload + const { identifier } = action.payload; + const download = action.payload; state.downloads[identifier] = { ...state.downloads[identifier], - ...download - } + ...download, + }; }, setUserAvatarHash: (state, action) => { - const avatar = action.payload + const avatar = action.payload; if (avatar?.name && avatar?.url) { - state.userAvatarHash[avatar?.name] = avatar?.url + state.userAvatarHash[avatar?.name] = avatar?.url; } }, addPublishNames: (state, action) => { - state.publishNames = action.payload + state.publishNames = action.payload; }, setVideoPlaying: (state, action) => { - state.videoPlaying = action.payload + state.videoPlaying = action.payload; }, setSuperlikesAll: (state, action) => { - state.superlikelistAll = action.payload + state.superlikelistAll = action.payload; }, - setTotalVideosPublished: (state, action) => { - state.totalVideosPublished = action.payload - }, - setTotalNamesPublished: (state, action) => { - state.totalNamesPublished = action.payload - }, - setVideosPerNamePublished: (state, action) => { - state.videosPerNamePublished = action.payload - }, - } -}) + }, +}); export const { setIsLoadingGlobal, @@ -78,9 +62,6 @@ export const { addPublishNames, setVideoPlaying, setSuperlikesAll, - setTotalVideosPublished, - setTotalNamesPublished, - setVideosPerNamePublished -} = globalSlice.actions +} = globalSlice.actions; -export default globalSlice.reducer +export default globalSlice.reducer; diff --git a/src/utils/numberFunctions.ts b/src/utils/numberFunctions.ts index d6247f3..583196c 100644 --- a/src/utils/numberFunctions.ts +++ b/src/utils/numberFunctions.ts @@ -1,5 +1,3 @@ - - export const truncateNumber = (value: string | number, sigDigits: number) => { return Number(value).toFixed(sigDigits); }; @@ -21,3 +19,45 @@ export const setNumberWithinBounds = ( export const numberToInt = (num: number) => { return Math.floor(num); }; + +type ByteFormat = "Decimal" | "Binary"; +export function formatBytes( + bytes: number, + decimals = 2, + format: ByteFormat = "Binary" +) { + if (bytes === 0) return "0 Bytes"; + + const k = format === "Binary" ? 1024 : 1000; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; +} + +export function formatTime(seconds: number): string { + seconds = Math.floor(seconds); + const minutes: number | string = Math.floor(seconds / 60); + let hours: number | string = Math.floor(minutes / 60); + + let remainingSeconds: number | string = seconds % 60; + let remainingMinutes: number | string = minutes % 60; + + if (remainingSeconds < 10) { + remainingSeconds = "0" + remainingSeconds; + } + + if (remainingMinutes < 10) { + remainingMinutes = "0" + remainingMinutes; + } + + if (hours === 0) { + hours = ""; + } else { + hours = hours + ":"; + } + + return hours + remainingMinutes + ":" + remainingSeconds; +}