diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..0a4d4fc --- /dev/null +++ b/.prettierrc @@ -0,0 +1,10 @@ +{ + "printWidth": 80, + "singleQuote": false, + "trailingComma": "es5", + "bracketSpacing": true, + "jsxBracketSameLine": false, + "arrowParens": "avoid", + "tabWidth": 2, + "semi": true +} diff --git a/package-lock.json b/package-lock.json index a0af72b..a519e66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "dompurify": "^3.0.6", "localforage": "^1.10.0", "moment": "^2.29.4", + "prettier": "^3.2.4", "quill-image-resize-module-react": "^3.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -3429,6 +3430,20 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", + "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -6574,6 +6589,11 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "prettier": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", + "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==" + }, "prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", diff --git a/package.json b/package.json index 244125a..7ba80c6 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "dompurify": "^3.0.6", "localforage": "^1.10.0", "moment": "^2.29.4", + "prettier": "^3.2.4", "quill-image-resize-module-react": "^3.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/src/App.tsx b/src/App.tsx index 9514526..72152d6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,7 +8,7 @@ import { Provider } from "react-redux"; import GlobalWrapper from "./wrappers/GlobalWrapper"; import Notification from "./components/common/Notification/Notification"; import { Home } from "./pages/Home/Home"; -import { VideoContent } from "./pages/VideoContent/VideoContent"; +import { FileContent } from "./pages/FileContent/FileContent.tsx"; import DownloadWrapper from "./wrappers/DownloadWrapper"; import { IndividualProfile } from "./pages/IndividualProfile/IndividualProfile"; @@ -22,14 +22,14 @@ function App() { - setTheme(val)}> - - - } /> - } /> - } /> - - + setTheme(val)}> + + + } /> + } /> + } /> + + diff --git a/src/assets/icons/book.webp b/src/assets/icons/book.webp new file mode 100644 index 0000000..27ea88f Binary files /dev/null and b/src/assets/icons/book.webp differ diff --git a/src/assets/icons/document.webp b/src/assets/icons/document.webp index 27ea88f..bd863ab 100644 Binary files a/src/assets/icons/document.webp and b/src/assets/icons/document.webp differ diff --git a/src/assets/icons/image.webp b/src/assets/icons/image.webp new file mode 100644 index 0000000..052d3ff Binary files /dev/null and b/src/assets/icons/image.webp differ diff --git a/src/assets/icons/unknown.webp b/src/assets/icons/unknown.webp new file mode 100644 index 0000000..e97a913 Binary files /dev/null and b/src/assets/icons/unknown.webp differ diff --git a/src/components/EditFile/EditFile.tsx b/src/components/EditFile/EditFile.tsx index 1804f60..54db6f7 100644 --- a/src/components/EditFile/EditFile.tsx +++ b/src/components/EditFile/EditFile.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useState} from "react"; +import React, { useEffect, useRef, useState } from "react"; import { CrowdfundActionButton, CrowdfundActionButtonRow, @@ -6,34 +6,32 @@ import { ModalBody, NewCrowdfundTitle, } from "./Upload-styles"; -import { - Box, - FormControl, - InputLabel, - MenuItem, - Modal, - OutlinedInput, - Select, - SelectChangeEvent, - Typography, - useTheme, -} from "@mui/material"; +import { Box, Modal, Typography, useTheme } from "@mui/material"; import RemoveIcon from "@mui/icons-material/Remove"; import ShortUniqueId from "short-unique-id"; -import {useDispatch, useSelector} from "react-redux"; -import {useDropzone} from "react-dropzone"; - -import {setNotification} from "../../state/features/notificationsSlice"; -import {objectToBase64} from "../../utils/toBase64"; -import {RootState} from "../../state/store"; -import {setEditVideo, updateInHashMap, updateVideo,} from "../../state/features/videoSlice"; -import {QSHARE_FILE_BASE,} from "../../constants/Identifiers.ts"; -import {MultiplePublish} from "../common/MultiplePublish/MultiplePublishAll"; -import {TextEditor} from "../common/TextEditor/TextEditor"; -import {extractTextFromHTML} from "../common/TextEditor/utils"; -import {categories, subCategories, subCategories2, subCategories3} from "../../constants/Categories.ts"; -import {titleFormatter} from "../../constants/Misc.ts"; +import { useDispatch, useSelector } from "react-redux"; +import { useDropzone } from "react-dropzone"; + +import { setNotification } from "../../state/features/notificationsSlice"; +import { objectToBase64 } from "../../utils/toBase64"; +import { RootState } from "../../state/store"; +import { + setEditFile, + updateFile, + updateInHashMap, +} from "../../state/features/fileSlice.ts"; +import { QSHARE_FILE_BASE } from "../../constants/Identifiers.ts"; +import { MultiplePublish } from "../common/MultiplePublish/MultiplePublishAll"; +import { TextEditor } from "../common/TextEditor/TextEditor"; +import { extractTextFromHTML } from "../common/TextEditor/utils"; +import { allCategoryData } from "../../constants/Categories/1stCategories.ts"; +import { titleFormatter } from "../../constants/Misc.ts"; +import { + CategoryList, + CategoryListRef, + getCategoriesFromObject, +} from "../common/CategoryList/CategoryList.tsx"; const uid = new ShortUniqueId(); const shortuid = new ShortUniqueId({ length: 5 }); @@ -52,8 +50,8 @@ interface VideoFile { title: string; description: string; coverImage?: string; - identifier?:string; - filename?:string + identifier?: string; + filename?: string; } export const EditFile = () => { const theme = useTheme(); @@ -62,8 +60,8 @@ export const EditFile = () => { const userAddress = useSelector( (state: RootState) => state.auth?.user?.address ); - const editVideoProperties = useSelector( - (state: RootState) => state.video.editVideoProperties + const editFileProperties = useSelector( + (state: RootState) => state.file.editFileProperties ); const [publishes, setPublishes] = useState(null); const [isOpenMultiplePublish, setIsOpenMultiplePublish] = useState(false); @@ -75,154 +73,59 @@ export const EditFile = () => { const [coverImage, setCoverImage] = useState(""); const [file, setFile] = useState(null); const [files, setFiles] = useState([]); + const [editCategories, setEditCategories] = useState([]); + const categoryListRef = useRef(null); + + const { getRootProps, getInputProps } = useDropzone({ + maxFiles: 10, + maxSize: 419430400, // 400 MB in bytes + onDrop: (acceptedFiles, rejectedFiles) => { + const formatArray = acceptedFiles.map(item => { + return { + file: item, + title: "", + description: "", + coverImage: "", + }; + }); - const [selectedCategoryVideos, setSelectedCategoryVideos] = - useState(null); - const [selectedSubCategoryVideos, setSelectedSubCategoryVideos] = - useState(null); - const [selectedSubCategoryVideos2, setSelectedSubCategoryVideos2] = - useState(null); - const [selectedSubCategoryVideos3, setSelectedSubCategoryVideos3] = - useState(null); - - const { getRootProps, getInputProps } = useDropzone({ - maxFiles: 10, - maxSize: 419430400, // 400 MB in bytes - onDrop: (acceptedFiles, rejectedFiles) => { - const formatArray = acceptedFiles.map((item) => { - return { - file: item, - title: "", - description: "", - coverImage: "", - }; - }); - - setFiles((prev) => [...prev, ...formatArray]); - - let errorString = null; - rejectedFiles.forEach(({ file, errors }) => { - errors.forEach((error) => { - if (error.code === "file-too-large") { - errorString = "File must be under 400mb"; - } - console.log(`Error with file ${file.name}: ${error.message}`); - }); - }); - if (errorString) { - const notificationObj = { - msg: errorString, - alertType: "error", - }; - - dispatch(setNotification(notificationObj)); - } - }, - }); - - // useEffect(() => { - // if (editVideoProperties) { - // const descriptionString = editVideoProperties?.description || ""; - // // Splitting the string at the asterisks - // const parts = descriptionString.split("**"); - - // // The part within the asterisks - // const extractedString = parts[1]; - - // // The part after the last asterisks - // const description = parts[2] || ""; // Using '|| '' to handle cases where there is no text after the last ** - // setTitle(editVideoProperties?.title || ""); - // setDescription(editVideoProperties?.fullDescription || ""); - // setCoverImage(editVideoProperties?.videoImage || ""); - - // // Split the extracted string into key-value pairs - // const keyValuePairs = extractedString.split(";"); - - // // Initialize variables to hold the category and subcategory values - // let category, subcategory; - - // // Loop through each key-value pair - // keyValuePairs.forEach((pair) => { - // const [key, value] = pair.split(":"); - - // // Check the key and assign the value to the appropriate variable - // if (key === "category") { - // category = value; - // } else if (key === "subcategory") { - // subcategory = value; - // } - // }); - - // if(category){ - // const selectedOption = categories.find((option) => option.id === +category); - // setSelectedCategoryVideos(selectedOption || null); - // } - - // if(subcategory){ - // const selectedOption = categories.find((option) => option.id === +subcategory); - // setSelectedCategoryVideos(selectedOption || null); - // } - - // } - // }, [editVideoProperties]); + setFiles(prev => [...prev, ...formatArray]); - useEffect(() => { - if (editVideoProperties) { - setTitle(editVideoProperties?.title || ""); - setFiles(editVideoProperties?.files || []) - if(editVideoProperties?.htmlDescription){ - setDescription(editVideoProperties?.htmlDescription); - - } else if(editVideoProperties?.fullDescription) { - const paragraph = `

