mirror of
https://github.com/Qortal/q-tube.git
synced 2025-02-14 19:25:52 +00:00
Merge pull request #51 from QortalSeth/main
New Published Videos show Duration and Filesize
This commit is contained in:
commit
eebf4fce78
@ -1,20 +1,5 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { SubscriptionData } from "./components/common/ContentButtons/SubscribeButton.tsx";
|
import { SubscriptionData } from "./components/common/ContentButtons/SubscribeButton.tsx";
|
||||||
import { setFilteredSubscriptions } from "./state/features/videoSlice.ts";
|
|
||||||
import { store } from "./state/store.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 () => {
|
export const getUserName = async () => {
|
||||||
const account = await qortalRequest({
|
const account = await qortalRequest({
|
||||||
@ -28,6 +13,7 @@ export const getUserName = async () => {
|
|||||||
if (nameData?.length > 0) return nameData[0].name;
|
if (nameData?.length > 0) return nameData[0].name;
|
||||||
else return "";
|
else return "";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const filterVideosByName = (
|
export const filterVideosByName = (
|
||||||
subscriptionList: SubscriptionData[],
|
subscriptionList: SubscriptionData[],
|
||||||
userName: string
|
userName: string
|
14
src/App.tsx
14
src/App.tsx
@ -1,15 +1,18 @@
|
|||||||
import { CssBaseline } from "@mui/material";
|
import { CssBaseline } from "@mui/material";
|
||||||
import { ThemeProvider } from "@mui/material/styles";
|
import { ThemeProvider } from "@mui/material/styles";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import { Route, Routes } from "react-router-dom";
|
import { Route, Routes } from "react-router-dom";
|
||||||
|
import { persistStore } from "redux-persist";
|
||||||
import { PersistGate } from "redux-persist/integration/react";
|
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 Notification from "./components/common/Notification/Notification";
|
||||||
import { useIframe } from "./hooks/useIframe.tsx";
|
import { useIframe } from "./hooks/useIframe.tsx";
|
||||||
import { IndividualProfile } from "./pages/ContentPages/IndividualProfile/IndividualProfile";
|
import { IndividualProfile } from "./pages/ContentPages/IndividualProfile/IndividualProfile";
|
||||||
import { PlaylistContent } from "./pages/ContentPages/PlaylistContent/PlaylistContent";
|
import { PlaylistContent } from "./pages/ContentPages/PlaylistContent/PlaylistContent";
|
||||||
import { VideoContent } from "./pages/ContentPages/VideoContent/VideoContent";
|
import { VideoContent } from "./pages/ContentPages/VideoContent/VideoContent";
|
||||||
import { Home } from "./pages/Home/Home";
|
import { Home } from "./pages/Home/Home";
|
||||||
|
import { setFilteredSubscriptions } from "./state/features/videoSlice.ts";
|
||||||
import { store } from "./state/store";
|
import { store } from "./state/store";
|
||||||
import { darkTheme, lightTheme } from "./styles/theme";
|
import { darkTheme, lightTheme } from "./styles/theme";
|
||||||
import DownloadWrapper from "./wrappers/DownloadWrapper";
|
import DownloadWrapper from "./wrappers/DownloadWrapper";
|
||||||
@ -18,9 +21,16 @@ import { ScrollWrapper } from "./wrappers/ScrollWrapper.tsx";
|
|||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
// const themeColor = window._qdnTheme
|
// const themeColor = window._qdnTheme
|
||||||
const { persistor, theme, setTheme } = useAppState();
|
const persistor = persistStore(store);
|
||||||
|
const [theme, setTheme] = useState("dark");
|
||||||
useIframe();
|
useIframe();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
subscriptionListFilter(false).then(filteredList => {
|
||||||
|
store.dispatch(setFilteredSubscriptions(filteredList));
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<PersistGate loading={null} persistor={persistor}>
|
<PersistGate loading={null} persistor={persistor}>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import Compressor from "compressorjs";
|
import Compressor from "compressorjs";
|
||||||
|
import { formatBytes } from "../../../utils/numberFunctions.ts";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AddCoverImageButton,
|
AddCoverImageButton,
|
||||||
@ -62,25 +63,11 @@ import {
|
|||||||
titleFormatter,
|
titleFormatter,
|
||||||
videoMaxSize,
|
videoMaxSize,
|
||||||
} from "../../../constants/Misc.ts";
|
} from "../../../constants/Misc.ts";
|
||||||
|
import { Signal, useSignal } from "@preact/signals-react";
|
||||||
|
|
||||||
const uid = new ShortUniqueId();
|
const uid = new ShortUniqueId();
|
||||||
const shortuid = new ShortUniqueId({ length: 5 });
|
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 = () => {
|
export const EditVideo = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -106,6 +93,10 @@ export const EditVideo = () => {
|
|||||||
useState<any>(null);
|
useState<any>(null);
|
||||||
const [imageExtracts, setImageExtracts] = useState<any>([]);
|
const [imageExtracts, setImageExtracts] = useState<any>([]);
|
||||||
|
|
||||||
|
const videoDuration: Signal<number[]> = useSignal([
|
||||||
|
editVideoProperties?.duration || 0,
|
||||||
|
]);
|
||||||
|
|
||||||
const { getRootProps, getInputProps } = useDropzone({
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
accept: {
|
accept: {
|
||||||
"video/*": [],
|
"video/*": [],
|
||||||
@ -293,6 +284,8 @@ export const EditVideo = () => {
|
|||||||
code: editVideoProperties.code,
|
code: editVideoProperties.code,
|
||||||
videoType: file?.type || "video/mp4",
|
videoType: file?.type || "video/mp4",
|
||||||
filename: `${alphanumericString.trim()}.${fileExtension}`,
|
filename: `${alphanumericString.trim()}.${fileExtension}`,
|
||||||
|
fileSize: file?.size || 0,
|
||||||
|
duration: videoDuration.value[0],
|
||||||
};
|
};
|
||||||
|
|
||||||
const metadescription =
|
const metadescription =
|
||||||
@ -508,6 +501,8 @@ export const EditVideo = () => {
|
|||||||
<FrameExtractor
|
<FrameExtractor
|
||||||
videoFile={file}
|
videoFile={file}
|
||||||
onFramesExtracted={imgs => onFramesExtracted(imgs)}
|
onFramesExtracted={imgs => onFramesExtracted(imgs)}
|
||||||
|
videoDurations={videoDuration}
|
||||||
|
index={0}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
@ -16,7 +16,7 @@ import {
|
|||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import Compressor from "compressorjs";
|
import Compressor from "compressorjs";
|
||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useDropzone } from "react-dropzone";
|
import { useDropzone } from "react-dropzone";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import ShortUniqueId from "short-unique-id";
|
import ShortUniqueId from "short-unique-id";
|
||||||
@ -38,6 +38,7 @@ import {
|
|||||||
|
|
||||||
import { setNotification } from "../../../state/features/notificationsSlice.ts";
|
import { setNotification } from "../../../state/features/notificationsSlice.ts";
|
||||||
import { RootState } from "../../../state/store.ts";
|
import { RootState } from "../../../state/store.ts";
|
||||||
|
import { formatBytes } from "../../../utils/numberFunctions.ts";
|
||||||
import { objectToBase64 } from "../../../utils/PublishFormatter.ts";
|
import { objectToBase64 } from "../../../utils/PublishFormatter.ts";
|
||||||
import { getFileName } from "../../../utils/stringFunctions.ts";
|
import { getFileName } from "../../../utils/stringFunctions.ts";
|
||||||
import { CardContentContainerComment } from "../../common/Comments/Comments-styles.tsx";
|
import { CardContentContainerComment } from "../../common/Comments/Comments-styles.tsx";
|
||||||
@ -65,6 +66,8 @@ import {
|
|||||||
StyledButton,
|
StyledButton,
|
||||||
TimesIcon,
|
TimesIcon,
|
||||||
} from "./PublishVideo-styles.tsx";
|
} 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<string | ArrayBuffer | null> =>
|
export const toBase64 = (file: File): Promise<string | ArrayBuffer | null> =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
@ -103,10 +106,8 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
(state: RootState) => state.auth?.user?.address
|
(state: RootState) => state.auth?.user?.address
|
||||||
);
|
);
|
||||||
const [files, setFiles] = useState<VideoFile[]>([]);
|
const [files, setFiles] = useState<VideoFile[]>([]);
|
||||||
|
const videoDurations = useSignal<number[]>([]);
|
||||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||||
const [title, setTitle] = useState<string>("");
|
|
||||||
const [description, setDescription] = useState<string>("");
|
|
||||||
const [coverImageForAll, setCoverImageForAll] = useState<null | string>("");
|
const [coverImageForAll, setCoverImageForAll] = useState<null | string>("");
|
||||||
|
|
||||||
const [step, setStep] = useState<string>("videos");
|
const [step, setStep] = useState<string>("videos");
|
||||||
@ -136,6 +137,14 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
useState(false);
|
useState(false);
|
||||||
const [imageExtracts, setImageExtracts] = useState<any>({});
|
const [imageExtracts, setImageExtracts] = useState<any>({});
|
||||||
|
|
||||||
|
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({
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
accept: {
|
accept: {
|
||||||
"video/*": [],
|
"video/*": [],
|
||||||
@ -302,6 +311,8 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
code,
|
code,
|
||||||
videoType: file?.type || "video/mp4",
|
videoType: file?.type || "video/mp4",
|
||||||
filename: `${alphanumericString.trim()}.${fileExtension}`,
|
filename: `${alphanumericString.trim()}.${fileExtension}`,
|
||||||
|
fileSize: file?.size || 0,
|
||||||
|
duration: videoDurations.value[i],
|
||||||
};
|
};
|
||||||
|
|
||||||
const metadescription =
|
const metadescription =
|
||||||
@ -818,11 +829,14 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{files.map((file, index) => {
|
{files.map((file, index) => {
|
||||||
|
assembleVideoDurations();
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={index}>
|
<React.Fragment key={index}>
|
||||||
<FrameExtractor
|
<FrameExtractor
|
||||||
videoFile={file.file}
|
videoFile={file.file}
|
||||||
onFramesExtracted={imgs => onFramesExtracted(imgs, index)}
|
onFramesExtracted={imgs => onFramesExtracted(imgs, index)}
|
||||||
|
videoDurations={videoDurations}
|
||||||
|
index={index}
|
||||||
/>
|
/>
|
||||||
<Typography>{file?.file?.name}</Typography>
|
<Typography>{file?.file?.name}</Typography>
|
||||||
{!isCheckSameCoverImage && (
|
{!isCheckSameCoverImage && (
|
||||||
@ -1159,7 +1173,7 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<TextEditor
|
<TextEditor
|
||||||
inlineContent={playlistDescription}
|
inlineContent={playlistDescription}
|
||||||
setInlineContent={value => {
|
setInlineContent={(value: string) => {
|
||||||
setPlaylistDescription(value);
|
setPlaylistDescription(value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -4,6 +4,12 @@ import { Grid } from "@mui/material";
|
|||||||
import { useFetchVideos } from "../hooks/useFetchVideos.tsx";
|
import { useFetchVideos } from "../hooks/useFetchVideos.tsx";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { RootState } from "../state/store.ts";
|
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 = () => {
|
export const StatsData = () => {
|
||||||
const StatsCol = styled(Grid)(({ theme }) => ({
|
const StatsCol = styled(Grid)(({ theme }) => ({
|
||||||
@ -14,44 +20,43 @@ export const StatsData = () => {
|
|||||||
backgroundColor: theme.palette.background.default,
|
backgroundColor: theme.palette.background.default,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const {
|
const { getVideosCount } = useFetchVideos();
|
||||||
getVideos,
|
|
||||||
getNewVideos,
|
|
||||||
checkNewVideos,
|
|
||||||
getVideosFiltered,
|
|
||||||
getVideosCount,
|
|
||||||
} = useFetchVideos();
|
|
||||||
|
|
||||||
const persistReducer = useSelector((state: RootState) => state.persist);
|
const showValueIfExists = (value: number) => {
|
||||||
const totalVideosPublished = useSelector(
|
return value > 0 ? "inline" : "none";
|
||||||
(state: RootState) => state.global.totalVideosPublished
|
};
|
||||||
);
|
|
||||||
const totalNamesPublished = useSelector(
|
|
||||||
(state: RootState) => state.global.totalNamesPublished
|
|
||||||
);
|
|
||||||
const videosPerNamePublished = useSelector(
|
|
||||||
(state: RootState) => state.global.videosPerNamePublished
|
|
||||||
);
|
|
||||||
|
|
||||||
|
const showStats = useSelector((state: RootState) => state.persist.showStats);
|
||||||
|
const showVideoCount = showValueIfExists(totalVideosPublished.value);
|
||||||
|
const showPublisherCount = showValueIfExists(totalNamesPublished.value);
|
||||||
|
const showAverage = showValueIfExists(videosPerNamePublished.value);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getVideosCount();
|
getVideosCount();
|
||||||
}, [getVideosCount]);
|
}, [getVideosCount]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StatsCol sx={{ display: persistReducer.showStats ? "block" : "none" }}>
|
<StatsCol sx={{ display: showStats ? "block" : "none" }}>
|
||||||
<div>
|
<div>
|
||||||
Videos:{" "}
|
Videos:{" "}
|
||||||
<span style={{ fontWeight: "bold" }}>{totalVideosPublished}</span>
|
<span style={{ fontWeight: "bold", display: showVideoCount }}>
|
||||||
|
{totalVideosPublished.value}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
Publishers:{" "}
|
Publishers:{" "}
|
||||||
<span style={{ fontWeight: "bold" }}>{totalNamesPublished}</span>
|
<span style={{ fontWeight: "bold", display: showPublisherCount }}>
|
||||||
|
{totalNamesPublished.value}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
Average:{" "}
|
Average:{" "}
|
||||||
<span style={{ fontWeight: "bold" }}>
|
<span
|
||||||
{videosPerNamePublished > 0 &&
|
style={{
|
||||||
Number(videosPerNamePublished).toFixed(0)}
|
fontWeight: "bold",
|
||||||
|
display: showAverage,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Number(videosPerNamePublished.value).toFixed(0)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</StatsCol>
|
</StatsCol>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Button, ButtonProps, Tooltip } from "@mui/material";
|
import { Button, ButtonProps, Tooltip } from "@mui/material";
|
||||||
import { MouseEvent, useEffect, useState } from "react";
|
import { MouseEvent, useEffect, useState } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
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 { RootState } from "../../../state/store.ts";
|
||||||
import {
|
import {
|
||||||
subscribe,
|
subscribe,
|
||||||
|
@ -1,20 +1,38 @@
|
|||||||
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 }) => {
|
export interface FrameExtractorProps {
|
||||||
|
videoFile: File;
|
||||||
|
onFramesExtracted: (imgs, index?: number) => Promise<void>;
|
||||||
|
videoDurations?: Signal<number[]>;
|
||||||
|
index?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FrameExtractor = ({
|
||||||
|
videoFile,
|
||||||
|
onFramesExtracted,
|
||||||
|
videoDurations,
|
||||||
|
index,
|
||||||
|
}: FrameExtractorProps) => {
|
||||||
|
useSignals();
|
||||||
const videoRef = useRef(null);
|
const videoRef = useRef(null);
|
||||||
const [durations, setDurations] = useState([]);
|
const [durations, setDurations] = useState([]);
|
||||||
const canvasRef = useRef(null);
|
const canvasRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const video = videoRef.current;
|
const video = videoRef.current;
|
||||||
video.addEventListener('loadedmetadata', () => {
|
video.addEventListener("loadedmetadata", () => {
|
||||||
const duration = video.duration;
|
const duration = video.duration;
|
||||||
if (isFinite(duration)) {
|
if (isFinite(duration)) {
|
||||||
// Proceed with your logic
|
// Proceed with your logic
|
||||||
|
|
||||||
console.log('duration', duration)
|
const newVideoDurations = [...videoDurations.value];
|
||||||
|
newVideoDurations[index] = duration;
|
||||||
|
videoDurations.value = [...newVideoDurations];
|
||||||
|
|
||||||
const section = duration / 4;
|
const section = duration / 4;
|
||||||
let timestamps = [];
|
const timestamps = [];
|
||||||
|
|
||||||
for (let i = 0; i < 4; i++) {
|
for (let i = 0; i < 4; i++) {
|
||||||
const randomTime = Math.random() * section + i * section;
|
const randomTime = Math.random() * section + i * section;
|
||||||
@ -23,13 +41,11 @@ export const FrameExtractor = ({ videoFile, onFramesExtracted }) => {
|
|||||||
|
|
||||||
setDurations(timestamps);
|
setDurations(timestamps);
|
||||||
} else {
|
} else {
|
||||||
onFramesExtracted([])
|
onFramesExtracted([]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [videoFile]);
|
}, [videoFile]);
|
||||||
|
|
||||||
console.log({durations})
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (durations.length === 4) {
|
if (durations.length === 4) {
|
||||||
extractFrames();
|
extractFrames();
|
||||||
@ -45,33 +61,32 @@ export const FrameExtractor = ({ videoFile, onFramesExtracted }) => {
|
|||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
canvas.width = video.videoWidth;
|
canvas.width = video.videoWidth;
|
||||||
canvas.height = video.videoHeight;
|
canvas.height = video.videoHeight;
|
||||||
const context = canvas.getContext('2d');
|
const context = canvas.getContext("2d");
|
||||||
|
|
||||||
let frameData = [];
|
const frameData = [];
|
||||||
|
|
||||||
for (const time of durations) {
|
for (const time of durations) {
|
||||||
await new Promise<void>((resolve) => {
|
await new Promise<void>(resolve => {
|
||||||
video.currentTime = time;
|
video.currentTime = time;
|
||||||
const onSeeked = () => {
|
const onSeeked = () => {
|
||||||
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||||
canvas.toBlob(blob => {
|
canvas.toBlob(blob => {
|
||||||
frameData.push(blob);
|
frameData.push(blob);
|
||||||
resolve();
|
resolve();
|
||||||
}, 'image/png');
|
}, "image/png");
|
||||||
video.removeEventListener('seeked', onSeeked);
|
video.removeEventListener("seeked", onSeeked);
|
||||||
};
|
};
|
||||||
video.addEventListener('seeked', onSeeked, { once: true });
|
video.addEventListener("seeked", onSeeked, { once: true });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onFramesExtracted(frameData);
|
onFramesExtracted(frameData);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<video ref={videoRef} style={{ display: 'none' }} src={fileUrl}></video>
|
<video ref={videoRef} style={{ display: "none" }} src={fileUrl}></video>
|
||||||
<canvas ref={canvasRef} style={{ display: 'none' }}></canvas>
|
<canvas ref={canvasRef} style={{ display: "none" }}></canvas>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,7 @@ import { Box, CircularProgress, Typography } from "@mui/material";
|
|||||||
import { setVideoPlaying } from "../../../../state/features/globalSlice.ts";
|
import { setVideoPlaying } from "../../../../state/features/globalSlice.ts";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
import { PlayArrow } from "@mui/icons-material";
|
import { PlayArrow } from "@mui/icons-material";
|
||||||
|
import { formatTime } from "../../../../utils/numberFunctions.ts";
|
||||||
import { useVideoContext } from "./VideoContext.ts";
|
import { useVideoContext } from "./VideoContext.ts";
|
||||||
|
|
||||||
export const LoadingVideo = () => {
|
export const LoadingVideo = () => {
|
||||||
@ -13,6 +14,7 @@ export const LoadingVideo = () => {
|
|||||||
canPlay,
|
canPlay,
|
||||||
from,
|
from,
|
||||||
togglePlay,
|
togglePlay,
|
||||||
|
duration,
|
||||||
} = useVideoContext();
|
} = useVideoContext();
|
||||||
|
|
||||||
const getDownloadProgress = (current: number, total: number) => {
|
const getDownloadProgress = (current: number, total: number) => {
|
||||||
@ -83,7 +85,20 @@ export const LoadingVideo = () => {
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{((!src && !isLoading.value) || (!startPlay.value && !canPlay.value)) && (
|
{((!src && !isLoading.value) || (!startPlay.value && !canPlay.value)) && (
|
||||||
|
<>
|
||||||
|
{duration && (
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
right={0}
|
||||||
|
bottom={0}
|
||||||
|
bgcolor="#202020"
|
||||||
|
zIndex={999}
|
||||||
|
>
|
||||||
|
<Typography color="white">{formatTime(duration)}</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
<Box
|
<Box
|
||||||
position="absolute"
|
position="absolute"
|
||||||
top={0}
|
top={0}
|
||||||
@ -113,6 +128,7 @@ export const LoadingVideo = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useSignal } from "@preact/signals-react";
|
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 { useEffect } from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
@ -35,6 +35,7 @@ export const useVideoControlsState = (
|
|||||||
progress,
|
progress,
|
||||||
videoObjectFit,
|
videoObjectFit,
|
||||||
canPlay,
|
canPlay,
|
||||||
|
isMobileView,
|
||||||
} = videoPlayerState;
|
} = videoPlayerState;
|
||||||
const { identifier, autoPlay } = props;
|
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 () => {
|
const reloadVideo = async () => {
|
||||||
if (!videoRef.current || !src) return;
|
if (!videoRef.current || !src) return;
|
||||||
const currentTime = videoRef.current.currentTime;
|
const currentTime = videoRef.current.currentTime;
|
||||||
@ -154,6 +130,7 @@ export const useVideoControlsState = (
|
|||||||
if (firstPlay.value) setPlaying(true); // makes the video play when fully loaded
|
if (firstPlay.value) setPlaying(true); // makes the video play when fully loaded
|
||||||
firstPlay.value = false;
|
firstPlay.value = false;
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
|
updatePlaybackRate(persistSelector.playbackRate);
|
||||||
};
|
};
|
||||||
|
|
||||||
const setPlaying = async (setPlay: boolean) => {
|
const setPlaying = async (setPlay: boolean) => {
|
||||||
@ -380,6 +357,14 @@ export const useVideoControlsState = (
|
|||||||
}
|
}
|
||||||
}, [videoPlaying, identifier, src]);
|
}, [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 {
|
return {
|
||||||
reloadVideo,
|
reloadVideo,
|
||||||
togglePlay,
|
togglePlay,
|
||||||
@ -387,7 +372,6 @@ export const useVideoControlsState = (
|
|||||||
increaseSpeed,
|
increaseSpeed,
|
||||||
togglePictureInPicture,
|
togglePictureInPicture,
|
||||||
toggleFullscreen,
|
toggleFullscreen,
|
||||||
formatTime,
|
|
||||||
keyboardShortcutsUp,
|
keyboardShortcutsUp,
|
||||||
keyboardShortcutsDown,
|
keyboardShortcutsDown,
|
||||||
handleCanPlay,
|
handleCanPlay,
|
||||||
|
@ -8,15 +8,13 @@ import {
|
|||||||
VolumeUp,
|
VolumeUp,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import { IconButton, Slider, Typography } from "@mui/material";
|
import { IconButton, Slider, Typography } from "@mui/material";
|
||||||
import { useSignalEffect, useSignals } from "@preact/signals-react/runtime";
|
import { formatTime } from "../../../../utils/numberFunctions.ts";
|
||||||
import { useEffect } from "react";
|
|
||||||
|
|
||||||
import { ControlsContainer } from "../VideoPlayer-styles.ts";
|
import { ControlsContainer } from "../VideoPlayer-styles.ts";
|
||||||
import { MobileControls } from "./MobileControls.tsx";
|
import { MobileControls } from "./MobileControls.tsx";
|
||||||
import { useVideoContext } from "./VideoContext.ts";
|
import { useVideoContext } from "./VideoContext.ts";
|
||||||
|
|
||||||
export const VideoControls = () => {
|
export const VideoControls = () => {
|
||||||
useSignals();
|
|
||||||
const {
|
const {
|
||||||
reloadVideo,
|
reloadVideo,
|
||||||
togglePlay,
|
togglePlay,
|
||||||
@ -24,7 +22,6 @@ export const VideoControls = () => {
|
|||||||
increaseSpeed,
|
increaseSpeed,
|
||||||
togglePictureInPicture,
|
togglePictureInPicture,
|
||||||
toggleFullscreen,
|
toggleFullscreen,
|
||||||
formatTime,
|
|
||||||
toggleMute,
|
toggleMute,
|
||||||
onProgressChange,
|
onProgressChange,
|
||||||
toggleRef,
|
toggleRef,
|
||||||
@ -40,14 +37,6 @@ export const VideoControls = () => {
|
|||||||
showControlsFullScreen,
|
showControlsFullScreen,
|
||||||
} = useVideoContext();
|
} = 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 =
|
const showMobileControls =
|
||||||
isMobileView.value && canPlay.value && showControlsFullScreen.value;
|
isMobileView.value && canPlay.value && showControlsFullScreen.value;
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { useSignals } from "@preact/signals-react/runtime";
|
|
||||||
import CSS from "csstype";
|
import CSS from "csstype";
|
||||||
import { forwardRef } from "react";
|
import { forwardRef } from "react";
|
||||||
import { LoadingVideo } from "./Components/LoadingVideo.tsx";
|
import { LoadingVideo } from "./Components/LoadingVideo.tsx";
|
||||||
@ -26,6 +25,7 @@ export interface VideoPlayerProps {
|
|||||||
onEnd?: () => void;
|
onEnd?: () => void;
|
||||||
autoPlay?: boolean;
|
autoPlay?: boolean;
|
||||||
style?: CSS.Properties;
|
style?: CSS.Properties;
|
||||||
|
duration?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type videoRefType = {
|
export type videoRefType = {
|
||||||
@ -34,7 +34,6 @@ export type videoRefType = {
|
|||||||
};
|
};
|
||||||
export const VideoPlayer = forwardRef<videoRefType, VideoPlayerProps>(
|
export const VideoPlayer = forwardRef<videoRefType, VideoPlayerProps>(
|
||||||
(props: VideoPlayerProps, ref) => {
|
(props: VideoPlayerProps, ref) => {
|
||||||
useSignals();
|
|
||||||
const contextData = useContextData(props, ref);
|
const contextData = useContextData(props, ref);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -56,6 +55,7 @@ export const VideoPlayer = forwardRef<videoRefType, VideoPlayerProps>(
|
|||||||
startPlay,
|
startPlay,
|
||||||
videoObjectFit,
|
videoObjectFit,
|
||||||
showControlsFullScreen,
|
showControlsFullScreen,
|
||||||
|
duration,
|
||||||
} = contextData;
|
} = contextData;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
VolumeOff,
|
VolumeOff,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import { styled } from "@mui/system";
|
import { styled } from "@mui/system";
|
||||||
|
import { formatTime } from "../../../utils/numberFunctions.ts";
|
||||||
import { MyContext } from "../../../wrappers/DownloadWrapper.tsx";
|
import { MyContext } from "../../../wrappers/DownloadWrapper.tsx";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { RootState } from "../../../state/store.ts";
|
import { RootState } from "../../../state/store.ts";
|
||||||
@ -250,31 +251,6 @@ export const VideoPlayerGlobal: React.FC<VideoPlayerProps> = ({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
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 () => {
|
const reloadVideo = async () => {
|
||||||
if (!videoRef.current) return;
|
if (!videoRef.current) return;
|
||||||
const src = videoRef.current.src;
|
const src = videoRef.current.src;
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
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 {
|
import {
|
||||||
addVideos,
|
addVideos,
|
||||||
addToHashMap,
|
addToHashMap,
|
||||||
@ -11,13 +16,7 @@ import {
|
|||||||
upsertFilteredVideos,
|
upsertFilteredVideos,
|
||||||
removeFromHashMap,
|
removeFromHashMap,
|
||||||
} from "../state/features/videoSlice";
|
} from "../state/features/videoSlice";
|
||||||
import {
|
import { setIsLoadingGlobal } from "../state/features/globalSlice";
|
||||||
setIsLoadingGlobal,
|
|
||||||
setUserAvatarHash,
|
|
||||||
setTotalVideosPublished,
|
|
||||||
setTotalNamesPublished,
|
|
||||||
setVideosPerNamePublished,
|
|
||||||
} from "../state/features/globalSlice";
|
|
||||||
import { RootState } from "../state/store";
|
import { RootState } from "../state/store";
|
||||||
import { fetchAndEvaluateVideos } from "../utils/fetchVideos";
|
import { fetchAndEvaluateVideos } from "../utils/fetchVideos";
|
||||||
import { RequestQueue } from "../utils/queue";
|
import { RequestQueue } from "../utils/queue";
|
||||||
@ -390,14 +389,11 @@ export const useFetchVideos = () => {
|
|||||||
});
|
});
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
|
|
||||||
const totalVideosPublished = responseData.length;
|
totalVideosPublished.value = responseData.length;
|
||||||
const uniqueNames = new Set(responseData.map(video => video.name));
|
const uniqueNames = new Set(responseData.map(video => video.name));
|
||||||
const totalNamesPublished = uniqueNames.size;
|
totalNamesPublished.value = uniqueNames.size;
|
||||||
const videosPerNamePublished = totalVideosPublished / totalNamesPublished;
|
videosPerNamePublished.value =
|
||||||
|
totalVideosPublished.value / totalNamesPublished.value;
|
||||||
dispatch(setTotalVideosPublished(totalVideosPublished));
|
|
||||||
dispatch(setTotalNamesPublished(totalNamesPublished));
|
|
||||||
dispatch(setVideosPerNamePublished(videosPerNamePublished));
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log({ error });
|
console.log({ error });
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { styled } from "@mui/system";
|
import { styled } from "@mui/system";
|
||||||
import { Box, Grid, Typography, Checkbox } from "@mui/material";
|
import { Box, Grid, Typography, Checkbox } from "@mui/material";
|
||||||
|
import { fontSizeMedium } from "../../../constants/Misc.ts";
|
||||||
|
|
||||||
export const VideoContentContainer = styled(Box)(({ theme }) => ({
|
export const VideoContentContainer = styled(Box)(({ theme }) => ({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@ -14,7 +15,7 @@ export const VideoPlayerContainer = styled(Box)(({ theme }) => ({
|
|||||||
|
|
||||||
export const VideoTitle = styled(Typography)(({ theme }) => ({
|
export const VideoTitle = styled(Typography)(({ theme }) => ({
|
||||||
fontFamily: "Raleway",
|
fontFamily: "Raleway",
|
||||||
fontSize: "20px",
|
fontSize: fontSizeMedium,
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
wordBreak: "break-word",
|
wordBreak: "break-word",
|
||||||
}));
|
}));
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
SUPER_LIKE_BASE,
|
SUPER_LIKE_BASE,
|
||||||
} from "../../../constants/Identifiers.ts";
|
} from "../../../constants/Identifiers.ts";
|
||||||
import {
|
import {
|
||||||
|
fontSizeMedium,
|
||||||
minPriceSuperlike,
|
minPriceSuperlike,
|
||||||
titleFormatterOnSave,
|
titleFormatterOnSave,
|
||||||
} from "../../../constants/Misc.ts";
|
} from "../../../constants/Misc.ts";
|
||||||
@ -21,6 +22,7 @@ import { useFetchSuperLikes } from "../../../hooks/useFetchSuperLikes.tsx";
|
|||||||
import { setIsLoadingGlobal } from "../../../state/features/globalSlice.ts";
|
import { setIsLoadingGlobal } from "../../../state/features/globalSlice.ts";
|
||||||
import { addToHashMap } from "../../../state/features/videoSlice.ts";
|
import { addToHashMap } from "../../../state/features/videoSlice.ts";
|
||||||
import { RootState } from "../../../state/store.ts";
|
import { RootState } from "../../../state/store.ts";
|
||||||
|
import { formatBytes } from "../../../utils/numberFunctions.ts";
|
||||||
import { formatDate } from "../../../utils/time.ts";
|
import { formatDate } from "../../../utils/time.ts";
|
||||||
import { VideoActionsBar } from "./VideoActionsBar.tsx";
|
import { VideoActionsBar } from "./VideoActionsBar.tsx";
|
||||||
import {
|
import {
|
||||||
@ -62,7 +64,6 @@ export const VideoContent = () => {
|
|||||||
superLikeList,
|
superLikeList,
|
||||||
setSuperLikeList,
|
setSuperLikeList,
|
||||||
} = useVideoContentState();
|
} = useVideoContentState();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box
|
<Box
|
||||||
@ -87,6 +88,7 @@ export const VideoContent = () => {
|
|||||||
videoContainer: { aspectRatio: "16 / 9" },
|
videoContainer: { aspectRatio: "16 / 9" },
|
||||||
video: { aspectRatio: "16 / 9" },
|
video: { aspectRatio: "16 / 9" },
|
||||||
}}
|
}}
|
||||||
|
duration={videoData?.duration}
|
||||||
/>
|
/>
|
||||||
</VideoPlayerContainer>
|
</VideoPlayerContainer>
|
||||||
) : isVideoLoaded ? (
|
) : isVideoLoaded ? (
|
||||||
@ -128,7 +130,17 @@ export const VideoContent = () => {
|
|||||||
{videoData?.title}
|
{videoData?.title}
|
||||||
</VideoTitle>
|
</VideoTitle>
|
||||||
</Box>
|
</Box>
|
||||||
|
{videoData?.fileSize && (
|
||||||
|
<Typography
|
||||||
|
variant="h1"
|
||||||
|
sx={{
|
||||||
|
fontSize: "90%",
|
||||||
|
}}
|
||||||
|
color={"green"}
|
||||||
|
>
|
||||||
|
{formatBytes(videoData.fileSize, 2, "Decimal")}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
{videoData?.created && (
|
{videoData?.created && (
|
||||||
<Typography
|
<Typography
|
||||||
variant="h6"
|
variant="h6"
|
||||||
@ -140,7 +152,6 @@ export const VideoContent = () => {
|
|||||||
{formatDate(videoData.created)}
|
{formatDate(videoData.created)}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Spacer height="30px" />
|
<Spacer height="30px" />
|
||||||
{videoData?.fullDescription && (
|
{videoData?.fullDescription && (
|
||||||
<Box
|
<Box
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import BlockIcon from "@mui/icons-material/Block";
|
import BlockIcon from "@mui/icons-material/Block";
|
||||||
import EditIcon from "@mui/icons-material/Edit";
|
import EditIcon from "@mui/icons-material/Edit";
|
||||||
import { Avatar, Box, Tooltip, useTheme } from "@mui/material";
|
import { Avatar, Box, Tooltip, Typography, useTheme } from "@mui/material";
|
||||||
import React, { useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
@ -14,6 +14,7 @@ import {
|
|||||||
Video,
|
Video,
|
||||||
} from "../../../state/features/videoSlice.ts";
|
} from "../../../state/features/videoSlice.ts";
|
||||||
import { RootState } from "../../../state/store.ts";
|
import { RootState } from "../../../state/store.ts";
|
||||||
|
import { formatTime } from "../../../utils/numberFunctions.ts";
|
||||||
import { formatDate } from "../../../utils/time.ts";
|
import { formatDate } from "../../../utils/time.ts";
|
||||||
import { VideoCardImageContainer } from "./VideoCardImageContainer.tsx";
|
import { VideoCardImageContainer } from "./VideoCardImageContainer.tsx";
|
||||||
import {
|
import {
|
||||||
@ -74,7 +75,6 @@ export const VideoList = ({ videos }: VideoListProps) => {
|
|||||||
videoObj = existingVideo;
|
videoObj = existingVideo;
|
||||||
hasHash = true;
|
hasHash = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// nb. this prevents showing metadata for a video which
|
// nb. this prevents showing metadata for a video which
|
||||||
// belongs to a different user
|
// belongs to a different user
|
||||||
if (
|
if (
|
||||||
@ -147,7 +147,6 @@ export const VideoList = ({ videos }: VideoListProps) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<VideoCardTitle>{videoObj?.title}</VideoCardTitle>
|
<VideoCardTitle>{videoObj?.title}</VideoCardTitle>
|
||||||
|
|
||||||
<BottomParent>
|
<BottomParent>
|
||||||
<NameContainer
|
<NameContainer
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
@ -237,6 +236,19 @@ export const VideoList = ({ videos }: VideoListProps) => {
|
|||||||
navigate(`/video/${videoObj?.user}/${videoObj?.id}`);
|
navigate(`/video/${videoObj?.user}/${videoObj?.id}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{videoObj?.duration && (
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
right={0}
|
||||||
|
bottom={0}
|
||||||
|
bgcolor="#202020"
|
||||||
|
zIndex={999}
|
||||||
|
>
|
||||||
|
<Typography color="white">
|
||||||
|
{formatTime(videoObj.duration)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
<VideoCardImageContainer
|
<VideoCardImageContainer
|
||||||
width={266}
|
width={266}
|
||||||
height={150}
|
height={150}
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
import { createSlice } from '@reduxjs/toolkit'
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
|
|
||||||
interface GlobalState {
|
interface GlobalState {
|
||||||
isLoadingGlobal: boolean
|
isLoadingGlobal: boolean;
|
||||||
downloads: any
|
downloads: any;
|
||||||
userAvatarHash: Record<string, string>
|
userAvatarHash: Record<string, string>;
|
||||||
publishNames: string[] | null
|
publishNames: string[] | null;
|
||||||
videoPlaying: any | null
|
videoPlaying: any | null;
|
||||||
superlikelistAll: any[]
|
superlikelistAll: any[];
|
||||||
totalVideosPublished: number
|
|
||||||
totalNamesPublished: number
|
|
||||||
videosPerNamePublished: number
|
|
||||||
}
|
}
|
||||||
const initialState: GlobalState = {
|
const initialState: GlobalState = {
|
||||||
isLoadingGlobal: false,
|
isLoadingGlobal: false,
|
||||||
@ -19,56 +15,44 @@ const initialState: GlobalState = {
|
|||||||
publishNames: null,
|
publishNames: null,
|
||||||
videoPlaying: null,
|
videoPlaying: null,
|
||||||
superlikelistAll: [],
|
superlikelistAll: [],
|
||||||
totalVideosPublished: null,
|
};
|
||||||
totalNamesPublished: null,
|
|
||||||
videosPerNamePublished: null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const globalSlice = createSlice({
|
export const globalSlice = createSlice({
|
||||||
name: 'global',
|
name: "global",
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
setIsLoadingGlobal: (state, action) => {
|
setIsLoadingGlobal: (state, action) => {
|
||||||
state.isLoadingGlobal = action.payload
|
state.isLoadingGlobal = action.payload;
|
||||||
},
|
},
|
||||||
setAddToDownloads: (state, action) => {
|
setAddToDownloads: (state, action) => {
|
||||||
const download = action.payload
|
const download = action.payload;
|
||||||
state.downloads[download.identifier] = download
|
state.downloads[download.identifier] = download;
|
||||||
},
|
},
|
||||||
updateDownloads: (state, action) => {
|
updateDownloads: (state, action) => {
|
||||||
const { identifier } = action.payload
|
const { identifier } = action.payload;
|
||||||
const download = action.payload
|
const download = action.payload;
|
||||||
state.downloads[identifier] = {
|
state.downloads[identifier] = {
|
||||||
...state.downloads[identifier],
|
...state.downloads[identifier],
|
||||||
...download
|
...download,
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
setUserAvatarHash: (state, action) => {
|
setUserAvatarHash: (state, action) => {
|
||||||
const avatar = action.payload
|
const avatar = action.payload;
|
||||||
if (avatar?.name && avatar?.url) {
|
if (avatar?.name && avatar?.url) {
|
||||||
state.userAvatarHash[avatar?.name] = avatar?.url
|
state.userAvatarHash[avatar?.name] = avatar?.url;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addPublishNames: (state, action) => {
|
addPublishNames: (state, action) => {
|
||||||
state.publishNames = action.payload
|
state.publishNames = action.payload;
|
||||||
},
|
},
|
||||||
setVideoPlaying: (state, action) => {
|
setVideoPlaying: (state, action) => {
|
||||||
state.videoPlaying = action.payload
|
state.videoPlaying = action.payload;
|
||||||
},
|
},
|
||||||
setSuperlikesAll: (state, action) => {
|
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 {
|
export const {
|
||||||
setIsLoadingGlobal,
|
setIsLoadingGlobal,
|
||||||
@ -78,9 +62,6 @@ export const {
|
|||||||
addPublishNames,
|
addPublishNames,
|
||||||
setVideoPlaying,
|
setVideoPlaying,
|
||||||
setSuperlikesAll,
|
setSuperlikesAll,
|
||||||
setTotalVideosPublished,
|
} = globalSlice.actions;
|
||||||
setTotalNamesPublished,
|
|
||||||
setVideosPerNamePublished
|
|
||||||
} = globalSlice.actions
|
|
||||||
|
|
||||||
export default globalSlice.reducer
|
export default globalSlice.reducer;
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
export const truncateNumber = (value: string | number, sigDigits: number) => {
|
export const truncateNumber = (value: string | number, sigDigits: number) => {
|
||||||
return Number(value).toFixed(sigDigits);
|
return Number(value).toFixed(sigDigits);
|
||||||
};
|
};
|
||||||
@ -21,3 +19,45 @@ export const setNumberWithinBounds = (
|
|||||||
export const numberToInt = (num: number) => {
|
export const numberToInt = (num: number) => {
|
||||||
return Math.floor(num);
|
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;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user