removed more reducers

This commit is contained in:
2025-06-29 09:48:57 +03:00
parent 161a85be53
commit 1df6a1d144
25 changed files with 391 additions and 622 deletions

View File

@@ -24,15 +24,8 @@ import {
useTheme,
} from '@mui/material';
import ShortUniqueId from 'short-unique-id';
import { useDispatch, useSelector } from 'react-redux';
import { objectToBase64 } from '../../../utils/PublishFormatter.ts';
import { RootState } from '../../../state/store.ts';
import {
updateVideo,
updateInHashMap,
setEditPlaylist,
} from '../../../state/features/videoSlice.ts';
import ImageUploader from '../../common/ImageUploader.tsx';
import { categories, subCategories } from '../../../constants/Categories.ts';
import { Playlists } from '../../Playlists/Playlists.tsx';
@@ -43,25 +36,25 @@ import {
QTUBE_PLAYLIST_BASE,
QTUBE_VIDEO_BASE,
} from '../../../constants/Identifiers.ts';
import { useAuth } from 'qapp-core';
import { useAuth, useGlobal } from 'qapp-core';
import {
AltertObject,
setNotificationAtom,
} from '../../../state/global/notifications.ts';
import { useSetAtom } from 'jotai';
import { useAtom, useSetAtom } from 'jotai';
import { editPlaylistAtom } from '../../../state/publish/playlist.ts';
const uid = new ShortUniqueId();
const shortuid = new ShortUniqueId({ length: 5 });
export const EditPlaylist = () => {
const theme = useTheme();
const dispatch = useDispatch();
const { name: username, address: userAddress } = useAuth();
const setNotification = useSetAtom(setNotificationAtom);
const { lists } = useGlobal();
const [editVideoProperties] = useAtom(editPlaylistAtom);
const setEditPlaylist = useSetAtom(editPlaylistAtom);
const editVideoProperties = useSelector(
(state: RootState) => state.video.editPlaylistProperties
);
const [playlistData, setPlaylistData] = useState<any>(null);
const [title, setTitle] = useState<string>('');
const [description, setDescription] = useState<string>('');
@@ -84,52 +77,6 @@ export const EditPlaylist = () => {
}
}, [isNew]);
// useEffect(() => {
// if (editVideoProperties) {
// const descriptionString = editVideoProperties?.description || "";
// // Splitting the string at the asterisks
// const parts = descriptionString.split("**");
// // The part within the asterisks
// const extractedString = parts[1];
// // The part after the last asterisks
// const description = parts[2] || ""; // Using '|| '' to handle cases where there is no text after the last **
// setTitle(editVideoProperties?.title || "");
// setDescription(editVideoProperties?.fullDescription || "");
// setCoverImage(editVideoProperties?.videoImage || "");
// // Split the extracted string into key-value pairs
// const keyValuePairs = extractedString.split(";");
// // Initialize variables to hold the category and subcategory values
// let category, subcategory;
// // Loop through each key-value pair
// keyValuePairs.forEach((pair) => {
// const [key, value] = pair.split(":");
// // Check the key and assign the value to the appropriate variable
// if (key === "category") {
// category = value;
// } else if (key === "subcategory") {
// subcategory = value;
// }
// });
// if(category){
// const selectedOption = categories.find((option) => option.id === +category);
// setSelectedCategoryVideos(selectedOption || null);
// }
// if(subcategory){
// const selectedOption = categories.find((option) => option.id === +subcategory);
// setSelectedCategoryVideos(selectedOption || null);
// }
// }
// }, [editVideoProperties]);
const checkforPlaylist = React.useCallback(async (videoList) => {
try {
const combinedData: any = {};
@@ -203,7 +150,7 @@ export const EditPlaylist = () => {
setSelectedCategoryVideos(null);
setSelectedSubCategoryVideos(null);
setCoverImage('');
dispatch(setEditPlaylist(null));
setEditPlaylist(null);
};
async function publishQDNResource() {
@@ -331,21 +278,25 @@ export const EditPlaylist = () => {
user: username,
...playlistObject,
};
dispatch(updateVideo(objectToStore));
dispatch(updateInHashMap(objectToStore));
} else {
dispatch(
updateVideo({
...editVideoProperties,
...playlistObject,
})
);
dispatch(
updateInHashMap({
...editVideoProperties,
...playlistObject,
})
);
lists.updateNewResources([
{
data: playlistObject,
qortalMetadata: {
identifier: identifier,
service: 'PLAYLIST',
name: username,
size: 100,
updated: Date.now(),
metadata: {
title: title.slice(0, 50),
description: metadescription,
tags: [QTUBE_VIDEO_BASE],
},
created: editVideoProperties?.created,
},
},
]);
}
const notificationObj: AltertObject = {
msg: 'Playlist published',

View File

@@ -11,12 +11,10 @@ import {
Typography,
useTheme,
} from '@mui/material';
import { Signal, useSignal, useSignalEffect } from '@preact/signals-react';
import { Signal, useSignal } from '@preact/signals-react';
import Compressor from 'compressorjs';
import React, { useEffect, useState } from 'react';
import { useDropzone } from 'react-dropzone';
import { useDispatch, useSelector } from 'react-redux';
import ShortUniqueId from 'short-unique-id';
import { categories, subCategories } from '../../../constants/Categories.ts';
import { QTUBE_VIDEO_BASE } from '../../../constants/Identifiers.ts';
import {
@@ -25,12 +23,6 @@ import {
videoMaxSize,
} from '../../../constants/Misc.ts';
import {
setEditVideo,
updateInHashMap,
updateVideo,
} from '../../../state/features/videoSlice.ts';
import { RootState } from '../../../state/store.ts';
import BoundedNumericTextField from '../../../utils/BoundedNumericTextField.tsx';
import { objectToBase64 } from '../../../utils/PublishFormatter.ts';
import { FrameExtractor } from '../../common/FrameExtractor/FrameExtractor.tsx';
@@ -52,33 +44,29 @@ import {
NewCrowdfundTitle,
TimesIcon,
} from './EditVideo-styles.tsx';
import { useAuth, usePublish } from 'qapp-core';
import { useSetAtom } from 'jotai';
import { useAuth, useGlobal, usePublish } from 'qapp-core';
import { useAtom, useSetAtom } from 'jotai';
import {
AltertObject,
setNotificationAtom,
} from '../../../state/global/notifications.ts';
import { editVideoAtom } from '../../../state/publish/video.ts';
export const EditVideo = () => {
const theme = useTheme();
const setNotification = useSetAtom(setNotificationAtom);
const dispatch = useDispatch();
const setEditVideo = useSetAtom(editVideoAtom);
const { name: username, address: userAddress } = useAuth();
const editVideoProperties = useSelector(
(state: RootState) => state.video.editVideoProperties
);
const { lists, auth } = useGlobal();
const [editVideoProperties] = useAtom(editVideoAtom);
const [publishes, setPublishes] = useState<any>(null);
const [isOpenMultiplePublish, setIsOpenMultiplePublish] = useState(false);
const [videoPropertiesToSetToRedux, setVideoPropertiesToSetToRedux] =
useState(null);
const publishFromLibrary = usePublish();
const [title, setTitle] = useState<string>('');
const [description, setDescription] = useState<string>('');
const [coverImage, setCoverImage] = useState<string>('');
const [file, setFile] = useState(null);
const [file, setFile] = useState<null | File>(null);
const [selectedCategoryVideos, setSelectedCategoryVideos] =
useState<any>(null);
const [selectedSubCategoryVideos, setSelectedSubCategoryVideos] =
@@ -104,7 +92,7 @@ export const EditVideo = () => {
setFile(firstFile);
let errorString = null;
let errorString: null | string = null;
rejectedFiles.forEach(({ file, errors }) => {
errors.forEach((error) => {
@@ -155,9 +143,10 @@ export const EditVideo = () => {
}
}, [editVideoProperties]);
console.log('editVideoProperties', editVideoProperties);
const onClose = () => {
dispatch(setEditVideo(null));
setVideoPropertiesToSetToRedux(null);
setEditVideo(null);
setFile(null);
setTitle('');
setImageExtracts([]);
@@ -167,6 +156,7 @@ export const EditVideo = () => {
async function publishQDNResource() {
try {
if (!username) throw new Error('A name is required to publish');
if (!title) throw new Error('Please enter a title');
if (!description) throw new Error('Please enter a description');
if (!coverImage) throw new Error('Please select cover image');
@@ -195,14 +185,14 @@ export const EditVideo = () => {
setNotification(notificationObj);
return;
}
const listOfPublishes = [];
const listOfPublishes: any[] = [];
const category = selectedCategoryVideos.id;
const subcategory = selectedSubCategoryVideos?.id || '';
const fullDescription = extractTextFromHTML(description);
let fileExtension = 'mp4';
const fileExtensionSplit = file?.name?.split('.');
if (fileExtensionSplit?.length > 1) {
if (fileExtensionSplit && fileExtensionSplit?.length > 1) {
fileExtension = fileExtensionSplit?.pop() || 'mp4';
}
@@ -268,18 +258,26 @@ export const EditVideo = () => {
listOfPublishes.push(requestBodyVideo);
}
setVideoPropertiesToSetToRedux({
...editVideoProperties,
...videoObject,
});
await publishFromLibrary.publishMultipleResources(listOfPublishes);
const clonedCopy = structuredClone({
...editVideoProperties,
...videoObject,
});
dispatch(updateVideo(clonedCopy));
dispatch(updateInHashMap(clonedCopy));
lists.updateNewResources([
{
data: videoObject,
qortalMetadata: {
identifier: editVideoProperties.id,
service: 'DOCUMENT',
name: username,
size: 100,
updated: Date.now(),
metadata: {
title: title.slice(0, 50),
description: metadescription,
tags: [QTUBE_VIDEO_BASE],
},
created: editVideoProperties?.created,
},
},
]);
const notificationObj: AltertObject = {
msg: 'Video updated',
alertType: 'success',
@@ -319,7 +317,7 @@ export const EditVideo = () => {
const onFramesExtracted = async (imgs) => {
try {
const imagesExtracts = [];
const imagesExtracts: string[] = [];
for (const img of imgs) {
try {
@@ -343,8 +341,11 @@ export const EditVideo = () => {
});
});
if (!compressedFile) continue;
const base64Img = await toBase64(compressedFile);
imagesExtracts.push(base64Img);
const result = await toBase64(compressedFile);
if (result && typeof result === 'string') {
imagesExtracts.push(result);
}
} catch (error) {
console.error(error);
}
@@ -536,7 +537,7 @@ export const EditVideo = () => {
onClick={() => {
publishQDNResource();
}}
disabled={file && imageExtracts.length === 0}
disabled={!!file && imageExtracts?.length === 0}
>
{file && imageExtracts.length === 0 && (
<CircularProgress color="secondary" size={14} />

View File

@@ -20,7 +20,6 @@ import { useSignals } from '@preact/signals-react/runtime';
import Compressor from 'compressorjs';
import React, { useState } from 'react';
import { useDropzone } from 'react-dropzone';
import { useDispatch } from 'react-redux';
import ShortUniqueId from 'short-unique-id';
import { categories, subCategories } from '../../../constants/Categories.ts';
import {
@@ -108,7 +107,6 @@ export const PublishVideo = ({
afterClose,
}: PublishVideoProps) => {
const theme = useTheme();
const dispatch = useDispatch();
const [isOpenMultiplePublish, setIsOpenMultiplePublish] = useState(false);
const setNotification = useSetAtom(setNotificationAtom);
@@ -183,7 +181,7 @@ export const PublishVideo = ({
setFiles((prev) => [...prev, ...formatArray]);
let errorString = null;
let errorString: string | null = null;
rejectedFiles.forEach(({ file, errors }) => {
errors.forEach((error) => {
if (error.code === 'file-too-large') {
@@ -255,7 +253,7 @@ export const PublishVideo = ({
return;
}
const listOfPublishes = [];
const listOfPublishes: any[] = [];
for (let i = 0; i < files.length; i++) {
const publish = files[i];
@@ -587,7 +585,7 @@ export const PublishVideo = ({
const onFramesExtracted = async (imgs, index) => {
try {
const imagesExtracts = [];
const imagesExtracts: string[] = [];
for (const img of imgs) {
try {
@@ -612,7 +610,9 @@ export const PublishVideo = ({
});
if (!compressedFile) continue;
const base64Img = await toBase64(compressedFile);
imagesExtracts.push(base64Img);
if (base64Img && typeof base64Img === 'string') {
imagesExtracts.push(base64Img);
}
} catch (error) {
console.error(error);
}
@@ -1065,7 +1065,7 @@ export const PublishVideo = ({
</Button>
</Box>
{searchResults?.map((vid, index) => {
{searchResults?.map((vid: any, index) => {
return (
<Box
key={vid?.identifier}

View File

@@ -1,10 +1,8 @@
import React, { useEffect } from "react";
import { styled } from "@mui/system";
import { Grid } from "@mui/material";
import { useFetchVideos } from "../hooks/useFetchVideos.tsx";
import { useSelector } from "react-redux";
import { RootState } from "../state/store.ts";
import { signal } from "@preact/signals-react";
import React, { useEffect } from 'react';
import { styled } from '@mui/system';
import { Grid } from '@mui/material';
import { useFetchVideos } from '../hooks/useFetchVideos.tsx';
import { signal } from '@preact/signals-react';
/* eslint-disable react-refresh/only-export-components */
export const totalVideosPublished = signal(0);
@@ -13,20 +11,19 @@ export const videosPerNamePublished = signal(0);
export const StatsData = () => {
const StatsCol = styled(Grid)(({ theme }) => ({
display: "flex",
flexDirection: "column",
width: "100%",
padding: "20px 0px",
display: 'flex',
flexDirection: 'column',
width: '100%',
padding: '20px 0px',
backgroundColor: theme.palette.background.default,
}));
const { getVideosCount } = useFetchVideos();
const showValueIfExists = (value: number) => {
return value > 0 ? "inline" : "none";
return value > 0 ? 'inline' : 'none';
};
const showStats = useSelector((state: RootState) => state.persist.showStats);
const showVideoCount = showValueIfExists(totalVideosPublished.value);
const showPublisherCount = showValueIfExists(totalNamesPublished.value);
const showAverage = showValueIfExists(videosPerNamePublished.value);
@@ -35,24 +32,24 @@ export const StatsData = () => {
}, [getVideosCount]);
return (
<StatsCol sx={{ display: showStats ? "block" : "none" }}>
<StatsCol sx={{ display: 'block' }}>
<div>
Videos:{" "}
<span style={{ fontWeight: "bold", display: showVideoCount }}>
Videos:{' '}
<span style={{ fontWeight: 'bold', display: showVideoCount }}>
{totalVideosPublished.value}
</span>
</div>
<div>
Publishers:{" "}
<span style={{ fontWeight: "bold", display: showPublisherCount }}>
Publishers:{' '}
<span style={{ fontWeight: 'bold', display: showPublisherCount }}>
{totalNamesPublished.value}
</span>
</div>
<div>
Average:{" "}
Average:{' '}
<span
style={{
fontWeight: "bold",
fontWeight: 'bold',
display: showAverage,
}}
>

View File

@@ -3,7 +3,6 @@ import { CommentEditor } from './CommentEditor';
import { Comment } from './Comment';
import { Box, Button, CircularProgress, useTheme } from '@mui/material';
import { styled } from '@mui/system';
import { RootState } from '../../../state/store';
import { useNavigate, useLocation } from 'react-router-dom';
import {
CommentContainer,

View File

@@ -22,7 +22,6 @@ import {
fontSizeMedium,
minPriceSuperLike,
} from '../../../constants/Misc.ts';
import { RootState } from '../../../state/store.ts';
import BoundedNumericTextField from '../../../utils/BoundedNumericTextField.tsx';
import { numberToInt, truncateNumber } from '../../../utils/numberFunctions.ts';
import { objectToBase64 } from '../../../utils/PublishFormatter.ts';

View File

@@ -1,40 +1,40 @@
import EmojiEventsIcon from "@mui/icons-material/EmojiEvents";
import ThumbUpIcon from "@mui/icons-material/ThumbUp";
import { Box } from "@mui/material";
import Avatar from "@mui/material/Avatar";
import Divider from "@mui/material/Divider";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemAvatar from "@mui/material/ListItemAvatar";
import ListItemText from "@mui/material/ListItemText";
import Typography from "@mui/material/Typography";
import * as React from "react";
import { useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import { fontSizeSmall } from "../../../constants/Misc.ts";
import { RootState } from "../../../state/store";
import { formatDate } from "../../../utils/time.ts";
import EmojiEventsIcon from '@mui/icons-material/EmojiEvents';
import ThumbUpIcon from '@mui/icons-material/ThumbUp';
import { Box } from '@mui/material';
import Avatar from '@mui/material/Avatar';
import Divider from '@mui/material/Divider';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemAvatar from '@mui/material/ListItemAvatar';
import ListItemText from '@mui/material/ListItemText';
import Typography from '@mui/material/Typography';
import * as React from 'react';
import { useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { fontSizeSmall } from '../../../constants/Misc.ts';
import { RootState } from '../../../state/store';
import { formatDate } from '../../../utils/time.ts';
import { useAtomValue } from 'jotai';
import { hashMapSuperlikesAtom } from '../../../state/global/superlikes.ts';
const truncateMessage = message => {
return message.length > 40 ? message.slice(0, 40) + "..." : message;
const truncateMessage = (message) => {
return message.length > 40 ? message.slice(0, 40) + '...' : message;
};
export default function ListSuperLikes({ superlikes }) {
const hashMapSuperlikes = useSelector(
(state: RootState) => state.video.hashMapSuperlikes
);
const hashMapSuperlikes = useAtomValue(hashMapSuperlikesAtom);
const navigate = useNavigate();
return (
<List sx={{ width: "100%", maxWidth: 360, bgcolor: "background.paper" }}>
<List sx={{ width: '100%', maxWidth: 360, bgcolor: 'background.paper' }}>
{superlikes?.map((superlike, index) => {
// let hasHash = false
let message = "";
let url = "";
let forName = "";
let message = '';
let url = '';
let forName = '';
// let hash = {}
if (hashMapSuperlikes[superlike?.identifier]) {
message = hashMapSuperlikes[superlike?.identifier]?.comment || "";
message = hashMapSuperlikes[superlike?.identifier]?.comment || '';
if (
hashMapSuperlikes[superlike?.identifier]?.notificationInformation
) {
@@ -56,8 +56,8 @@ export default function ListSuperLikes({ superlikes }) {
<ListItem
alignItems="flex-start"
sx={{
cursor: url ? "pointer" : "default",
minHeight: "130px",
cursor: url ? 'pointer' : 'default',
minHeight: '130px',
}}
onClick={async () => {
if (url) {
@@ -67,13 +67,13 @@ export default function ListSuperLikes({ superlikes }) {
>
<Box
sx={{
width: "100%",
width: '100%',
}}
>
<List sx={{ padding: "0px" }}>
<List sx={{ padding: '0px' }}>
<ListItem
sx={{
padding: "0px",
padding: '0px',
}}
alignItems="flex-start"
>
@@ -87,23 +87,23 @@ export default function ListSuperLikes({ superlikes }) {
primary={
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "5px",
fontSize: "18px",
display: 'flex',
alignItems: 'center',
gap: '5px',
fontSize: '18px',
}}
>
<ThumbUpIcon
style={{
color: "gold",
color: 'gold',
}}
/>
<Typography
sx={{
fontSize: "18px",
fontSize: '18px',
}}
>
{amount ? amount : ""} QORT
{amount ? amount : ''} QORT
</Typography>
</Box>
}
@@ -111,9 +111,9 @@ export default function ListSuperLikes({ superlikes }) {
<>
<Typography
sx={{
display: "inline",
wordBreak: "break-word",
fontSize: "15px",
display: 'inline',
wordBreak: 'break-word',
fontSize: '15px',
}}
component="span"
variant="body2"
@@ -131,11 +131,11 @@ export default function ListSuperLikes({ superlikes }) {
{forName && (
<Box
sx={{
display: "flex",
alignItems: "center",
fontSize: "17px",
gap: "10px",
justifyContent: "flex-end",
display: 'flex',
alignItems: 'center',
fontSize: '17px',
gap: '10px',
justifyContent: 'flex-end',
}}
>
<EmojiEventsIcon />
@@ -149,7 +149,7 @@ export default function ListSuperLikes({ superlikes }) {
</ListItem>
<Box
sx={{
width: "100%",
width: '100%',
}}
>
{superlikes.length === index + 1 ? null : <Divider />}

View File

@@ -1,7 +1,5 @@
import { useDispatch, useSelector } from 'react-redux';
import { toast, ToastContainer, Zoom, Slide } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { RootState } from '../../../state/store';
import { useAtom, useSetAtom } from 'jotai';
import {
alertAtom,
@@ -9,8 +7,6 @@ import {
} from '../../../state/global/notifications';
const Notification = () => {
const dispatch = useDispatch();
const [alertTypes] = useAtom(alertAtom);
const removeNotification = useSetAtom(removeNotificationAtom);

View File

@@ -2,7 +2,6 @@ import localforage from 'localforage';
import { useEffect, useState } from 'react';
import ShortUniqueId from 'short-unique-id';
import { COMMENT_BASE } from '../../../constants/Identifiers.ts';
import { addtoHashMapSuperlikes } from '../../../state/features/videoSlice';
import { objectToBase64 } from '../../../utils/PublishFormatter.ts';
import {
CommentInput,
@@ -15,6 +14,7 @@ import {
AltertObject,
setNotificationAtom,
} from '../../../state/global/notifications.ts';
import { addToHashMapSuperlikesAtom } from '../../../state/global/superlikes.ts';
const uid = new ShortUniqueId({ length: 7 });
@@ -120,6 +120,7 @@ export const CommentEditor = ({
}: CommentEditorProps) => {
const [value, setValue] = useState<string>('');
const setNotification = useSetAtom(setNotificationAtom);
const addSuperlike = useSetAtom(addToHashMapSuperlikesAtom);
const { name, address } = useAuth();
useEffect(() => {
@@ -197,13 +198,11 @@ export const CommentEditor = ({
setNotification(notificationObj);
if (isSuperLike) {
dispatch(
addtoHashMapSuperlikes({
...superObj,
...comment,
message: value,
})
);
addSuperlike({
...superObj,
...comment,
message: value,
});
}
if (idForNotification) {
addItem({

View File

@@ -16,6 +16,8 @@ import {
import { COMMENT_BASE } from '../../../constants/Identifiers.ts';
import { hashWordWithoutPublicSalt } from 'qapp-core';
import { useAtomValue } from 'jotai';
import { hashMapSuperlikesAtom } from '../../../state/global/superlikes.ts';
interface CommentSectionProps {
postId: string;
@@ -61,9 +63,9 @@ export const SuperLikesSection = ({
const [listComments, setListComments] = useState<any[]>([]);
const [isOpen, setIsOpen] = useState<boolean>(false);
const [loadingComments, setLoadingComments] = useState<boolean>(null);
const hashMapSuperlikes = useSelector(
(state: RootState) => state.video.hashMapSuperlikes
);
const hashMapSuperlikes = useAtomValue(hashMapSuperlikesAtom);
const onSubmit = (obj?: any, isEdit?: boolean) => {
if (isEdit) {
setListComments((prev: any[]) => {

View File

@@ -1,28 +1,27 @@
import { useDispatch } from "react-redux";
import { headerIconSize, menuIconSize } from "../../../../constants/Misc.ts";
import { setEditPlaylist } from "../../../../state/features/videoSlice.ts";
import { StyledButton } from "../../../Publish/PublishVideo/PublishVideo-styles.tsx";
import { PublishVideo } from "../../../Publish/PublishVideo/PublishVideo.tsx";
import { headerIconSize, menuIconSize } from '../../../../constants/Misc.ts';
import { StyledButton } from '../../../Publish/PublishVideo/PublishVideo-styles.tsx';
import { PublishVideo } from '../../../Publish/PublishVideo/PublishVideo.tsx';
import {
AvatarContainer,
DropdownContainer,
DropdownText,
NavbarName,
} from "../Navbar-styles.tsx";
import AddBoxIcon from "@mui/icons-material/AddBox";
import { PopMenu, PopMenuRefType } from "../../../common/PopMenu.tsx";
import { useRef } from "react";
import { useMediaQuery } from "@mui/material";
} from '../Navbar-styles.tsx';
import AddBoxIcon from '@mui/icons-material/AddBox';
import { PopMenu, PopMenuRefType } from '../../../common/PopMenu.tsx';
import { useRef } from 'react';
import { useMediaQuery } from '@mui/material';
export interface PublishButtonsProps {
isDisplayed: boolean;
}
import PlaylistAddIcon from "@mui/icons-material/PlaylistAdd";
import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
import { editPlaylistAtom } from '../../../../state/publish/playlist.ts';
import { useSetAtom } from 'jotai';
export const PublishMenu = ({ isDisplayed }: PublishButtonsProps) => {
const dispatch = useDispatch();
const popMenuRef = useRef<PopMenuRefType>(null);
const setEditPlaylist = useSetAtom(editPlaylistAtom);
const isScreenSmall = !useMediaQuery(`(min-width:600px)`);
return (
<>
@@ -31,11 +30,11 @@ export const PublishMenu = ({ isDisplayed }: PublishButtonsProps) => {
MenuHeader={
<>
{!isScreenSmall && (
<NavbarName sx={{ marginRight: "5px" }}>Publish</NavbarName>
<NavbarName sx={{ marginRight: '5px' }}>Publish</NavbarName>
)}
<AddBoxIcon
sx={{
color: "DarkGreen",
color: 'DarkGreen',
width: headerIconSize,
height: headerIconSize,
}}
@@ -53,14 +52,14 @@ export const PublishMenu = ({ isDisplayed }: PublishButtonsProps) => {
startIcon={
<PlaylistAddIcon
sx={{
color: "#00BFFF",
color: '#00BFFF',
width: menuIconSize,
height: menuIconSize,
}}
/>
}
onClick={() => {
dispatch(setEditPlaylist({ mode: "new" }));
setEditPlaylist({ mode: 'new' });
}}
>
Playlist

View File

@@ -1,17 +1,11 @@
import { useNavigate } from "react-router-dom";
import Logo from "../../../../assets/img/logo.webp";
import {
addFilteredVideos,
setFilterValue,
setIsFiltering,
} from "../../../../state/features/videoSlice.ts";
import { LogoContainer, ThemeSelectRow } from "../Navbar-styles.tsx";
import { useDispatch } from "react-redux";
import { useMediaQuery } from "@mui/material";
import { useNavigate } from 'react-router-dom';
import Logo from '../../../../assets/img/logo.webp';
import { LogoContainer, ThemeSelectRow } from '../Navbar-styles.tsx';
import { useMediaQuery } from '@mui/material';
export const QtubeLogo = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const isScreenSmall = !useMediaQuery(`(min-width:600px)`);
@@ -19,19 +13,16 @@ export const QtubeLogo = () => {
<ThemeSelectRow>
<LogoContainer
onClick={() => {
navigate("/");
dispatch(setIsFiltering(false));
dispatch(setFilterValue(""));
dispatch(addFilteredVideos([]));
navigate('/');
}}
>
<img
src={Logo}
style={{
width: isScreenSmall ? "50px" : "auto",
height: "45px",
padding: "2px",
marginTop: "5px",
width: isScreenSmall ? '50px' : 'auto',
height: '45px',
padding: '2px',
marginTop: '5px',
}}
/>
</LogoContainer>

View File

@@ -1,6 +1,5 @@
import { Popover, useMediaQuery, useTheme, Avatar } from '@mui/material';
import { AccountCircleSVG } from '../../../../assets/svgs/AccountCircleSVG.tsx';
import { headerIconSize, menuIconSize } from '../../../../constants/Misc.ts';
import { useMediaQuery, useTheme, Avatar } from '@mui/material';
import { menuIconSize } from '../../../../constants/Misc.ts';
import { BlockedNamesModal } from '../../../common/BlockedNamesModal/BlockedNamesModal.tsx';
import {
AvatarContainer,
@@ -8,11 +7,8 @@ import {
DropdownText,
NavbarName,
} from '../Navbar-styles.tsx';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { useCallback, useRef, useState } from 'react';
import PersonOffIcon from '@mui/icons-material/PersonOff';
import { RootState } from '../../../../state/store';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { PopMenu, PopMenuRefType } from '../../../common/PopMenu.tsx';
import { UserDropDown } from '../../../UserDropDown.tsx';
@@ -38,7 +34,6 @@ export const UserMenu = ({
useState<boolean>(false);
const popMenuRef = useRef<PopMenuRefType>(null);
const navigate = useNavigate();
const dispatch = useDispatch();
const handleMyChannelLink = useCallback(
(switchToName: string) => {

View File

@@ -1,4 +1,4 @@
export const useTestIdentifiers = false;
export const useTestIdentifiers = true;
export const QTUBE_VIDEO_BASE = useTestIdentifiers
? 'MYTEST2_vid_'
: 'qtube_vid_';

View File

@@ -1,114 +1,107 @@
import React, { useCallback } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import React, { useCallback } from 'react';
import { queueSuperlikes } from '../wrappers/GlobalWrapper';
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
import {
addtoHashMapSuperlikes
} from '../state/features/videoSlice'
import { RootState } from '../state/store'
import { queueSuperlikes } from '../wrappers/GlobalWrapper'
addToHashMapSuperlikesAtom,
hashMapSuperlikesAtom,
} from '../state/global/superlikes';
export const useFetchSuperLikes = () => {
const dispatch = useDispatch()
const hashMapSuperlikes = useSelector(
(state: RootState) => state.video.hashMapSuperlikes
)
const checkAndUpdateSuperlike= React.useCallback(
const addSuperlike = useSetAtom(addToHashMapSuperlikesAtom);
const hashMapSuperlikes = useAtomValue(hashMapSuperlikesAtom);
const checkAndUpdateSuperlike = React.useCallback(
(superlike: any) => {
const existingVideo = hashMapSuperlikes[superlike.identifier]
const existingVideo = hashMapSuperlikes[superlike.identifier];
if (!existingVideo) {
return true
return true;
} else if (
superlike?.updated &&
existingVideo?.updated &&
(!existingVideo?.updated || superlike?.updated) > existingVideo?.updated
) {
return true
return true;
} else {
return false
return false;
}
},
[hashMapSuperlikes]
)
);
const fetchSuperlike = async (data: any) => {
const getsuper = async () => {
const { user, videoId, content } = data
const { user, videoId, content } = data;
let obj: any = {
...content,
isValid: false
}
if (!user || !videoId) return obj
isValid: false,
};
if (!user || !videoId) return obj;
try {
const responseData = await qortalRequest({
action: 'FETCH_QDN_RESOURCE',
name: user,
service: content?.service || 'BLOG_COMMENT',
identifier: videoId
})
obj = {
...content,
...responseData,
isValid: true
}
return obj
identifier: videoId,
});
obj = {
...content,
...responseData,
isValid: true,
};
return obj;
} catch (error: any) {
throw new Error(error?.message || 'error')
throw new Error(error?.message || 'error');
}
}
const res = await getsuper()
return res
}
};
const res = await getsuper();
return res;
};
const getSuperLikes = async (user: string, videoId: string, content: any, retries: number = 0) => {
const getSuperLikes = async (
user: string,
videoId: string,
content: any,
retries: number = 0
) => {
try {
const res = await fetchSuperlike({
user,
videoId,
content
})
dispatch(addtoHashMapSuperlikes(res))
content,
});
addSuperlike(res);
} catch (error) {
retries= retries + 1
if (retries < 2) { // 3 is the maximum number of retries here, you can adjust it to your needs
queueSuperlikes.push(() => getSuperLikes(user, videoId, content, retries + 1));
retries = retries + 1;
if (retries < 2) {
// 3 is the maximum number of retries here, you can adjust it to your needs
queueSuperlikes.push(() =>
getSuperLikes(user, videoId, content, retries + 1)
);
} else {
console.error('Failed to get video after 3 attempts', error);
}
}
}
};
const addSuperlikeRawDataGetToList = useCallback(({name, identifier, content})=> {
const addSuperlikeRawDataGetToList = useCallback(
({ name, identifier, content }) => {
if (name && identifier) {
const res = checkAndUpdateSuperlike(content)
const res = checkAndUpdateSuperlike(content);
if (res) {
queueSuperlikes.push(() => getSuperLikes(name, identifier, content));
}
}
}, [checkAndUpdateSuperlike])
},
[checkAndUpdateSuperlike]
);
return {
addSuperlikeRawDataGetToList
}
}
addSuperlikeRawDataGetToList,
};
};

View File

@@ -1,5 +1,3 @@
import { useSelector } from 'react-redux';
import { RootState } from '../../../state/store.ts';
import { useVideoContentState } from '../VideoContent/VideoContent-State.ts';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
@@ -43,92 +41,85 @@ export const usePlaylistContentState = () => {
const navigate = useNavigate();
const [playlistData, setPlaylistData] = useState<any>(null);
const hashMapVideos = useSelector(
(state: RootState) => state.video.hashMapVideos
);
const checkforPlaylist = useCallback(async (name, id) => {
try {
setIsLoadingPlaylist(true);
const checkforPlaylist = useCallback(
async (name, id) => {
try {
setIsLoadingPlaylist(true);
if (!name || !id) return;
if (!name || !id) return;
const url = `/arbitrary/resources/search?mode=ALL&service=PLAYLIST&identifier=${id}&limit=1&includemetadata=true&reverse=true&excludeblocked=true&name=${name}&exactmatchnames=true&offset=0`;
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
const responseDataSearch = await response.json();
const url = `/arbitrary/resources/search?mode=ALL&service=PLAYLIST&identifier=${id}&limit=1&includemetadata=true&reverse=true&excludeblocked=true&name=${name}&exactmatchnames=true&offset=0`;
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
if (responseDataSearch?.length > 0) {
let resourceData = responseDataSearch[0];
resourceData = {
title: resourceData?.metadata?.title,
category: resourceData?.metadata?.category,
categoryName: resourceData?.metadata?.categoryName,
tags: resourceData?.metadata?.tags || [],
description: resourceData?.metadata?.description,
created: resourceData?.created,
updated: resourceData?.updated,
name: resourceData.name,
videoImage: '',
identifier: resourceData.identifier,
service: resourceData.service,
};
const responseData = await qortalRequest({
action: 'FETCH_QDN_RESOURCE',
name: resourceData.name,
service: resourceData.service,
identifier: resourceData.identifier,
});
const responseDataSearch = await response.json();
if (responseDataSearch?.length > 0) {
let resourceData = responseDataSearch[0];
resourceData = {
title: resourceData?.metadata?.title,
category: resourceData?.metadata?.category,
categoryName: resourceData?.metadata?.categoryName,
tags: resourceData?.metadata?.tags || [],
description: resourceData?.metadata?.description,
created: resourceData?.created,
updated: resourceData?.updated,
name: resourceData.name,
videoImage: '',
identifier: resourceData.identifier,
service: resourceData.service,
if (responseData && !responseData.error) {
const combinedData = {
...resourceData,
...responseData,
};
const videos = [];
if (combinedData?.videos) {
for (const vid of combinedData.videos) {
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&identifier=${vid.identifier}&limit=1&includemetadata=true&reverse=true&name=${vid.name}&exactmatchnames=true&offset=0`;
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
const responseDataSearchVid = await response.json();
const responseData = await qortalRequest({
action: 'FETCH_QDN_RESOURCE',
name: resourceData.name,
service: resourceData.service,
identifier: resourceData.identifier,
});
if (responseData && !responseData.error) {
const combinedData = {
...resourceData,
...responseData,
};
const videos = [];
if (combinedData?.videos) {
for (const vid of combinedData.videos) {
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&identifier=${vid.identifier}&limit=1&includemetadata=true&reverse=true&name=${vid.name}&exactmatchnames=true&offset=0`;
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
const responseDataSearchVid = await response.json();
if (responseDataSearchVid?.length > 0) {
const resourceData2 = responseDataSearchVid[0];
videos.push(resourceData2);
}
if (responseDataSearchVid?.length > 0) {
const resourceData2 = responseDataSearchVid[0];
videos.push(resourceData2);
}
}
combinedData.videos = videos;
setPlaylistData(combinedData);
if (combinedData?.videos?.length > 0) {
const vid = combinedData?.videos[0];
setVideoMetadataResource({
name: vid?.name,
identifier: vid?.identifier,
service: 'DOCUMENT',
});
}
}
combinedData.videos = videos;
setPlaylistData(combinedData);
if (combinedData?.videos?.length > 0) {
const vid = combinedData?.videos[0];
setVideoMetadataResource({
name: vid?.name,
identifier: vid?.identifier,
service: 'DOCUMENT',
});
}
}
} catch (error) {
console.error(error);
} finally {
setIsLoadingPlaylist(false);
}
},
[hashMapVideos]
);
} catch (error) {
console.error(error);
} finally {
setIsLoadingPlaylist(false);
}
}, []);
useEffect(() => {
if (channelName && id) {

View File

@@ -1,18 +1,17 @@
import DownloadIcon from "@mui/icons-material/Download";
import { Box, SxProps, Theme } from "@mui/material";
import { useMemo } from "react";
import { useDispatch } from "react-redux";
import { CopyLinkButton } from "../../../components/common/ContentButtons/CopyLinkButton.tsx";
import { IndexButton } from "../../../components/common/ContentButtons/IndexButton.tsx";
import { LikeAndDislike } from "../../../components/common/ContentButtons/LikeAndDislike.tsx";
import { SuperLike } from "../../../components/common/ContentButtons/SuperLike.tsx";
import FileElement from "../../../components/common/FileElement.tsx";
import { titleFormatterOnSave } from "../../../constants/Misc.ts";
import { ChannelActions } from "./ChannelActions.tsx";
import DownloadIcon from '@mui/icons-material/Download';
import { Box, SxProps, Theme } from '@mui/material';
import { useMemo } from 'react';
import { CopyLinkButton } from '../../../components/common/ContentButtons/CopyLinkButton.tsx';
import { IndexButton } from '../../../components/common/ContentButtons/IndexButton.tsx';
import { LikeAndDislike } from '../../../components/common/ContentButtons/LikeAndDislike.tsx';
import { SuperLike } from '../../../components/common/ContentButtons/SuperLike.tsx';
import FileElement from '../../../components/common/FileElement.tsx';
import { titleFormatterOnSave } from '../../../constants/Misc.ts';
import { ChannelActions } from './ChannelActions.tsx';
import {
FileAttachmentContainer,
FileAttachmentFont,
} from "./VideoContent-styles.tsx";
} from './VideoContent-styles.tsx';
export interface VideoActionsBarProps {
channelName: string;
@@ -48,55 +47,53 @@ export const VideoActionsBar = ({
return superLikeList?.length ?? 0;
}, [superLikeList]);
const dispatch = useDispatch();
const saveAsFilename = useMemo(() => {
// nb. we prefer to construct the local filename to use for
// saving, from the video "title" when possible
if (videoData?.title) {
// figure out filename extension
let ext = ".mp4";
let ext = '.mp4';
if (videoData?.filename) {
// nb. this regex copied from https://stackoverflow.com/a/680982
const re = /(?:\.([^.]+))?$/;
const match = re.exec(videoData.filename);
if (match[1]) {
ext = "." + match[1];
ext = '.' + match[1];
}
}
return (videoData.title + ext).replace(titleFormatterOnSave, "");
return (videoData.title + ext).replace(titleFormatterOnSave, '');
}
// otherwise use QDN filename if applicable
if (videoData?.filename) {
return videoData.filename.replace(titleFormatterOnSave, "");
return videoData.filename.replace(titleFormatterOnSave, '');
}
// TODO: this was the previous value, leaving here as the
// fallback for now even though it probably is not needed..?
return videoData?.filename || videoData?.title?.slice(0, 20) + ".mp4";
return videoData?.filename || videoData?.title?.slice(0, 20) + '.mp4';
}, [videoData]);
return (
<Box
sx={{
marginTop: "15px",
display: "flex",
flexDirection: "row",
alignItems: "center",
flexWrap: "wrap",
gap: "20px",
marginTop: '15px',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
flexWrap: 'wrap',
gap: '20px',
...sx,
}}
>
<ChannelActions channelName={channelName} />
<Box
sx={{
display: "flex",
flexDirection: "row",
height: "100%",
alignItems: "center",
display: 'flex',
flexDirection: 'row',
height: '100%',
alignItems: 'center',
}}
>
{videoData && (
@@ -108,14 +105,14 @@ export const VideoActionsBar = ({
name={videoData?.user}
service={videoData?.service}
identifier={videoData?.id}
onSuccess={val => {
setSuperLikeList(prev => [val, ...prev]);
onSuccess={(val) => {
setSuperLikeList((prev) => [val, ...prev]);
}}
/>
</>
)}
</Box>
<Box sx={{ display: "flex", gap: "5px" }}>
<Box sx={{ display: 'flex', gap: '5px' }}>
<IndexButton channelName={channelName} />
<CopyLinkButton
link={`qortal://APP/Q-Tube/video/${encodeURIComponent(videoData?.user)}/${encodeURIComponent(videoData?.id)}`}
@@ -123,7 +120,7 @@ export const VideoActionsBar = ({
/>
</Box>
{videoData && (
<FileAttachmentContainer sx={{ width: "100%", maxWidth: "340px" }}>
<FileAttachmentContainer sx={{ width: '100%', maxWidth: '340px' }}>
<FileAttachmentFont>Save Video</FileAttachmentFont>
<FileElement
fileInfo={{
@@ -133,9 +130,9 @@ export const VideoActionsBar = ({
}}
title={videoData?.filename || videoData?.title?.slice(0, 20)}
customStyles={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
}}
>
<DownloadIcon />

View File

@@ -1,12 +1,19 @@
import { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { blockUser, setEditVideo } from '../../../state/features/videoSlice.ts';
import { blockUser } from '../../../state/features/videoSlice.ts';
import { VideoCardContainer } from './VideoList-styles.tsx';
import { QortalSearchParams, ResourceListDisplay, useAuth } from 'qapp-core';
import {
QortalSearchParams,
ResourceListDisplay,
useAuth,
useGlobal,
} from 'qapp-core';
import { VideoListItem } from './VideoListItem.tsx';
import { VideoLoaderItem } from './VideoLoaderItem.tsx';
import { useSetAtom } from 'jotai';
import { editVideoAtom } from '../../../state/publish/video.ts';
interface VideoListProps {
searchParameters: QortalSearchParams;
@@ -14,8 +21,9 @@ interface VideoListProps {
}
export const VideoList = ({ searchParameters, listName }: VideoListProps) => {
const { name: username } = useAuth();
const { lists } = useGlobal();
const dispatch = useDispatch();
const setEditVideo = useSetAtom(editVideoAtom);
const blockUserFunc = async (user: string) => {
if (user === 'Q-Tube') return;
@@ -28,7 +36,8 @@ export const VideoList = ({ searchParameters, listName }: VideoListProps) => {
});
if (response === true) {
dispatch(blockUser(user));
lists.deleteList('AllVideos');
// dispatch(blockUser(user));
}
} catch (error) {
console.error(error);

View File

@@ -10,7 +10,6 @@ import {
VideoCardTitle,
VideoUploadDate,
} from './VideoList-styles';
import { setEditPlaylist } from '../../../state/features/videoSlice';
import ResponsiveImage from '../../../components/ResponsiveImage';
import { PlaylistSVG } from '../../../assets/svgs/PlaylistSVG';
import { formatTime } from '../../../utils/numberFunctions';
@@ -21,9 +20,10 @@ import { useNavigate } from 'react-router-dom';
import DeleteIcon from '@mui/icons-material/Delete';
import BlockIcon from '@mui/icons-material/Block';
import EditIcon from '@mui/icons-material/Edit';
import { useDispatch } from 'react-redux';
import { useGlobal } from 'qapp-core';
import { useState } from 'react';
import { editPlaylistAtom } from '../../../state/publish/playlist';
import { useSetAtom } from 'jotai';
export const VideoListItem = ({
qortalMetadata,
@@ -33,12 +33,12 @@ export const VideoListItem = ({
setEditVideo,
}) => {
const navigate = useNavigate();
const dispatch = useDispatch();
const [showIcons, setShowIcons] = useState(null);
const theme = useTheme();
const { lists } = useGlobal();
const { deleteResource } = lists;
const setEditPlaylist = useSetAtom(editPlaylistAtom);
const isPlaylist = qortalMetadata?.service === 'PLAYLIST';
@@ -73,7 +73,7 @@ export const VideoListItem = ({
identifier: qortalMetadata.identifier,
service: qortalMetadata.service,
};
dispatch(setEditPlaylist({ ...resourceData, ...video }));
setEditPlaylist({ ...resourceData, ...video });
}}
/>
</BlockIconContainer>
@@ -187,7 +187,7 @@ export const VideoListItem = ({
id: qortalMetadata.identifier,
};
dispatch(setEditVideo({ ...resourceData, ...video }));
setEditVideo({ ...resourceData, ...video });
}}
/>
</BlockIconContainer>

View File

@@ -1,38 +1,12 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { SubscriptionData } from "../../components/common/ContentButtons/SubscribeButton.tsx";
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { SubscriptionData } from '../../components/common/ContentButtons/SubscribeButton.tsx';
interface GlobalState {
videos: Video[];
filteredVideos: Video[];
hashMapVideos: Record<string, Video>;
hashMapSuperlikes: Record<string, any>;
countNewVideos: number;
isFiltering: boolean;
filterValue: string;
filterSearch: string;
filterName: string;
selectedCategoryVideos: any;
selectedSubCategoryVideos: any;
editVideoProperties: any;
editPlaylistProperties: any;
filteredSubscriptionList: SubscriptionData[];
}
const initialState: GlobalState = {
videos: [],
filteredVideos: [],
hashMapVideos: {},
hashMapSuperlikes: {},
countNewVideos: 0,
isFiltering: false,
filterValue: "",
filterSearch: "",
filterName: "",
selectedCategoryVideos: null,
selectedSubCategoryVideos: null,
editVideoProperties: null,
editPlaylistProperties: null,
filteredSubscriptionList: [],
};
export interface Video {
@@ -52,164 +26,17 @@ export interface Video {
}
export const videoSlice = createSlice({
name: "video",
name: 'video',
initialState,
reducers: {
setEditVideo: (state, action) => {
state.editVideoProperties = action.payload;
},
setEditPlaylist: (state, action) => {
state.editPlaylistProperties = action.payload;
},
changefilterSearch: (state, action) => {
state.filterSearch = action.payload;
},
changefilterName: (state, action) => {
state.filterName = action.payload;
},
changeSelectedCategoryVideos: (state, action) => {
state.selectedCategoryVideos = action.payload;
},
changeSelectedSubCategoryVideos: (state, action) => {
state.selectedSubCategoryVideos = action.payload;
},
setCountNewVideos: (state, action) => {
state.countNewVideos = action.payload;
},
addVideos: (state, action) => {
state.videos = action.payload;
},
addFilteredVideos: (state, action) => {
state.filteredVideos = action.payload;
},
removeVideo: (state, action) => {
const idToDelete = action.payload;
state.videos = state.videos.filter(item => item.id !== idToDelete);
state.filteredVideos = state.filteredVideos.filter(
item => item.id !== idToDelete
);
},
addVideoToBeginning: (state, action) => {
state.videos.unshift(action.payload);
},
clearVideoList: state => {
state.videos = [];
},
updateVideo: (state, action) => {
const { id } = action.payload;
const index = state.videos.findIndex(video => video.id === id);
if (index !== -1) {
state.videos[index] = { ...action.payload };
}
const index2 = state.filteredVideos.findIndex(video => video.id === id);
if (index2 !== -1) {
state.filteredVideos[index2] = { ...action.payload };
}
},
addToHashMap: (state, action) => {
const video = action.payload;
state.hashMapVideos[video.id + "-" + video.user] = video;
},
addtoHashMapSuperlikes: (state, action) => {
const superlike = action.payload;
state.hashMapSuperlikes[superlike.identifier] = superlike;
},
updateInHashMap: (state, action) => {
const { id, user } = action.payload;
const video = action.payload;
state.hashMapVideos[id + "-" + user] = { ...video };
},
removeFromHashMap: (state, action) => {
const idToDelete = action.payload;
delete state.hashMapVideos[idToDelete];
},
addArrayToHashMap: (state, action) => {
const videos = action.payload;
videos.forEach((video: Video) => {
state.hashMapVideos[video.id + "-" + video.user] = video;
});
},
upsertVideos: (state, action) => {
action.payload.forEach((video: Video) => {
const index = state.videos.findIndex(p => p.id === video.id);
if (index !== -1) {
state.videos[index] = video;
} else {
state.videos.push(video);
}
});
},
upsertFilteredVideos: (state, action) => {
action.payload.forEach((video: Video) => {
const index = state.filteredVideos.findIndex(p => p.id === video.id);
if (index !== -1) {
state.filteredVideos[index] = video;
} else {
state.filteredVideos.push(video);
}
});
},
upsertVideosBeginning: (state, action) => {
action.payload.reverse().forEach((video: Video) => {
const index = state.videos.findIndex(p => p.id === video.id);
if (index !== -1) {
state.videos[index] = video;
} else {
state.videos.unshift(video);
}
});
},
setIsFiltering: (state, action) => {
state.isFiltering = action.payload;
},
setFilterValue: (state, action) => {
state.filterValue = action.payload;
},
blockUser: (state, action) => {
const username = action.payload;
state.videos = state.videos.filter(item => item.user !== username);
state.videos = state.videos.filter((item) => item.user !== username);
},
setFilteredSubscriptions: (
state,
action: PayloadAction<SubscriptionData[]>
) => {
state.filteredSubscriptionList = action.payload;
},
resetVideoState: () => initialState,
},
});
export const {
setCountNewVideos,
addVideos,
addFilteredVideos,
removeVideo,
addVideoToBeginning,
updateVideo,
addToHashMap,
updateInHashMap,
removeFromHashMap,
addArrayToHashMap,
upsertVideos,
upsertFilteredVideos,
upsertVideosBeginning,
setIsFiltering,
setFilterValue,
clearVideoList,
changefilterSearch,
changefilterName,
changeSelectedCategoryVideos,
changeSelectedSubCategoryVideos,
blockUser,
setEditVideo,
setEditPlaylist,
addtoHashMapSuperlikes,
setFilteredSubscriptions,
resetVideoState
} = videoSlice.actions;
export const { blockUser } = videoSlice.actions;
export default videoSlice.reducer;

View File

@@ -2,4 +2,24 @@ import { atom } from 'jotai';
type SuperLikes = any[];
interface Superlike {
identifier: string;
[key: string]: any; // You can define more fields as needed
}
// Atom to hold the hashMapSuperlikes
export const hashMapSuperlikesAtom = atom<Record<string, Superlike>>({});
// Write-only atom to add a superlike
export const addToHashMapSuperlikesAtom = atom(
null,
(get, set, superlike: Superlike) => {
const prev = get(hashMapSuperlikesAtom);
set(hashMapSuperlikesAtom, {
...prev,
[superlike.identifier]: superlike,
});
}
);
export const superlikesAtom = atom<SuperLikes>([]);

View File

@@ -0,0 +1,3 @@
import { atom } from 'jotai';
export const editPlaylistAtom = atom<any>(null);

View File

@@ -0,0 +1,3 @@
import { atom } from 'jotai';
export const editVideoAtom = atom<any>(null);

View File

@@ -5,7 +5,6 @@ import React, {
useRef,
useMemo,
} from 'react';
import { useDispatch } from 'react-redux';
import {
extractSigValue,
getPaymentInfo,
@@ -35,7 +34,6 @@ export const queue = new RequestQueue();
export const queueSuperlikes = new RequestQueue();
const GlobalWrapper: React.FC<Props> = ({ children }) => {
const dispatch = useDispatch();
const setSuperlikesAll = useSetAtom(superlikesAtom);
const { addSuperlikeRawDataGetToList } = useFetchSuperLikes();
const interval = useRef<any>(null);

View File

@@ -15,11 +15,10 @@
"jsx": "react-jsx",
/* Linting */
"strict": false,
"strict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noFallthroughCasesInSwitch": true,
"strictNullChecks": false,
"types": ["node", "qapp-core/global"]
},
"include": ["src"],