${editVideoProperties?.fullDescription}

` - setDescription(paragraph); + let errorString = null; + rejectedFiles.forEach(({ file, errors }) => { + errors.forEach(error => { + if (error.code === "file-too-large") { + errorString = "File must be under 400mb"; + } + console.log(`Error with file ${file.name}: ${error.message}`); + }); + }); + if (errorString) { + const notificationObj = { + msg: errorString, + alertType: "error", + }; + dispatch(setNotification(notificationObj)); } + }, + }); - if (editVideoProperties?.category) { - const selectedOption = categories.find( - (option) => option.id === +editVideoProperties.category - ); - setSelectedCategoryVideos(selectedOption || null); - } - if ( - editVideoProperties?.category && - editVideoProperties?.subcategory && - subCategories[+editVideoProperties?.category] - ) { - const selectedOption = subCategories[ - +editVideoProperties?.category - ]?.find((option) => option.id === +editVideoProperties.subcategory); - setSelectedSubCategoryVideos(selectedOption || null); - } - if ( - editVideoProperties?.category && - editVideoProperties?.subcategory2 && - subCategories2[+editVideoProperties?.subcategory] - ) { - const selectedOption = subCategories2[ - +editVideoProperties?.subcategory - ]?.find((option) => option.id === +editVideoProperties.subcategory2); - setSelectedSubCategoryVideos2(selectedOption || null); - } - if ( - editVideoProperties?.category && - editVideoProperties?.subcategory3 && - subCategories3[+editVideoProperties?.subcategory2] - ) { - - const selectedOption = subCategories3[ - +editVideoProperties?.subcategory2 - ]?.find((option) => option.id === +editVideoProperties.subcategory3); - setSelectedSubCategoryVideos3(selectedOption || null); + useEffect(() => { + if (editFileProperties) { + setTitle(editFileProperties?.title || ""); + setFiles(editFileProperties?.files || []); + if (editFileProperties?.htmlDescription) { + setDescription(editFileProperties?.htmlDescription); + } else if (editFileProperties?.fullDescription) { + const paragraph = `

