From 9d483cf65dcf86af7ead33f7d5d0b6524132f8d2 Mon Sep 17 00:00:00 2001 From: Qortal Seth Date: Mon, 11 Mar 2024 09:22:17 -0600 Subject: [PATCH] Massive Category overhaul. Many new categories added, All categories have an "Other" field that is sorted last Media category removed, its subcategories are now main categories. WARNING: This is a breaking change that will disrupt categories for some currently published files Categories associated with Copyright Infringement have been removed. Categories are sorted by name, "Other" is always last The FileList component now only stores the file list. The rest of it was moved to Home.tsx Category } - value={selectedCategoryVideos?.id || ""} - onChange={handleOptionCategoryChangeVideos} - > - {categories.map((option) => ( - - {option.name} - - ))} - - - - {selectedCategoryVideos && ( - <> - - - - {selectedCategoryVideos && - subCategories[selectedCategoryVideos?.id] && ( - - - Select a Sub-Category - - - - )} - {selectedSubCategoryVideos && - subCategories2[selectedSubCategoryVideos?.id] && ( - - - Select a Sub-sub-Category - - - - )} - {selectedSubCategoryVideos2 && - subCategories3[selectedSubCategoryVideos2?.id] && ( - - - Select a Sub-3x-subCategory - - - - )} - - - )} - - - )} - - {files?.length > 0 && ( + {files?.length > 0 && ( <> - { - const value = e.target.value; - const formattedValue = value.replace(titleFormatter, ""); - setTitle(formattedValue); - }} - inputProps={{ maxLength: 180 }} - required - /> - - Description of share - - { - setDescription(value); - }} - /> + + )} + + {files?.length > 0 && ( + <> + { + const value = e.target.value; + const formattedValue = value.replace(titleFormatter, ""); + setTitle(formattedValue); + }} + inputProps={{ maxLength: 180 }} + required + /> + + Description of share + + { + setDescription(value); + }} + /> + + )} @@ -730,22 +474,22 @@ export const EditFile = () => { {isOpenMultiplePublish && ( { + onError={messageNotification => { setIsOpenMultiplePublish(false); - setPublishes(null) - if(messageNotification){ + setPublishes(null); + if (messageNotification) { dispatch( - setNotification({ - msg: messageNotification, - alertType: 'error' - }) - ) + setNotification({ + msg: messageNotification, + alertType: "error", + }) + ); } }} onSubmit={() => { setIsOpenMultiplePublish(false); const clonedCopy = structuredClone(videoPropertiesToSetToRedux); - dispatch(updateVideo(clonedCopy)); + dispatch(updateFile(clonedCopy)); dispatch(updateInHashMap(clonedCopy)); dispatch( setNotification({ diff --git a/src/components/EditPlaylist/EditPlaylist.tsx b/src/components/EditPlaylist/EditPlaylist.tsx index 10e99a5..2c8c8ae 100644 --- a/src/components/EditPlaylist/EditPlaylist.tsx +++ b/src/components/EditPlaylist/EditPlaylist.tsx @@ -34,21 +34,27 @@ import { setNotification } from "../../state/features/notificationsSlice"; import { objectToBase64, uint8ArrayToBase64 } from "../../utils/toBase64"; import { RootState } from "../../state/store"; import { - upsertVideosBeginning, + upsertFilesBeginning, addToHashMap, - upsertVideos, - setEditVideo, - updateVideo, + upsertFiles, + setEditFile, + updateFile, updateInHashMap, setEditPlaylist, -} from "../../state/features/videoSlice"; +} from "../../state/features/fileSlice.ts"; import ImageUploader from "../common/ImageUploader"; -import { QSHARE_PLAYLIST_BASE, QSHARE_FILE_BASE } from "../../constants/Identifiers.ts"; +import { + QSHARE_PLAYLIST_BASE, + QSHARE_FILE_BASE, +} from "../../constants/Identifiers.ts"; import { Playlists } from "../Playlists/Playlists"; import { PlaylistListEdit } from "../PlaylistListEdit/PlaylistListEdit"; import { TextEditor } from "../common/TextEditor/TextEditor"; import { extractTextFromHTML } from "../common/TextEditor/utils"; -import {categories, subCategories} from "../../constants/Categories.ts"; +import { + firstCategories, + secondCategories, +} from "../../constants/Categories/1stCategories.ts"; const uid = new ShortUniqueId(); const shortuid = new ShortUniqueId({ length: 5 }); @@ -76,7 +82,7 @@ export const EditPlaylist = () => { (state: RootState) => state.auth?.user?.address ); const editVideoProperties = useSelector( - (state: RootState) => state.video.editPlaylistProperties + (state: RootState) => state.file.editPlaylistProperties ); const [playlistData, setPlaylistData] = useState(null); const [title, setTitle] = useState(""); @@ -88,17 +94,17 @@ export const EditPlaylist = () => { const [selectedSubCategoryVideos, setSelectedSubCategoryVideos] = useState(null); - const isNew = useMemo(()=> { - return editVideoProperties?.mode === 'new' - }, [editVideoProperties]) + const isNew = useMemo(() => { + return editVideoProperties?.mode === "new"; + }, [editVideoProperties]); - useEffect(()=> { - if(isNew){ + useEffect(() => { + if (isNew) { setPlaylistData({ - videos: [] - }) + videos: [], + }); } - }, [isNew]) + }, [isNew]); // useEffect(() => { // if (editVideoProperties) { @@ -146,7 +152,7 @@ export const EditPlaylist = () => { // } // }, [editVideoProperties]); - const checkforPlaylist = React.useCallback(async (videoList) => { + const checkforPlaylist = React.useCallback(async videoList => { try { const combinedData: any = {}; const videos = []; @@ -175,21 +181,19 @@ export const EditPlaylist = () => { useEffect(() => { if (editVideoProperties) { setTitle(editVideoProperties?.title || ""); - - if(editVideoProperties?.htmlDescription){ - setDescription(editVideoProperties?.htmlDescription); - } else if(editVideoProperties?.description) { - const paragraph = `

${editVideoProperties?.description}

` + if (editVideoProperties?.htmlDescription) { + setDescription(editVideoProperties?.htmlDescription); + } else if (editVideoProperties?.description) { + const paragraph = `

${editVideoProperties?.description}

`; setDescription(paragraph); - } setCoverImage(editVideoProperties?.image || ""); setVideos(editVideoProperties?.videos || []); if (editVideoProperties?.category) { - const selectedOption = categories.find( - (option) => option.id === +editVideoProperties.category + const selectedOption = firstCategories.find( + option => option.id === +editVideoProperties.category ); setSelectedCategoryVideos(selectedOption || null); } @@ -197,11 +201,11 @@ export const EditPlaylist = () => { if ( editVideoProperties?.category && editVideoProperties?.subcategory && - subCategories[+editVideoProperties?.category] + secondCategories[+editVideoProperties?.category] ) { - const selectedOption = subCategories[ + const selectedOption = secondCategories[ +editVideoProperties?.category - ]?.find((option) => option.id === +editVideoProperties.subcategory); + ]?.find(option => option.id === +editVideoProperties.subcategory); setSelectedSubCategoryVideos(selectedOption || null); } @@ -212,24 +216,22 @@ export const EditPlaylist = () => { }, [editVideoProperties]); const onClose = () => { - setTitle("") - setDescription("") - setVideos([]) - setPlaylistData(null) - setSelectedCategoryVideos(null) - setSelectedSubCategoryVideos(null) - setCoverImage("") + setTitle(""); + setDescription(""); + setVideos([]); + setPlaylistData(null); + setSelectedCategoryVideos(null); + setSelectedSubCategoryVideos(null); + setCoverImage(""); dispatch(setEditPlaylist(null)); - }; async function publishQDNResource() { try { - - 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') - if(!selectedCategoryVideos) throw new Error('Please select a category') + 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"); + if (!selectedCategoryVideos) throw new Error("Please select a category"); if (!editVideoProperties) return; if (!userAddress) throw new Error("Unable to locate user address"); @@ -259,7 +261,7 @@ export const EditPlaylist = () => { const category = selectedCategoryVideos.id; const subcategory = selectedSubCategoryVideos?.id || ""; - const videoStructured = playlistData.videos.map((item) => { + const videoStructured = playlistData.videos.map(item => { const descriptionVid = item?.metadata?.description; if (!descriptionVid) throw new Error("cannot find video code"); @@ -287,13 +289,12 @@ export const EditPlaylist = () => { }); const id = uid(); - let commentsId = editVideoProperties?.id - - if(isNew){ - commentsId = `${QSHARE_PLAYLIST_BASE}_cm_${id}` - } - const stringDescription = extractTextFromHTML(description) + let commentsId = editVideoProperties?.id; + if (isNew) { + commentsId = `${QSHARE_PLAYLIST_BASE}_cm_${id}`; + } + const stringDescription = extractTextFromHTML(description); const playlistObject: any = { title, @@ -304,10 +305,10 @@ export const EditPlaylist = () => { videos: videoStructured, commentsId: commentsId, category, - subcategory + subcategory, }; - const codes = videoStructured.map((item) => `c:${item.code};`).join(""); + const codes = videoStructured.map(item => `c:${item.code};`).join(""); let metadescription = `**category:${category};subcategory:${subcategory};${codes}**` + stringDescription.slice(0, 120); @@ -315,14 +316,14 @@ export const EditPlaylist = () => { const crowdfundObjectToBase64 = await objectToBase64(playlistObject); // Description is obtained from raw data - let identifier = editVideoProperties?.id + let identifier = editVideoProperties?.id; const sanitizeTitle = title .replace(/[^a-zA-Z0-9\s-]/g, "") .replace(/\s+/g, "-") .replace(/-+/g, "-") .trim() .toLowerCase(); - if(isNew){ + if (isNew) { identifier = `${QSHARE_PLAYLIST_BASE}${sanitizeTitle.slice(0, 30)}_${id}`; } const requestBodyJson: any = { @@ -337,24 +338,20 @@ export const EditPlaylist = () => { }; await qortalRequest(requestBodyJson); - if(isNew){ + if (isNew) { const objectToStore = { title: title.slice(0, 50), description: metadescription, id: identifier, service: "PLAYLIST", name: username, - ...playlistObject - } - dispatch( - updateVideo(objectToStore) - ); - dispatch( - updateInHashMap(objectToStore) - ); + ...playlistObject, + }; + dispatch(updateFile(objectToStore)); + dispatch(updateInHashMap(objectToStore)); } else { dispatch( - updateVideo({ + updateFile({ ...editVideoProperties, ...playlistObject, }) @@ -366,8 +363,6 @@ export const EditPlaylist = () => { }) ); } - - onClose(); } catch (error: any) { @@ -415,7 +410,9 @@ export const EditPlaylist = () => { event: SelectChangeEvent ) => { const optionId = event.target.value; - const selectedOption = categories.find((option) => option.id === +optionId); + const selectedOption = firstCategories.find( + option => option.id === +optionId + ); setSelectedCategoryVideos(selectedOption || null); }; const handleOptionSubCategoryChangeVideos = ( @@ -424,25 +421,26 @@ export const EditPlaylist = () => { ) => { const optionId = event.target.value; const selectedOption = subcategories.find( - (option) => option.id === +optionId + option => option.id === +optionId ); setSelectedSubCategoryVideos(selectedOption || null); }; - - const removeVideo = (index) => { + const removeVideo = index => { const copyData = structuredClone(playlistData); copyData.videos.splice(index, 1); setPlaylistData(copyData); }; - const addVideo = (data) => { - if(playlistData?.videos?.length > 9){ - dispatch(setNotification({ - msg: "Max 10 videos per playlist", - alertType: "error", - })); - return + const addVideo = data => { + if (playlistData?.videos?.length > 9) { + dispatch( + setNotification({ + msg: "Max 10 videos per playlist", + alertType: "error", + }) + ); + return; } const copyData = structuredClone(playlistData); copyData.videos = [...copyData.videos, { ...data }]; @@ -466,10 +464,8 @@ export const EditPlaylist = () => { > {isNew ? ( Create new playlist - ) : ( - Update Playlist properties - + Update Playlist properties )} <> @@ -488,7 +484,7 @@ export const EditPlaylist = () => { value={selectedCategoryVideos?.id || ""} onChange={handleOptionCategoryChangeVideos} > - {categories.map((option) => ( + {firstCategories.map(option => ( {option.name} @@ -496,22 +492,22 @@ export const EditPlaylist = () => { {selectedCategoryVideos && - subCategories[selectedCategoryVideos?.id] && ( + secondCategories[selectedCategoryVideos?.id] && ( Select a Sub-Category { - setFilterSearch(e.target.value); - }} - value={filterSearch} - placeholder="Search by title" + + Add videos to playlist + + + - - - - {searchResults?.map((vid, index) => { - return ( - { + setFilterSearch(e.target.value); + }} + value={filterSearch} + placeholder="Search by title" sx={{ - display: "flex", - gap: "10px", - width: "100%", - alignItems: "center", - padding: "10px", - borderRadius: "5px", - userSelect: "none", + borderBottom: "1px solid white", + "&&:before": { + borderBottom: "none", + }, + "&&:after": { + borderBottom: "none", + }, + "&&:hover:before": { + borderBottom: "none", + }, + "&&.Mui-focused:before": { + borderBottom: "none", + }, + "&&.Mui-focused": { + outline: "none", + }, + fontSize: "18px", }} + /> + - - )} - - - - ); + {publish?.identifier} + {!isPublishing && hasStarted.current ? ( + <> + {!unpublished.includes(publish.identifier) ? ( + + ) : ( + + )} + + ) : ( + + )} + + ); + })} + {!isPublishing && listOfUnsuccessfulPublishes.length > 0 && ( + <> + + Some files were not published. Please try again. It's important + that all the files get published. Maybe wait a couple minutes if + the error keeps occurring + + + + )} + + + ); }; - export const ModalBody = styled(Box)(({ theme }) => ({ - position: "absolute", - backgroundColor: theme.palette.background.default, - borderRadius: "4px", - top: "50%", - left: "50%", - transform: "translate(-50%, -50%)", - width: "75%", - maxWidth: "900px", - padding: "15px 35px", - display: "flex", - flexDirection: "column", - gap: "17px", - overflowY: "auto", - maxHeight: "95vh", - boxShadow: - theme.palette.mode === "dark" - ? "0px 4px 5px 0px hsla(0,0%,0%,0.14), 0px 1px 10px 0px hsla(0,0%,0%,0.12), 0px 2px 4px -1px hsla(0,0%,0%,0.2)" - : "rgba(99, 99, 99, 0.2) 0px 2px 8px 0px", - "&::-webkit-scrollbar-track": { - backgroundColor: theme.palette.background.paper, - }, - "&::-webkit-scrollbar-track:hover": { - backgroundColor: theme.palette.background.paper, - }, - "&::-webkit-scrollbar": { - width: "16px", - height: "10px", - backgroundColor: theme.palette.mode === "light" ? "#f6f8fa" : "#292d3e", - }, - "&::-webkit-scrollbar-thumb": { - backgroundColor: theme.palette.mode === "light" ? "#d3d9e1" : "#575757", - borderRadius: "8px", - backgroundClip: "content-box", - border: "4px solid transparent", - }, - "&::-webkit-scrollbar-thumb:hover": { - backgroundColor: theme.palette.mode === "light" ? "#b7bcc4" : "#474646", - }, -})); \ No newline at end of file + position: "absolute", + backgroundColor: theme.palette.background.default, + borderRadius: "4px", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + width: "75%", + maxWidth: "900px", + padding: "15px 35px", + display: "flex", + flexDirection: "column", + gap: "17px", + overflowY: "auto", + maxHeight: "95vh", + boxShadow: + theme.palette.mode === "dark" + ? "0px 4px 5px 0px hsla(0,0%,0%,0.14), 0px 1px 10px 0px hsla(0,0%,0%,0.12), 0px 2px 4px -1px hsla(0,0%,0%,0.2)" + : "rgba(99, 99, 99, 0.2) 0px 2px 8px 0px", + "&::-webkit-scrollbar-track": { + backgroundColor: theme.palette.background.paper, + }, + "&::-webkit-scrollbar-track:hover": { + backgroundColor: theme.palette.background.paper, + }, + "&::-webkit-scrollbar": { + width: "16px", + height: "10px", + backgroundColor: theme.palette.mode === "light" ? "#f6f8fa" : "#292d3e", + }, + "&::-webkit-scrollbar-thumb": { + backgroundColor: theme.palette.mode === "light" ? "#d3d9e1" : "#575757", + borderRadius: "8px", + backgroundClip: "content-box", + border: "4px solid transparent", + }, + "&::-webkit-scrollbar-thumb:hover": { + backgroundColor: theme.palette.mode === "light" ? "#b7bcc4" : "#474646", + }, +})); diff --git a/src/components/layout/Navbar/Navbar.tsx b/src/components/layout/Navbar/Navbar.tsx index 3e6b40d..3fee969 100644 --- a/src/components/layout/Navbar/Navbar.tsx +++ b/src/components/layout/Navbar/Navbar.tsx @@ -1,5 +1,12 @@ import React, { useState, useRef } from "react"; -import { Box, Button, Input, Popover, Typography, useTheme } from "@mui/material"; +import { + Box, + Button, + Input, + Popover, + Typography, + useTheme, +} from "@mui/material"; import ExitToAppIcon from "@mui/icons-material/ExitToApp"; import { BlockedNamesModal } from "../../common/BlockedNamesModal/BlockedNamesModal"; import AddBoxIcon from "@mui/icons-material/AddBox"; @@ -28,11 +35,11 @@ import { DownloadTaskManager } from "../../common/DownloadTaskManager"; import QShareLogo from "../../../assets/img/q-share-icon.webp"; import { useDispatch, useSelector } from "react-redux"; import { - addFilteredVideos, + addFilteredFiles, setEditPlaylist, setFilterValue, setIsFiltering, -} from "../../../state/features/videoSlice"; +} from "../../../state/features/fileSlice.ts"; import { RootState } from "../../../state/store"; import { useWindowSize } from "../../../hooks/useWindowSize"; import { PublishFile } from "../../PublishFile/PublishFile.tsx"; @@ -67,9 +74,7 @@ const NavBar: React.FC = ({ const [anchorElNotification, setAnchorElNotification] = React.useState(null); - const filterValue = useSelector( - (state: RootState) => state.video.filterValue - ); + const filterValue = useSelector((state: RootState) => state.file.filterValue); const handleClick = (event: React.MouseEvent) => { const target = event.currentTarget as unknown as HTMLButtonElement | null; @@ -100,36 +105,42 @@ const NavBar: React.FC = ({ return ( - - { - navigate("/"); - dispatch(setIsFiltering(false)); - dispatch(setFilterValue("")); - dispatch(addFilteredVideos([])); - searchValRef.current = ""; - if (!inputRef.current) return; - inputRef.current.value = ""; + - { + navigate("/"); + dispatch(setIsFiltering(false)); + dispatch(setFilterValue("")); + dispatch(addFilteredFiles([])); + searchValRef.current = ""; + if (!inputRef.current) return; + inputRef.current.value = ""; }} - /> - - Sharing is caring + > + + + + Sharing is caring + = ({ { + onChange={e => { searchValRef.current = e.target.value; }} - onKeyDown={(event) => { + onKeyDown={event => { if (event.key === "Enter" || event.keyCode === 13) { if (!searchValRef.current) { dispatch(setIsFiltering(false)); dispatch(setFilterValue("")); - dispatch(addFilteredVideos([])); + dispatch(addFilteredFiles([])); searchValRef.current = ""; if (!inputRef.current) return; inputRef.current.value = ""; @@ -305,7 +316,7 @@ const NavBar: React.FC = ({ } navigate("/"); dispatch(setIsFiltering(true)); - dispatch(addFilteredVideos([])); + dispatch(addFilteredFiles([])); dispatch(setFilterValue(searchValRef.current)); } }} @@ -338,7 +349,7 @@ const NavBar: React.FC = ({ if (!searchValRef.current) { dispatch(setIsFiltering(false)); dispatch(setFilterValue("")); - dispatch(addFilteredVideos([])); + dispatch(addFilteredFiles([])); searchValRef.current = ""; if (!inputRef.current) return; inputRef.current.value = ""; @@ -346,7 +357,7 @@ const NavBar: React.FC = ({ } navigate("/"); dispatch(setIsFiltering(true)); - dispatch(addFilteredVideos([])); + dispatch(addFilteredFiles([])); dispatch(setFilterValue(searchValRef.current)); }} /> @@ -357,7 +368,7 @@ const NavBar: React.FC = ({ onClick={() => { dispatch(setIsFiltering(false)); dispatch(setFilterValue("")); - dispatch(addFilteredVideos([])); + dispatch(addFilteredFiles([])); searchValRef.current = ""; if (!inputRef.current) return; inputRef.current.value = ""; @@ -400,11 +411,9 @@ const NavBar: React.FC = ({ {isAuthenticated && userName && ( <> - + )} - - { - if (a.name === "Other") return 1; - else if (b.name === "Other") return -1; - else return a.name.localeCompare(b.name); -}; - -export const categories = [ - {"id": 1, "name": "Software"}, - {"id": 2, "name": "Gaming"}, - {"id": 3, "name": "Media"}, - {"id": 4, "name": "Other"} -].sort(sortCategory); -export const subCategories: Categories = { - 1: [ - {"id": 101, "name": "OS"}, - {"id": 102, "name": "Application"}, - {"id": 103, "name": "Source Code"}, - {"id": 104, "name": "Other"} - ].sort(sortCategory), - 2: [ - {"id": 201, "name": "NES"}, - {"id": 202, "name": "SNES"}, - {"id": 203, "name": "PC"}, - {"id": 204, "name": "Other"} - ].sort(sortCategory), - 3: [ - {"id": 301, "name": "Audio"}, - {"id": 302, "name": "Video"}, - {"id": 303, "name": "Image"}, - {"id": 304, "name": "Document"}, - {"id": 305, "name": "Other"} - ].sort(sortCategory) -}; - -const gamingSystems = [ - {"id": 20101, "name": "ROM"}, - {"id": 20102, "name": "Romhack"}, - {"id": 20103, "name": "Emulator"}, - {"id": 20104, "name": "Guide"}, - {"id": 20105, "name": "Other"}, - ].sort(sortCategory) -export const subCategories2: Categories = { - 201: gamingSystems, // NES - 202: gamingSystems, // SNES - 301: [ // Audio - {"id": 30101, "name": "Music"}, - {"id": 30102, "name": "Podcasts"}, - {"id": 30103, "name": "Audiobooks"}, - {"id": 30104, "name": "Sound Effects"}, - {"id": 30105, "name": "Lectures & Speeches"}, - {"id": 30106, "name": "Radio Shows"}, - {"id": 30107, "name": "Ambient Sounds"}, - {"id": 30108, "name": "Language Learning Material"}, - {"id": 30109, "name": "Comedy & Satire"}, - {"id": 30110, "name": "Documentaries"}, - {"id": 30111, "name": "Guided Meditations & Yoga"}, - {"id": 30112, "name": "Live Performances"}, - {"id": 30113, "name": "Nature Sounds"}, - {"id": 30114, "name": "Soundtracks"}, - {"id": 30115, "name": "Interviews"} - ].sort(sortCategory), - 302: [ // Under Video - {"id": 30201, "name": "Movies"}, - {"id": 30202, "name": "Series"}, - {"id": 30203, "name": "Music"}, - {"id": 30204, "name": "Education"}, - {"id": 30205, "name": "Lifestyle"}, - {"id": 30206, "name": "Gaming"}, - {"id": 30207, "name": "Technology"}, - {"id": 30208, "name": "Sports"}, - {"id": 30209, "name": "News & Politics"}, - {"id": 30210, "name": "Cooking & Food"}, - {"id": 30211, "name": "Animation"}, - {"id": 30212, "name": "Science"}, - {"id": 30213, "name": "Health & Wellness"}, - {"id": 30214, "name": "DIY & Crafts"}, - {"id": 30215, "name": "Kids & Family"}, - {"id": 30216, "name": "Comedy"}, - {"id": 30217, "name": "Travel & Adventure"}, - {"id": 30218, "name": "Art & Design"}, - {"id": 30219, "name": "Nature & Environment"}, - {"id": 30220, "name": "Business & Finance"}, - {"id": 30221, "name": "Personal Development"}, - {"id": 30222, "name": "Other"}, - {"id": 30223, "name": "History"} - ].sort(sortCategory), - 303: [ // Image - {"id": 30301, "name": "Nature"}, - {"id": 30302, "name": "Urban & Cityscapes"}, - {"id": 30303, "name": "People & Portraits"}, - {"id": 30304, "name": "Art & Abstract"}, - {"id": 30305, "name": "Travel & Adventure"}, - {"id": 30306, "name": "Animals & Wildlife"}, - {"id": 30307, "name": "Sports & Action"}, - {"id": 30308, "name": "Food & Cuisine"}, - {"id": 30309, "name": "Fashion & Beauty"}, - {"id": 30310, "name": "Technology & Science"}, - {"id": 30311, "name": "Historical & Cultural"}, - {"id": 30312, "name": "Aerial & Drone"}, - {"id": 30313, "name": "Black & White"}, - {"id": 30314, "name": "Events & Celebrations"}, - {"id": 30315, "name": "Business & Corporate"}, - {"id": 30316, "name": "Health & Wellness"}, - {"id": 30317, "name": "Transportation & Vehicles"}, - {"id": 30318, "name": "Still Life & Objects"}, - {"id": 30319, "name": "Architecture & Buildings"}, - {"id": 30320, "name": "Landscapes & Seascapes"} - ].sort(sortCategory), - 304: [ // Document - {"id": 30401, "name": "PDF"}, - {"id": 30402, "name": "Word Document"}, - {"id": 30403, "name": "Spreadsheet"}, - {"id": 30404, "name": "Powerpoint"}, - {"id": 30405, "name": "Books"} - ].sort(sortCategory) -}; -export const subCategories3: Categories = { - 30201: [ // Under Movies - {"id": 3020101, "name": "Action & Adventure"}, - {"id": 3020102, "name": "Comedy"}, - {"id": 3020103, "name": "Drama"}, - {"id": 3020104, "name": "Fantasy & Science Fiction"}, - {"id": 3020105, "name": "Horror & Thriller"}, - {"id": 3020106, "name": "Documentaries"}, - {"id": 3020107, "name": "Animated"}, - {"id": 3020108, "name": "Family & Kids"}, - {"id": 3020109, "name": "Romance"}, - {"id": 3020110, "name": "Mystery & Crime"}, - {"id": 3020111, "name": "Historical & War"}, - {"id": 3020112, "name": "Musicals & Music Films"}, - {"id": 3020113, "name": "Indie Films"}, - {"id": 3020114, "name": "International Films"}, - {"id": 3020115, "name": "Biographies & True Stories"}, - {"id": 3020116, "name": "Other"} - ].sort(sortCategory), - 30202: [ // Under Series - {"id": 3020201, "name": "Dramas"}, - {"id": 3020202, "name": "Comedies"}, - {"id": 3020203, "name": "Reality & Competition"}, - {"id": 3020204, "name": "Documentaries & Docuseries"}, - {"id": 3020205, "name": "Sci-Fi & Fantasy"}, - {"id": 3020206, "name": "Crime & Mystery"}, - {"id": 3020207, "name": "Animated Series"}, - {"id": 3020208, "name": "Kids & Family"}, - {"id": 3020209, "name": "Historical & Period Pieces"}, - {"id": 3020210, "name": "Action & Adventure"}, - {"id": 3020211, "name": "Horror & Thriller"}, - {"id": 3020212, "name": "Romance"}, - {"id": 3020213, "name": "Anthologies"}, - {"id": 3020214, "name": "International Series"}, - {"id": 3020215, "name": "Miniseries"}, - {"id": 3020216, "name": "Other"} - ].sort(sortCategory), - 30405: [ // Under Books - {"id": 3040501, "name": "Fiction"}, - {"id": 3040502, "name": "Non-Fiction"}, - {"id": 3040503, "name": "Science Fiction & Fantasy"}, - {"id": 3040504, "name": "Biographies & Memoirs"}, - {"id": 3040505, "name": "Children's Books"}, - {"id": 3040506, "name": "Educational"}, - {"id": 3040507, "name": "Self-Help"}, - {"id": 3040508, "name": "Cookbooks, Food & Wine"}, - {"id": 3040509, "name": "Mystery & Thriller"}, - {"id": 3040510, "name": "History"}, - {"id": 3040511, "name": "Poetry"}, - {"id": 3040512, "name": "Art & Photography"}, - {"id": 3040513, "name": "Religion & Spirituality"}, - {"id": 3040514, "name": "Travel"}, - {"id": 3040515, "name": "Comics & Graphic Novels"}, - - ].sort(sortCategory), - 30101: [ // Under Music - {"id": 3010101, "name": "Rock"}, - {"id": 3010102, "name": "Pop"}, - {"id": 3010103, "name": "Classical"}, - {"id": 3010104, "name": "Jazz"}, - {"id": 3010105, "name": "Electronic"}, - {"id": 3010106, "name": "Country"}, - {"id": 3010107, "name": "Hip Hop/Rap"}, - {"id": 3010108, "name": "Blues"}, - {"id": 3010109, "name": "R&B/Soul"}, - {"id": 3010110, "name": "Reggae"}, - {"id": 3010111, "name": "Folk"}, - {"id": 3010112, "name": "Metal"}, - {"id": 3010113, "name": "World Music"}, - {"id": 3010114, "name": "Latin"}, - {"id": 3010115, "name": "Indie"}, - {"id": 3010116, "name": "Punk"}, - {"id": 3010117, "name": "Soundtracks"}, - {"id": 3010118, "name": "Children's Music"}, - {"id": 3010119, "name": "New Age"}, - {"id": 3010120, "name": "Classical Crossover"} - ].sort(sortCategory) - - -}; -export const icons = { - 1: softwareIcon, - 2: gamingIcon, - 3: mediaIcon, - 4: softwareIcon, - 302: videoIcon, - 301: audioIcon, - 304: documentIcon -} \ No newline at end of file diff --git a/src/constants/Categories/1stCategories.ts b/src/constants/Categories/1stCategories.ts new file mode 100644 index 0000000..dea8761 --- /dev/null +++ b/src/constants/Categories/1stCategories.ts @@ -0,0 +1,57 @@ +import audioIcon from "../../assets/icons/audio.webp"; +import bookIcon from "../../assets/icons/book.webp"; +import documentIcon from "../../assets/icons/document.webp"; +import gamingIcon from "../../assets/icons/gaming.webp"; +import imageIcon from "../../assets/icons/image.webp"; +import softwareIcon from "../../assets/icons/software.webp"; +import unknownIcon from "../../assets/icons/unknown.webp"; +import videoIcon from "../../assets/icons/video.webp"; + +import { + audioSubCategories, + bookSubCategories, + documentSubCategories, + imageSubCategories, + softwareSubCategories, + videoSubCategories, +} from "./2ndCategories.ts"; +import { musicSubCategories } from "./3rdCategories.ts"; +import { + Categories, + Category, + CategoryData, +} from "../../components/common/CategoryList/CategoryList.tsx"; +import { + getAllCategoriesWithIcons, + sortCategory, +} from "./CategoryFunctions.ts"; + +export const firstCategories: Category[] = [ + { id: 1, name: "Software", icon: softwareIcon }, + { id: 2, name: "Gaming", icon: gamingIcon }, + { id: 3, name: "Audio", icon: audioIcon }, + { id: 4, name: "Video", icon: videoIcon }, + { id: 5, name: "Image", icon: imageIcon }, + { id: 6, name: "Document", icon: documentIcon }, + { id: 7, name: "Book", icon: bookIcon }, + { id: 99, name: "Other", icon: unknownIcon }, +].sort(sortCategory); +export const secondCategories: Categories = { + 1: softwareSubCategories.sort(sortCategory), + 3: audioSubCategories.sort(sortCategory), + 4: videoSubCategories.sort(sortCategory), + 5: imageSubCategories.sort(sortCategory), + 6: documentSubCategories.sort(sortCategory), + 7: bookSubCategories.sort(sortCategory), +}; + +export const thirdCategories: Categories = { + 301: musicSubCategories, +}; + +export const allCategoryData: CategoryData = { + category: firstCategories, + subCategories: [secondCategories, thirdCategories], +}; + +export const iconCategories = getAllCategoriesWithIcons(); diff --git a/src/constants/Categories/2ndCategories.ts b/src/constants/Categories/2ndCategories.ts new file mode 100644 index 0000000..4f5dc26 --- /dev/null +++ b/src/constants/Categories/2ndCategories.ts @@ -0,0 +1,88 @@ +export const softwareSubCategories = [ + { id: 101, name: "OS" }, + { id: 102, name: "Application" }, + { id: 103, name: "Source Code" }, + { id: 104, name: "Plugin" }, + { id: 199, name: "Other" }, +]; + +export const audioSubCategories = [ + { id: 301, name: "Music" }, + { id: 302, name: "Podcast" }, + { id: 303, name: "Audiobook" }, + { id: 304, name: "Sound Effect" }, + { id: 305, name: "Lecture or Speech" }, + { id: 306, name: "Radio Show" }, + { id: 307, name: "Ambient Sound" }, + { id: 308, name: "Language Learning Material" }, + { id: 309, name: "Comedy & Satire" }, + { id: 310, name: "Documentary" }, + { id: 311, name: "Guided Meditation & Yoga" }, + { id: 312, name: "Live Performance" }, + { id: 313, name: "Nature Sound" }, + { id: 314, name: "Soundtrack" }, + { id: 315, name: "Interview" }, + { id: 399, name: "Other" }, +]; + +export const videoSubCategories = [ + { id: 404, name: "Education" }, + { id: 405, name: "Lifestyle" }, + { id: 406, name: "Gaming" }, + { id: 407, name: "Technology" }, + { id: 408, name: "Sports" }, + { id: 409, name: "News & Politics" }, + { id: 410, name: "Cooking & Food" }, + { id: 411, name: "Animation" }, + { id: 412, name: "Science" }, + { id: 413, name: "Health & Wellness" }, + { id: 414, name: "DIY & Crafts" }, + { id: 415, name: "Kids & Family" }, + { id: 416, name: "Comedy" }, + { id: 417, name: "Travel & Adventure" }, + { id: 418, name: "Art & Design" }, + { id: 419, name: "Nature & Environment" }, + { id: 420, name: "Business & Finance" }, + { id: 421, name: "Personal Development" }, + { id: 423, name: "History" }, + { id: 499, name: "Other" }, +]; + +export const imageSubCategories = [ + { id: 501, name: "Nature" }, + { id: 502, name: "Urban & Cityscapes" }, + { id: 503, name: "People & Portraits" }, + { id: 504, name: "Art & Abstract" }, + { id: 505, name: "Travel & Adventure" }, + { id: 506, name: "Animals & Wildlife" }, + { id: 507, name: "Sports & Action" }, + { id: 508, name: "Food & Cuisine" }, + { id: 509, name: "Fashion & Beauty" }, + { id: 510, name: "Technology & Science" }, + { id: 511, name: "Historical & Cultural" }, + { id: 512, name: "Aerial & Drone" }, + { id: 513, name: "Black & White" }, + { id: 514, name: "Events & Celebrations" }, + { id: 515, name: "Business & Corporate" }, + { id: 516, name: "Health & Wellness" }, + { id: 517, name: "Transportation & Vehicles" }, + { id: 518, name: "Still Life & Objects" }, + { id: 519, name: "Architecture & Buildings" }, + { id: 520, name: "Landscapes & Seascapes" }, + { id: 599, name: "Other" }, +]; + +export const documentSubCategories = [ + { id: 601, name: "PDF" }, + { id: 602, name: "Word Document" }, + { id: 603, name: "Spreadsheet" }, + { id: 604, name: "Powerpoint" }, + { id: 699, name: "Other" }, +]; + +export const bookSubCategories = [ + { id: 701, name: "Audiobook" }, + { id: 702, name: "Comic" }, + { id: 703, name: "Magazine" }, + { id: 799, name: "Other" }, +]; diff --git a/src/constants/Categories/3rdCategories.ts b/src/constants/Categories/3rdCategories.ts new file mode 100644 index 0000000..b31d3ac --- /dev/null +++ b/src/constants/Categories/3rdCategories.ts @@ -0,0 +1,23 @@ +export const musicSubCategories = [ + { id: 30101, name: "Rock" }, + { id: 30102, name: "Pop" }, + { id: 30103, name: "Classical" }, + { id: 30104, name: "Jazz" }, + { id: 30105, name: "Electronic" }, + { id: 30106, name: "Country" }, + { id: 30107, name: "Hip Hop/Rap" }, + { id: 30108, name: "Blues" }, + { id: 30109, name: "R&B/Soul" }, + { id: 30110, name: "Reggae" }, + { id: 30111, name: "Folk" }, + { id: 30112, name: "Metal" }, + { id: 30113, name: "World Music" }, + { id: 30114, name: "Latin" }, + { id: 30115, name: "Indie" }, + { id: 30116, name: "Punk" }, + { id: 30117, name: "Soundtracks" }, + { id: 30118, name: "Children's Music" }, + { id: 30119, name: "New Age" }, + { id: 30120, name: "Classical Crossover" }, + { id: 30199, name: "Other" }, +]; diff --git a/src/constants/Categories/CategoryFunctions.ts b/src/constants/Categories/CategoryFunctions.ts new file mode 100644 index 0000000..3c228b7 --- /dev/null +++ b/src/constants/Categories/CategoryFunctions.ts @@ -0,0 +1,91 @@ +import { + Category, + getCategoriesFromObject, +} from "../../components/common/CategoryList/CategoryList.tsx"; +import { allCategoryData, iconCategories } from "./1stCategories.ts"; + +export const sortCategory = (a: Category, b: Category) => { + if (a.name === "Other") return 1; + else if (b.name === "Other") return -1; + else return a.name.localeCompare(b.name); +}; +type Direction = "forward" | "backward"; +const findCategory = (categoryID: number) => { + return allCategoryData.category.find(category => { + return category.id === categoryID; + }); +}; +const findSubCategory = ( + categoryID: number, + direction: Direction = "forward" +) => { + const subCategoriesList = allCategoryData.subCategories; + if (direction === "backward") subCategoriesList.reverse(); + + for (const subCategories of subCategoriesList) { + for (const subCategoryID in subCategories) { + const returnValue = subCategories[subCategoryID].find(categoryObj => { + return categoryObj.id === categoryID; + }); + if (returnValue) return returnValue; + } + } +}; +export const findCategoryData = ( + categoryID: number, + direction: Direction = "forward" +) => { + return direction === "forward" + ? findCategory(categoryID) || findSubCategory(categoryID, "forward") + : findSubCategory(categoryID, "backward") || findCategory(categoryID); +}; +export const findAllCategoryData = ( + categories: string[], + direction: Direction = "forward" +) => { + let foundIcons: Category[] = []; + if (direction === "backward") categories.reverse(); + + categories.map(category => { + if (category) { + const icon = findCategoryData(+category, "backward"); + if (icon) foundIcons.push(icon); + } + }); + return foundIcons; +}; + +export const getCategoriesWithIcons = (categories: Category[]) => { + return categories.filter(category => { + return category.icon; + }); +}; + +export const getAllCategoriesWithIcons = () => { + const categoriesWithIcons: Category[] = []; + + allCategoryData.category.map(category => { + if (category.icon) categoriesWithIcons.push(category); + }); + const subCategoriesList = allCategoryData.subCategories; + + for (const subCategories of subCategoriesList) { + for (const subCategoryID in subCategories) { + const categoryWithIcon = subCategories[subCategoryID].map(categoryObj => { + if (categoryObj.icon) categoriesWithIcons.push(categoryObj); + }); + } + } + return categoriesWithIcons; +}; + +export const getIconsFromObject = (fileObj: any) => { + const categories = getCategoriesFromObject(fileObj); + const icons = categories + .map(categoryID => { + return iconCategories.find(category => category.id === +categoryID)?.icon; + }) + .reverse(); + + return icons.find(icon => icon !== undefined); +}; diff --git a/src/constants/Identifiers.ts b/src/constants/Identifiers.ts index 66b2c8b..b09ba1a 100644 --- a/src/constants/Identifiers.ts +++ b/src/constants/Identifiers.ts @@ -4,14 +4,10 @@ export const QSHARE_FILE_BASE = useTestIdentifiers ? "MYTEST_share_vid_" : "qshare_file_"; - export const QSHARE_PLAYLIST_BASE = useTestIdentifiers +export const QSHARE_PLAYLIST_BASE = useTestIdentifiers ? "MYTEST_share_playlist_" : "qshare_playlist_"; - export const QSHARE_COMMENT_BASE = useTestIdentifiers +export const QSHARE_COMMENT_BASE = useTestIdentifiers ? "qcomment_v1_MYTEST_" : "qcomment_v1_qshare_"; - - - - diff --git a/src/constants/Misc.ts b/src/constants/Misc.ts index 8aaa2a0..0edb22b 100644 --- a/src/constants/Misc.ts +++ b/src/constants/Misc.ts @@ -1 +1,3 @@ -export const titleFormatter = /[^a-zA-Z0-9\s-_!?()&'",.~;:|]/g; \ No newline at end of file +export const minPriceSuperlike = 10; +export const titleFormatter = /[^a-zA-Z0-9\s-_!?()&'",.;:|—~@#$%^*+=<>]/g; +export const titleFormatterOnSave = /[^a-zA-Z0-9\s-_!()&',.;—~@#$%^+=]/g; \ No newline at end of file diff --git a/src/hooks/useFetchFiles.tsx b/src/hooks/useFetchFiles.tsx index 55d5446..d6c81f4 100644 --- a/src/hooks/useFetchFiles.tsx +++ b/src/hooks/useFetchFiles.tsx @@ -1,109 +1,128 @@ -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' +import React from "react"; +import { useDispatch, useSelector } from "react-redux"; import { - addVideos, + addFiles, addToHashMap, - setCountNewVideos, - upsertVideos, - upsertVideosBeginning, + setCountNewFiles, + upsertFiles, + upsertFilesBeginning, Video, - upsertFilteredVideos -} from '../state/features/videoSlice' + upsertFilteredFiles, +} from "../state/features/fileSlice.ts"; import { - setIsLoadingGlobal, setUserAvatarHash -} from '../state/features/globalSlice' -import { RootState } from '../state/store' -import { fetchAndEvaluateVideos } from '../utils/fetchVideos' -import { QSHARE_PLAYLIST_BASE, QSHARE_FILE_BASE } from '../constants/Identifiers.ts' -import { RequestQueue } from '../utils/queue' -import { queue } from '../wrappers/GlobalWrapper' - - + setIsLoadingGlobal, + setUserAvatarHash, + setTotalFilesPublished, + setTotalNamesPublished, + setFilesPerNamePublished, +} from "../state/features/globalSlice"; +import { RootState } from "../state/store"; +import { fetchAndEvaluateVideos } from "../utils/fetchVideos"; +import { + QSHARE_PLAYLIST_BASE, + QSHARE_FILE_BASE, +} from "../constants/Identifiers.ts"; +import { RequestQueue } from "../utils/queue"; +import { queue } from "../wrappers/GlobalWrapper"; +import { getCategoriesFetchString } from "../components/common/CategoryList/CategoryList.tsx"; export const useFetchFiles = () => { - const dispatch = useDispatch() - const hashMapVideos = useSelector( - (state: RootState) => state.video.hashMapVideos - ) - const videos = useSelector((state: RootState) => state.video.videos) + const dispatch = useDispatch(); + const hashMapFiles = useSelector( + (state: RootState) => state.file.hashMapFiles + ); + const videos = useSelector((state: RootState) => state.file.files); const userAvatarHash = useSelector( (state: RootState) => state.global.userAvatarHash - ) + ); const filteredVideos = useSelector( - (state: RootState) => state.video.filteredVideos - ) + (state: RootState) => state.file.filteredFiles + ); + + const totalFilesPublished = useSelector( + (state: RootState) => state.global.totalFilesPublished + ); + const totalNamesPublished = useSelector( + (state: RootState) => state.global.totalNamesPublished + ); + const filesPerNamePublished = useSelector( + (state: RootState) => state.global.filesPerNamePublished + ); - const checkAndUpdateVideo = React.useCallback( + const checkAndUpdateFile = React.useCallback( (video: Video) => { - const existingVideo = hashMapVideos[video.id] + const existingVideo = hashMapFiles[video.id]; if (!existingVideo) { - return true + return true; } else if ( video?.updated && existingVideo?.updated && (!existingVideo?.updated || video?.updated) > existingVideo?.updated ) { - return true + return true; } else { - return false + return false; } }, - [hashMapVideos] - ) + [hashMapFiles] + ); const getAvatar = React.useCallback(async (author: string) => { try { let url = await qortalRequest({ - action: 'GET_QDN_RESOURCE_URL', + action: "GET_QDN_RESOURCE_URL", name: author, - service: 'THUMBNAIL', - identifier: 'qortal_avatar' - }) + service: "THUMBNAIL", + identifier: "qortal_avatar", + }); - dispatch(setUserAvatarHash({ - name: author, - url - })) - } catch (error) { } - }, []) + dispatch( + setUserAvatarHash({ + name: author, + url, + }) + ); + } catch (error) {} + }, []); - const getVideo = async (user: string, videoId: string, content: any, retries: number = 0) => { + const getFile = async ( + user: string, + videoId: string, + content: any, + retries: number = 0 + ) => { try { const res = await fetchAndEvaluateVideos({ user, videoId, - content - }) - - dispatch(addToHashMap(res)) + content, + }); + + dispatch(addToHashMap(res)); } catch (error) { - retries= retries + 1 - if (retries < 2) { // 3 is the maximum number of retries here, you can adjust it to your needs - queue.push(() => getVideo(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 + queue.push(() => getFile(user, videoId, content, retries + 1)); } else { - console.error('Failed to get video after 3 attempts', error); + console.error("Failed to get video after 3 attempts", error); } } - - - } + }; - - - const getNewVideos = React.useCallback(async () => { + const getNewFiles = React.useCallback(async () => { try { - dispatch(setIsLoadingGlobal(true)) - + dispatch(setIsLoadingGlobal(true)); - const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QSHARE_FILE_BASE}&limit=20&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true` + const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QSHARE_FILE_BASE}&limit=20&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true`; const response = await fetch(url, { - method: 'GET', + method: "GET", headers: { - 'Content-Type': 'application/json' - } - }) - const responseData = await response.json() - + "Content-Type": "application/json", + }, + }); + const responseData = await response.json(); + // const responseData = await qortalRequest({ // action: "SEARCH_QDN_RESOURCES", // mode: "ALL", @@ -116,16 +135,16 @@ export const useFetchFiles = () => { // exactMatchNames: true, // name: names // }) - const latestVideo = videos[0] - if (!latestVideo) return + const latestVideo = videos[0]; + if (!latestVideo) return; const findVideo = responseData?.findIndex( (item: any) => item?.identifier === latestVideo?.id - ) - let fetchAll = responseData - let willFetchAll = true + ); + let fetchAll = responseData; + let willFetchAll = true; if (findVideo !== -1) { - willFetchAll = false - fetchAll = responseData.slice(0, findVideo) + willFetchAll = false; + fetchAll = responseData.slice(0, findVideo); } const structureData = fetchAll.map((video: any): Video => { @@ -138,221 +157,202 @@ export const useFetchFiles = () => { created: video?.created, updated: video?.updated, user: video.name, - videoImage: '', - id: video.identifier - } - }) + videoImage: "", + id: video.identifier, + }; + }); if (!willFetchAll) { - dispatch(upsertVideosBeginning(structureData)) + dispatch(upsertFilesBeginning(structureData)); } if (willFetchAll) { - dispatch(addVideos(structureData)) + dispatch(addFiles(structureData)); } - setTimeout(()=> { - dispatch(setCountNewVideos(0)) - }, 1000) + setTimeout(() => { + dispatch(setCountNewFiles(0)); + }, 1000); for (const content of structureData) { if (content.user && content.id) { - const res = checkAndUpdateVideo(content) + const res = checkAndUpdateFile(content); if (res) { - queue.push(() => getVideo(content.user, content.id, content)); + queue.push(() => getFile(content.user, content.id, content)); } } } } catch (error) { } finally { - dispatch(setIsLoadingGlobal(false)) + dispatch(setIsLoadingGlobal(false)); } - }, [videos, hashMapVideos]) + }, [videos, hashMapFiles]); - const getVideos = React.useCallback(async (filters = {}, reset?:boolean, resetFilers?: boolean,limit?: number) => { - try { - const {name = '', - category = '', - subcategory = '', - subcategory2 = '', - subcategory3 = '', - keywords = '', - type = '' }: any = resetFilers ? {} : filters - let offset = videos.length - if(reset){ - offset = 0 - } - const videoLimit = limit || 50 - let defaultUrl = `/arbitrary/resources/search?mode=ALL&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}&limit=${videoLimit}`; - - if (name) { - defaultUrl += `&name=${name}`; - } - - if (category) { - // Start with the category - let description = `cat:${category}`; - - // Check and append subcategory - if (subcategory) { - description += `;sub:${subcategory}`; + const getFiles = React.useCallback( + async ( + filters = {}, + reset?: boolean, + resetFilers?: boolean, + limit?: number + ) => { + try { + const { + name = "", + categories = [], + keywords = "", + type = "", + }: any = resetFilers ? {} : filters; + let offset = videos.length; + if (reset) { + offset = 0; } - - // Check and append subcategory2 - if (subcategory2) { - description += `;sub2:${subcategory2}`; + const videoLimit = limit || 50; + let defaultUrl = `/arbitrary/resources/search?mode=ALL&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}&limit=${videoLimit}`; + + if (name) { + defaultUrl += `&name=${name}`; } - - // Check and append subcategory3 - if (subcategory3) { - description += `;sub3:${subcategory3}`; + + if (categories.length > 0) { + defaultUrl += "&description=" + getCategoriesFetchString(categories); } - - // Append the description to the URL - defaultUrl += `&description=${description}`; - } - - if(keywords){ - defaultUrl = defaultUrl + `&query=${keywords}` - } - if(type === 'playlists'){ - defaultUrl = defaultUrl + `&service=PLAYLIST` - defaultUrl = defaultUrl + `&identifier=${QSHARE_PLAYLIST_BASE}` - - } else { - defaultUrl = defaultUrl + `&service=DOCUMENT` - defaultUrl = defaultUrl + `&identifier=${QSHARE_FILE_BASE}` - } - // const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QTUBE_VIDEO_BASE}&limit=${videoLimit}&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}` - const url = defaultUrl - const response = await fetch(url, { - method: 'GET', - headers: { - 'Content-Type': 'application/json' + if (keywords) { + defaultUrl = defaultUrl + `&query=${keywords}`; } - }) - const responseData = await response.json() - - - // const responseData = await qortalRequest({ - // action: "SEARCH_QDN_RESOURCES", - // mode: "ALL", - // service: "DOCUMENT", - // query: "${QTUBE_VIDEO_BASE}", - // limit: 20, - // includeMetadata: true, - // offset: offset, - // reverse: true, - // excludeBlocked: true, - // exactMatchNames: true, - // name: names - // }) - const structureData = responseData.map((video: any): Video => { - return { - title: video?.metadata?.title, - service: video?.service, - category: video?.metadata?.category, - categoryName: video?.metadata?.categoryName, - tags: video?.metadata?.tags || [], - description: video?.metadata?.description, - created: video?.created, - updated: video?.updated, - user: video.name, - videoImage: '', - id: video.identifier + if (type === "playlists") { + defaultUrl = defaultUrl + `&service=PLAYLIST`; + defaultUrl = defaultUrl + `&identifier=${QSHARE_PLAYLIST_BASE}`; + } else { + defaultUrl = defaultUrl + `&service=DOCUMENT`; + defaultUrl = defaultUrl + `&identifier=${QSHARE_FILE_BASE}`; } - }) - if(reset){ - dispatch(addVideos(structureData)) - } else { - dispatch(upsertVideos(structureData)) + // const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QTUBE_VIDEO_BASE}&limit=${videoLimit}&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}` + const url = defaultUrl; + const response = await fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + const responseData = await response.json(); - } - for (const content of structureData) { - if (content.user && content.id) { - const res = checkAndUpdateVideo(content) - if (res) { - queue.push(() => getVideo(content.user, content.id, content)); - - + // const responseData = await qortalRequest({ + // action: "SEARCH_QDN_RESOURCES", + // mode: "ALL", + // service: "DOCUMENT", + // query: "${QTUBE_VIDEO_BASE}", + // limit: 20, + // includeMetadata: true, + // offset: offset, + // reverse: true, + // excludeBlocked: true, + // exactMatchNames: true, + // name: names + // }) + const structureData = responseData.map((video: any): Video => { + return { + title: video?.metadata?.title, + service: video?.service, + category: video?.metadata?.category, + categoryName: video?.metadata?.categoryName, + tags: video?.metadata?.tags || [], + description: video?.metadata?.description, + created: video?.created, + updated: video?.updated, + user: video.name, + videoImage: "", + id: video.identifier, + }; + }); + if (reset) { + dispatch(addFiles(structureData)); + } else { + dispatch(upsertFiles(structureData)); + } + for (const content of structureData) { + if (content.user && content.id) { + const res = checkAndUpdateFile(content); + if (res) { + queue.push(() => getFile(content.user, content.id, content)); + } } } + } catch (error) { + console.log({ error }); + } finally { } - } catch (error) { - console.log({error}) - } finally { - - } - }, [videos, hashMapVideos]) + }, + [videos, hashMapFiles] + ); - const getVideosFiltered = React.useCallback(async (filterValue: string) => { - try { - const offset = filteredVideos.length - const replaceSpacesWithUnderscore = filterValue.replace(/ /g, '_'); + const getFilesFiltered = React.useCallback( + async (filterValue: string) => { + try { + const offset = filteredVideos.length; + const replaceSpacesWithUnderscore = filterValue.replace(/ /g, "_"); - const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${replaceSpacesWithUnderscore}&identifier=${QSHARE_FILE_BASE}&limit=10&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}` - const response = await fetch(url, { - method: 'GET', - headers: { - 'Content-Type': 'application/json' - } - }) - const responseData = await response.json() - - // const responseData = await qortalRequest({ - // action: "SEARCH_QDN_RESOURCES", - // mode: "ALL", - // service: "DOCUMENT", - // query: replaceSpacesWithUnderscore, - // identifier: "${QTUBE_VIDEO_BASE}", - // limit: 20, - // includeMetadata: true, - // offset: offset, - // reverse: true, - // excludeBlocked: true, - // exactMatchNames: true, - // name: names - // }) - const structureData = responseData.map((video: any): Video => { - return { - title: video?.metadata?.title, - category: video?.metadata?.category, - categoryName: video?.metadata?.categoryName, - tags: video?.metadata?.tags || [], - description: video?.metadata?.description, - created: video?.created, - updated: video?.updated, - user: video.name, - videoImage: '', - id: video.identifier - } - }) - dispatch(upsertFilteredVideos(structureData)) + const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${replaceSpacesWithUnderscore}&identifier=${QSHARE_FILE_BASE}&limit=10&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}`; + const response = await fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + const responseData = await response.json(); - for (const content of structureData) { - if (content.user && content.id) { - const res = checkAndUpdateVideo(content) - if (res) { - queue.push(() => getVideo(content.user, content.id, content)); + // const responseData = await qortalRequest({ + // action: "SEARCH_QDN_RESOURCES", + // mode: "ALL", + // service: "DOCUMENT", + // query: replaceSpacesWithUnderscore, + // identifier: "${QTUBE_VIDEO_BASE}", + // limit: 20, + // includeMetadata: true, + // offset: offset, + // reverse: true, + // excludeBlocked: true, + // exactMatchNames: true, + // name: names + // }) + const structureData = responseData.map((video: any): Video => { + return { + title: video?.metadata?.title, + category: video?.metadata?.category, + categoryName: video?.metadata?.categoryName, + tags: video?.metadata?.tags || [], + description: video?.metadata?.description, + created: video?.created, + updated: video?.updated, + user: video.name, + videoImage: "", + id: video.identifier, + }; + }); + dispatch(upsertFilteredFiles(structureData)); + + for (const content of structureData) { + if (content.user && content.id) { + const res = checkAndUpdateFile(content); + if (res) { + queue.push(() => getFile(content.user, content.id, content)); + } } } + } catch (error) { + } finally { } - } catch (error) { - } finally { - - } - }, [filteredVideos, hashMapVideos]) + }, + [filteredVideos, hashMapFiles] + ); - const checkNewVideos = React.useCallback(async () => { + const checkNewFiles = React.useCallback(async () => { try { - - - const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QSHARE_FILE_BASE}&limit=20&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true` + const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QSHARE_FILE_BASE}&limit=20&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true`; const response = await fetch(url, { - method: 'GET', + method: "GET", headers: { - 'Content-Type': 'application/json' - } - }) - const responseData = await response.json() + "Content-Type": "application/json", + }, + }); + const responseData = await response.json(); // const responseData = await qortalRequest({ // action: "SEARCH_QDN_RESOURCES", // mode: "ALL", @@ -365,29 +365,57 @@ export const useFetchFiles = () => { // exactMatchNames: true, // name: names // }) - const latestVideo = videos[0] - if (!latestVideo) return + const latestVideo = videos[0]; + if (!latestVideo) return; const findVideo = responseData?.findIndex( (item: any) => item?.identifier === latestVideo?.id - ) + ); if (findVideo === -1) { - dispatch(setCountNewVideos(responseData.length)) - return + dispatch(setCountNewFiles(responseData.length)); + return; } - const newArray = responseData.slice(0, findVideo) - dispatch(setCountNewVideos(newArray.length)) - return + const newArray = responseData.slice(0, findVideo); + dispatch(setCountNewFiles(newArray.length)); + return; } catch (error) {} - }, [videos]) + }, [videos]); + + const getFilesCount = React.useCallback(async () => { + try { + let url = `/arbitrary/resources/search?mode=ALL&includemetadata=false&limit=0&service=DOCUMENT&identifier=${QSHARE_FILE_BASE}`; + + const response = await fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + const responseData = await response.json(); + + const totalFilesPublished = responseData.length; + const uniqueNames = new Set(responseData.map(video => video.name)); + const totalNamesPublished = uniqueNames.size; + const filesPerNamePublished = ( + totalFilesPublished / totalNamesPublished + ).toFixed(2); + dispatch(setTotalFilesPublished(totalFilesPublished)); + dispatch(setTotalNamesPublished(totalNamesPublished)); + dispatch(setFilesPerNamePublished(filesPerNamePublished)); + } catch (error) { + console.log({ error }); + } finally { + } + }, []); return { - getFiles: getVideos, - checkAndUpdateVideo, - getVideo, - hashMapVideos, - getNewFiles: getNewVideos, - checkNewFiles: checkNewVideos, - getFilesFiltered: getVideosFiltered - } -} + getFiles, + checkAndUpdateFile, + getFile, + hashMapFiles, + getNewFiles, + checkNewFiles, + getFilesFiltered, + getFilesCount, + }; +}; diff --git a/src/pages/FileContent/FileContent-styles.tsx b/src/pages/FileContent/FileContent-styles.tsx new file mode 100644 index 0000000..35c53eb --- /dev/null +++ b/src/pages/FileContent/FileContent-styles.tsx @@ -0,0 +1,85 @@ +import { styled } from "@mui/system"; +import { Box, Grid, Typography, Checkbox } from "@mui/material"; + +export const FilePlayerContainer = styled(Box)(({ theme }) => ({ + maxWidth: "95%", + width: "1000px", + display: "flex", + flexDirection: "column", + alignItems: "flex-start", +})); + +export const FileTitle = styled(Typography)(({ theme }) => ({ + fontFamily: "Raleway", + fontSize: "20px", + color: theme.palette.text.primary, + userSelect: "none", + wordBreak: "break-word", +})); + +export const FileDescription = styled(Typography)(({ theme }) => ({ + fontFamily: "Raleway", + fontSize: "16px", + color: theme.palette.text.primary, + userSelect: "none", + wordBreak: "break-word", +})); + +export const Spacer = ({ height }: any) => { + return ( + + ); +}; + +export const StyledCardHeaderComment = styled(Box)({ + display: "flex", + alignItems: "center", + justifyContent: "flex-start", + gap: "5px", + padding: "7px 0px", +}); +export const StyledCardCol = styled(Box)({ + display: "flex", + overflow: "hidden", + flexDirection: "column", + gap: "2px", + alignItems: "flex-start", + width: "100%", +}); + +export const StyledCardColComment = styled(Box)({ + display: "flex", + overflow: "hidden", + flexDirection: "column", + gap: "2px", + alignItems: "flex-start", + width: "100%", +}); + +export const AuthorTextComment = styled(Typography)({ + fontFamily: "Raleway, sans-serif", + fontSize: "16px", + lineHeight: "1.2", +}); + +export const FileAttachmentContainer = styled(Box)(({ theme }) => ({ + display: "flex", + alignItems: "center", + gap: "20px", + padding: "5px 10px", + border: `1px solid ${theme.palette.text.primary}`, +})); + +export const FileAttachmentFont = styled(Typography)(({ theme }) => ({ + fontFamily: "Mulish", + color: theme.palette.text.primary, + fontSize: "16px", + letterSpacing: 0, + fontWeight: 400, + userSelect: "none", + whiteSpace: "nowrap", +})); diff --git a/src/pages/VideoContent/VideoContent.tsx b/src/pages/FileContent/FileContent.tsx similarity index 60% rename from src/pages/VideoContent/VideoContent.tsx rename to src/pages/FileContent/FileContent.tsx index 74e3842..51a3a49 100644 --- a/src/pages/VideoContent/VideoContent.tsx +++ b/src/pages/FileContent/FileContent.tsx @@ -1,72 +1,67 @@ -import React, { useState, useMemo, useRef, useEffect } from "react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { useNavigate, useParams } from "react-router-dom"; import { setIsLoadingGlobal } from "../../state/features/globalSlice"; import { Avatar, Box, Typography, useTheme } from "@mui/material"; -import { VideoPlayer } from "../../components/common/VideoPlayer"; import { RootState } from "../../state/store"; -import { addToHashMap } from "../../state/features/videoSlice"; +import { addToHashMap } from "../../state/features/fileSlice.ts"; import AttachFileIcon from "@mui/icons-material/AttachFile"; import DownloadIcon from "@mui/icons-material/Download"; - -import mockImg from "../../test/mockimg.jpg"; import { AuthorTextComment, FileAttachmentContainer, FileAttachmentFont, + FileDescription, + FilePlayerContainer, + FileTitle, Spacer, StyledCardColComment, StyledCardHeaderComment, - VideoDescription, - VideoPlayerContainer, - VideoTitle, -} from "./VideoContent-styles"; -import { setUserAvatarHash } from "../../state/features/globalSlice"; -import { - formatDate, - formatDateSeconds, - formatTimestampSeconds, -} from "../../utils/time"; -import { NavbarName } from "../../components/layout/Navbar/Navbar-styles"; +} from "./FileContent-styles.tsx"; +import { formatDate } from "../../utils/time"; import { CommentSection } from "../../components/common/Comments/CommentSection"; -import { - CrowdfundSubTitle, - CrowdfundSubTitleRow, -} from "../../components/PublishFile/Upload-styles.tsx"; import { QSHARE_FILE_BASE } from "../../constants/Identifiers.ts"; -import { Playlists } from "../../components/Playlists/Playlists"; import { DisplayHtml } from "../../components/common/TextEditor/DisplayHtml"; import FileElement from "../../components/common/FileElement"; -import {categories, subCategories, subCategories2, subCategories3} from "../../constants/Categories.ts"; +import { + allCategoryData, + iconCategories, +} from "../../constants/Categories/1stCategories.ts"; +import { + Category, + getCategoriesFromObject, +} from "../../components/common/CategoryList/CategoryList.tsx"; +import { + findAllCategoryData, + findCategoryData, + getCategoriesWithIcons, + getIconsFromObject, +} from "../../constants/Categories/CategoryFunctions.ts"; export function formatBytes(bytes, decimals = 2) { - if (bytes === 0) return '0 Bytes'; + if (bytes === 0) return "0 Bytes"; const k = 1024; const dm = decimals < 0 ? 0 : decimals; - const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); - return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; } - - - -export const VideoContent = () => { +export const FileContent = () => { const { name, id } = useParams(); const [isExpandedDescription, setIsExpandedDescription] = useState(false); - const [descriptionHeight, setDescriptionHeight] = - useState(null); - + const [descriptionHeight, setDescriptionHeight] = useState( + null + ); + const [icon, setIcon] = useState(""); const userAvatarHash = useSelector( (state: RootState) => state.global.userAvatarHash ); const contentRef = useRef(null); - - const avatarUrl = useMemo(() => { let url = ""; @@ -79,15 +74,15 @@ export const VideoContent = () => { const navigate = useNavigate(); const theme = useTheme(); - const [videoData, setVideoData] = useState(null); + const [fileData, setFileData] = useState(null); const [playlistData, setPlaylistData] = useState(null); const hashMapVideos = useSelector( - (state: RootState) => state.video.hashMapVideos + (state: RootState) => state.file.hashMapFiles ); const videoReference = useMemo(() => { - if (!videoData) return null; - const { videoReference } = videoData; + if (!fileData) return null; + const { videoReference } = fileData; if ( videoReference?.identifier && videoReference?.name && @@ -97,13 +92,13 @@ export const VideoContent = () => { } else { return null; } - }, [videoData]); + }, [fileData]); const videoCover = useMemo(() => { - if (!videoData) return null; - const { videoImage } = videoData; + if (!fileData) return null; + const { videoImage } = fileData; return videoImage || null; - }, [videoData]); + }, [fileData]); const dispatch = useDispatch(); const getVideoData = React.useCallback(async (name: string, id: string) => { @@ -147,8 +142,7 @@ export const VideoContent = () => { ...resourceData, ...responseData, }; - - setVideoData(combinedData); + setFileData(combinedData); dispatch(addToHashMap(combinedData)); checkforPlaylist(name, id, combinedData?.code); } @@ -230,7 +224,7 @@ export const VideoContent = () => { const existingVideo = hashMapVideos[id]; if (existingVideo) { - setVideoData(existingVideo); + setFileData(existingVideo); checkforPlaylist(name, id, existingVideo?.code); } else { getVideoData(name, id); @@ -272,25 +266,51 @@ export const VideoContent = () => { useEffect(() => { if (contentRef.current) { const height = contentRef.current.offsetHeight; - if (height > 100) { // Assuming 100px is your threshold - setDescriptionHeight(100) + if (height > 100) { + // Assuming 100px is your threshold + setDescriptionHeight(100); } } - }, [videoData]); - - const categoriesDisplay = useMemo(()=> { - const category = categories?.find((item)=> item?.id === videoData?.category) - if(!category) return null - const subcategory = subCategories[category?.id]?.find(item=> item?.id === videoData?.subcategory) - if(!subcategory) return category?.name - - const subcategory2 = subCategories2[subcategory?.id]?.find(item => item.id === videoData?.subcategory2) - if(!subcategory2) return `${category?.name} > ${subcategory?.name}` - const subcategory3 = subCategories3[subcategory2?.id]?.find(item => item.id === videoData?.subcategory3) - if(!subcategory3) return `${category?.name} > ${subcategory?.name} > ${subcategory2?.name}` - return `${category?.name} > ${subcategory?.name} > ${subcategory2?.name} > ${subcategory3?.name}` - }, [videoData]) - + if (fileData) { + //const icon = getIconsFromObject(fileData)[0]?.icon || null; + + const icon = getIconsFromObject(fileData); + setIcon(icon); + } + }, [fileData]); + + const categoriesDisplay = useMemo(() => { + if (fileData) { + const categoryList = getCategoriesFromObject(fileData); + + const categoryNames = categoryList.map((categoryID, index) => { + let categoryName: Category; + if (index === 0) { + categoryName = allCategoryData.category.find( + item => item?.id === +categoryList[0] + ); + } else { + const subCategories = allCategoryData.subCategories[index - 1]; + const selectedSubCategory = subCategories[categoryList[index - 1]]; + if (selectedSubCategory) { + categoryName = selectedSubCategory.find( + item => item?.id === +categoryList[index] + ); + } + } + return categoryName?.name; + }); + const filteredCategoryNames = categoryNames.filter(name => name); + let categoryDisplay = ""; + const separator = " > "; + filteredCategoryNames.map((name, index) => { + categoryDisplay += + index !== filteredCategoryNames.length - 1 ? name + separator : name; + }); + return categoryDisplay; + } + return "no videodata"; + }, [fileData]); return ( { padding: "20px 10px", }} > - - - - - {videoData?.title} - - {videoData?.created && ( + {icon ? ( + + ) : ( + + )} + + {fileData?.title} + + + {fileData?.created && ( { }} color={theme.palette.text.primary} > - {formatDate(videoData.created)} + {formatDate(fileData.created)} )} @@ -367,11 +405,15 @@ export const VideoContent = () => { - {categoriesDisplay} + + {categoriesDisplay} + { borderRadius: "5px", padding: "5px", width: "100%", - cursor: !descriptionHeight ? "default" : isExpandedDescription ? "default" : "pointer", + cursor: !descriptionHeight + ? "default" + : isExpandedDescription + ? "default" + : "pointer", position: "relative", }} - className={!descriptionHeight ? "": isExpandedDescription ? "" : "hover-click"} + className={ + !descriptionHeight ? "" : isExpandedDescription ? "" : "hover-click" + } > {descriptionHeight && !isExpandedDescription && ( { /> )} - {videoData?.htmlDescription ? ( - + {fileData?.htmlDescription ? ( + ) : ( - - {videoData?.fullDescription} - + + {fileData?.fullDescription} + )} {descriptionHeight && ( - { - setIsExpandedDescription((prev) => !prev); - }} - sx={{ - fontWeight: "bold", - fontSize: "16px", - cursor: "pointer", - paddingLeft: "15px", - paddingTop: "15px", - }} - > - {isExpandedDescription ? "Show less" : "...more"} - + { + setIsExpandedDescription(prev => !prev); + }} + sx={{ + fontWeight: "bold", + fontSize: "16px", + cursor: "pointer", + paddingLeft: "15px", + paddingTop: "15px", + }} + > + {isExpandedDescription ? "Show less" : "...more"} + )} - - - {videoData?.files?.map((file)=> { + + {fileData?.files?.map((file, index) => { return ( - - - - {file.filename} - - - - - - {formatBytes(file?.size || 0)} - - - - - - - ) + + {file.filename} + + + {formatBytes(file?.size || 0)} + + + + + + + ); })} - - - - + + + { - const theme = useTheme() - const navigate = useNavigate() - const publishNames = useSelector((state: RootState)=> state.global.publishNames) + const theme = useTheme(); + const navigate = useNavigate(); + const publishNames = useSelector( + (state: RootState) => state.global.publishNames + ); const userAvatarHash = useSelector( (state: RootState) => state.global.userAvatarHash - ) - - - + ); return ( - - - {publishNames && publishNames?.slice(0, 10).map((name)=> { - let avatarUrl = '' - if(userAvatarHash[name]){ - avatarUrl = userAvatarHash[name] - } - return ( - - + {publishNames && + publishNames?.slice(0, 10).map(name => { + let avatarUrl = ""; + if (userAvatarHash[name]) { + avatarUrl = userAvatarHash[name]; + } + return ( + + { - navigate(`/channel/${name}`) + navigate(`/channel/${name}`); }} - > - {name} - - - - ) - })} - + > + {name} + + + + ); + })} + - ) -} - - + ); +}; diff --git a/src/pages/Home/FileList-styles.tsx b/src/pages/Home/FileList-styles.tsx index 7931938..f026dc7 100644 --- a/src/pages/Home/FileList-styles.tsx +++ b/src/pages/Home/FileList-styles.tsx @@ -1,7 +1,15 @@ import { styled } from "@mui/system"; -import { Box, Grid, Typography, Checkbox, TextField, InputLabel, Autocomplete } from "@mui/material"; +import { + Box, + Grid, + Typography, + Checkbox, + TextField, + InputLabel, + Autocomplete, +} from "@mui/material"; -export const VideoContainer = styled(Box)(({ theme }) => ({ +export const FileContainer = styled(Box)(({ theme }) => ({ position: "relative", display: "flex", padding: "15px", @@ -9,7 +17,7 @@ export const VideoContainer = styled(Box)(({ theme }) => ({ gap: "20px", flexWrap: "wrap", justifyContent: "flex-start", - width: '100%' + width: "100%", })); export const StoresRow = styled(Grid)(({ theme }) => ({ @@ -21,8 +29,8 @@ export const StoresRow = styled(Grid)(({ theme }) => ({ width: "auto", position: "relative", "@media (max-width: 450px)": { - width: "100%" - } + width: "100%", + }, })); export const VideoCard = styled(Grid)(({ theme }) => ({ @@ -30,7 +38,7 @@ export const VideoCard = styled(Grid)(({ theme }) => ({ display: "flex", flexDirection: "column", height: "320px", - width: '300px', + width: "300px", backgroundColor: theme.palette.background.paper, borderRadius: "8px", padding: "10px 15px", @@ -49,8 +57,8 @@ export const VideoCard = styled(Grid)(({ theme }) => ({ boxShadow: theme.palette.mode === "dark" ? "0px 8px 10px 1px hsla(0,0%,0%,0.14), 0px 3px 14px 2px hsla(0,0%,0%,0.12), 0px 5px 5px -3px hsla(0,0%,0%,0.2)" - : "rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;" - } + : "rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;", + }, })); export const StoreCardInfo = styled(Grid)(({ theme }) => ({ @@ -58,7 +66,7 @@ export const StoreCardInfo = styled(Grid)(({ theme }) => ({ flexDirection: "column", gap: "10px", padding: "5px", - marginTop: "15px" + marginTop: "15px", })); export const VideoImageContainer = styled(Grid)(({ theme }) => ({})); @@ -67,9 +75,9 @@ export const VideoCardImage = styled("img")(({ theme }) => ({ maxWidth: "300px", minWidth: "150px", borderRadius: "5px", - height: '150px', - objectFit: 'fill', - width: '266px', + height: "150px", + objectFit: "fill", + width: "266px", })); const DoubleLine = styled(Typography)` @@ -77,44 +85,44 @@ const DoubleLine = styled(Typography)` -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; -` +`; export const VideoCardTitle = styled(DoubleLine)(({ theme }) => ({ fontFamily: "Cairo", fontSize: "16px", letterSpacing: "0.4px", color: theme.palette.text.primary, - userSelect: "none" + userSelect: "none", })); export const VideoCardName = styled(Typography)(({ theme }) => ({ - fontFamily: "Cairo", - fontSize: "14px", - letterSpacing: "0.4px", - color: theme.palette.text.primary, - userSelect: "none", - overflow: "hidden", - whiteSpace: "nowrap", - textOverflow: "ellipsis", - width: "100%", - })); - export const VideoUploadDate = styled(Typography)(({ theme }) => ({ - fontFamily: "Cairo", - fontSize: "12px", - letterSpacing: "0.4px", - color: theme.palette.text.primary, - userSelect: "none" - })); + fontFamily: "Cairo", + fontSize: "14px", + letterSpacing: "0.4px", + color: theme.palette.text.primary, + userSelect: "none", + overflow: "hidden", + whiteSpace: "nowrap", + textOverflow: "ellipsis", + width: "100%", +})); +export const VideoUploadDate = styled(Typography)(({ theme }) => ({ + fontFamily: "Cairo", + fontSize: "12px", + letterSpacing: "0.4px", + color: theme.palette.text.primary, + userSelect: "none", +})); export const BottomParent = styled(Box)(({ theme }) => ({ - display: 'flex', - alignItems: 'flex-start', - flexDirection: 'column' + display: "flex", + alignItems: "flex-start", + flexDirection: "column", })); export const VideoCardDescription = styled(Typography)(({ theme }) => ({ fontFamily: "Karla", fontSize: "20px", letterSpacing: "0px", color: theme.palette.text.primary, - userSelect: "none" + userSelect: "none", })); export const StoreCardOwner = styled(Typography)(({ theme }) => ({ @@ -124,7 +132,7 @@ export const StoreCardOwner = styled(Typography)(({ theme }) => ({ position: "absolute", bottom: "5px", right: "10px", - userSelect: "none" + userSelect: "none", })); export const StoreCardYouOwn = styled(Box)(({ theme }) => ({ @@ -136,7 +144,7 @@ export const StoreCardYouOwn = styled(Box)(({ theme }) => ({ gap: "5px", fontFamily: "Livvic", fontSize: "15px", - color: theme.palette.text.primary + color: theme.palette.text.primary, })); export const MyStoresRow = styled(Grid)(({ theme }) => ({ @@ -144,16 +152,16 @@ export const MyStoresRow = styled(Grid)(({ theme }) => ({ flexDirection: "row", justifyContent: "flex-end", padding: "5px", - width: "100%" + width: "100%", })); export const NameContainer = styled(Box)(({ theme }) => ({ display: "flex", flexDirection: "row", justifyContent: "flex-start", - alignItems: 'center', - gap: '10px', - marginBottom: '10px' + alignItems: "center", + gap: "10px", + marginBottom: "10px", })); export const MyStoresCard = styled(Box)(({ theme }) => ({ @@ -166,14 +174,14 @@ export const MyStoresCard = styled(Box)(({ theme }) => ({ padding: "5px 10px", fontFamily: "Raleway", fontSize: "18px", - color: theme.palette.text.primary + color: theme.palette.text.primary, })); export const MyStoresCheckbox = styled(Checkbox)(({ theme }) => ({ color: "#c0d4ff", "&.Mui-checked": { - color: "#6596ff" - } + color: "#6596ff", + }, })); export const FiltersCol = styled(Grid)(({ theme }) => ({ @@ -183,13 +191,13 @@ export const FiltersCol = styled(Grid)(({ theme }) => ({ padding: "20px 15px", backgroundColor: theme.palette.background.default, borderTop: `1px solid ${theme.palette.background.paper}`, - borderRight: `1px solid ${theme.palette.background.paper}` + borderRight: `1px solid ${theme.palette.background.paper}`, })); export const FiltersContainer = styled(Box)(({ theme }) => ({ display: "flex", flexDirection: "column", - justifyContent: "space-between" + justifyContent: "space-between", })); export const FiltersRow = styled(Box)(({ theme }) => ({ @@ -199,7 +207,7 @@ export const FiltersRow = styled(Box)(({ theme }) => ({ width: "100%", padding: "0 15px", fontSize: "16px", - userSelect: "none" + userSelect: "none", })); export const FiltersTitle = styled(Typography)(({ theme }) => ({ @@ -210,74 +218,73 @@ export const FiltersTitle = styled(Typography)(({ theme }) => ({ fontFamily: "Raleway", fontSize: "17px", color: theme.palette.text.primary, - userSelect: "none" + userSelect: "none", })); export const FiltersCheckbox = styled(Checkbox)(({ theme }) => ({ color: "#c0d4ff", "&.Mui-checked": { - color: "#6596ff" - } + color: "#6596ff", + }, })); export const FilterSelect = styled(Autocomplete)(({ theme }) => ({ "& #categories-select": { - padding: "7px" + padding: "7px", }, "& .MuiSelect-placeholder": { fontFamily: "Raleway", fontSize: "17px", color: theme.palette.text.primary, - userSelect: "none" + userSelect: "none", }, "& MuiFormLabel-root": { fontFamily: "Raleway", fontSize: "17px", color: theme.palette.text.primary, - userSelect: "none" - } + userSelect: "none", + }, })); export const FilterSelectMenuItems = styled(TextField)(({ theme }) => ({ fontFamily: "Raleway", fontSize: "17px", color: theme.palette.text.primary, - userSelect: "none" + userSelect: "none", })); - export const FiltersSubContainer = styled(Box)(({ theme }) => ({ display: "flex", alignItems: "center", flexDirection: "column", - gap: "5px" + gap: "5px", })); export const FilterDropdownLabel = styled(InputLabel)(({ theme }) => ({ fontFamily: "Raleway", fontSize: "16px", - color: theme.palette.text.primary + color: theme.palette.text.primary, })); export const IconsBox = styled(Box)({ - display: 'flex', + display: "flex", gap: "3px", - position: 'absolute', - top: '-20px', - right: '-5px', - transition: 'all 0.3s ease-in-out', + position: "absolute", + top: "-20px", + right: "-5px", + transition: "all 0.3s ease-in-out", }); export const BlockIconContainer = styled(Box)({ - display: 'flex', + display: "flex", boxShadow: "rgba(99, 99, 99, 0.2) 0px 2px 8px 0px;", - backgroundColor: '#fbfbfb', + backgroundColor: "#fbfbfb", color: "#c25252", - padding: '2px', - borderRadius: '3px', - transition: 'all 0.3s ease-in-out', + padding: "2px", + borderRadius: "3px", + transition: "all 0.3s ease-in-out", "&:hover": { - cursor: 'pointer', + cursor: "pointer", transform: "scale(1.1)", - } -}) \ No newline at end of file + }, +}); diff --git a/src/pages/Home/FileList.tsx b/src/pages/Home/FileList.tsx index 1badb83..ff0c07d 100644 --- a/src/pages/Home/FileList.tsx +++ b/src/pages/Home/FileList.tsx @@ -1,326 +1,44 @@ -import React, { useCallback, useEffect, useRef, useState } from "react"; -import { useNavigate } from "react-router-dom"; -import ReactDOM from "react-dom"; -import { useSelector, useDispatch } from "react-redux"; -import { RootState } from "../../state/store"; -import AttachFileIcon from '@mui/icons-material/AttachFile'; -import { - Avatar, - Box, - Button, - FormControl, - Grid, - Input, - InputLabel, - MenuItem, - OutlinedInput, - Select, - SelectChangeEvent, - Skeleton, - Tooltip, - Typography, - useTheme, -} from "@mui/material"; -import { useFetchFiles } from "../../hooks/useFetchFiles.tsx"; -import LazyLoad from "../../components/common/LazyLoad"; +import { Avatar, Box, Skeleton, Tooltip } from "@mui/material"; import { BlockIconContainer, BottomParent, - FilterSelect, - FiltersCheckbox, - FiltersCol, - FiltersContainer, - FiltersRow, - FiltersSubContainer, - FiltersTitle, IconsBox, NameContainer, VideoCard, VideoCardName, VideoCardTitle, - VideoContainer, + FileContainer, VideoUploadDate, } from "./FileList-styles.tsx"; -import ResponsiveImage from "../../components/ResponsiveImage"; -import { formatDate, formatTimestampSeconds } from "../../utils/time"; -import { Subtitle, SubtitleContainer } from "./Home-styles"; -import { ExpandMoreSVG } from "../../assets/svgs/ExpandMoreSVG"; +import EditIcon from "@mui/icons-material/Edit"; import { - addVideos, blockUser, - changeFilterType, - changeSelectedCategoryVideos, - changeSelectedSubCategoryVideos, - changeSelectedSubCategoryVideos2, - changeSelectedSubCategoryVideos3, - changefilterName, - changefilterSearch, - clearVideoList, - setEditPlaylist, - setEditVideo, -} from "../../state/features/videoSlice"; -import { Playlists } from "../../components/Playlists/Playlists"; -import { PlaylistSVG } from "../../assets/svgs/PlaylistSVG"; + setEditFile, + Video, +} from "../../state/features/fileSlice.ts"; import BlockIcon from "@mui/icons-material/Block"; -import EditIcon from '@mui/icons-material/Edit'; -import { formatBytes } from "../VideoContent/VideoContent"; -import {categories, icons, subCategories, subCategories2, subCategories3} from "../../constants/Categories.ts"; +import AttachFileIcon from "@mui/icons-material/AttachFile"; +import { formatBytes } from "../FileContent/FileContent.tsx"; +import { formatDate } from "../../utils/time.ts"; +import React, { useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { RootState } from "../../state/store.ts"; +import { useNavigate } from "react-router-dom"; +import { getIconsFromObject } from "../../constants/Categories/CategoryFunctions.ts"; -interface VideoListProps { - mode?: string; +interface FileListProps { + files: Video[]; } -export const FileList = ({ mode }: VideoListProps) => { - const theme = useTheme(); - const prevVal = useRef(""); - const isFiltering = useSelector( - (state: RootState) => state.video.isFiltering - ); - const filterValue = useSelector( - (state: RootState) => state.video.filterValue - ); - const [isLoading, setIsLoading] = useState(false); - const [showIcons, setShowIcons] = useState(null); - - const filterType = useSelector((state: RootState) => state.video.filterType); - - const setFilterType = (payload) => { - dispatch(changeFilterType(payload)); - }; - const filterSearch = useSelector( - (state: RootState) => state.video.filterSearch - ); - - const setFilterSearch = (payload) => { - dispatch(changefilterSearch(payload)); - }; - const filterName = useSelector((state: RootState) => state.video.filterName); - - const setFilterName = (payload) => { - dispatch(changefilterName(payload)); - }; - - const selectedCategoryVideos = useSelector( - (state: RootState) => state.video.selectedCategoryVideos - ); - - const setSelectedCategoryVideos = (payload) => { - dispatch(changeSelectedCategoryVideos(payload)); - }; - - const selectedSubCategoryVideos = useSelector( - (state: RootState) => state.video.selectedSubCategoryVideos - ); - const selectedSubCategoryVideos2 = useSelector( - (state: RootState) => state.video.selectedSubCategoryVideos2 - ); - const selectedSubCategoryVideos3 = useSelector( - (state: RootState) => state.video.selectedSubCategoryVideos3 +export const FileList = ({ files }: FileListProps) => { + const hashMapFiles = useSelector( + (state: RootState) => state.file.hashMapFiles ); - const setSelectedSubCategoryVideos = (payload) => { - dispatch(changeSelectedSubCategoryVideos(payload)); - }; - const setSelectedSubCategoryVideos2 = (payload) => { - dispatch(changeSelectedSubCategoryVideos2(payload)); - }; - const setSelectedSubCategoryVideos3 = (payload) => { - dispatch(changeSelectedSubCategoryVideos3(payload)); - }; - - const dispatch = useDispatch(); - const filteredVideos = useSelector( - (state: RootState) => state.video.filteredVideos - ); + const [showIcons, setShowIcons] = useState(null); const username = useSelector((state: RootState) => state.auth?.user?.name); - const isFilterMode = useRef(false); - const firstFetch = useRef(false); - const afterFetch = useRef(false); - const isFetchingFiltered = useRef(false); - const isFetching = useRef(false); - const hashMapVideos = useSelector( - (state: RootState) => state.video.hashMapVideos - ); - - const countNewVideos = useSelector( - (state: RootState) => state.video.countNewVideos - ); - const userAvatarHash = useSelector( - (state: RootState) => state.global.userAvatarHash - ); - - const { videos: globalVideos } = useSelector( - (state: RootState) => state.video - ); + const dispatch = useDispatch(); const navigate = useNavigate(); - const { getFiles, getNewFiles, checkNewFiles, getFilesFiltered } = - useFetchFiles(); - - const getFilesHandler = React.useCallback( - async (reset?: boolean, resetFilers?: boolean) => { - - - if (!firstFetch.current || !afterFetch.current) return; - if (isFetching.current) return; - isFetching.current = true; - console.log({ - category: selectedCategoryVideos?.id, - subcategory: selectedSubCategoryVideos?.id, - subcategory2: selectedSubCategoryVideos2?.id, - subcategory3: selectedSubCategoryVideos3?.id, - }) - await getFiles( - { - name: filterName, - category: selectedCategoryVideos?.id, - subcategory: selectedSubCategoryVideos?.id, - subcategory2: selectedSubCategoryVideos2?.id, - subcategory3: selectedSubCategoryVideos3?.id, - keywords: filterSearch, - type: filterType, - }, - reset ? true : false, - resetFilers - ); - isFetching.current = false; - }, - [ - getFiles, - filterValue, - getFilesFiltered, - isFiltering, - filterName, - selectedCategoryVideos, - selectedSubCategoryVideos, - selectedSubCategoryVideos2, - selectedSubCategoryVideos3, - filterSearch, - filterType, - ] - ); - - const searchOnEnter = e => { - if (e.keyCode == 13) { - getFilesHandler(true); - } - }; - - useEffect(() => { - if (isFiltering && filterValue !== prevVal?.current) { - prevVal.current = filterValue; - getFilesHandler(); - } - }, [filterValue, isFiltering, filteredVideos]); - - const getFilesHandlerMount = React.useCallback(async () => { - if (firstFetch.current) return; - firstFetch.current = true; - setIsLoading(true); - - await getFiles(); - afterFetch.current = true; - isFetching.current = false; - - setIsLoading(false); - }, [getFiles]); - - let videos = globalVideos; - - if (isFiltering) { - videos = filteredVideos; - isFilterMode.current = true; - } else { - isFilterMode.current = false; - } - // const interval = useRef(null); - - // const checkNewVideosFunc = useCallback(() => { - // let isCalling = false; - // interval.current = setInterval(async () => { - // if (isCalling || !firstFetch.current) return; - // isCalling = true; - // await checkNewVideos(); - // isCalling = false; - // }, 30000); // 1 second interval - // }, [checkNewVideos]); - - // useEffect(() => { - // if (isFiltering && interval.current) { - // clearInterval(interval.current); - // return; - // } - // checkNewVideosFunc(); - - // return () => { - // if (interval?.current) { - // clearInterval(interval.current); - // } - // }; - // }, [mode, checkNewVideosFunc, isFiltering]); - - useEffect(() => { - if ( - !firstFetch.current && - !isFilterMode.current && - globalVideos.length === 0 - ) { - isFetching.current = true; - getFilesHandlerMount(); - } else { - firstFetch.current = true; - afterFetch.current = true; - } - }, [getFilesHandlerMount, globalVideos]); - - const filtersToDefault = async () => { - setFilterType("videos"); - setFilterSearch(""); - setFilterName(""); - setSelectedCategoryVideos(null); - setSelectedSubCategoryVideos(null); - - ReactDOM.flushSync(() => { - getFilesHandler(true, true); - }); - }; - - const handleOptionCategoryChangeVideos = ( - event: SelectChangeEvent - ) => { - const optionId = event.target.value; - const selectedOption = categories.find((option) => option.id === +optionId); - setSelectedCategoryVideos(selectedOption || null); - }; - const handleOptionSubCategoryChangeVideos = ( - event: SelectChangeEvent, - subcategories: any[] - ) => { - const optionId = event.target.value; - const selectedOption = subcategories.find( - (option) => option.id === +optionId - ); - setSelectedSubCategoryVideos(selectedOption || null); - }; - const handleOptionSubCategoryChangeVideos2 = ( - event: SelectChangeEvent, - subcategories: any[] - ) => { - const optionId = event.target.value; - const selectedOption = subcategories.find( - (option) => option.id === +optionId - ); - setSelectedSubCategoryVideos2(selectedOption || null); - }; - const handleOptionSubCategoryChangeVideos3 = ( - event: SelectChangeEvent, - subcategories: any[] - ) => { - const optionId = event.target.value; - const selectedOption = subcategories.find( - (option) => option.id === +optionId - ); - setSelectedSubCategoryVideos3(selectedOption || null); - }; const blockUserFunc = async (user: string) => { if (user === "Q-Share") return; @@ -332,520 +50,158 @@ export const FileList = ({ mode }: VideoListProps) => { }); if (response === true) { - dispatch(blockUser(user)) + dispatch(blockUser(user)); } } catch (error) {} }; return ( - - - - { - setFilterSearch(e.target.value); - }} - onKeyDown={searchOnEnter} - value={filterSearch} - placeholder="Search" - sx={{ - borderBottom: "1px solid white", - "&&:before": { - borderBottom: "none", - }, - "&&:after": { - borderBottom: "none", - }, - "&&:hover:before": { - borderBottom: "none", - }, - "&&.Mui-focused:before": { - borderBottom: "none", - }, - "&&.Mui-focused": { - outline: "none", - }, - fontSize: "18px", - }} - /> - { - setFilterName(e.target.value); - }} - onKeyDown={searchOnEnter} - value={filterName} - placeholder="User's Name (Exact)" - sx={{ - marginTop: "20px", - borderBottom: "1px solid white", - "&&:before": { - borderBottom: "none", - }, - "&&:after": { - borderBottom: "none", - }, - "&&:hover:before": { - borderBottom: "none", - }, - "&&.Mui-focused:before": { - borderBottom: "none", - }, - "&&.Mui-focused": { - outline: "none", - }, - fontSize: "18px", - }} - /> - - Categories - - - - - - - - Category - - - - {selectedCategoryVideos && - subCategories[selectedCategoryVideos?.id] && ( - - - Sub-Category - - - - )} - {selectedSubCategoryVideos && - subCategories2[selectedSubCategoryVideos?.id] && ( - - - Sub-2x-Category - - - - )} - {selectedSubCategoryVideos2 && - subCategories3[selectedSubCategoryVideos2?.id] && ( - - - Sub-3x-Category - - - - )} - - - - {/* - Type - - - - - Videos - ) => { - setFilterType("videos"); - }} - inputProps={{ "aria-label": "controlled" }} - /> - - - Playlists - ) => { - setFilterType("playlists"); - }} - inputProps={{ "aria-label": "controlled" }} - /> - - */} - - - - - - - setShowIcons(fileObj.id)} + onMouseLeave={() => setShowIcons(null)} > - - - - - {videos.map((video: any, index: number) => { - const existingVideo = hashMapVideos[video?.id]; - let hasHash = false; - let videoObj = video; - if (existingVideo) { - videoObj = existingVideo; - hasHash = true; - } - - const category = categories?.find(item => item?.id === videoObj?.category); - const subcategory = subCategories[category?.id]?.find(item => item?.id === videoObj?.subcategory); - const subcategory2 = subCategories2[subcategory?.id]?.find(item => item.id === videoObj?.subcategory2); - const subcategory3 = subCategories3[subcategory2?.id]?.find(item => item.id === videoObj?.subcategory3); - - const catId = category?.id || null; - const subId = subcategory?.id || null; - const sub2Id = subcategory2?.id || null; - const sub3Id = subcategory3?.id || null; - - const icon = icons[sub3Id] || icons[sub2Id] || icons[subId] || icons[catId] || null; - - - - - - - return ( - + setShowIcons(videoObj.id)} - onMouseLeave={() => setShowIcons(null)} > - {hasHash ? ( - <> - - {videoObj?.user === username && ( - - - { - dispatch(setEditVideo(videoObj)); - }} - /> - - - - )} - - + {fileObj?.user === username && ( + - { - blockUserFunc(videoObj?.user); + dispatch(setEditFile(fileObj)); }} /> - - { - navigate(`/share/${videoObj?.user}/${videoObj?.id}`); - }} + )} + + + + { + blockUserFunc(fileObj?.user); + }} + /> + + + + { + navigate(`/share/${fileObj?.user}/${fileObj?.id}`); + }} + sx={{ + height: "100%", + width: "100%", + display: "flex", + gap: "25px", + flexDirection: "row", + justifyContent: "space-between", + }} + > + - - - {icon ? : ( - + {icon ? ( + + ) : ( + + )} + + + {formatBytes( + fileObj?.files.reduce( + (acc, cur) => acc + (cur?.size || 0), + 0 + ) )} - - - {formatBytes(videoObj?.files.reduce((acc, cur) => acc + (cur?.size || 0), 0))} - - {videoObj.title} - - - - - - - { - e.stopPropagation(); - navigate(`/channel/${videoObj?.user}`); + + {fileObj.title} + + + { + e.stopPropagation(); + navigate(`/channel/${fileObj?.user}`); + }} + > + + - - - {videoObj?.user} - - - - {videoObj?.created && ( - - {formatDate(videoObj.created)} - - )} - - - - ) : ( - - )} - - - ); - })} - - - - - - + {fileObj?.user} + + + + {fileObj?.created && ( + + {formatDate(fileObj.created)} + + )} + + + + ) : ( + + )} + + ); + })} + ); }; diff --git a/src/pages/Home/FileListComponentLevel.tsx b/src/pages/Home/FileListComponentLevel.tsx index e576de6..8b1229d 100644 --- a/src/pages/Home/FileListComponentLevel.tsx +++ b/src/pages/Home/FileListComponentLevel.tsx @@ -1,8 +1,8 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react' -import { useNavigate, useParams } from 'react-router-dom' -import { useSelector } from 'react-redux' -import { RootState } from '../../state/store' -import AttachFileIcon from '@mui/icons-material/AttachFile'; +import React, { useCallback, useEffect, useRef, useState } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { useSelector } from "react-redux"; +import { RootState } from "../../state/store"; +import AttachFileIcon from "@mui/icons-material/AttachFile"; import { Avatar, @@ -10,62 +10,72 @@ import { Button, Skeleton, Typography, - useTheme -} from '@mui/material' -import { useFetchFiles } from '../../hooks/useFetchFiles.tsx' -import LazyLoad from '../../components/common/LazyLoad' -import { BottomParent, NameContainer, VideoCard, VideoCardName, VideoCardTitle, VideoContainer, VideoUploadDate } from './FileList-styles.tsx' -import ResponsiveImage from '../../components/ResponsiveImage' -import { formatDate, formatTimestampSeconds } from '../../utils/time' -import { Video } from '../../state/features/videoSlice' -import { queue } from '../../wrappers/GlobalWrapper' -import { QSHARE_FILE_BASE } from '../../constants/Identifiers.ts' -import { formatBytes } from '../VideoContent/VideoContent' -import {categories, icons, subCategories, subCategories2, subCategories3} from "../../constants/Categories.ts"; + useTheme, +} from "@mui/material"; +import { useFetchFiles } from "../../hooks/useFetchFiles.tsx"; +import LazyLoad from "../../components/common/LazyLoad"; +import { + BottomParent, + NameContainer, + VideoCard, + VideoCardName, + VideoCardTitle, + FileContainer, + VideoUploadDate, +} from "./FileList-styles.tsx"; +import ResponsiveImage from "../../components/ResponsiveImage"; +import { formatDate, formatTimestampSeconds } from "../../utils/time"; +import { Video } from "../../state/features/fileSlice.ts"; +import { queue } from "../../wrappers/GlobalWrapper"; +import { QSHARE_FILE_BASE } from "../../constants/Identifiers.ts"; +import { formatBytes } from "../FileContent/FileContent.tsx"; +import { + firstCategories, + secondCategories, + thirdCategories, + fourthCategories, + iconCategories, +} from "../../constants/Categories/1stCategories.ts"; +import { getCategoriesFromObject } from "../../components/common/CategoryList/CategoryList.tsx"; +import { + findAllCategoryData, + findCategoryData, + getCategoriesWithIcons, + getIconsFromObject, +} from "../../constants/Categories/CategoryFunctions.ts"; interface VideoListProps { - mode?: string + mode?: string; } export const FileListComponentLevel = ({ mode }: VideoListProps) => { - const { name: paramName } = useParams() - const theme = useTheme() - const [isLoading, setIsLoading] = useState(true) + const { name: paramName } = useParams(); + const theme = useTheme(); + const [isLoading, setIsLoading] = useState(true); - const firstFetch = useRef(false) - const afterFetch = useRef(false) + const firstFetch = useRef(false); + const afterFetch = useRef(false); const hashMapVideos = useSelector( - (state: RootState) => state.video.hashMapVideos - ) - - const countNewVideos = useSelector( - (state: RootState) => state.video.countNewVideos - ) - const userAvatarHash = useSelector( - (state: RootState) => state.global.userAvatarHash - ) - - const [videos, setVideos] = React.useState([]) - - const navigate = useNavigate() - const { - getVideo, - getNewFiles, - checkNewFiles, - checkAndUpdateVideo - } = useFetchFiles() + (state: RootState) => state.file.hashMapFiles + ); + + const [videos, setVideos] = React.useState([]); + + const navigate = useNavigate(); + const { getFile, getNewFiles, checkNewFiles, checkAndUpdateFile } = + useFetchFiles(); const getVideos = React.useCallback(async () => { try { - const offset = videos.length - const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QSHARE_FILE_BASE}_&limit=50&includemetadata=false&reverse=true&excludeblocked=true&name=${paramName}&exactmatchnames=true&offset=${offset}` + const offset = videos.length; + const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QSHARE_FILE_BASE}_&limit=50&includemetadata=false&reverse=true&excludeblocked=true&name=${paramName}&exactmatchnames=true&offset=${offset}`; const response = await fetch(url, { - method: 'GET', + method: "GET", headers: { - 'Content-Type': 'application/json' - } - }) - const responseData = await response.json() - + "Content-Type": "application/json", + }, + }); + const responseData = await response.json(); + const structureData = responseData.map((video: any): Video => { return { title: video?.metadata?.title, @@ -76,161 +86,144 @@ export const FileListComponentLevel = ({ mode }: VideoListProps) => { created: video?.created, updated: video?.updated, user: video.name, - videoImage: '', - id: video.identifier - } - }) - - const copiedVideos: Video[] = [...videos] + videoImage: "", + id: video.identifier, + }; + }); + + const copiedVideos: Video[] = [...videos]; structureData.forEach((video: Video) => { - const index = videos.findIndex((p) => p.id === video.id) + const index = videos.findIndex(p => p.id === video.id); if (index !== -1) { - copiedVideos[index] = video + copiedVideos[index] = video; } else { - copiedVideos.push(video) + copiedVideos.push(video); } - }) - setVideos(copiedVideos) + }); + setVideos(copiedVideos); for (const content of structureData) { if (content.user && content.id) { - const res = checkAndUpdateVideo(content) + const res = checkAndUpdateFile(content); if (res) { - queue.push(() => getVideo(content.user, content.id, content)); - + queue.push(() => getFile(content.user, content.id, content)); } } } } catch (error) { } finally { - } - }, [videos, hashMapVideos]) + }, [videos, hashMapVideos]); - const getVideosHandler = React.useCallback(async () => { - if(!firstFetch.current || !afterFetch.current) return - await getVideos() - }, [getVideos]) - + if (!firstFetch.current || !afterFetch.current) return; + await getVideos(); + }, [getVideos]); const getVideosHandlerMount = React.useCallback(async () => { - if(firstFetch.current) return - firstFetch.current = true - await getVideos() - afterFetch.current = true - setIsLoading(false) - }, [getVideos]) - - + if (firstFetch.current) return; + firstFetch.current = true; + await getVideos(); + afterFetch.current = true; + setIsLoading(false); + }, [getVideos]); - - - useEffect(()=> { - if(!firstFetch.current){ - getVideosHandlerMount() + useEffect(() => { + if (!firstFetch.current) { + getVideosHandlerMount(); } + }, [getVideosHandlerMount]); - }, [getVideosHandlerMount ]) - - return ( - - - - - {videos.map((video: any, index: number) => { - const existingVideo = hashMapVideos[video?.id]; - let hasHash = false; - let videoObj = video; - if (existingVideo) { - videoObj = existingVideo; - hasHash = true; - } - + + + {videos.map((file: any, index: number) => { + const existingFile = hashMapVideos[file?.id]; + let hasHash = false; + let fileObj = file; + if (existingFile) { + fileObj = existingFile; + hasHash = true; + } - const category = categories?.find(item => item?.id === videoObj?.category); - const subcategory = subCategories[category?.id]?.find(item => item?.id === videoObj?.subcategory); - const subcategory2 = subCategories2[subcategory?.id]?.find(item => item.id === videoObj?.subcategory2); - const subcategory3 = subCategories3[subcategory2?.id]?.find(item => item.id === videoObj?.subcategory3); - - const catId = category?.id || null; - const subId = subcategory?.id || null; - const sub2Id = subcategory2?.id || null; - const sub3Id = subcategory3?.id || null; - - const icon = icons[sub3Id] || icons[sub2Id] || icons[subId] || icons[catId] || null; - - - + const icon = getIconsFromObject(fileObj); - return ( - - {hasHash ? ( - <> + return ( + + {hasHash ? ( + <> { - navigate(`/share/${videoObj?.user}/${videoObj?.id}`); + navigate(`/share/${fileObj?.user}/${fileObj?.id}`); }} sx={{ - height: '100%', - width: '100%', - display: 'flex', - gap: '25px', - flexDirection: 'row', - justifyContent: 'space-between' + height: "100%", + width: "100%", + display: "flex", + gap: "25px", + flexDirection: "row", + justifyContent: "space-between", }} > - - - {icon ? : ( - + + {icon ? ( + + ) : ( + )} - - {formatBytes(videoObj?.files.reduce((acc, cur) => acc + (cur?.size || 0), 0))} - - {videoObj.title} - - - - + + {formatBytes( + fileObj?.files.reduce( + (acc, cur) => acc + (cur?.size || 0), + 0 + ) + )} + + {fileObj.title} { + onClick={e => { e.stopPropagation(); - navigate(`/channel/${videoObj?.user}`); + navigate(`/channel/${fileObj?.user}`); }} > { }, }} > - {videoObj?.user} + {fileObj?.user} - {videoObj?.created && ( + {fileObj?.created && ( - {formatDate(videoObj.created)} + {formatDate(fileObj.created)} )} - - ) : ( - - )} - - - ); - })} - + + ) : ( + + )} + + ); + })} + - ) -} - - + ); +}; diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx index fa1e5b0..48620be 100644 --- a/src/pages/Home/Home.tsx +++ b/src/pages/Home/Home.tsx @@ -1,15 +1,323 @@ -import React from 'react' -import { FileList } from './FileList.tsx' +import React, { useEffect, useRef, useState } from "react"; +import ReactDOM from "react-dom"; +import { useDispatch, useSelector } from "react-redux"; +import { RootState } from "../../state/store"; +import { FileList } from "./FileList.tsx"; +import { Box, Button, Grid, Input, useTheme } from "@mui/material"; +import { useFetchFiles } from "../../hooks/useFetchFiles.tsx"; +import LazyLoad from "../../components/common/LazyLoad"; +import { FiltersCol, FiltersContainer } from "./FileList-styles.tsx"; +import { SubtitleContainer } from "./Home-styles"; +import { + changefilterName, + changefilterSearch, + changeFilterType, +} from "../../state/features/fileSlice.ts"; +import { allCategoryData } from "../../constants/Categories/1stCategories.ts"; +import { + CategoryList, + CategoryListRef, +} from "../../components/common/CategoryList/CategoryList.tsx"; +import { StatsData } from "../../components/StatsData.tsx"; -import { useSelector } from 'react-redux' -import { RootState } from '../../state/store' +interface HomeProps { + mode?: string; +} +export const Home = ({ mode }: HomeProps) => { + const theme = useTheme(); + const prevVal = useRef(""); + const categoryListRef = useRef(null); + const isFiltering = useSelector((state: RootState) => state.file.isFiltering); + const filterValue = useSelector((state: RootState) => state.file.filterValue); + const [isLoading, setIsLoading] = useState(false); + const filterType = useSelector((state: RootState) => state.file.filterType); + const totalFilesPublished = useSelector( + (state: RootState) => state.global.totalFilesPublished + ); + const totalNamesPublished = useSelector( + (state: RootState) => state.global.totalNamesPublished + ); + const filesPerNamePublished = useSelector( + (state: RootState) => state.global.filesPerNamePublished + ); + const setFilterType = payload => { + dispatch(changeFilterType(payload)); + }; + const filterSearch = useSelector( + (state: RootState) => state.file.filterSearch + ); + + const setFilterSearch = payload => { + dispatch(changefilterSearch(payload)); + }; + const filterName = useSelector((state: RootState) => state.file.filterName); + + const setFilterName = payload => { + dispatch(changefilterName(payload)); + }; + + const isFilterMode = useRef(false); + const firstFetch = useRef(false); + const afterFetch = useRef(false); + const isFetchingFiltered = useRef(false); + const isFetching = useRef(false); + + const countNewFiles = useSelector( + (state: RootState) => state.file.countNewFiles + ); + const userAvatarHash = useSelector( + (state: RootState) => state.global.userAvatarHash + ); + + const { files: globalVideos } = useSelector((state: RootState) => state.file); + + const setSelectedCategoryFiles = payload => {}; + + const dispatch = useDispatch(); + const filteredFiles = useSelector( + (state: RootState) => state.file.filteredFiles + ); + + const { + getFiles, + checkAndUpdateFile, + getFile, + hashMapFiles, + getNewFiles, + checkNewFiles, + getFilesFiltered, + getFilesCount, + } = useFetchFiles(); + + const getFilesHandler = React.useCallback( + async (reset?: boolean, resetFilers?: boolean) => { + if (!firstFetch.current || !afterFetch.current) return; + if (isFetching.current) return; + isFetching.current = true; + const selectedCategories = + categoryListRef.current.getSelectedCategories() || []; + + await getFiles( + { + name: filterName, + categories: selectedCategories, + keywords: filterSearch, + type: filterType, + }, + reset, + resetFilers + ); + isFetching.current = false; + }, + [ + getFiles, + filterValue, + getFilesFiltered, + isFiltering, + filterName, + filterSearch, + filterType, + ] + ); + + const searchOnEnter = e => { + if (e.keyCode == 13) { + getFilesHandler(true); + } + }; + + useEffect(() => { + if (isFiltering && filterValue !== prevVal?.current) { + prevVal.current = filterValue; + getFilesHandler(); + } + }, [filterValue, isFiltering, filteredFiles, getFilesCount]); -export const Home = () => { + const getFilesHandlerMount = React.useCallback(async () => { + if (firstFetch.current) return; + firstFetch.current = true; + setIsLoading(true); + + await getFiles(); + afterFetch.current = true; + isFetching.current = false; + + setIsLoading(false); + }, [getFiles]); + + let videos = globalVideos; + + if (isFiltering) { + videos = filteredFiles; + isFilterMode.current = true; + } else { + isFilterMode.current = false; + } + + // const interval = useRef(null); + + // const checkNewVideosFunc = useCallback(() => { + // let isCalling = false; + // interval.current = setInterval(async () => { + // if (isCalling || !firstFetch.current) return; + // isCalling = true; + // await checkNewVideos(); + // isCalling = false; + // }, 30000); // 1 second interval + // }, [checkNewVideos]); + + // useEffect(() => { + // if (isFiltering && interval.current) { + // clearInterval(interval.current); + // return; + // } + // checkNewVideosFunc(); + + // return () => { + // if (interval?.current) { + // clearInterval(interval.current); + // } + // }; + // }, [mode, checkNewVideosFunc, isFiltering]); + + useEffect(() => { + if ( + !firstFetch.current && + !isFilterMode.current && + globalVideos.length === 0 + ) { + isFetching.current = true; + getFilesHandlerMount(); + } else { + firstFetch.current = true; + afterFetch.current = true; + } + }, [getFilesHandlerMount, globalVideos]); + + const filtersToDefault = async () => { + setFilterType("videos"); + setFilterSearch(""); + setFilterName(""); + categoryListRef.current?.clearCategories(); + + ReactDOM.flushSync(() => { + getFilesHandler(true, true); + }); + }; return ( - <> - - - - ) -} + + + + + { + setFilterSearch(e.target.value); + }} + onKeyDown={searchOnEnter} + value={filterSearch} + placeholder="Search" + sx={{ + borderBottom: "1px solid white", + "&&:before": { + borderBottom: "none", + }, + "&&:after": { + borderBottom: "none", + }, + "&&:hover:before": { + borderBottom: "none", + }, + "&&.Mui-focused:before": { + borderBottom: "none", + }, + "&&.Mui-focused": { + outline: "none", + }, + fontSize: "18px", + }} + /> + { + setFilterName(e.target.value); + }} + onKeyDown={searchOnEnter} + value={filterName} + placeholder="User's Name (Exact)" + sx={{ + marginTop: "20px", + borderBottom: "1px solid white", + "&&:before": { + borderBottom: "none", + }, + "&&:after": { + borderBottom: "none", + }, + "&&:hover:before": { + borderBottom: "none", + }, + "&&.Mui-focused:before": { + borderBottom: "none", + }, + "&&.Mui-focused": { + outline: "none", + }, + fontSize: "18px", + }} + /> + + + + + + + + + + + + + + + ); +}; diff --git a/src/pages/IndividualProfile/IndividualProfile.tsx b/src/pages/IndividualProfile/IndividualProfile.tsx index 0061933..d77ca1c 100644 --- a/src/pages/IndividualProfile/IndividualProfile.tsx +++ b/src/pages/IndividualProfile/IndividualProfile.tsx @@ -1,64 +1,69 @@ -import React, { useMemo } from 'react' -import { FileListComponentLevel } from '../Home/FileListComponentLevel.tsx' -import { HeaderContainer, ProfileContainer } from './Profile-styles' -import { AuthorTextComment, StyledCardColComment, StyledCardHeaderComment } from '../VideoContent/VideoContent-styles' -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 React, { useMemo } from "react"; +import { FileListComponentLevel } from "../Home/FileListComponentLevel.tsx"; +import { HeaderContainer, ProfileContainer } from "./Profile-styles"; +import { + AuthorTextComment, + StyledCardColComment, + StyledCardHeaderComment, +} from "../FileContent/FileContent-styles.tsx"; +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"; export const IndividualProfile = () => { - const { name: paramName } = useParams() + const { name: paramName } = useParams(); const userAvatarHash = useSelector( (state: RootState) => state.global.userAvatarHash - ) - const theme = useTheme() + ); + const theme = useTheme(); - - - const avatarUrl = useMemo(()=> { - let url = '' - if(paramName && userAvatarHash[paramName]){ - url = userAvatarHash[paramName] + const avatarUrl = useMemo(() => { + let url = ""; + if (paramName && userAvatarHash[paramName]) { + url = userAvatarHash[paramName]; } - return url - }, [userAvatarHash, paramName]) + return url; + }, [userAvatarHash, paramName]); return ( - + - + {paramName} - - - ) -} + ); +}; diff --git a/src/pages/VideoContent/VideoContent-styles.tsx b/src/pages/VideoContent/VideoContent-styles.tsx deleted file mode 100644 index 0eef234..0000000 --- a/src/pages/VideoContent/VideoContent-styles.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { styled } from "@mui/system"; -import { Box, Grid, Typography, Checkbox } from "@mui/material"; - -export const VideoPlayerContainer = styled(Box)(({ theme }) => ({ - maxWidth: '95%', - width: '1000px', - display: 'flex', - flexDirection: 'column', - alignItems: 'flex-start', -})); - -export const VideoTitle = styled(Typography)(({ theme }) => ({ - fontFamily: "Raleway", - fontSize: "20px", - color: theme.palette.text.primary, - userSelect: "none", - wordBreak: "break-word" -})); - -export const VideoDescription = styled(Typography)(({ theme }) => ({ - fontFamily: "Raleway", - fontSize: "16px", - color: theme.palette.text.primary, - userSelect: "none", - wordBreak: "break-word" -})); - -export const Spacer = ({height}: any)=> { - return -} - -export const StyledCardHeaderComment = styled(Box)({ - display: 'flex', - alignItems: 'center', - justifyContent: 'flex-start', - gap: '5px', - padding: '7px 0px' -}) -export const StyledCardCol = styled(Box)({ - display: 'flex', - overflow: 'hidden', - flexDirection: 'column', - gap: '2px', - alignItems: 'flex-start', - width: '100%' -}) - -export const StyledCardColComment = styled(Box)({ - display: 'flex', - overflow: 'hidden', - flexDirection: 'column', - gap: '2px', - alignItems: 'flex-start', - width: '100%' -}) - -export const AuthorTextComment = styled(Typography)({ - fontFamily: 'Raleway, sans-serif', - fontSize: '16px', - lineHeight: '1.2' -}) - -export const FileAttachmentContainer = styled(Box)(({ theme }) =>({ - display: "flex", - alignItems: "center", - gap: "20px", - padding: "5px 10px", - border: `1px solid ${theme.palette.text.primary}`, -})); - -export const FileAttachmentFont = styled(Typography)(({ theme }) => ({ - fontFamily: "Mulish", - color: theme.palette.text.primary, - fontSize: "16px", - letterSpacing: 0, - fontWeight: 400, - userSelect: "none", - whiteSpace: 'nowrap' -})); \ No newline at end of file diff --git a/src/state/features/fileSlice.ts b/src/state/features/fileSlice.ts new file mode 100644 index 0000000..0621c37 --- /dev/null +++ b/src/state/features/fileSlice.ts @@ -0,0 +1,188 @@ +import { createSlice } from "@reduxjs/toolkit"; +import { RootState } from "../store"; + +interface GlobalState { + files: Video[]; + filteredFiles: Video[]; + hashMapFiles: Record; + countNewFiles: number; + isFiltering: boolean; + filterValue: string; + filterType: string; + filterSearch: string; + filterName: string; + selectedCategoryFiles: any[]; + editFileProperties: any; + editPlaylistProperties: any; +} +const initialState: GlobalState = { + files: [], + filteredFiles: [], + hashMapFiles: {}, + countNewFiles: 0, + isFiltering: false, + filterValue: "", + filterType: "videos", + filterSearch: "", + filterName: "", + selectedCategoryFiles: [null, null, null, null], + editFileProperties: null, + editPlaylistProperties: null, +}; + +export interface Video { + title: string; + description: string; + created: number | string; + user: string; + service?: string; + videoImage?: string; + id: string; + category?: string; + categoryName?: string; + tags?: string[]; + updated?: number | string; + isValid?: boolean; + code?: string; +} + +export const fileSlice = createSlice({ + name: "file", + initialState, + reducers: { + setEditFile: (state, action) => { + state.editFileProperties = action.payload; + }, + setEditPlaylist: (state, action) => { + state.editPlaylistProperties = action.payload; + }, + changeFilterType: (state, action) => { + state.filterType = action.payload; + }, + changefilterSearch: (state, action) => { + state.filterSearch = action.payload; + }, + changefilterName: (state, action) => { + state.filterName = action.payload; + }, + setCountNewFiles: (state, action) => { + state.countNewFiles = action.payload; + }, + addFiles: (state, action) => { + state.files = action.payload; + }, + addFilteredFiles: (state, action) => { + state.filteredFiles = action.payload; + }, + removeFile: (state, action) => { + const idToDelete = action.payload; + state.files = state.files.filter(item => item.id !== idToDelete); + state.filteredFiles = state.filteredFiles.filter( + item => item.id !== idToDelete + ); + }, + addFileToBeginning: (state, action) => { + state.files.unshift(action.payload); + }, + clearFileList: state => { + state.files = []; + }, + updateFile: (state, action) => { + const { id } = action.payload; + const index = state.files.findIndex(video => video.id === id); + if (index !== -1) { + state.files[index] = { ...action.payload }; + } + const index2 = state.filteredFiles.findIndex(video => video.id === id); + if (index2 !== -1) { + state.filteredFiles[index2] = { ...action.payload }; + } + }, + addToHashMap: (state, action) => { + const video = action.payload; + state.hashMapFiles[video.id] = video; + }, + updateInHashMap: (state, action) => { + const { id } = action.payload; + const video = action.payload; + state.hashMapFiles[id] = { ...video }; + }, + removeFromHashMap: (state, action) => { + const idToDelete = action.payload; + delete state.hashMapFiles[idToDelete]; + }, + addArrayToHashMap: (state, action) => { + const videos = action.payload; + videos.forEach((video: Video) => { + state.hashMapFiles[video.id] = video; + }); + }, + upsertFiles: (state, action) => { + action.payload.forEach((video: Video) => { + const index = state.files.findIndex(p => p.id === video.id); + if (index !== -1) { + state.files[index] = video; + } else { + state.files.push(video); + } + }); + }, + upsertFilteredFiles: (state, action) => { + action.payload.forEach((video: Video) => { + const index = state.filteredFiles.findIndex(p => p.id === video.id); + if (index !== -1) { + state.filteredFiles[index] = video; + } else { + state.filteredFiles.push(video); + } + }); + }, + upsertFilesBeginning: (state, action) => { + action.payload.reverse().forEach((video: Video) => { + const index = state.files.findIndex(p => p.id === video.id); + if (index !== -1) { + state.files[index] = video; + } else { + state.files.unshift(video); + } + }); + }, + setIsFiltering: (state, action) => { + state.isFiltering = action.payload; + }, + setFilterValue: (state, action) => { + state.filterValue = action.payload; + }, + blockUser: (state, action) => { + const username = action.payload; + state.files = state.files.filter(item => item.user !== username); + }, + }, +}); + +export const { + setCountNewFiles, + addFiles, + addFilteredFiles, + removeFile, + addFileToBeginning, + updateFile, + addToHashMap, + updateInHashMap, + removeFromHashMap, + addArrayToHashMap, + upsertFiles, + upsertFilteredFiles, + upsertFilesBeginning, + setIsFiltering, + setFilterValue, + clearFileList, + changeFilterType, + changefilterSearch, + changefilterName, + blockUser, + setEditFile, + setEditPlaylist, +} = fileSlice.actions; + +export default fileSlice.reducer; diff --git a/src/state/features/globalSlice.ts b/src/state/features/globalSlice.ts index ee2765b..13f3415 100644 --- a/src/state/features/globalSlice.ts +++ b/src/state/features/globalSlice.ts @@ -1,54 +1,68 @@ -import { createSlice } from '@reduxjs/toolkit' - +import { createSlice } from "@reduxjs/toolkit"; interface GlobalState { - isLoadingGlobal: boolean - downloads: any - userAvatarHash: Record - publishNames: string[] | null - videoPlaying: any | null + isLoadingGlobal: boolean; + downloads: any; + userAvatarHash: Record; + publishNames: string[] | null; + videoPlaying: any | null; + totalFilesPublished: number; + totalNamesPublished: number; + filesPerNamePublished: number; } const initialState: GlobalState = { isLoadingGlobal: false, downloads: {}, userAvatarHash: {}, publishNames: null, - videoPlaying: null -} + videoPlaying: null, + totalFilesPublished: null, + totalNamesPublished: null, + filesPerNamePublished: null, +}; export const globalSlice = createSlice({ - name: 'global', + name: "global", initialState, reducers: { setIsLoadingGlobal: (state, action) => { - state.isLoadingGlobal = action.payload + state.isLoadingGlobal = action.payload; }, setAddToDownloads: (state, action) => { - const download = action.payload - state.downloads[download.identifier] = download + const download = action.payload; + state.downloads[download.identifier] = download; }, updateDownloads: (state, action) => { - const { identifier } = action.payload - const download = action.payload + const { identifier } = action.payload; + const download = action.payload; state.downloads[identifier] = { ...state.downloads[identifier], - ...download - } + ...download, + }; }, setUserAvatarHash: (state, action) => { - const avatar = action.payload + const avatar = action.payload; if (avatar?.name && avatar?.url) { - state.userAvatarHash[avatar?.name] = avatar?.url + state.userAvatarHash[avatar?.name] = avatar?.url; } }, addPublishNames: (state, action) => { - state.publishNames = action.payload + state.publishNames = action.payload; }, setVideoPlaying: (state, action) => { - state.videoPlaying = action.payload + state.videoPlaying = action.payload; + }, + setTotalFilesPublished: (state, action) => { + state.totalFilesPublished = action.payload; + }, + setTotalNamesPublished: (state, action) => { + state.totalNamesPublished = action.payload; + }, + setFilesPerNamePublished: (state, action) => { + state.filesPerNamePublished = action.payload; }, - } -}) + }, +}); export const { setIsLoadingGlobal, @@ -56,7 +70,10 @@ export const { updateDownloads, setUserAvatarHash, addPublishNames, - setVideoPlaying -} = globalSlice.actions + setVideoPlaying, + setTotalFilesPublished, + setTotalNamesPublished, + setFilesPerNamePublished, +} = globalSlice.actions; -export default globalSlice.reducer +export default globalSlice.reducer; diff --git a/src/state/features/videoSlice.ts b/src/state/features/videoSlice.ts deleted file mode 100644 index 2416939..0000000 --- a/src/state/features/videoSlice.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit'; -import { RootState } from '../store' - - -interface GlobalState { - videos: Video[] - filteredVideos: Video[] - hashMapVideos: Record - countNewVideos: number - isFiltering: boolean - filterValue: string - filterType: string - filterSearch: string - filterName: string - selectedCategoryVideos: any - selectedSubCategoryVideos: any - selectedSubCategoryVideos2: any - selectedSubCategoryVideos3: any - editVideoProperties: any - editPlaylistProperties: any -} -const initialState: GlobalState = { - videos: [], - filteredVideos: [], - hashMapVideos: {}, - countNewVideos: 0, - isFiltering: false, - filterValue: '', - filterType: 'videos', - filterSearch: '', - filterName: '', - selectedCategoryVideos: null, - selectedSubCategoryVideos: null, - selectedSubCategoryVideos2: null, - selectedSubCategoryVideos3: null, - editVideoProperties: null, - editPlaylistProperties: null -} - -export interface Video { - title: string - description: string - created: number | string - user: string - service?: string - videoImage?: string - id: string - category?: string - categoryName?: string - tags?: string[] - updated?: number | string - isValid?: boolean - code?: string -} - - - -export const videoSlice = createSlice({ - name: 'video', - initialState, - reducers: { - setEditVideo: (state, action) => { - state.editVideoProperties = action.payload - }, - setEditPlaylist: (state, action) => { - state.editPlaylistProperties = action.payload - }, - changeFilterType: (state, action) => { - state.filterType = 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 - }, - changeSelectedSubCategoryVideos2: (state, action) => { - state.selectedSubCategoryVideos2 = action.payload - }, - changeSelectedSubCategoryVideos3: (state, action) => { - state.selectedSubCategoryVideos3 = 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 - }, - updateInHashMap: (state, action) => { - const { id } = action.payload - const video = action.payload - state.hashMapVideos[id] = { ...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 - }) - }, - 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) - - } - } -}) - -export const { - setCountNewVideos, - addVideos, - addFilteredVideos, - removeVideo, - addVideoToBeginning, - updateVideo, - addToHashMap, - updateInHashMap, - removeFromHashMap, - addArrayToHashMap, - upsertVideos, - upsertFilteredVideos, - upsertVideosBeginning, - setIsFiltering, - setFilterValue, - clearVideoList, - changeFilterType, - changefilterSearch, - changefilterName, - changeSelectedCategoryVideos, - changeSelectedSubCategoryVideos, - changeSelectedSubCategoryVideos2, - changeSelectedSubCategoryVideos3, - blockUser, - setEditVideo, - setEditPlaylist -} = videoSlice.actions - -export default videoSlice.reducer - diff --git a/src/state/store.ts b/src/state/store.ts index 0400841..f99bd05 100644 --- a/src/state/store.ts +++ b/src/state/store.ts @@ -1,27 +1,27 @@ -import { configureStore } from '@reduxjs/toolkit' -import notificationsReducer from './features/notificationsSlice' -import authReducer from './features/authSlice' -import globalReducer from './features/globalSlice' -import videoReducer from './features/videoSlice' +import { configureStore } from "@reduxjs/toolkit"; +import notificationsReducer from "./features/notificationsSlice"; +import authReducer from "./features/authSlice"; +import globalReducer from "./features/globalSlice"; +import fileReducer from "./features/fileSlice.ts"; export const store = configureStore({ reducer: { notifications: notificationsReducer, auth: authReducer, global: globalReducer, - video: videoReducer, + file: fileReducer, }, - middleware: (getDefaultMiddleware) => + middleware: getDefaultMiddleware => getDefaultMiddleware({ - serializableCheck: false + serializableCheck: false, }), - preloadedState: undefined // optional, can be any valid state object -}) + preloadedState: undefined, // optional, can be any valid state object +}); // Define the RootState type, which is the type of the entire Redux state tree. // This is useful when you need to access the state in a component or elsewhere. -export type RootState = ReturnType +export type RootState = ReturnType; // Define the AppDispatch type, which is the type of the Redux store's dispatch function. // This is useful when you need to dispatch an action in a component or elsewhere. -export type AppDispatch = typeof store.dispatch +export type AppDispatch = typeof store.dispatch; diff --git a/src/utils/utilFunctions.ts b/src/utils/utilFunctions.ts new file mode 100644 index 0000000..5b12a4c --- /dev/null +++ b/src/utils/utilFunctions.ts @@ -0,0 +1,20 @@ +export const objectIsNull = (variable: object) => { + return Object.is(variable, null); +}; +export const objectIsUndefined = (variable: object) => { + return Object.is(variable, undefined); +}; + +export const printVar = (variable: object) => { + if (objectIsNull(variable)) { + console.log("variable is NULL"); + return; + } + if (objectIsUndefined(variable)) { + console.log("variable is UNDEFINED"); + return; + } + + const [key, value] = Object.entries(variable)[0]; + console.log(key, " is: ", value); +};