Browse Source

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.
pull/18/head
Qortal Dev 7 months ago
parent
commit
1a4dc8375b
  1. 2
      package.json
  2. 48
      src/App.tsx
  3. 29
      src/components/PublishVideo/PublishVideo-styles.tsx
  4. 27
      src/components/PublishVideo/PublishVideo.tsx
  5. 44
      src/components/common/SubscribeButton.tsx
  6. 32
      src/hooks/useFetchVideos.tsx
  7. 18
      src/main.tsx
  8. 73
      src/pages/Home/Home.tsx
  9. 10
      src/pages/Home/VideoList-styles.tsx
  10. 75
      src/pages/IndividualProfile/IndividualProfile.tsx
  11. 2
      src/pages/PlaylistContent/PlaylistContent.tsx
  12. 5
      src/pages/VideoContent/VideoContent.tsx
  13. 34
      src/state/features/persistSlice.ts
  14. 11
      src/state/features/videoSlice.ts
  15. 4
      src/state/store.ts

2
package.json

@ -1,7 +1,7 @@
{ {
"name": "qtube", "name": "qtube",
"private": true, "private": true,
"version": "0.0.0", "version": "2.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

48
src/App.tsx

@ -1,8 +1,8 @@
import { useState } from "react"; import { useEffect, useState } from "react";
import { Routes, Route } from "react-router-dom"; import { Route, Routes } from "react-router-dom";
import { ThemeProvider } from "@mui/material/styles"; import { ThemeProvider } from "@mui/material/styles";
import { CssBaseline } from "@mui/material"; import { CssBaseline } from "@mui/material";
import { lightTheme, darkTheme } from "./styles/theme"; import { darkTheme, lightTheme } from "./styles/theme";
import { store } from "./state/store"; import { store } from "./state/store";
import { Provider } from "react-redux"; import { Provider } from "react-redux";
import GlobalWrapper from "./wrappers/GlobalWrapper"; import GlobalWrapper from "./wrappers/GlobalWrapper";
@ -14,6 +14,8 @@ import { IndividualProfile } from "./pages/IndividualProfile/IndividualProfile";
import { PlaylistContent } from "./pages/PlaylistContent/PlaylistContent"; import { PlaylistContent } from "./pages/PlaylistContent/PlaylistContent";
import { PersistGate } from "redux-persist/integration/react"; import { PersistGate } from "redux-persist/integration/react";
import { persistStore } from "redux-persist"; import { persistStore } from "redux-persist";
import { setFilteredSubscriptions } from "./state/features/videoSlice.ts";
import { SubscriptionObject } from "./state/features/persistSlice.ts";
function App() { function App() {
// const themeColor = window._qdnTheme // const themeColor = window._qdnTheme
@ -21,6 +23,46 @@ function App() {
const [theme, setTheme] = useState("dark"); const [theme, setTheme] = useState("dark");
let persistor = persistStore(store); 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 ( return (
<Provider store={store}> <Provider store={store}>
<PersistGate loading={null} persistor={persistor}> <PersistGate loading={null} persistor={persistor}>

29
src/components/PublishVideo/PublishVideo-styles.tsx

@ -9,7 +9,7 @@ import {
Rating, Rating,
TextField, TextField,
Typography, Typography,
Select Select,
} from "@mui/material"; } from "@mui/material";
import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternate"; import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternate";
import { TimesSVG } from "../../assets/svgs/TimesSVG"; import { TimesSVG } from "../../assets/svgs/TimesSVG";
@ -51,6 +51,9 @@ export const CreateContainer = styled(Box)(({ theme }) => ({
borderRadius: "50%", borderRadius: "50%",
})); }));
export const CodecTypography = styled(Typography)(({ theme }) => ({
fontSize: "18px",
}));
export const ModalBody = styled(Box)(({ theme }) => ({ export const ModalBody = styled(Box)(({ theme }) => ({
position: "absolute", position: "absolute",
backgroundColor: theme.palette.background.default, backgroundColor: theme.palette.background.default,
@ -159,8 +162,6 @@ export const CustomInputField = styled(TextField)(({ theme }) => ({
}, },
})); }));
export const CrowdfundTitle = styled(Typography)(({ theme }) => ({ export const CrowdfundTitle = styled(Typography)(({ theme }) => ({
fontFamily: "Copse", fontFamily: "Copse",
letterSpacing: "1px", letterSpacing: "1px",
@ -539,8 +540,8 @@ export const NoReviewsFont = styled(Typography)(({ theme }) => ({
export const StyledButton = styled(Button)(({ theme }) => ({ export const StyledButton = styled(Button)(({ theme }) => ({
fontWeight: 600, fontWeight: 600,
color: theme.palette.text.primary color: theme.palette.text.primary,
})) }));
export const CustomSelect = styled(Select)(({ theme }) => ({ export const CustomSelect = styled(Select)(({ theme }) => ({
fontFamily: "Mulish", fontFamily: "Mulish",
@ -549,34 +550,34 @@ export const CustomSelect = styled(Select)(({ theme }) => ({
fontWeight: 400, fontWeight: 400,
color: theme.palette.text.primary, color: theme.palette.text.primary,
backgroundColor: theme.palette.background.default, backgroundColor: theme.palette.background.default,
'& .MuiSelect-select': { "& .MuiSelect-select": {
padding: '12px', padding: "12px",
fontFamily: "Mulish", fontFamily: "Mulish",
fontSize: "19px", fontSize: "19px",
letterSpacing: "0px", letterSpacing: "0px",
fontWeight: 400, fontWeight: 400,
borderRadius: theme.shape.borderRadius, // Match border radius borderRadius: theme.shape.borderRadius, // Match border radius
}, },
'&:before': { "&:before": {
// Underline style // Underline style
borderBottomColor: theme.palette.mode === "light" ? "#B2BAC2" : "#c9cccf", borderBottomColor: theme.palette.mode === "light" ? "#B2BAC2" : "#c9cccf",
}, },
'&:after': { "&:after": {
// Underline style when focused // Underline style when focused
borderBottomColor: theme.palette.secondary.main, borderBottomColor: theme.palette.secondary.main,
}, },
'& .MuiOutlinedInput-root': { "& .MuiOutlinedInput-root": {
'& fieldset': { "& fieldset": {
borderColor: "#E0E3E7", borderColor: "#E0E3E7",
}, },
'&:hover fieldset': { "&:hover fieldset": {
borderColor: "#B2BAC2", borderColor: "#B2BAC2",
}, },
'&.Mui-focused fieldset': { "&.Mui-focused fieldset": {
borderColor: "#6F7E8C", borderColor: "#6F7E8C",
}, },
}, },
'& .MuiInputBase-root': { "& .MuiInputBase-root": {
fontFamily: "Mulish", fontFamily: "Mulish",
fontSize: "19px", fontSize: "19px",
letterSpacing: "0px", letterSpacing: "0px",

27
src/components/PublishVideo/PublishVideo.tsx

@ -3,6 +3,7 @@ import Compressor from "compressorjs";
import { import {
AddCoverImageButton, AddCoverImageButton,
AddLogoIcon, AddLogoIcon,
CodecTypography,
CoverImagePreview, CoverImagePreview,
CrowdfundActionButton, CrowdfundActionButton,
CrowdfundActionButtonRow, CrowdfundActionButtonRow,
@ -713,15 +714,23 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
</Typography> </Typography>
</Box> </Box>
<Box> <Box>
<Typography sx={{fontSize: "14px"}}> <CodecTypography>
Supported File Containers: MP4, Ogg, WebM, WAV Supported File Containers:{" "}
</Typography> <span style={{ fontWeight: "bold" }}>MP4</span>, Ogg, WebM,
<Typography sx={{fontSize: "14px"}}> WAV
Audio Codecs: FLAC, MP3, Opus, PCM (8/16/32-bit, μ-law), Vorbis </CodecTypography>
</Typography> <CodecTypography>
<Typography sx={{fontSize: "14px"}}> Audio Codecs: <span style={{ fontWeight: "bold" }}>Opus</span>
Video Codecs: AV1, VP8, VP9 , MP3, FLAC, PCM (8/16/32-bit, μ-law), Vorbis
</Typography> </CodecTypography>
<CodecTypography>
Video Codecs: <span style={{ fontWeight: "bold" }}>AV1</span>,
VP8, VP9, H.264
</CodecTypography>
<CodecTypography sx={{ fontWeight: "800", color: "red" }}>
Using unsupported Codecs may result in video or audio not
working properly
</CodecTypography>
</Box> </Box>
<Box <Box

44
src/components/common/SubscribeButton.tsx

@ -2,29 +2,57 @@ import { Button, ButtonProps } 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 { RootState } from "../../state/store.ts"; import { RootState } from "../../state/store.ts";
import { subscribe, unSubscribe } from "../../state/features/persistSlice.ts"; import {
resetSubscriptions,
subscribe,
unSubscribe,
} from "../../state/features/persistSlice.ts";
import { setFilteredSubscriptions } from "../../state/features/videoSlice.ts";
interface SubscribeButtonProps extends ButtonProps { interface SubscribeButtonProps extends ButtonProps {
name: string; subscriberName: string;
} }
export const SubscribeButton = ({ name, ...props }: SubscribeButtonProps) => { export const SubscribeButton = ({
subscriberName,
...props
}: SubscribeButtonProps) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const persistSelector = useSelector((state: RootState) => { const filteredSubscriptionList = useSelector((state: RootState) => {
return state.persist; return state.video.filteredSubscriptionList;
}); });
const userName = useSelector((state: RootState) => state.auth.user?.name);
const [isSubscribed, setIsSubscribed] = useState<boolean>(false); const [isSubscribed, setIsSubscribed] = useState<boolean>(false);
useEffect(() => { 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 = () => { const subscribeToRedux = () => {
dispatch(subscribe(name)); dispatch(subscribe(subscriptionData));
dispatch(
setFilteredSubscriptions([...filteredSubscriptionList, subscriptionData])
);
setIsSubscribed(true); setIsSubscribed(true);
}; };
const unSubscribeFromRedux = () => { const unSubscribeFromRedux = () => {
dispatch(unSubscribe(name)); dispatch(unSubscribe(subscriptionData));
dispatch(
setFilteredSubscriptions(
filteredSubscriptionList.filter(
item => item.subscriberName !== subscriptionData.subscriberName
)
)
);
setIsSubscribed(false); setIsSubscribed(false);
}; };

32
src/hooks/useFetchVideos.tsx

@ -25,6 +25,7 @@ import {
QTUBE_VIDEO_BASE, QTUBE_VIDEO_BASE,
} from "../constants/Identifiers.ts"; } from "../constants/Identifiers.ts";
import { allTabValue, subscriptionTabValue } from "../constants/Misc.ts"; import { allTabValue, subscriptionTabValue } from "../constants/Misc.ts";
import { persistReducer } from "redux-persist";
export const useFetchVideos = () => { export const useFetchVideos = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -48,9 +49,7 @@ export const useFetchVideos = () => {
(state: RootState) => state.video.filteredVideos (state: RootState) => state.video.filteredVideos
); );
const subscriptions = useSelector( const videoReducer = useSelector((state: RootState) => state.video);
(state: RootState) => state.persist.subscriptionList
);
const checkAndUpdateVideo = React.useCallback( const checkAndUpdateVideo = React.useCallback(
(video: Video) => { (video: Video) => {
@ -189,22 +188,38 @@ export const useFetchVideos = () => {
} }
}, [videos, hashMapVideos]); }, [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( const getVideos = React.useCallback(
async ( async (
filters = {}, filters = emptyFilters,
reset?: boolean, reset?: boolean,
resetFilters?: boolean, resetFilters?: boolean,
limit?: number, limit?: number,
listType = allTabValue listType = allTabValue
) => { ) => {
emptyFilters.type = filters.type;
try { try {
const { const {
name = "", name = "",
category = "", category = "",
subcategory = "", subcategory = "",
keywords = "", keywords = "",
type = "", type = filters.type,
}: any = resetFilters ? {} : filters; }: FilterType = resetFilters ? emptyFilters : filters;
let offset = videos.length; let offset = videos.length;
if (reset) { if (reset) {
offset = 0; offset = 0;
@ -217,8 +232,9 @@ export const useFetchVideos = () => {
defaultUrl = defaultUrl + `&name=${name}`; defaultUrl = defaultUrl + `&name=${name}`;
} }
if (listType === subscriptionTabValue) { if (listType === subscriptionTabValue) {
subscriptions.map(sub => { const filteredSubscribeList = videoReducer.filteredSubscriptionList;
defaultUrl += `&name=${sub}`; filteredSubscribeList.map(sub => {
defaultUrl += `&name=${sub.subscriberName}`;
}); });
} }

18
src/main.tsx

@ -1,17 +1,17 @@
import ReactDOM from 'react-dom/client' import ReactDOM from "react-dom/client";
import App from './App' import App from "./App";
import './index.css' import "./index.css";
import { BrowserRouter } from 'react-router-dom' import { BrowserRouter } from "react-router-dom";
interface CustomWindow extends Window { 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 || '' const baseUrl = customWindow?._qdnBase || "";
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<BrowserRouter basename={baseUrl}> <BrowserRouter basename={baseUrl}>
<App /> <App />
<div id="modal-root" /> <div id="modal-root" />
</BrowserRouter> </BrowserRouter>
) );

73
src/pages/Home/Home.tsx

@ -25,6 +25,7 @@ import {
FiltersSubContainer, FiltersSubContainer,
ProductManagerRow, ProductManagerRow,
FiltersRadioButton, FiltersRadioButton,
StatsCol,
} from "./VideoList-styles"; } from "./VideoList-styles";
import { SubtitleContainer } from "./Home-styles"; import { SubtitleContainer } from "./Home-styles";
@ -34,7 +35,10 @@ import {
changefilterName, changefilterName,
changefilterSearch, changefilterSearch,
} from "../../state/features/videoSlice"; } from "../../state/features/videoSlice";
import { changeFilterType } from "../../state/features/persistSlice.ts"; import {
changeFilterType,
resetSubscriptions,
} from "../../state/features/persistSlice.ts";
import { categories, subCategories } from "../../constants/Categories.ts"; import { categories, subCategories } from "../../constants/Categories.ts";
import { ListSuperLikeContainer } from "../../components/common/ListSuperLikes/ListSuperLikeContainer.tsx"; import { ListSuperLikeContainer } from "../../components/common/ListSuperLikes/ListSuperLikeContainer.tsx";
import { TabContext, TabList, TabPanel } from "@mui/lab"; import { TabContext, TabList, TabPanel } from "@mui/lab";
@ -53,7 +57,7 @@ export const Home = ({ mode }: HomeProps) => {
const filterValue = useSelector( const filterValue = useSelector(
(state: RootState) => state.video.filterValue (state: RootState) => state.video.filterValue
); );
const persistSelector = useSelector((state: RootState) => state.persist); const persistReducer = useSelector((state: RootState) => state.persist);
const filterType = useSelector( const filterType = useSelector(
(state: RootState) => state.persist.filterType (state: RootState) => state.persist.filterType
); );
@ -74,12 +78,12 @@ export const Home = ({ mode }: HomeProps) => {
(state: RootState) => state.global.videosPerNamePublished (state: RootState) => state.global.videosPerNamePublished
); );
const { videos: globalVideos } = useSelector( const { videos: globalVideos, filteredSubscriptionList } = useSelector(
(state: RootState) => state.video (state: RootState) => state.video
); );
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const [tabValue, setTabValue] = useState<string>(persistSelector.selectedTab); const [tabValue, setTabValue] = useState<string>(persistReducer.selectedTab);
const tabFontSize = "20px"; const tabFontSize = "20px";
@ -120,8 +124,13 @@ export const Home = ({ mode }: HomeProps) => {
const afterFetch = useRef(false); const afterFetch = useRef(false);
const isFetching = useRef(false); const isFetching = useRef(false);
const { getVideos, getNewVideos, checkNewVideos, getVideosFiltered, getVideosCount } = const {
useFetchVideos(); getVideos,
getNewVideos,
checkNewVideos,
getVideosFiltered,
getVideosCount,
} = useFetchVideos();
const getVideosHandler = React.useCallback( const getVideosHandler = React.useCallback(
async (reset?: boolean, resetFilters?: boolean) => { async (reset?: boolean, resetFilters?: boolean) => {
@ -170,7 +179,19 @@ export const Home = ({ mode }: HomeProps) => {
firstFetch.current = true; firstFetch.current = true;
setIsLoading(true); setIsLoading(true);
await getVideos({ type: filterType }, null, null, 20, tabValue); await getVideos(
{
name: "",
category: "",
subcategory: "",
keywords: "",
type: filterType,
},
null,
null,
20,
tabValue
);
afterFetch.current = true; afterFetch.current = true;
isFetching.current = false; isFetching.current = false;
@ -227,7 +248,6 @@ export const Home = ({ mode }: HomeProps) => {
}, [getVideosHandlerMount, globalVideos]); }, [getVideosHandlerMount, globalVideos]);
const filtersToDefault = async () => { const filtersToDefault = async () => {
setFilterType("videos");
setFilterSearch(""); setFilterSearch("");
setFilterName(""); setFilterName("");
setSelectedCategoryVideos(null); setSelectedCategoryVideos(null);
@ -273,22 +293,31 @@ export const Home = ({ mode }: HomeProps) => {
return ( return (
<> <>
<Box sx={{ width: "100%" }}>
<Grid container spacing={2} justifyContent="space-around">
<Grid item xs={12} sm={4}>
Total Videos Published: {totalVideosPublished}
</Grid>
<Grid item xs={12} sm={4}>
Total Names Publishing: {totalNamesPublished}
</Grid>
<Grid item xs={12} sm={4}>
Average Videos per Name: {videosPerNamePublished}
</Grid>
</Grid>
</Box>
<Grid container sx={{ width: "100%" }}> <Grid container sx={{ width: "100%" }}>
<FiltersCol item xs={12} md={2} lg={2} xl={2} sm={3}> <FiltersCol item xs={12} md={2} lg={2} xl={2} sm={3}>
<FiltersContainer> <FiltersContainer>
<StatsCol
sx={{ display: persistReducer.showStats ? "block" : "none" }}
>
<div>
# of Videos:{" "}
<span style={{ fontWeight: "bold" }}>
{totalVideosPublished}
</span>
</div>
<div>
Names Publishing:{" "}
<span style={{ fontWeight: "bold" }}>
{totalNamesPublished}
</span>
</div>
<div>
Videos per Name:{" "}
<span style={{ fontWeight: "bold" }}>
{videosPerNamePublished}
</span>
</div>
</StatsCol>
<Input <Input
id="standard-adornment-name" id="standard-adornment-name"
onChange={e => { onChange={e => {
@ -538,7 +567,7 @@ export const Home = ({ mode }: HomeProps) => {
></LazyLoad> ></LazyLoad>
</TabPanel> </TabPanel>
<TabPanel value={subscriptionTabValue} sx={{ width: "100%" }}> <TabPanel value={subscriptionTabValue} sx={{ width: "100%" }}>
{persistSelector.subscriptionList.length > 0 ? ( {filteredSubscriptionList.length > 0 ? (
<> <>
<VideoList videos={videos} /> <VideoList videos={videos} />
<LazyLoad <LazyLoad

10
src/pages/Home/VideoList-styles.tsx

@ -237,6 +237,16 @@ export const FiltersCol = styled(Grid)(({ theme }) => ({
borderRight: `1px solid ${theme.palette.background.paper}`, borderRight: `1px solid ${theme.palette.background.paper}`,
})); }));
export const StatsCol = styled(Grid)(({ theme }) => ({
display: "flex",
flexDirection: "column",
width: "100%",
padding: "20px 15px",
backgroundColor: theme.palette.background.default,
borderTop: `1px solid ${theme.palette.background.paper}`,
borderRight: `1px solid ${theme.palette.background.paper}`,
}));
export const FiltersContainer = styled(Box)(({ theme }) => ({ export const FiltersContainer = styled(Box)(({ theme }) => ({
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",

75
src/pages/IndividualProfile/IndividualProfile.tsx

@ -1,65 +1,74 @@
import React, { useMemo } from 'react' import React, { useMemo } from "react";
import { VideoListComponentLevel } from '../Home/VideoListComponentLevel' import { VideoListComponentLevel } from "../Home/VideoListComponentLevel";
import { HeaderContainer, ProfileContainer } from './Profile-styles' import { HeaderContainer, ProfileContainer } from "./Profile-styles";
import { AuthorTextComment, StyledCardColComment, StyledCardHeaderComment } from '../VideoContent/VideoContent-styles' import {
import { Avatar, Box, useTheme } from '@mui/material' AuthorTextComment,
import { useParams } from 'react-router-dom' StyledCardColComment,
import { useSelector } from 'react-redux' StyledCardHeaderComment,
import { setUserAvatarHash } from '../../state/features/globalSlice' } from "../VideoContent/VideoContent-styles";
import { RootState } from '../../state/store' import { Avatar, Box, useTheme } from "@mui/material";
import { useParams } from "react-router-dom";
import { useSelector } from "react-redux";
import { setUserAvatarHash } from "../../state/features/globalSlice";
import { RootState } from "../../state/store";
import { SubscribeButton } from "../../components/common/SubscribeButton.tsx"; import { SubscribeButton } from "../../components/common/SubscribeButton.tsx";
export const IndividualProfile = () => { export const IndividualProfile = () => {
const { name: paramName } = useParams() const { name: paramName } = useParams();
const userAvatarHash = useSelector( const userAvatarHash = useSelector(
(state: RootState) => state.global.userAvatarHash (state: RootState) => state.global.userAvatarHash
) );
const theme = useTheme() const theme = useTheme();
const avatarUrl = useMemo(() => {
let url = "";
const avatarUrl = useMemo(()=> { if (paramName && userAvatarHash[paramName]) {
let url = '' url = userAvatarHash[paramName];
if(paramName && userAvatarHash[paramName]){
url = userAvatarHash[paramName]
} }
return url return url;
}, [userAvatarHash, paramName]) }, [userAvatarHash, paramName]);
return ( return (
<ProfileContainer> <ProfileContainer>
<HeaderContainer> <HeaderContainer>
<Box sx={{ <Box
cursor: 'pointer' sx={{
}} > cursor: "pointer",
}}
>
<StyledCardHeaderComment <StyledCardHeaderComment
sx={{ sx={{
'& .MuiCardHeader-content': { "& .MuiCardHeader-content": {
overflow: 'hidden' overflow: "hidden",
} },
}} }}
> >
<Box> <Box>
<Avatar src={`/arbitrary/THUMBNAIL/${paramName}/qortal_avatar`} alt={`${paramName}'s avatar`} /> <Avatar
src={`/arbitrary/THUMBNAIL/${paramName}/qortal_avatar`}
alt={`${paramName}'s avatar`}
/>
</Box> </Box>
<StyledCardColComment> <StyledCardColComment>
<AuthorTextComment <AuthorTextComment
color={ color={
theme.palette.mode === 'light' theme.palette.mode === "light"
? theme.palette.text.secondary ? theme.palette.text.secondary
: '#d6e8ff' : "#d6e8ff"
} }
> >
{paramName} {paramName}
</AuthorTextComment> </AuthorTextComment>
</StyledCardColComment> </StyledCardColComment>
<SubscribeButton name={paramName} sx={{marginLeft:'10px'}}/> <SubscribeButton
subscriberName={paramName}
sx={{ marginLeft: "10px" }}
/>
</StyledCardHeaderComment> </StyledCardHeaderComment>
</Box> </Box>
</HeaderContainer> </HeaderContainer>
<VideoListComponentLevel /> <VideoListComponentLevel />
</ProfileContainer> </ProfileContainer>
);
) };
}

2
src/pages/PlaylistContent/PlaylistContent.tsx

@ -497,7 +497,7 @@ export const PlaylistContent = () => {
> >
{name} {name}
<SubscribeButton <SubscribeButton
name={name} subscriberName={name}
sx={{ marginLeft: "20px" }} sx={{ marginLeft: "20px" }}
/> />
</AuthorTextComment> </AuthorTextComment>

5
src/pages/VideoContent/VideoContent.tsx

@ -437,7 +437,10 @@ export const VideoContent = () => {
}} }}
> >
{name} {name}
<SubscribeButton name={name} sx={{ marginLeft: "20px" }} /> <SubscribeButton
subscriberName={name}
sx={{ marginLeft: "20px" }}
/>
</AuthorTextComment> </AuthorTextComment>
</StyledCardColComment> </StyledCardColComment>
</StyledCardHeaderComment> </StyledCardHeaderComment>

34
src/state/features/persistSlice.ts

@ -2,12 +2,21 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { subscriptionTabValue } from "../../constants/Misc.ts"; import { subscriptionTabValue } from "../../constants/Misc.ts";
type StretchVideoType = "contain" | "fill" | "cover" | "none" | "scale-down"; type StretchVideoType = "contain" | "fill" | "cover" | "none" | "scale-down";
type SubscriptionListFilterType = "ALL" | "currentNameOnly";
export type SubscriptionObject = {
userName: string;
subscriberName: string;
};
interface settingsState { interface settingsState {
selectedTab: string; selectedTab: string;
stretchVideoSetting: StretchVideoType; stretchVideoSetting: StretchVideoType;
filterType: string; filterType: string;
subscriptionList: string[]; subscriptionList: SubscriptionObject[];
playbackRate: number; playbackRate: number;
subscriptionListFilter: SubscriptionListFilterType;
showStats: boolean;
} }
const initialState: settingsState = { const initialState: settingsState = {
@ -16,6 +25,8 @@ const initialState: settingsState = {
filterType: "videos", filterType: "videos",
subscriptionList: [], subscriptionList: [],
playbackRate: 1, playbackRate: 1,
subscriptionListFilter: "currentNameOnly",
showStats: true,
}; };
export const persistSlice = createSlice({ export const persistSlice = createSlice({
@ -28,16 +39,28 @@ export const persistSlice = createSlice({
setStretchVideoSetting: (state, action) => { setStretchVideoSetting: (state, action) => {
state.stretchVideoSetting = action.payload; state.stretchVideoSetting = action.payload;
}, },
subscribe: (state, action: PayloadAction<string>) => { setShowStats: (state, action) => {
state.showStats = action.payload;
},
subscribe: (state, action: PayloadAction<SubscriptionObject>) => {
const currentSubscriptions = state.subscriptionList; const currentSubscriptions = state.subscriptionList;
if (!currentSubscriptions.includes(action.payload)) { const notSubscribedToName =
currentSubscriptions.find(item => {
return item.subscriberName === action.payload.subscriberName;
}) === undefined;
if (notSubscribedToName) {
state.subscriptionList = [...currentSubscriptions, action.payload]; state.subscriptionList = [...currentSubscriptions, action.payload];
} }
console.log("subscribeList after subscribe: ", state.subscriptionList);
}, },
unSubscribe: (state, action) => { unSubscribe: (state, action: PayloadAction<SubscriptionObject>) => {
state.subscriptionList = state.subscriptionList.filter( state.subscriptionList = state.subscriptionList.filter(
item => item !== action.payload item => item.subscriberName !== action.payload.subscriberName
); );
console.log("subscribeList after unsubscribe: ", state.subscriptionList);
},
resetSubscriptions: state => {
state.subscriptionList = [];
}, },
setReduxPlaybackRate: (state, action) => { setReduxPlaybackRate: (state, action) => {
state.playbackRate = action.payload; state.playbackRate = action.payload;
@ -54,6 +77,7 @@ export const {
unSubscribe, unSubscribe,
setReduxPlaybackRate, setReduxPlaybackRate,
changeFilterType, changeFilterType,
resetSubscriptions,
} = persistSlice.actions; } = persistSlice.actions;
export default persistSlice.reducer; export default persistSlice.reducer;

11
src/state/features/videoSlice.ts

@ -1,4 +1,5 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { SubscriptionObject } from "./persistSlice.ts";
interface GlobalState { interface GlobalState {
videos: Video[]; videos: Video[];
@ -14,6 +15,7 @@ interface GlobalState {
selectedSubCategoryVideos: any; selectedSubCategoryVideos: any;
editVideoProperties: any; editVideoProperties: any;
editPlaylistProperties: any; editPlaylistProperties: any;
filteredSubscriptionList: SubscriptionObject[];
} }
const initialState: GlobalState = { const initialState: GlobalState = {
@ -30,6 +32,7 @@ const initialState: GlobalState = {
selectedSubCategoryVideos: null, selectedSubCategoryVideos: null,
editVideoProperties: null, editVideoProperties: null,
editPlaylistProperties: null, editPlaylistProperties: null,
filteredSubscriptionList: [],
}; };
export interface Video { export interface Video {
@ -168,6 +171,13 @@ export const videoSlice = createSlice({
state.videos = state.videos.filter(item => item.user !== username); state.videos = state.videos.filter(item => item.user !== username);
}, },
setFilteredSubscriptions: (
state,
action: PayloadAction<SubscriptionObject[]>
) => {
state.filteredSubscriptionList = action.payload;
},
}, },
}); });
@ -196,6 +206,7 @@ export const {
setEditVideo, setEditVideo,
setEditPlaylist, setEditPlaylist,
addtoHashMapSuperlikes, addtoHashMapSuperlikes,
setFilteredSubscriptions,
} = videoSlice.actions; } = videoSlice.actions;
export default videoSlice.reducer; export default videoSlice.reducer;

4
src/state/store.ts

@ -3,7 +3,7 @@ import notificationsReducer from "./features/notificationsSlice";
import authReducer from "./features/authSlice"; import authReducer from "./features/authSlice";
import globalReducer from "./features/globalSlice"; import globalReducer from "./features/globalSlice";
import videoReducer from "./features/videoSlice"; import videoReducer from "./features/videoSlice";
import settingsReducer from "./features/persistSlice.ts"; import persistDataReducer from "./features/persistSlice.ts";
import { import {
persistReducer, persistReducer,
FLUSH, FLUSH,
@ -26,7 +26,7 @@ const reducer = combineReducers({
auth: authReducer, auth: authReducer,
global: globalReducer, global: globalReducer,
video: videoReducer, video: videoReducer,
persist: persistReducer(persistSettingsConfig, settingsReducer), persist: persistReducer(persistSettingsConfig, persistDataReducer),
}); });
export const store = configureStore({ export const store = configureStore({

Loading…
Cancel
Save