${editFileProperties?.fullDescription}

`; + setDescription(paragraph); } - - + setEditCategories(getCategoriesFromObject(editFileProperties)); } - }, [editVideoProperties]); - + }, [editFileProperties]); const onClose = () => { - dispatch(setEditVideo(null)); + dispatch(setEditFile(null)); setVideoPropertiesToSetToRedux(null); setFile(null); setTitle(""); @@ -232,12 +135,13 @@ export const EditFile = () => { async function publishQDNResource() { try { + const categoryList = categoryListRef.current?.getSelectedCategories(); if (!title) throw new Error("Please enter a title"); if (!description) throw new Error("Please enter a description"); - if (!selectedCategoryVideos) throw new Error("Please select a category"); - if (!editVideoProperties) return; + if (!categoryList[0]) throw new Error("Please select a category"); + if (!editFileProperties) return; if (!userAddress) throw new Error("Unable to locate user address"); - if(files.length === 0) throw new Error("Add at least one file"); + if (files.length === 0) throw new Error("Add at least one file"); let errorMsg = ""; let name = ""; @@ -249,7 +153,7 @@ export const EditFile = () => { "Cannot publish without access to your name. Please authenticate."; } - if (editVideoProperties?.user !== username) { + if (editFileProperties?.user !== username) { errorMsg = "Cannot publish another user's resource"; } @@ -262,44 +166,37 @@ export const EditFile = () => { ); return; } - let fileReferences = [] + let fileReferences = []; let listOfPublishes = []; const fullDescription = extractTextFromHTML(description); - const category = selectedCategoryVideos.id; - const subcategory = selectedSubCategoryVideos?.id || ""; - const subcategory2 = selectedSubCategoryVideos2?.id || ""; - const subcategory3 = selectedSubCategoryVideos3?.id || ""; const sanitizeTitle = title - .replace(/[^a-zA-Z0-9\s-]/g, "") - .replace(/\s+/g, "-") - .replace(/-+/g, "-") - .trim() - .toLowerCase(); - + .replace(/[^a-zA-Z0-9\s-]/g, "") + .replace(/\s+/g, "-") + .replace(/-+/g, "-") + .trim() + .toLowerCase(); for (const publish of files) { - if(publish?.identifier){ - fileReferences.push(publish) - continue + if (publish?.identifier) { + fileReferences.push(publish); + continue; } const file = publish.file; const id = uid(); const identifier = `${QSHARE_FILE_BASE}${sanitizeTitle.slice(0, 30)}_${id}`; - - let fileExtension = ""; const fileExtensionSplit = file?.name?.split("."); if (fileExtensionSplit?.length > 1) { fileExtension = fileExtensionSplit?.pop() || ""; } - let firstPartName = fileExtensionSplit[0] + let firstPartName = fileExtensionSplit[0]; let filename = firstPartName.slice(0, 15); - + // Step 1: Replace all white spaces with underscores // Replace all forms of whitespace (including non-standard ones) with underscores @@ -311,18 +208,16 @@ export const EditFile = () => { "" ); - if(fileExtension){ - filename = `${alphanumericString.trim()}.${fileExtension}` + if (fileExtension) { + filename = `${alphanumericString.trim()}.${fileExtension}`; } else { - filename = alphanumericString + filename = alphanumericString; } - - let metadescription = - `**cat:${category};sub:${subcategory};sub2:${subcategory2};sub3:${subcategory3}**` + + `**${categoryListRef.current?.getCategoriesFetchString()}**` + fullDescription.slice(0, 150); - + const requestBodyVideo: any = { action: "PUBLISH_QDN_RESOURCE", name: name, @@ -339,27 +234,24 @@ export const EditFile = () => { filename: file.name, identifier, name, - service: 'FILE', + service: "FILE", mimetype: file.type, - size: file.size - }) + size: file.size, + }); } const fileObject: any = { title, - version: editVideoProperties.version, + version: editFileProperties.version, fullDescription, htmlDescription: description, - commentsId: editVideoProperties.commentsId, - category, - subcategory, - subcategory2, - subcategory3, - files: fileReferences + commentsId: editFileProperties.commentsId, + ...categoryListRef.current?.categoriesToObject(), + files: fileReferences, }; let metadescription = - `**cat:${category};sub:${subcategory};sub2:${subcategory2}**` + + `**${categoryListRef.current?.getCategoriesFetchString()}**` + fullDescription.slice(0, 150); const crowdfundObjectToBase64 = await objectToBase64(fileObject); @@ -372,7 +264,7 @@ export const EditFile = () => { data64: crowdfundObjectToBase64, title: title.slice(0, 50), description: metadescription, - identifier: editVideoProperties.id, + identifier: editFileProperties.id, tag1: QSHARE_FILE_BASE, filename: `video_metadata.json`, }; @@ -385,7 +277,7 @@ export const EditFile = () => { setPublishes(multiplePublish); setIsOpenMultiplePublish(true); setVideoPropertiesToSetToRedux({ - ...editVideoProperties, + ...editFileProperties, ...fileObject, }); } catch (error: any) { @@ -429,52 +321,10 @@ export const EditFile = () => { // }); }; - 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); - }; - - return ( <> @@ -503,34 +353,36 @@ export const EditFile = () => { Click to add more files {files.map((file, index) => { - const isExistingFile = !!file?.identifier - return ( - - + + + {isExistingFile ? file.filename : file?.file?.name} + + { + setFiles(prev => { + const copyPrev = [...prev]; + copyPrev.splice(index, 1); + return copyPrev; + }); + }} sx={{ - display: "flex", - justifyContent: "space-between", - alignItems: "center", + cursor: "pointer", }} - > - {isExistingFile? file.filename : file?.file?.name} - { - setFiles((prev) => { - const copyPrev = [...prev]; - copyPrev.splice(index, 1); - return copyPrev; - }); - }} - sx={{ - cursor: "pointer", - }} - /> - - - ); - })} - + /> + + + ); + })} + { alignItems: "flex-start", }} > - {files?.length > 0 && ( - <> - - - Select a Category - - - - {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); +};