From 1a4dc8375b3625b2bed7569971660b31c8c25e51 Mon Sep 17 00:00:00 2001 From: Qortal Seth Date: Wed, 14 Feb 2024 15:56:39 -0700 Subject: [PATCH] Final updates to new Q-Tube 2.0 release settingsSlice.ts renamed to persistentSlice, it is only used to store persistent data. reset button no longer changes video type from "playlist" to "video". Subscriptions are now stored as list of objects that have the name of the user and the name of the channel that is followed. When the application starts in App.tsx the subscription list is filtered by the user's name, so they only see channels by the name they have subscribed to. Edits to QuickMythril's bounty commits (showing home page stats and publish video form displaying supported codecs) to improve visibility and readability. --- package.json | 2 +- src/App.tsx | 48 +- .../PublishVideo/PublishVideo-styles.tsx | 29 +- src/components/PublishVideo/PublishVideo.tsx | 27 +- src/components/common/SubscribeButton.tsx | 44 +- src/hooks/useFetchVideos.tsx | 32 +- src/main.tsx | 18 +- src/pages/Home/Home.tsx | 605 +++++++++--------- src/pages/Home/VideoList-styles.tsx | 10 + .../IndividualProfile/IndividualProfile.tsx | 75 ++- src/pages/PlaylistContent/PlaylistContent.tsx | 2 +- src/pages/VideoContent/VideoContent.tsx | 5 +- src/state/features/persistSlice.ts | 34 +- src/state/features/videoSlice.ts | 11 + src/state/store.ts | 4 +- 15 files changed, 564 insertions(+), 382 deletions(-) diff --git a/package.json b/package.json index ba7d281..b753d69 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "qtube", "private": true, - "version": "0.0.0", + "version": "2.0.0", "type": "module", "scripts": { "dev": "vite", diff --git a/src/App.tsx b/src/App.tsx index 1c7c968..e6f629b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,8 @@ -import { useState } from "react"; -import { Routes, Route } from "react-router-dom"; +import { useEffect, useState } from "react"; +import { Route, Routes } from "react-router-dom"; import { ThemeProvider } from "@mui/material/styles"; import { CssBaseline } from "@mui/material"; -import { lightTheme, darkTheme } from "./styles/theme"; +import { darkTheme, lightTheme } from "./styles/theme"; import { store } from "./state/store"; import { Provider } from "react-redux"; import GlobalWrapper from "./wrappers/GlobalWrapper"; @@ -14,6 +14,8 @@ import { IndividualProfile } from "./pages/IndividualProfile/IndividualProfile"; import { PlaylistContent } from "./pages/PlaylistContent/PlaylistContent"; import { PersistGate } from "redux-persist/integration/react"; import { persistStore } from "redux-persist"; +import { setFilteredSubscriptions } from "./state/features/videoSlice.ts"; +import { SubscriptionObject } from "./state/features/persistSlice.ts"; function App() { // const themeColor = window._qdnTheme @@ -21,6 +23,46 @@ function App() { const [theme, setTheme] = useState("dark"); let persistor = persistStore(store); + const filterVideosByName = ( + subscriptionList: SubscriptionObject[], + userName: string + ) => { + return subscriptionList.filter(item => { + return item.userName === userName; + }); + }; + + const getUserName = async () => { + const account = await qortalRequest({ + action: "GET_USER_ACCOUNT", + }); + const nameData = await qortalRequest({ + action: "GET_ACCOUNT_NAMES", + address: account.address, + }); + + if (nameData?.length > 0) return nameData[0].name; + else return ""; + }; + + const subscriptionListFilter = async () => { + const subscriptionList = store.getState().persist.subscriptionList; + const filterByUserName = + store.getState().persist.subscriptionListFilter === "currentNameOnly"; + const userName = await getUserName(); + + if (filterByUserName && userName) { + return filterVideosByName(subscriptionList, userName); + } else return subscriptionList; + }; + + useEffect(() => { + const subscriptionList = store.getState().persist.subscriptionList; + subscriptionListFilter().then(filteredList => { + store.dispatch(setFilteredSubscriptions(filteredList)); + }); + }, []); + return ( diff --git a/src/components/PublishVideo/PublishVideo-styles.tsx b/src/components/PublishVideo/PublishVideo-styles.tsx index b1b8813..6495a8a 100644 --- a/src/components/PublishVideo/PublishVideo-styles.tsx +++ b/src/components/PublishVideo/PublishVideo-styles.tsx @@ -9,7 +9,7 @@ import { Rating, TextField, Typography, - Select + Select, } from "@mui/material"; import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternate"; import { TimesSVG } from "../../assets/svgs/TimesSVG"; @@ -51,6 +51,9 @@ export const CreateContainer = styled(Box)(({ theme }) => ({ borderRadius: "50%", })); +export const CodecTypography = styled(Typography)(({ theme }) => ({ + fontSize: "18px", +})); export const ModalBody = styled(Box)(({ theme }) => ({ position: "absolute", backgroundColor: theme.palette.background.default, @@ -159,8 +162,6 @@ export const CustomInputField = styled(TextField)(({ theme }) => ({ }, })); - - export const CrowdfundTitle = styled(Typography)(({ theme }) => ({ fontFamily: "Copse", letterSpacing: "1px", @@ -539,8 +540,8 @@ export const NoReviewsFont = styled(Typography)(({ theme }) => ({ export const StyledButton = styled(Button)(({ theme }) => ({ fontWeight: 600, - color: theme.palette.text.primary -})) + color: theme.palette.text.primary, +})); export const CustomSelect = styled(Select)(({ theme }) => ({ fontFamily: "Mulish", @@ -549,34 +550,34 @@ export const CustomSelect = styled(Select)(({ theme }) => ({ fontWeight: 400, color: theme.palette.text.primary, backgroundColor: theme.palette.background.default, - '& .MuiSelect-select': { - padding: '12px', + "& .MuiSelect-select": { + padding: "12px", fontFamily: "Mulish", fontSize: "19px", letterSpacing: "0px", fontWeight: 400, borderRadius: theme.shape.borderRadius, // Match border radius }, - '&:before': { + "&:before": { // Underline style borderBottomColor: theme.palette.mode === "light" ? "#B2BAC2" : "#c9cccf", }, - '&:after': { + "&:after": { // Underline style when focused borderBottomColor: theme.palette.secondary.main, }, - '& .MuiOutlinedInput-root': { - '& fieldset': { + "& .MuiOutlinedInput-root": { + "& fieldset": { borderColor: "#E0E3E7", }, - '&:hover fieldset': { + "&:hover fieldset": { borderColor: "#B2BAC2", }, - '&.Mui-focused fieldset': { + "&.Mui-focused fieldset": { borderColor: "#6F7E8C", }, }, - '& .MuiInputBase-root': { + "& .MuiInputBase-root": { fontFamily: "Mulish", fontSize: "19px", letterSpacing: "0px", diff --git a/src/components/PublishVideo/PublishVideo.tsx b/src/components/PublishVideo/PublishVideo.tsx index 3b9307e..ab59967 100644 --- a/src/components/PublishVideo/PublishVideo.tsx +++ b/src/components/PublishVideo/PublishVideo.tsx @@ -3,6 +3,7 @@ import Compressor from "compressorjs"; import { AddCoverImageButton, AddLogoIcon, + CodecTypography, CoverImagePreview, CrowdfundActionButton, CrowdfundActionButtonRow, @@ -713,15 +714,23 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => { - - Supported File Containers: MP4, Ogg, WebM, WAV - - - Audio Codecs: FLAC, MP3, Opus, PCM (8/16/32-bit, μ-law), Vorbis - - - Video Codecs: AV1, VP8, VP9 - + + Supported File Containers:{" "} + MP4, Ogg, WebM, + WAV + + + Audio Codecs: Opus + , MP3, FLAC, PCM (8/16/32-bit, μ-law), Vorbis + + + Video Codecs: AV1, + VP8, VP9, H.264 + + + Using unsupported Codecs may result in video or audio not + working properly + { +export const SubscribeButton = ({ + subscriberName, + ...props +}: SubscribeButtonProps) => { const dispatch = useDispatch(); - const persistSelector = useSelector((state: RootState) => { - return state.persist; + const filteredSubscriptionList = useSelector((state: RootState) => { + return state.video.filteredSubscriptionList; }); + const userName = useSelector((state: RootState) => state.auth.user?.name); const [isSubscribed, setIsSubscribed] = useState(false); useEffect(() => { - setIsSubscribed(persistSelector.subscriptionList.includes(name)); + const isSubscribedToName = + filteredSubscriptionList.find(item => { + return item.subscriberName === subscriberName; + }) !== undefined; + + setIsSubscribed(isSubscribedToName); }, []); + const subscriptionData = { + userName: userName, + subscriberName: subscriberName, + }; const subscribeToRedux = () => { - dispatch(subscribe(name)); + dispatch(subscribe(subscriptionData)); + dispatch( + setFilteredSubscriptions([...filteredSubscriptionList, subscriptionData]) + ); setIsSubscribed(true); }; const unSubscribeFromRedux = () => { - dispatch(unSubscribe(name)); + dispatch(unSubscribe(subscriptionData)); + dispatch( + setFilteredSubscriptions( + filteredSubscriptionList.filter( + item => item.subscriberName !== subscriptionData.subscriberName + ) + ) + ); setIsSubscribed(false); }; diff --git a/src/hooks/useFetchVideos.tsx b/src/hooks/useFetchVideos.tsx index f6904f9..8a8b3d4 100644 --- a/src/hooks/useFetchVideos.tsx +++ b/src/hooks/useFetchVideos.tsx @@ -25,6 +25,7 @@ import { QTUBE_VIDEO_BASE, } from "../constants/Identifiers.ts"; import { allTabValue, subscriptionTabValue } from "../constants/Misc.ts"; +import { persistReducer } from "redux-persist"; export const useFetchVideos = () => { const dispatch = useDispatch(); @@ -48,9 +49,7 @@ export const useFetchVideos = () => { (state: RootState) => state.video.filteredVideos ); - const subscriptions = useSelector( - (state: RootState) => state.persist.subscriptionList - ); + const videoReducer = useSelector((state: RootState) => state.video); const checkAndUpdateVideo = React.useCallback( (video: Video) => { @@ -189,22 +188,38 @@ export const useFetchVideos = () => { } }, [videos, hashMapVideos]); + type FilterType = { + name?: string; + category?: string; + subcategory?: string; + keywords?: string; + type?: string; + }; + + const emptyFilters = { + name: "", + category: "", + subcategory: "", + keywords: "", + type: "", + }; const getVideos = React.useCallback( async ( - filters = {}, + filters = emptyFilters, reset?: boolean, resetFilters?: boolean, limit?: number, listType = allTabValue ) => { + emptyFilters.type = filters.type; try { const { name = "", category = "", subcategory = "", keywords = "", - type = "", - }: any = resetFilters ? {} : filters; + type = filters.type, + }: FilterType = resetFilters ? emptyFilters : filters; let offset = videos.length; if (reset) { offset = 0; @@ -217,8 +232,9 @@ export const useFetchVideos = () => { defaultUrl = defaultUrl + `&name=${name}`; } if (listType === subscriptionTabValue) { - subscriptions.map(sub => { - defaultUrl += `&name=${sub}`; + const filteredSubscribeList = videoReducer.filteredSubscriptionList; + filteredSubscribeList.map(sub => { + defaultUrl += `&name=${sub.subscriberName}`; }); } diff --git a/src/main.tsx b/src/main.tsx index b13bb52..17b98a1 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,17 +1,17 @@ -import ReactDOM from 'react-dom/client' -import App from './App' -import './index.css' -import { BrowserRouter } from 'react-router-dom' +import ReactDOM from "react-dom/client"; +import App from "./App"; +import "./index.css"; +import { BrowserRouter } from "react-router-dom"; interface CustomWindow extends Window { - _qdnBase: string + _qdnBase: string; } -const customWindow = window as unknown as CustomWindow +const customWindow = window as unknown as CustomWindow; -const baseUrl = customWindow?._qdnBase || '' -ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( +const baseUrl = customWindow?._qdnBase || ""; +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(