mirror of
https://github.com/Qortal/q-share.git
synced 2025-01-29 06:12:21 +00:00
Massive Category overhaul.
Many new categories added, All categories have an "Other" field that is sorted last Media category removed, its subcategories are now main categories. WARNING: This is a breaking change that will disrupt categories for some currently published files Categories associated with Copyright Infringement have been removed. Categories are sorted by name, "Other" is always last The FileList component now only stores the file list. The rest of it was moved to Home.tsx Category <select> components moved into a CategoryList.tsx component Icons now load before file title in FileContent.tsx Icons are an optional field of each category and loaded dynamically from Category Data to improve performance
This commit is contained in:
parent
fa0a1dc16a
commit
9d483cf65d
10
.prettierrc
Normal file
10
.prettierrc
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"printWidth": 80,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": false,
|
||||
"arrowParens": "avoid",
|
||||
"tabWidth": 2,
|
||||
"semi": true
|
||||
}
|
20
package-lock.json
generated
20
package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
18
src/App.tsx
18
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() {
|
||||
<ThemeProvider theme={theme === "light" ? lightTheme : darkTheme}>
|
||||
<Notification />
|
||||
<DownloadWrapper>
|
||||
<GlobalWrapper setTheme={(val: string) => setTheme(val)}>
|
||||
<CssBaseline />
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/share/:name/:id" element={<VideoContent />} />
|
||||
<Route path="/channel/:name" element={<IndividualProfile />} />
|
||||
</Routes>
|
||||
</GlobalWrapper>
|
||||
<GlobalWrapper setTheme={(val: string) => setTheme(val)}>
|
||||
<CssBaseline />
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/share/:name/:id" element={<FileContent />} />
|
||||
<Route path="/channel/:name" element={<IndividualProfile />} />
|
||||
</Routes>
|
||||
</GlobalWrapper>
|
||||
</DownloadWrapper>
|
||||
</ThemeProvider>
|
||||
</Provider>
|
||||
|
BIN
src/assets/icons/book.webp
Normal file
BIN
src/assets/icons/book.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 84 KiB |
Binary file not shown.
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 28 KiB |
BIN
src/assets/icons/image.webp
Normal file
BIN
src/assets/icons/image.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
BIN
src/assets/icons/unknown.webp
Normal file
BIN
src/assets/icons/unknown.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
@ -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 { 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 { 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<any>(null);
|
||||
const [isOpenMultiplePublish, setIsOpenMultiplePublish] = useState(false);
|
||||
@ -75,154 +73,59 @@ export const EditFile = () => {
|
||||
const [coverImage, setCoverImage] = useState<string>("");
|
||||
const [file, setFile] = useState(null);
|
||||
const [files, setFiles] = useState<VideoFile[]>([]);
|
||||
const [editCategories, setEditCategories] = useState<string[]>([]);
|
||||
const categoryListRef = useRef<CategoryListRef>(null);
|
||||
|
||||
const [selectedCategoryVideos, setSelectedCategoryVideos] =
|
||||
useState<any>(null);
|
||||
const [selectedSubCategoryVideos, setSelectedSubCategoryVideos] =
|
||||
useState<any>(null);
|
||||
const [selectedSubCategoryVideos2, setSelectedSubCategoryVideos2] =
|
||||
useState<any>(null);
|
||||
const [selectedSubCategoryVideos3, setSelectedSubCategoryVideos3] =
|
||||
useState<any>(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 { 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}`);
|
||||
});
|
||||
|
||||
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));
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
if (errorString) {
|
||||
const notificationObj = {
|
||||
msg: errorString,
|
||||
alertType: "error",
|
||||
};
|
||||
|
||||
// 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]);
|
||||
dispatch(setNotification(notificationObj));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (editVideoProperties) {
|
||||
setTitle(editVideoProperties?.title || "");
|
||||
setFiles(editVideoProperties?.files || [])
|
||||
if(editVideoProperties?.htmlDescription){
|
||||
setDescription(editVideoProperties?.htmlDescription);
|
||||
|
||||
} else if(editVideoProperties?.fullDescription) {
|
||||
const paragraph = `<p>${editVideoProperties?.fullDescription}</p>`
|
||||
if (editFileProperties) {
|
||||
setTitle(editFileProperties?.title || "");
|
||||
setFiles(editFileProperties?.files || []);
|
||||
if (editFileProperties?.htmlDescription) {
|
||||
setDescription(editFileProperties?.htmlDescription);
|
||||
} else if (editFileProperties?.fullDescription) {
|
||||
const paragraph = `<p>${editFileProperties?.fullDescription}</p>`;
|
||||
setDescription(paragraph);
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
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<string>
|
||||
) => {
|
||||
const optionId = event.target.value;
|
||||
const selectedOption = categories.find((option) => option.id === +optionId);
|
||||
setSelectedCategoryVideos(selectedOption || null);
|
||||
};
|
||||
const handleOptionSubCategoryChangeVideos = (
|
||||
event: SelectChangeEvent<string>,
|
||||
subcategories: any[]
|
||||
) => {
|
||||
const optionId = event.target.value;
|
||||
const selectedOption = subcategories.find(
|
||||
(option) => option.id === +optionId
|
||||
);
|
||||
setSelectedSubCategoryVideos(selectedOption || null);
|
||||
};
|
||||
|
||||
|
||||
const handleOptionSubCategoryChangeVideos2 = (
|
||||
event: SelectChangeEvent<string>,
|
||||
subcategories: any[]
|
||||
) => {
|
||||
const optionId = event.target.value;
|
||||
const selectedOption = subcategories.find(
|
||||
(option) => option.id === +optionId
|
||||
);
|
||||
setSelectedSubCategoryVideos2(selectedOption || null);
|
||||
};
|
||||
|
||||
const handleOptionSubCategoryChangeVideos3 = (
|
||||
event: SelectChangeEvent<string>,
|
||||
subcategories: any[]
|
||||
) => {
|
||||
const optionId = event.target.value;
|
||||
const selectedOption = subcategories.find(
|
||||
(option) => option.id === +optionId
|
||||
);
|
||||
setSelectedSubCategoryVideos3(selectedOption || null);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
open={!!editVideoProperties}
|
||||
open={!!editFileProperties}
|
||||
aria-labelledby="modal-title"
|
||||
aria-describedby="modal-description"
|
||||
>
|
||||
@ -503,34 +353,36 @@ export const EditFile = () => {
|
||||
<Typography>Click to add more files</Typography>
|
||||
</Box>
|
||||
{files.map((file, index) => {
|
||||
const isExistingFile = !!file?.identifier
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
const isExistingFile = !!file?.identifier;
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Typography>
|
||||
{isExistingFile ? file.filename : file?.file?.name}
|
||||
</Typography>
|
||||
<RemoveIcon
|
||||
onClick={() => {
|
||||
setFiles(prev => {
|
||||
const copyPrev = [...prev];
|
||||
copyPrev.splice(index, 1);
|
||||
return copyPrev;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Typography>{isExistingFile? file.filename : file?.file?.name}</Typography>
|
||||
<RemoveIcon
|
||||
onClick={() => {
|
||||
setFiles((prev) => {
|
||||
const copyPrev = [...prev];
|
||||
copyPrev.splice(index, 1);
|
||||
return copyPrev;
|
||||
});
|
||||
}}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@ -538,164 +390,56 @@ export const EditFile = () => {
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
{files?.length > 0 && (
|
||||
<>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '20px',
|
||||
width: '50%'
|
||||
}}>
|
||||
<FormControl fullWidth sx={{ marginBottom: 2 }}>
|
||||
<InputLabel id="Category">Select a Category</InputLabel>
|
||||
<Select
|
||||
labelId="Category"
|
||||
input={<OutlinedInput label="Select a Category" />}
|
||||
value={selectedCategoryVideos?.id || ""}
|
||||
onChange={handleOptionCategoryChangeVideos}
|
||||
>
|
||||
{categories.map((option) => (
|
||||
<MenuItem key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
{selectedCategoryVideos && (
|
||||
<>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '20px',
|
||||
width: '50%'
|
||||
}}>
|
||||
|
||||
|
||||
{selectedCategoryVideos &&
|
||||
subCategories[selectedCategoryVideos?.id] && (
|
||||
<FormControl fullWidth sx={{ marginBottom: 2 }}>
|
||||
<InputLabel id="Category">
|
||||
Select a Sub-Category
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="Sub-Category"
|
||||
input={
|
||||
<OutlinedInput label="Select a Sub-Category" />
|
||||
}
|
||||
value={selectedSubCategoryVideos?.id || ""}
|
||||
onChange={(e) =>
|
||||
handleOptionSubCategoryChangeVideos(
|
||||
e,
|
||||
subCategories[selectedCategoryVideos?.id]
|
||||
)
|
||||
}
|
||||
>
|
||||
{subCategories[selectedCategoryVideos.id].map(
|
||||
(option) => (
|
||||
<MenuItem key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</MenuItem>
|
||||
)
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
{selectedSubCategoryVideos &&
|
||||
subCategories2[selectedSubCategoryVideos?.id] && (
|
||||
<FormControl fullWidth sx={{ marginBottom: 2 }}>
|
||||
<InputLabel id="Category">
|
||||
Select a Sub-sub-Category
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="Sub-Category"
|
||||
input={
|
||||
<OutlinedInput label="Select a Sub-sub-Category" />
|
||||
}
|
||||
value={selectedSubCategoryVideos2?.id || ""}
|
||||
onChange={(e) =>
|
||||
handleOptionSubCategoryChangeVideos2(
|
||||
e,
|
||||
subCategories2[selectedSubCategoryVideos?.id]
|
||||
)
|
||||
}
|
||||
>
|
||||
{subCategories2[selectedSubCategoryVideos.id].map(
|
||||
(option) => (
|
||||
<MenuItem key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</MenuItem>
|
||||
)
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
{selectedSubCategoryVideos2 &&
|
||||
subCategories3[selectedSubCategoryVideos2?.id] && (
|
||||
<FormControl fullWidth sx={{ marginBottom: 2 }}>
|
||||
<InputLabel id="Category">
|
||||
Select a Sub-3x-subCategory
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="Sub-Category"
|
||||
input={
|
||||
<OutlinedInput label="Select a Sub-3x-Category" />
|
||||
}
|
||||
value={selectedSubCategoryVideos3?.id || ""}
|
||||
onChange={(e) =>
|
||||
handleOptionSubCategoryChangeVideos3(
|
||||
e,
|
||||
subCategories3[selectedSubCategoryVideos2?.id]
|
||||
)
|
||||
}
|
||||
>
|
||||
{subCategories3[selectedSubCategoryVideos2.id].map(
|
||||
(option) => (
|
||||
<MenuItem key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</MenuItem>
|
||||
)
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
{files?.length > 0 && (
|
||||
{files?.length > 0 && (
|
||||
<>
|
||||
<CustomInputField
|
||||
name="title"
|
||||
label="Title of share"
|
||||
variant="filled"
|
||||
value={title}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
const formattedValue = value.replace(titleFormatter, "");
|
||||
setTitle(formattedValue);
|
||||
}}
|
||||
inputProps={{ maxLength: 180 }}
|
||||
required
|
||||
/>
|
||||
<Typography
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: "18px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "20px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
Description of share
|
||||
</Typography>
|
||||
<TextEditor
|
||||
inlineContent={description}
|
||||
setInlineContent={(value) => {
|
||||
setDescription(value);
|
||||
}}
|
||||
/>
|
||||
<CategoryList
|
||||
categoryData={allCategoryData}
|
||||
initialCategories={editCategories}
|
||||
columns={3}
|
||||
ref={categoryListRef}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
{files?.length > 0 && (
|
||||
<>
|
||||
<CustomInputField
|
||||
name="title"
|
||||
label="Title of share"
|
||||
variant="filled"
|
||||
value={title}
|
||||
onChange={e => {
|
||||
const value = e.target.value;
|
||||
const formattedValue = value.replace(titleFormatter, "");
|
||||
setTitle(formattedValue);
|
||||
}}
|
||||
inputProps={{ maxLength: 180 }}
|
||||
required
|
||||
/>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "18px",
|
||||
}}
|
||||
>
|
||||
Description of share
|
||||
</Typography>
|
||||
<TextEditor
|
||||
inlineContent={description}
|
||||
setInlineContent={value => {
|
||||
setDescription(value);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
<CrowdfundActionButtonRow>
|
||||
@ -730,22 +474,22 @@ export const EditFile = () => {
|
||||
{isOpenMultiplePublish && (
|
||||
<MultiplePublish
|
||||
isOpen={isOpenMultiplePublish}
|
||||
onError={(messageNotification)=> {
|
||||
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({
|
||||
|
@ -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<any>(null);
|
||||
const [title, setTitle] = useState<string>("");
|
||||
@ -88,17 +94,17 @@ export const EditPlaylist = () => {
|
||||
const [selectedSubCategoryVideos, setSelectedSubCategoryVideos] =
|
||||
useState<any>(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){
|
||||
|
||||
if (editVideoProperties?.htmlDescription) {
|
||||
setDescription(editVideoProperties?.htmlDescription);
|
||||
|
||||
} else if(editVideoProperties?.description) {
|
||||
const paragraph = `<p>${editVideoProperties?.description}</p>`
|
||||
} else if (editVideoProperties?.description) {
|
||||
const paragraph = `<p>${editVideoProperties?.description}</p>`;
|
||||
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<string>
|
||||
) => {
|
||||
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 ? (
|
||||
<NewCrowdfundTitle>Create new playlist</NewCrowdfundTitle>
|
||||
|
||||
) : (
|
||||
<NewCrowdfundTitle>Update Playlist properties</NewCrowdfundTitle>
|
||||
|
||||
<NewCrowdfundTitle>Update Playlist properties</NewCrowdfundTitle>
|
||||
)}
|
||||
</Box>
|
||||
<>
|
||||
@ -488,7 +484,7 @@ export const EditPlaylist = () => {
|
||||
value={selectedCategoryVideos?.id || ""}
|
||||
onChange={handleOptionCategoryChangeVideos}
|
||||
>
|
||||
{categories.map((option) => (
|
||||
{firstCategories.map(option => (
|
||||
<MenuItem key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</MenuItem>
|
||||
@ -496,22 +492,22 @@ export const EditPlaylist = () => {
|
||||
</Select>
|
||||
</FormControl>
|
||||
{selectedCategoryVideos &&
|
||||
subCategories[selectedCategoryVideos?.id] && (
|
||||
secondCategories[selectedCategoryVideos?.id] && (
|
||||
<FormControl fullWidth sx={{ marginBottom: 2 }}>
|
||||
<InputLabel id="Category">Select a Sub-Category</InputLabel>
|
||||
<Select
|
||||
labelId="Sub-Category"
|
||||
input={<OutlinedInput label="Select a Sub-Category" />}
|
||||
value={selectedSubCategoryVideos?.id || ""}
|
||||
onChange={(e) =>
|
||||
onChange={e =>
|
||||
handleOptionSubCategoryChangeVideos(
|
||||
e,
|
||||
subCategories[selectedCategoryVideos?.id]
|
||||
secondCategories[selectedCategoryVideos?.id]
|
||||
)
|
||||
}
|
||||
>
|
||||
{subCategories[selectedCategoryVideos.id].map(
|
||||
(option) => (
|
||||
{secondCategories[selectedCategoryVideos.id].map(
|
||||
option => (
|
||||
<MenuItem key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</MenuItem>
|
||||
@ -550,9 +546,12 @@ export const EditPlaylist = () => {
|
||||
label="Title of playlist"
|
||||
variant="filled"
|
||||
value={title}
|
||||
onChange={(e) => {
|
||||
onChange={e => {
|
||||
const value = e.target.value;
|
||||
const formattedValue = value.replace(/[^a-zA-Z0-9\s-_!?]/g, "");
|
||||
const formattedValue = value.replace(
|
||||
/[^a-zA-Z0-9\s-_!?]/g,
|
||||
""
|
||||
);
|
||||
setTitle(formattedValue);
|
||||
}}
|
||||
inputProps={{ maxLength: 180 }}
|
||||
@ -569,12 +568,19 @@ export const EditPlaylist = () => {
|
||||
maxRows={3}
|
||||
required
|
||||
/> */}
|
||||
<Typography sx={{
|
||||
fontSize: '18px'
|
||||
}}>Description of playlist</Typography>
|
||||
<TextEditor inlineContent={description} setInlineContent={(value)=> {
|
||||
setDescription(value)
|
||||
}} />
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "18px",
|
||||
}}
|
||||
>
|
||||
Description of playlist
|
||||
</Typography>
|
||||
<TextEditor
|
||||
inlineContent={description}
|
||||
setInlineContent={value => {
|
||||
setDescription(value);
|
||||
}}
|
||||
/>
|
||||
</React.Fragment>
|
||||
|
||||
<PlaylistListEdit
|
||||
|
@ -7,8 +7,8 @@ import {
|
||||
import { Box, Button, Input, Typography, useTheme } from "@mui/material";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
|
||||
import { removeVideo } from "../../state/features/videoSlice";
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import { removeFile } from "../../state/features/fileSlice.ts";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import { QSHARE_FILE_BASE } from "../../constants/Identifiers.ts";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "../../state/store";
|
||||
@ -17,194 +17,194 @@ export const PlaylistListEdit = ({ playlistData, removeVideo, addVideo }) => {
|
||||
const navigate = useNavigate();
|
||||
const username = useSelector((state: RootState) => state.auth?.user?.name);
|
||||
|
||||
const [searchResults, setSearchResults] = useState([])
|
||||
const [filterSearch, setFilterSearch] = useState("")
|
||||
const search = async()=> {
|
||||
|
||||
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&mode=ALL&identifier=${QSHARE_FILE_BASE}&title=${filterSearch}&limit=20&includemetadata=true&reverse=true&name=${username}&exactmatchnames=true&offset=0`
|
||||
const [searchResults, setSearchResults] = useState([]);
|
||||
const [filterSearch, setFilterSearch] = useState("");
|
||||
const search = async () => {
|
||||
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&mode=ALL&identifier=${QSHARE_FILE_BASE}&title=${filterSearch}&limit=20&includemetadata=true&reverse=true&name=${username}&exactmatchnames=true&offset=0`;
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
method: "GET",
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
const responseDataSearchVid = await response.json()
|
||||
setSearchResults(responseDataSearchVid)
|
||||
}
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
const responseDataSearchVid = await response.json();
|
||||
setSearchResults(responseDataSearchVid);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
gap: '10px',
|
||||
width: '100%',
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
|
||||
maxWidth: "300px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<CrowdfundSubTitleRow>
|
||||
<CrowdfundSubTitle>Playlist</CrowdfundSubTitle>
|
||||
</CrowdfundSubTitleRow>
|
||||
<CardContentContainerComment
|
||||
sx={{
|
||||
marginTop: "25px",
|
||||
height: "450px",
|
||||
overflow: 'auto'
|
||||
}}
|
||||
>
|
||||
{playlistData?.videos?.map((vid, index) => {
|
||||
return (
|
||||
<Box
|
||||
key={vid?.identifier}
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
padding: "10px",
|
||||
borderRadius: "5px",
|
||||
userSelect: "none",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
}}
|
||||
>
|
||||
{index + 1}
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "18px",
|
||||
wordBreak: 'break-word'
|
||||
}}
|
||||
>
|
||||
{vid?.metadata?.title}
|
||||
</Typography>
|
||||
<DeleteOutlineIcon
|
||||
onClick={() => {
|
||||
removeVideo(index);
|
||||
}}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</CardContentContainerComment>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
|
||||
maxWidth: "300px",
|
||||
gap: "10px",
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
|
||||
<CrowdfundSubTitleRow>
|
||||
<CrowdfundSubTitle>Add videos to playlist</CrowdfundSubTitle>
|
||||
</CrowdfundSubTitleRow>
|
||||
<CardContentContainerComment
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: "25px",
|
||||
height: "450px",
|
||||
overflow: 'auto'
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
|
||||
maxWidth: "300px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
gap: '10px'
|
||||
}}>
|
||||
<Input
|
||||
id="standard-adornment-name"
|
||||
onChange={(e) => {
|
||||
setFilterSearch(e.target.value);
|
||||
}}
|
||||
value={filterSearch}
|
||||
placeholder="Search by title"
|
||||
<CrowdfundSubTitleRow>
|
||||
<CrowdfundSubTitle>Playlist</CrowdfundSubTitle>
|
||||
</CrowdfundSubTitleRow>
|
||||
<CardContentContainerComment
|
||||
sx={{
|
||||
marginTop: "25px",
|
||||
height: "450px",
|
||||
overflow: "auto",
|
||||
}}
|
||||
>
|
||||
{playlistData?.videos?.map((vid, index) => {
|
||||
return (
|
||||
<Box
|
||||
key={vid?.identifier}
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
padding: "10px",
|
||||
borderRadius: "5px",
|
||||
userSelect: "none",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
}}
|
||||
>
|
||||
{index + 1}
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "18px",
|
||||
wordBreak: "break-word",
|
||||
}}
|
||||
>
|
||||
{vid?.metadata?.title}
|
||||
</Typography>
|
||||
<DeleteOutlineIcon
|
||||
onClick={() => {
|
||||
removeVideo(index);
|
||||
}}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</CardContentContainerComment>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
|
||||
maxWidth: "300px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<CrowdfundSubTitleRow>
|
||||
<CrowdfundSubTitle>Add videos to playlist</CrowdfundSubTitle>
|
||||
</CrowdfundSubTitleRow>
|
||||
<CardContentContainerComment
|
||||
sx={{
|
||||
marginTop: "25px",
|
||||
height: "450px",
|
||||
overflow: "auto",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
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",
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => {
|
||||
search();
|
||||
}}
|
||||
|
||||
variant="contained"
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{searchResults?.map((vid, index) => {
|
||||
return (
|
||||
<Box
|
||||
key={vid?.identifier}
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
padding: "10px",
|
||||
borderRadius: "5px",
|
||||
userSelect: "none",
|
||||
<Input
|
||||
id="standard-adornment-name"
|
||||
onChange={e => {
|
||||
setFilterSearch(e.target.value);
|
||||
}}
|
||||
value={filterSearch}
|
||||
placeholder="Search by title"
|
||||
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",
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => {
|
||||
search();
|
||||
}}
|
||||
variant="contained"
|
||||
>
|
||||
<Typography
|
||||
Search
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{searchResults?.map((vid, index) => {
|
||||
return (
|
||||
<Box
|
||||
key={vid?.identifier}
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
padding: "10px",
|
||||
borderRadius: "5px",
|
||||
userSelect: "none",
|
||||
}}
|
||||
>
|
||||
{index + 1}
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "18px",
|
||||
wordBreak: 'break-word'
|
||||
}}
|
||||
>
|
||||
{vid?.metadata?.title}
|
||||
</Typography>
|
||||
<AddIcon
|
||||
onClick={() => {
|
||||
addVideo(vid);
|
||||
}}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</CardContentContainerComment>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
}}
|
||||
>
|
||||
{index + 1}
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "18px",
|
||||
wordBreak: "break-word",
|
||||
}}
|
||||
>
|
||||
{vid?.metadata?.title}
|
||||
</Typography>
|
||||
<AddIcon
|
||||
onClick={() => {
|
||||
addVideo(vid);
|
||||
}}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</CardContentContainerComment>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
);
|
||||
};
|
||||
|
@ -1,23 +1,15 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
AddCoverImageButton,
|
||||
AddLogoIcon,
|
||||
CoverImagePreview,
|
||||
CrowdfundActionButton,
|
||||
CrowdfundActionButtonRow,
|
||||
CustomInputField,
|
||||
CustomSelect,
|
||||
LogoPreviewRow,
|
||||
ModalBody,
|
||||
NewCrowdfundTitle,
|
||||
StyledButton,
|
||||
TimesIcon,
|
||||
} from "./Upload-styles";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
FormControl,
|
||||
Input,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
Modal,
|
||||
@ -32,35 +24,26 @@ import ShortUniqueId from "short-unique-id";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import AddBoxIcon from "@mui/icons-material/AddBox";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
|
||||
import { setNotification } from "../../state/features/notificationsSlice";
|
||||
import { objectToBase64, uint8ArrayToBase64 } from "../../utils/toBase64";
|
||||
import { objectToBase64 } from "../../utils/toBase64";
|
||||
import { RootState } from "../../state/store";
|
||||
import {
|
||||
upsertVideosBeginning,
|
||||
addToHashMap,
|
||||
upsertVideos,
|
||||
} from "../../state/features/videoSlice";
|
||||
import ImageUploader from "../common/ImageUploader";
|
||||
import {
|
||||
QSHARE_PLAYLIST_BASE,
|
||||
QSHARE_FILE_BASE,
|
||||
|
||||
|
||||
|
||||
|
||||
} from "../../constants/Identifiers.ts";
|
||||
import { QSHARE_FILE_BASE } from "../../constants/Identifiers.ts";
|
||||
import { MultiplePublish } from "../common/MultiplePublish/MultiplePublishAll";
|
||||
import {
|
||||
CrowdfundSubTitle,
|
||||
CrowdfundSubTitleRow,
|
||||
} from "../EditPlaylist/Upload-styles.tsx";
|
||||
import { CardContentContainerComment } from "../common/Comments/Comments-styles";
|
||||
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 {
|
||||
firstCategories,
|
||||
secondCategories,
|
||||
thirdCategories,
|
||||
fourthCategories,
|
||||
allCategoryData,
|
||||
} from "../../constants/Categories/1stCategories.ts";
|
||||
import { titleFormatter } from "../../constants/Misc.ts";
|
||||
import {
|
||||
CategoryList,
|
||||
CategoryListRef,
|
||||
} from "../common/CategoryList/CategoryList.tsx";
|
||||
|
||||
const uid = new ShortUniqueId();
|
||||
const shortuid = new ShortUniqueId({ length: 5 });
|
||||
@ -104,22 +87,15 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
const [selectedCategory, setSelectedCategory] = useState<any>(null);
|
||||
const [selectedSubCategory, setSelectedSubCategory] = useState<any>(null);
|
||||
|
||||
const [selectedCategoryVideos, setSelectedCategoryVideos] =
|
||||
useState<any>(null);
|
||||
const [selectedSubCategoryVideos, setSelectedSubCategoryVideos] =
|
||||
useState<any>(null);
|
||||
const [selectedSubCategoryVideos2, setSelectedSubCategoryVideos2] =
|
||||
useState<any>(null);
|
||||
const [selectedSubCategoryVideos3, setSelectedSubCategoryVideos3] =
|
||||
useState<any>(null);
|
||||
|
||||
const [playlistSetting, setPlaylistSetting] = useState<null | string>(null);
|
||||
const [publishes, setPublishes] = useState<any>(null);
|
||||
const categoryListRef = useRef<CategoryListRef>(null);
|
||||
|
||||
const { getRootProps, getInputProps } = useDropzone({
|
||||
maxFiles: 10,
|
||||
maxSize: 419430400, // 400 MB in bytes
|
||||
onDrop: (acceptedFiles, rejectedFiles) => {
|
||||
const formatArray = acceptedFiles.map((item) => {
|
||||
const formatArray = acceptedFiles.map(item => {
|
||||
return {
|
||||
file: item,
|
||||
title: "",
|
||||
@ -128,11 +104,11 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
};
|
||||
});
|
||||
|
||||
setFiles((prev) => [...prev, ...formatArray]);
|
||||
setFiles(prev => [...prev, ...formatArray]);
|
||||
|
||||
let errorString = null;
|
||||
rejectedFiles.forEach(({ file, errors }) => {
|
||||
errors.forEach((error) => {
|
||||
errors.forEach(error => {
|
||||
if (error.code === "file-too-large") {
|
||||
errorString = "File must be under 400mb";
|
||||
}
|
||||
@ -159,17 +135,16 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
|
||||
|
||||
async function publishQDNResource() {
|
||||
try {
|
||||
if (!categoryListRef.current) throw new Error("No CategoryListRef found");
|
||||
if (!userAddress) throw new Error("Unable to locate user address");
|
||||
|
||||
try {
|
||||
if (!userAddress) throw new Error("Unable to locate user address");
|
||||
|
||||
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(files.length === 0) throw new Error("Add at least one file");
|
||||
if (!title) throw new Error("Please enter a title");
|
||||
if (!description) throw new Error("Please enter a description");
|
||||
if (!categoryListRef.current?.getSelectedCategories()[0])
|
||||
throw new Error("Please select a category");
|
||||
if (files.length === 0) throw new Error("Add at least one file");
|
||||
let errorMsg = "";
|
||||
let name = "";
|
||||
if (username) {
|
||||
@ -194,41 +169,34 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
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 fullDescription = extractTextFromHTML(description);
|
||||
|
||||
const sanitizeTitle = title
|
||||
.replace(/[^a-zA-Z0-9\s-]/g, "")
|
||||
.replace(/\s+/g, "-")
|
||||
.replace(/-+/g, "-")
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
const sanitizeTitle = title
|
||||
.replace(/[^a-zA-Z0-9\s-]/g, "")
|
||||
.replace(/\s+/g, "-")
|
||||
.replace(/-+/g, "-")
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
|
||||
for (const publish of files) {
|
||||
|
||||
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
|
||||
@ -240,19 +208,16 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
""
|
||||
);
|
||||
|
||||
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,
|
||||
@ -269,10 +234,10 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
filename: file.name,
|
||||
identifier,
|
||||
name,
|
||||
service: 'FILE',
|
||||
service: "FILE",
|
||||
mimetype: file.type,
|
||||
size: file.size
|
||||
})
|
||||
size: file.size,
|
||||
});
|
||||
}
|
||||
|
||||
const idMeta = uid();
|
||||
@ -283,15 +248,12 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
fullDescription,
|
||||
htmlDescription: description,
|
||||
commentsId: `${QSHARE_FILE_BASE}_cm_${idMeta}`,
|
||||
category,
|
||||
subcategory,
|
||||
subcategory2,
|
||||
subcategory3,
|
||||
files: fileReferences
|
||||
...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);
|
||||
@ -309,13 +271,12 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
};
|
||||
listOfPublishes.push(requestBodyJson);
|
||||
|
||||
const multiplePublish = {
|
||||
action: "PUBLISH_MULTIPLE_QDN_RESOURCES",
|
||||
resources: [...listOfPublishes],
|
||||
};
|
||||
setPublishes(multiplePublish);
|
||||
const multiplePublish = {
|
||||
action: "PUBLISH_MULTIPLE_QDN_RESOURCES",
|
||||
resources: [...listOfPublishes],
|
||||
};
|
||||
setPublishes(multiplePublish);
|
||||
setIsOpenMultiplePublish(true);
|
||||
|
||||
} catch (error: any) {
|
||||
let notificationObj: any = null;
|
||||
if (typeof error === "string") {
|
||||
@ -339,50 +300,6 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const handleOptionCategoryChangeVideos = (
|
||||
event: SelectChangeEvent<string>
|
||||
) => {
|
||||
const optionId = event.target.value;
|
||||
const selectedOption = categories.find((option) => option.id === +optionId);
|
||||
setSelectedCategoryVideos(selectedOption || null);
|
||||
};
|
||||
const handleOptionSubCategoryChangeVideos = (
|
||||
event: SelectChangeEvent<string>,
|
||||
subcategories: any[]
|
||||
) => {
|
||||
const optionId = event.target.value;
|
||||
const selectedOption = subcategories.find(
|
||||
(option) => option.id === +optionId
|
||||
);
|
||||
setSelectedSubCategoryVideos(selectedOption || null);
|
||||
};
|
||||
|
||||
const handleOptionSubCategoryChangeVideos2 = (
|
||||
event: SelectChangeEvent<string>,
|
||||
subcategories: any[]
|
||||
) => {
|
||||
const optionId = event.target.value;
|
||||
const selectedOption = subcategories.find(
|
||||
(option) => option.id === +optionId
|
||||
);
|
||||
setSelectedSubCategoryVideos2(selectedOption || null);
|
||||
};
|
||||
|
||||
const handleOptionSubCategoryChangeVideos3 = (
|
||||
event: SelectChangeEvent<string>,
|
||||
subcategories: any[]
|
||||
) => {
|
||||
const optionId = event.target.value;
|
||||
const selectedOption = subcategories.find(
|
||||
(option) => option.id === +optionId
|
||||
);
|
||||
setSelectedSubCategoryVideos3(selectedOption || null);
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{username && (
|
||||
@ -414,9 +331,7 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
|
||||
<NewCrowdfundTitle>Share</NewCrowdfundTitle>
|
||||
|
||||
<NewCrowdfundTitle>Share</NewCrowdfundTitle>
|
||||
</Box>
|
||||
|
||||
{step === "videos" && (
|
||||
@ -449,7 +364,7 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
<Typography>{file?.file?.name}</Typography>
|
||||
<RemoveIcon
|
||||
onClick={() => {
|
||||
setFiles((prev) => {
|
||||
setFiles(prev => {
|
||||
const copyPrev = [...prev];
|
||||
copyPrev.splice(index, 1);
|
||||
return copyPrev;
|
||||
@ -463,149 +378,28 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "20px",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
{files?.length > 0 && (
|
||||
<>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '20px',
|
||||
width: '50%'
|
||||
}}>
|
||||
<FormControl fullWidth sx={{ marginBottom: 2 }}>
|
||||
<InputLabel id="Category">Select a Category</InputLabel>
|
||||
<Select
|
||||
labelId="Category"
|
||||
input={<OutlinedInput label="Select a Category" />}
|
||||
value={selectedCategoryVideos?.id || ""}
|
||||
onChange={handleOptionCategoryChangeVideos}
|
||||
>
|
||||
{categories.map((option) => (
|
||||
<MenuItem key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
{selectedCategoryVideos && (
|
||||
<>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '20px',
|
||||
width: '50%'
|
||||
}}>
|
||||
|
||||
|
||||
{selectedCategoryVideos &&
|
||||
subCategories[selectedCategoryVideos?.id] && (
|
||||
<FormControl fullWidth sx={{ marginBottom: 2 }}>
|
||||
<InputLabel id="Category">
|
||||
Select a Sub-Category
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="Sub-Category"
|
||||
input={
|
||||
<OutlinedInput label="Select a Sub-Category" />
|
||||
}
|
||||
value={selectedSubCategoryVideos?.id || ""}
|
||||
onChange={(e) =>
|
||||
handleOptionSubCategoryChangeVideos(
|
||||
e,
|
||||
subCategories[selectedCategoryVideos?.id]
|
||||
)
|
||||
}
|
||||
>
|
||||
{subCategories[selectedCategoryVideos.id].map(
|
||||
(option) => (
|
||||
<MenuItem key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</MenuItem>
|
||||
)
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
{selectedSubCategoryVideos &&
|
||||
subCategories2[selectedSubCategoryVideos?.id] && (
|
||||
<FormControl fullWidth sx={{ marginBottom: 2 }}>
|
||||
<InputLabel id="Category">
|
||||
Select a Sub-sub-Category
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="Sub-Category"
|
||||
input={
|
||||
<OutlinedInput label="Select a Sub-sub-Category" />
|
||||
}
|
||||
value={selectedSubCategoryVideos2?.id || ""}
|
||||
onChange={(e) =>
|
||||
handleOptionSubCategoryChangeVideos2(
|
||||
e,
|
||||
subCategories2[selectedSubCategoryVideos?.id]
|
||||
)
|
||||
}
|
||||
>
|
||||
{subCategories2[selectedSubCategoryVideos.id].map(
|
||||
(option) => (
|
||||
<MenuItem key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</MenuItem>
|
||||
)
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
{selectedSubCategoryVideos2 &&
|
||||
subCategories3[selectedSubCategoryVideos2?.id] && (
|
||||
<FormControl fullWidth sx={{ marginBottom: 2 }}>
|
||||
<InputLabel id="Category">
|
||||
Select a Sub-3x-subCategory
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="Sub-Category"
|
||||
input={
|
||||
<OutlinedInput label="Select a Sub-3x-Category" />
|
||||
}
|
||||
value={selectedSubCategoryVideos3?.id || ""}
|
||||
onChange={(e) =>
|
||||
handleOptionSubCategoryChangeVideos3(
|
||||
e,
|
||||
subCategories3[selectedSubCategoryVideos2?.id]
|
||||
)
|
||||
}
|
||||
>
|
||||
{subCategories3[selectedSubCategoryVideos2.id].map(
|
||||
(option) => (
|
||||
<MenuItem key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</MenuItem>
|
||||
)
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
{files?.length > 0 && (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "20px",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
<CategoryList
|
||||
categoryData={allCategoryData}
|
||||
ref={categoryListRef}
|
||||
columns={3}
|
||||
/>
|
||||
</Box>
|
||||
<CustomInputField
|
||||
name="title"
|
||||
label="Title of share"
|
||||
variant="filled"
|
||||
value={title}
|
||||
onChange={(e) => {
|
||||
onChange={e => {
|
||||
const value = e.target.value;
|
||||
const formattedValue = value.replace(titleFormatter, "");
|
||||
setTitle(formattedValue);
|
||||
@ -622,13 +416,12 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
</Typography>
|
||||
<TextEditor
|
||||
inlineContent={description}
|
||||
setInlineContent={(value) => {
|
||||
setInlineContent={value => {
|
||||
setDescription(value);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
</>
|
||||
)}
|
||||
<CrowdfundActionButtonRow>
|
||||
@ -664,16 +457,16 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
{isOpenMultiplePublish && (
|
||||
<MultiplePublish
|
||||
isOpen={isOpenMultiplePublish}
|
||||
onError={(messageNotification)=> {
|
||||
onError={messageNotification => {
|
||||
setIsOpenMultiplePublish(false);
|
||||
setPublishes(null)
|
||||
if(messageNotification){
|
||||
setPublishes(null);
|
||||
if (messageNotification) {
|
||||
dispatch(
|
||||
setNotification({
|
||||
msg: messageNotification,
|
||||
alertType: 'error'
|
||||
})
|
||||
)
|
||||
setNotification({
|
||||
msg: messageNotification,
|
||||
alertType: "error",
|
||||
})
|
||||
);
|
||||
}
|
||||
}}
|
||||
onSubmit={() => {
|
||||
@ -686,9 +479,8 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
setPlaylistDescription("");
|
||||
setSelectedCategory(null);
|
||||
setSelectedSubCategory(null);
|
||||
setSelectedCategoryVideos(null);
|
||||
setSelectedSubCategoryVideos(null);
|
||||
setPlaylistSetting(null);
|
||||
categoryListRef.current?.clearCategories();
|
||||
dispatch(
|
||||
setNotification({
|
||||
msg: "Files published",
|
||||
|
61
src/components/StatsData.tsx
Normal file
61
src/components/StatsData.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { styled } from "@mui/system";
|
||||
import { Grid } from "@mui/material";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "../state/store.ts";
|
||||
import { useFetchFiles } from "../hooks/useFetchFiles.tsx";
|
||||
|
||||
export const StatsData = () => {
|
||||
const StatsCol = styled(Grid)(({ theme }) => ({
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
padding: "20px 0px",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
}));
|
||||
|
||||
const {
|
||||
getFiles,
|
||||
checkAndUpdateFile,
|
||||
getFile,
|
||||
hashMapFiles,
|
||||
getNewFiles,
|
||||
checkNewFiles,
|
||||
getFilesFiltered,
|
||||
getFilesCount,
|
||||
} = useFetchFiles();
|
||||
|
||||
const totalVideosPublished = useSelector(
|
||||
(state: RootState) => state.global.totalFilesPublished
|
||||
);
|
||||
const totalNamesPublished = useSelector(
|
||||
(state: RootState) => state.global.totalNamesPublished
|
||||
);
|
||||
const videosPerNamePublished = useSelector(
|
||||
(state: RootState) => state.global.filesPerNamePublished
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
getFilesCount();
|
||||
}, [getFilesCount]);
|
||||
|
||||
return (
|
||||
<StatsCol>
|
||||
<div>
|
||||
Shares:{" "}
|
||||
<span style={{ fontWeight: "bold" }}>{totalVideosPublished}</span>
|
||||
</div>
|
||||
<div>
|
||||
Publishers:{" "}
|
||||
<span style={{ fontWeight: "bold" }}>{totalNamesPublished}</span>
|
||||
</div>
|
||||
<div>
|
||||
Average:{" "}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
{videosPerNamePublished > 0 &&
|
||||
Number(videosPerNamePublished).toFixed(0)}
|
||||
</span>
|
||||
</div>
|
||||
</StatsCol>
|
||||
);
|
||||
};
|
@ -0,0 +1,9 @@
|
||||
import { styled } from "@mui/system";
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
export const CategoryContainer = styled(Box)(({ theme }) => ({
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexDirection: "row",
|
||||
gap: "5px",
|
||||
}));
|
284
src/components/common/CategoryList/CategoryList.tsx
Normal file
284
src/components/common/CategoryList/CategoryList.tsx
Normal file
@ -0,0 +1,284 @@
|
||||
import {
|
||||
Box,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
OutlinedInput,
|
||||
Select,
|
||||
SelectChangeEvent,
|
||||
SxProps,
|
||||
Theme,
|
||||
} from "@mui/material";
|
||||
|
||||
import React, { forwardRef, useImperativeHandle, useState } from "react";
|
||||
import { CategoryContainer } from "./CategoryList-styles.tsx";
|
||||
import { allCategoryData } from "../../../constants/Categories/1stCategories.ts";
|
||||
|
||||
export interface Category {
|
||||
id: number;
|
||||
name: string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
export interface Categories {
|
||||
[key: number]: Category[];
|
||||
}
|
||||
export interface CategoryData {
|
||||
category: Category[];
|
||||
subCategories: Categories[];
|
||||
}
|
||||
|
||||
type ListDirection = "column" | "row";
|
||||
interface CategoryListProps {
|
||||
sx?: SxProps<Theme>;
|
||||
categoryData: CategoryData;
|
||||
initialCategories?: string[];
|
||||
columns?: number;
|
||||
}
|
||||
|
||||
export type CategoryListRef = {
|
||||
getSelectedCategories: () => string[];
|
||||
setSelectedCategories: (arr: string[]) => void;
|
||||
clearCategories: () => void;
|
||||
getCategoriesFetchString: () => string;
|
||||
categoriesToObject: () => object;
|
||||
};
|
||||
|
||||
export const CategoryList = React.forwardRef<
|
||||
CategoryListRef,
|
||||
CategoryListProps
|
||||
>(
|
||||
(
|
||||
{ sx, categoryData, initialCategories, columns = 1 }: CategoryListProps,
|
||||
ref
|
||||
) => {
|
||||
const categoriesLength = categoryData.subCategories.length + 1;
|
||||
|
||||
let emptyCategories: string[] = [];
|
||||
for (let i = 0; i < categoriesLength; i++) emptyCategories.push("");
|
||||
|
||||
const [selectedCategories, setSelectedCategories] = useState<string[]>(
|
||||
initialCategories || emptyCategories
|
||||
);
|
||||
|
||||
const categoriesToObject = () => {
|
||||
let categoriesObject = {};
|
||||
selectedCategories.map((category, index) => {
|
||||
if (index === 0) categoriesObject["category"] = category;
|
||||
else if (index === 1) categoriesObject["subcategory"] = category;
|
||||
else categoriesObject[`subcategory${index}`] = category;
|
||||
});
|
||||
console.log("categoriesObject is: ", categoriesObject);
|
||||
return categoriesObject;
|
||||
};
|
||||
|
||||
const clearCategories = () => {
|
||||
setSelectedCategories(emptyCategories);
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
getSelectedCategories: () => {
|
||||
return selectedCategories;
|
||||
},
|
||||
setSelectedCategories: categories => {
|
||||
console.log("setSelectedCategories: ", categories);
|
||||
//categories.map((category, index) => selectCategory(category, index));
|
||||
setSelectedCategories(categories);
|
||||
},
|
||||
clearCategories,
|
||||
getCategoriesFetchString: () =>
|
||||
getCategoriesFetchString(selectedCategories),
|
||||
categoriesToObject,
|
||||
}));
|
||||
|
||||
const selectCategory = (optionId: string, index: number) => {
|
||||
const isMainCategory = index === 0;
|
||||
const subCategoryIndex = index - 1;
|
||||
|
||||
const selectedOption = isMainCategory
|
||||
? categoryData.category.find(option => option.id === +optionId)
|
||||
: categoryData.subCategories[subCategoryIndex][
|
||||
selectedCategories[subCategoryIndex]
|
||||
].find(option => option.id === +optionId);
|
||||
|
||||
const newSelectedCategories: string[] = selectedCategories.map(
|
||||
(category, categoryIndex) => {
|
||||
if (index > categoryIndex) return category;
|
||||
else if (index === categoryIndex) return selectedOption.id.toString();
|
||||
else return "";
|
||||
}
|
||||
);
|
||||
setSelectedCategories(newSelectedCategories);
|
||||
};
|
||||
|
||||
const selectCategoryEvent = (event: SelectChangeEvent, index: number) => {
|
||||
const optionId = event.target.value;
|
||||
selectCategory(optionId, index);
|
||||
};
|
||||
|
||||
const categorySelectSX = {
|
||||
// Target the input field
|
||||
".MuiSelect-select": {
|
||||
fontSize: "16px", // Change font size for the selected value
|
||||
padding: "10px 5px 15px 15px;",
|
||||
},
|
||||
// Target the dropdown icon
|
||||
".MuiSelect-icon": {
|
||||
fontSize: "20px", // Adjust if needed
|
||||
},
|
||||
// Target the dropdown menu
|
||||
"& .MuiMenu-paper": {
|
||||
".MuiMenuItem-root": {
|
||||
fontSize: "14px", // Change font size for the menu items
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const fillMenu = (category: Categories, index: number) => {
|
||||
const subCategoryIndex = selectedCategories[index];
|
||||
console.log("selected categories: ", selectedCategories);
|
||||
console.log("index is: ", index);
|
||||
console.log("subCategoryIndex is: ", subCategoryIndex);
|
||||
console.log("category is: ", category);
|
||||
console.log(
|
||||
"subCategoryIndex within category: ",
|
||||
selectedCategories[subCategoryIndex]
|
||||
);
|
||||
console.log("categoryData: ", categoryData);
|
||||
|
||||
const menuToFill = category[subCategoryIndex];
|
||||
if (menuToFill)
|
||||
return menuToFill.map(option => (
|
||||
<MenuItem key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</MenuItem>
|
||||
));
|
||||
};
|
||||
|
||||
const hasSubCategory = (category: Categories, index: number) => {
|
||||
const subCategoryIndex = selectedCategories[index];
|
||||
const subCategory = category[subCategoryIndex];
|
||||
return subCategory && subCategoryIndex;
|
||||
};
|
||||
|
||||
return (
|
||||
<CategoryContainer sx={{ width: "100%", ...sx }}>
|
||||
<FormControl sx={{ width: "100%" }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(" + columns + ", 1fr)",
|
||||
width: "100%",
|
||||
gap: "20px",
|
||||
alignItems: "center",
|
||||
marginTop: "30px",
|
||||
}}
|
||||
>
|
||||
<FormControl fullWidth sx={{ marginBottom: 1 }}>
|
||||
<InputLabel
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
}}
|
||||
id="Category-1"
|
||||
>
|
||||
Category
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="Category 1"
|
||||
input={<OutlinedInput label="Category 1" />}
|
||||
value={selectedCategories[0] || ""}
|
||||
onChange={e => {
|
||||
selectCategoryEvent(e, 0);
|
||||
}}
|
||||
sx={categorySelectSX}
|
||||
>
|
||||
{categoryData.category.map(option => (
|
||||
<MenuItem key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{categoryData.subCategories.map(
|
||||
(category, index) =>
|
||||
hasSubCategory(category, index) && (
|
||||
<FormControl
|
||||
fullWidth
|
||||
sx={{
|
||||
marginBottom: 1,
|
||||
}}
|
||||
key={selectedCategories[index] + index}
|
||||
>
|
||||
<InputLabel
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
}}
|
||||
id={`Category-${index + 2}`}
|
||||
>
|
||||
{`Category-${index + 2}`}
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`Category ${index + 2}`}
|
||||
input={<OutlinedInput label={`Category ${index + 2}`} />}
|
||||
value={selectedCategories[index + 1] || ""}
|
||||
onChange={e => {
|
||||
selectCategoryEvent(e, index + 1);
|
||||
}}
|
||||
sx={{
|
||||
width: "100%",
|
||||
// Target the input field
|
||||
".MuiSelect-select": {
|
||||
fontSize: "16px", // Change font size for the selected value
|
||||
padding: "10px 5px 15px 15px;",
|
||||
},
|
||||
// Target the dropdown icon
|
||||
".MuiSelect-icon": {
|
||||
fontSize: "20px", // Adjust if needed
|
||||
},
|
||||
// Target the dropdown menu
|
||||
"& .MuiMenu-paper": {
|
||||
".MuiMenuItem-root": {
|
||||
fontSize: "14px", // Change font size for the menu items
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{fillMenu(category, index)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)
|
||||
)}
|
||||
</Box>
|
||||
</FormControl>
|
||||
</CategoryContainer>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const getCategoriesFetchString = (categories: string[]) => {
|
||||
let fetchString = "";
|
||||
categories.map((category, index) => {
|
||||
if (category) {
|
||||
if (index === 0) fetchString += `cat:${category}`;
|
||||
else if (index === 1) fetchString += `;sub:${category}`;
|
||||
else fetchString += `;sub${index}:${category}`;
|
||||
}
|
||||
});
|
||||
console.log("categoriesAsDescription: ", fetchString);
|
||||
return fetchString;
|
||||
};
|
||||
|
||||
export const getCategoriesFromObject = (editFileProperties: any) => {
|
||||
const categoryList: string[] = [];
|
||||
const categoryCount = allCategoryData.subCategories.length + 1;
|
||||
|
||||
for (let i = 0; i < categoryCount; i++) {
|
||||
if (i === 0 && editFileProperties.category)
|
||||
categoryList.push(editFileProperties.category);
|
||||
else if (i === 1 && editFileProperties.subcategory)
|
||||
categoryList.push(editFileProperties.subcategory);
|
||||
else categoryList.push(editFileProperties[`subcategory${i}`] || "");
|
||||
}
|
||||
return categoryList;
|
||||
};
|
@ -1,11 +1,11 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
CircularProgress,
|
||||
Modal,
|
||||
Typography,
|
||||
useTheme,
|
||||
Box,
|
||||
Button,
|
||||
CircularProgress,
|
||||
Modal,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import React, { useCallback, useEffect, useState, useRef } from "react";
|
||||
import { CircleSVG } from "../../../assets/svgs/CircleSVG";
|
||||
@ -13,199 +13,213 @@ import { EmptyCircleSVG } from "../../../assets/svgs/EmptyCircleSVG";
|
||||
import { styled } from "@mui/system";
|
||||
|
||||
interface Publish {
|
||||
resources: any[];
|
||||
action: string;
|
||||
resources: any[];
|
||||
action: string;
|
||||
}
|
||||
|
||||
interface MultiplePublishProps {
|
||||
publishes: Publish;
|
||||
isOpen: boolean;
|
||||
onSubmit: ()=> void
|
||||
onError: (message?: string)=> void
|
||||
publishes: Publish;
|
||||
isOpen: boolean;
|
||||
onSubmit: () => void;
|
||||
onError: (message?: string) => void;
|
||||
}
|
||||
export const MultiplePublish = ({ publishes, isOpen, onSubmit, onError}: MultiplePublishProps) => {
|
||||
const theme = useTheme();
|
||||
const listOfSuccessfulPublishesRef = useRef([])
|
||||
const [listOfSuccessfulPublishes, setListOfSuccessfulPublishes] = useState<
|
||||
any[]
|
||||
>([]);
|
||||
const [listOfUnsuccessfulPublishes, setListOfUnSuccessfulPublishes] = useState<
|
||||
any[]
|
||||
>([]);
|
||||
const [currentlyInPublish, setCurrentlyInPublish] = useState(null);
|
||||
const hasStarted = useRef(false);
|
||||
const publish = useCallback(async (pub: any) => {
|
||||
const lengthOfResources = pub?.resources?.length
|
||||
const lengthOfTimeout = lengthOfResources * 30000
|
||||
return await qortalRequestWithTimeout(pub, lengthOfTimeout);
|
||||
}, []);
|
||||
const [isPublishing, setIsPublishing] = useState(true)
|
||||
export const MultiplePublish = ({
|
||||
publishes,
|
||||
isOpen,
|
||||
onSubmit,
|
||||
onError,
|
||||
}: MultiplePublishProps) => {
|
||||
const theme = useTheme();
|
||||
const listOfSuccessfulPublishesRef = useRef([]);
|
||||
const [listOfSuccessfulPublishes, setListOfSuccessfulPublishes] = useState<
|
||||
any[]
|
||||
>([]);
|
||||
const [listOfUnsuccessfulPublishes, setListOfUnSuccessfulPublishes] =
|
||||
useState<any[]>([]);
|
||||
const [currentlyInPublish, setCurrentlyInPublish] = useState(null);
|
||||
const hasStarted = useRef(false);
|
||||
const publish = useCallback(async (pub: any) => {
|
||||
const lengthOfResources = pub?.resources?.length;
|
||||
const lengthOfTimeout = lengthOfResources * 30000;
|
||||
return await qortalRequestWithTimeout(pub, lengthOfTimeout);
|
||||
}, []);
|
||||
const [isPublishing, setIsPublishing] = useState(true);
|
||||
|
||||
const handlePublish = useCallback(
|
||||
async (pub: any) => {
|
||||
try {
|
||||
setCurrentlyInPublish(pub?.identifier);
|
||||
setIsPublishing(true)
|
||||
const res = await publish(pub);
|
||||
const handlePublish = useCallback(
|
||||
async (pub: any) => {
|
||||
try {
|
||||
setCurrentlyInPublish(pub?.identifier);
|
||||
setIsPublishing(true);
|
||||
const res = await publish(pub);
|
||||
|
||||
onSubmit()
|
||||
setListOfUnSuccessfulPublishes([])
|
||||
|
||||
} catch (error: any) {
|
||||
const unsuccessfulPublishes = error?.error?.unsuccessfulPublishes || []
|
||||
if(error?.error === 'User declined request'){
|
||||
onError()
|
||||
return
|
||||
}
|
||||
|
||||
if(error?.error === 'The request timed out'){
|
||||
onError("The request timed out")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if(unsuccessfulPublishes?.length > 0){
|
||||
setListOfUnSuccessfulPublishes(unsuccessfulPublishes)
|
||||
|
||||
}
|
||||
} finally {
|
||||
|
||||
setIsPublishing(false)
|
||||
}
|
||||
},
|
||||
[publish]
|
||||
);
|
||||
|
||||
const retry = ()=> {
|
||||
let newlistOfMultiplePublishes: any[] = [];
|
||||
listOfUnsuccessfulPublishes?.forEach((item)=> {
|
||||
const findPub = publishes?.resources.find((res: any)=> res?.identifier === item.identifier)
|
||||
if(findPub){
|
||||
newlistOfMultiplePublishes.push(findPub)
|
||||
}
|
||||
})
|
||||
const multiplePublish = {
|
||||
...publishes,
|
||||
resources: newlistOfMultiplePublishes
|
||||
};
|
||||
handlePublish(multiplePublish)
|
||||
}
|
||||
|
||||
const startPublish = useCallback(
|
||||
async (pubs: any) => {
|
||||
await handlePublish(pubs);
|
||||
},
|
||||
[handlePublish, onSubmit, listOfSuccessfulPublishes, publishes]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (publishes && !hasStarted.current) {
|
||||
hasStarted.current = true;
|
||||
startPublish(publishes);
|
||||
onSubmit();
|
||||
setListOfUnSuccessfulPublishes([]);
|
||||
} catch (error: any) {
|
||||
const unsuccessfulPublishes = error?.error?.unsuccessfulPublishes || [];
|
||||
if (error?.error === "User declined request") {
|
||||
onError();
|
||||
return;
|
||||
}
|
||||
}, [startPublish, publishes, listOfSuccessfulPublishes]);
|
||||
|
||||
if (error?.error === "The request timed out") {
|
||||
onError("The request timed out");
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={isOpen}
|
||||
aria-labelledby="modal-title"
|
||||
aria-describedby="modal-description"
|
||||
>
|
||||
<ModalBody
|
||||
sx={{
|
||||
minHeight: "50vh",
|
||||
}}
|
||||
return;
|
||||
}
|
||||
|
||||
if (unsuccessfulPublishes?.length > 0) {
|
||||
setListOfUnSuccessfulPublishes(unsuccessfulPublishes);
|
||||
}
|
||||
} finally {
|
||||
setIsPublishing(false);
|
||||
}
|
||||
},
|
||||
[publish]
|
||||
);
|
||||
|
||||
const retry = () => {
|
||||
let newlistOfMultiplePublishes: any[] = [];
|
||||
listOfUnsuccessfulPublishes?.forEach(item => {
|
||||
const findPub = publishes?.resources.find(
|
||||
(res: any) => res?.identifier === item.identifier
|
||||
);
|
||||
if (findPub) {
|
||||
newlistOfMultiplePublishes.push(findPub);
|
||||
}
|
||||
});
|
||||
const multiplePublish = {
|
||||
...publishes,
|
||||
resources: newlistOfMultiplePublishes,
|
||||
};
|
||||
handlePublish(multiplePublish);
|
||||
};
|
||||
|
||||
const startPublish = useCallback(
|
||||
async (pubs: any) => {
|
||||
await handlePublish(pubs);
|
||||
},
|
||||
[handlePublish, onSubmit, listOfSuccessfulPublishes, publishes]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (publishes && !hasStarted.current) {
|
||||
hasStarted.current = true;
|
||||
startPublish(publishes);
|
||||
}
|
||||
}, [startPublish, publishes, listOfSuccessfulPublishes]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={isOpen}
|
||||
aria-labelledby="modal-title"
|
||||
aria-describedby="modal-description"
|
||||
>
|
||||
<ModalBody
|
||||
sx={{
|
||||
minHeight: "50vh",
|
||||
}}
|
||||
>
|
||||
{publishes?.resources?.map((publish: any) => {
|
||||
const unpublished = listOfUnsuccessfulPublishes.map(
|
||||
item => item?.identifier
|
||||
);
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "20px",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
key={publish?.identifier}
|
||||
>
|
||||
{publishes?.resources?.map((publish: any) => {
|
||||
const unpublished = listOfUnsuccessfulPublishes.map(item => item?.identifier)
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "20px",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Typography>{publish?.identifier}</Typography>
|
||||
{!isPublishing && hasStarted.current ? (
|
||||
<>
|
||||
{!unpublished.includes(publish.identifier) ? (
|
||||
<CircleSVG
|
||||
color={theme.palette.text.primary}
|
||||
height="24px"
|
||||
width="24px"
|
||||
/>
|
||||
) : (
|
||||
<EmptyCircleSVG
|
||||
color={theme.palette.text.primary}
|
||||
height="24px"
|
||||
width="24px"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
): <CircularProgress size={16} color="secondary"/>}
|
||||
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
{!isPublishing && listOfUnsuccessfulPublishes.length > 0 && (
|
||||
<>
|
||||
<Typography sx={{
|
||||
marginTop: '20px',
|
||||
fontSize: '16px'
|
||||
}}>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</Typography>
|
||||
<Button variant="contained" onClick={()=> {
|
||||
retry()
|
||||
}}>Try again</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
</ModalBody>
|
||||
</Modal>
|
||||
);
|
||||
<Typography>{publish?.identifier}</Typography>
|
||||
{!isPublishing && hasStarted.current ? (
|
||||
<>
|
||||
{!unpublished.includes(publish.identifier) ? (
|
||||
<CircleSVG
|
||||
color={theme.palette.text.primary}
|
||||
height="24px"
|
||||
width="24px"
|
||||
/>
|
||||
) : (
|
||||
<EmptyCircleSVG
|
||||
color={theme.palette.text.primary}
|
||||
height="24px"
|
||||
width="24px"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<CircularProgress size={16} color="secondary" />
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
{!isPublishing && listOfUnsuccessfulPublishes.length > 0 && (
|
||||
<>
|
||||
<Typography
|
||||
sx={{
|
||||
marginTop: "20px",
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
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
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
retry();
|
||||
}}
|
||||
>
|
||||
Try again
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</ModalBody>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
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",
|
||||
},
|
||||
}));
|
||||
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",
|
||||
},
|
||||
}));
|
||||
|
@ -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<Props> = ({
|
||||
|
||||
const [anchorElNotification, setAnchorElNotification] =
|
||||
React.useState<HTMLButtonElement | null>(null);
|
||||
const filterValue = useSelector(
|
||||
(state: RootState) => state.video.filterValue
|
||||
);
|
||||
const filterValue = useSelector((state: RootState) => state.file.filterValue);
|
||||
|
||||
const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
const target = event.currentTarget as unknown as HTMLButtonElement | null;
|
||||
@ -100,36 +105,42 @@ const NavBar: React.FC<Props> = ({
|
||||
return (
|
||||
<CustomAppBar position="sticky" elevation={2}>
|
||||
<ThemeSelectRow>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
height: '100%',
|
||||
alignItems: 'center',
|
||||
gap: '20px'
|
||||
}}>
|
||||
<LogoContainer
|
||||
onClick={() => {
|
||||
navigate("/");
|
||||
dispatch(setIsFiltering(false));
|
||||
dispatch(setFilterValue(""));
|
||||
dispatch(addFilteredVideos([]));
|
||||
searchValRef.current = "";
|
||||
if (!inputRef.current) return;
|
||||
inputRef.current.value = "";
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
height: "100%",
|
||||
alignItems: "center",
|
||||
gap: "20px",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={QShareLogo}
|
||||
style={{
|
||||
width: "auto",
|
||||
height: "55px",
|
||||
padding: "2px",
|
||||
<LogoContainer
|
||||
onClick={() => {
|
||||
navigate("/");
|
||||
dispatch(setIsFiltering(false));
|
||||
dispatch(setFilterValue(""));
|
||||
dispatch(addFilteredFiles([]));
|
||||
searchValRef.current = "";
|
||||
if (!inputRef.current) return;
|
||||
inputRef.current.value = "";
|
||||
}}
|
||||
/>
|
||||
</LogoContainer>
|
||||
<Typography sx={{
|
||||
fontSize: '16px',
|
||||
whiteSpace: 'nowrap'
|
||||
}}>Sharing is caring</Typography>
|
||||
>
|
||||
<img
|
||||
src={QShareLogo}
|
||||
style={{
|
||||
width: "auto",
|
||||
height: "55px",
|
||||
padding: "2px",
|
||||
}}
|
||||
/>
|
||||
</LogoContainer>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
Sharing is caring
|
||||
</Typography>
|
||||
</Box>
|
||||
</ThemeSelectRow>
|
||||
<Box
|
||||
@ -289,15 +300,15 @@ const NavBar: React.FC<Props> = ({
|
||||
<Input
|
||||
id="standard-adornment-name"
|
||||
inputRef={inputRef}
|
||||
onChange={(e) => {
|
||||
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<Props> = ({
|
||||
}
|
||||
navigate("/");
|
||||
dispatch(setIsFiltering(true));
|
||||
dispatch(addFilteredVideos([]));
|
||||
dispatch(addFilteredFiles([]));
|
||||
dispatch(setFilterValue(searchValRef.current));
|
||||
}
|
||||
}}
|
||||
@ -338,7 +349,7 @@ const NavBar: React.FC<Props> = ({
|
||||
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<Props> = ({
|
||||
}
|
||||
navigate("/");
|
||||
dispatch(setIsFiltering(true));
|
||||
dispatch(addFilteredVideos([]));
|
||||
dispatch(addFilteredFiles([]));
|
||||
dispatch(setFilterValue(searchValRef.current));
|
||||
}}
|
||||
/>
|
||||
@ -357,7 +368,7 @@ const NavBar: React.FC<Props> = ({
|
||||
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<Props> = ({
|
||||
<AvatarContainer>
|
||||
{isAuthenticated && userName && (
|
||||
<>
|
||||
<PublishFile />
|
||||
<PublishFile />
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
</AvatarContainer>
|
||||
|
||||
<Popover
|
||||
|
@ -1,221 +0,0 @@
|
||||
import softwareIcon from "../assets/icons/software.webp";
|
||||
import gamingIcon from "../assets/icons/gaming.webp";
|
||||
import mediaIcon from "../assets/icons/media.webp";
|
||||
import videoIcon from "../assets/icons/video.webp";
|
||||
import audioIcon from "../assets/icons/audio.webp";
|
||||
import documentIcon from "../assets/icons/document.webp";
|
||||
|
||||
interface SubCategory {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface Categories {
|
||||
[key: number]: SubCategory[];
|
||||
}
|
||||
|
||||
const sortCategory = (a: SubCategory, b: SubCategory) => {
|
||||
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
|
||||
}
|
57
src/constants/Categories/1stCategories.ts
Normal file
57
src/constants/Categories/1stCategories.ts
Normal file
@ -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();
|
88
src/constants/Categories/2ndCategories.ts
Normal file
88
src/constants/Categories/2ndCategories.ts
Normal file
@ -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" },
|
||||
];
|
23
src/constants/Categories/3rdCategories.ts
Normal file
23
src/constants/Categories/3rdCategories.ts
Normal file
@ -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" },
|
||||
];
|
91
src/constants/Categories/CategoryFunctions.ts
Normal file
91
src/constants/Categories/CategoryFunctions.ts
Normal file
@ -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);
|
||||
};
|
@ -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_";
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1 +1,3 @@
|
||||
export const titleFormatter = /[^a-zA-Z0-9\s-_!?()&'",.~;:|]/g;
|
||||
export const minPriceSuperlike = 10;
|
||||
export const titleFormatter = /[^a-zA-Z0-9\s-_!?()&'",.;:|—~@#$%^*+=<>]/g;
|
||||
export const titleFormatterOnSave = /[^a-zA-Z0-9\s-_!()&',.;—~@#$%^+=]/g;
|
@ -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 checkAndUpdateVideo = React.useCallback(
|
||||
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 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}`;
|
||||
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;
|
||||
}
|
||||
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}`;
|
||||
if (name) {
|
||||
defaultUrl += `&name=${name}`;
|
||||
}
|
||||
|
||||
// Check and append subcategory2
|
||||
if (subcategory2) {
|
||||
description += `;sub2:${subcategory2}`;
|
||||
}
|
||||
|
||||
// Check and append subcategory3
|
||||
if (subcategory3) {
|
||||
description += `;sub3:${subcategory3}`;
|
||||
}
|
||||
|
||||
// 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 (categories.length > 0) {
|
||||
defaultUrl += "&description=" + getCategoriesFetchString(categories);
|
||||
}
|
||||
})
|
||||
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 (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}`;
|
||||
}
|
||||
})
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
85
src/pages/FileContent/FileContent-styles.tsx
Normal file
85
src/pages/FileContent/FileContent-styles.tsx
Normal file
@ -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 (
|
||||
<Box
|
||||
sx={{
|
||||
height: height,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
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",
|
||||
}));
|
@ -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<boolean>(false);
|
||||
const [descriptionHeight, setDescriptionHeight] =
|
||||
useState<null | number>(null);
|
||||
|
||||
const [descriptionHeight, setDescriptionHeight] = useState<null | number>(
|
||||
null
|
||||
);
|
||||
const [icon, setIcon] = useState<string>("");
|
||||
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<any>(null);
|
||||
const [fileData, setFileData] = useState<any>(null);
|
||||
const [playlistData, setPlaylistData] = useState<any>(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]);
|
||||
if (fileData) {
|
||||
//const icon = getIconsFromObject(fileData)[0]?.icon || null;
|
||||
|
||||
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 icon = getIconsFromObject(fileData);
|
||||
setIcon(icon);
|
||||
}
|
||||
}, [fileData]);
|
||||
|
||||
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])
|
||||
|
||||
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 (
|
||||
<Box
|
||||
@ -301,24 +321,42 @@ export const VideoContent = () => {
|
||||
padding: "20px 10px",
|
||||
}}
|
||||
>
|
||||
<VideoPlayerContainer
|
||||
<FilePlayerContainer
|
||||
sx={{
|
||||
marginBottom: "30px",
|
||||
}}
|
||||
>
|
||||
|
||||
<Spacer height="15px" />
|
||||
|
||||
<VideoTitle
|
||||
variant="h1"
|
||||
color="textPrimary"
|
||||
sx={{
|
||||
textAlign: "center",
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{videoData?.title}
|
||||
</VideoTitle>
|
||||
{videoData?.created && (
|
||||
{icon ? (
|
||||
<img
|
||||
src={icon}
|
||||
width="50px"
|
||||
style={{
|
||||
borderRadius: "5px",
|
||||
marginRight: "10px",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<AttachFileIcon />
|
||||
)}
|
||||
<FileTitle
|
||||
variant="h1"
|
||||
color="textPrimary"
|
||||
sx={{
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
{fileData?.title}
|
||||
</FileTitle>
|
||||
</div>
|
||||
{fileData?.created && (
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
@ -326,7 +364,7 @@ export const VideoContent = () => {
|
||||
}}
|
||||
color={theme.palette.text.primary}
|
||||
>
|
||||
{formatDate(videoData.created)}
|
||||
{formatDate(fileData.created)}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
@ -367,11 +405,15 @@ export const VideoContent = () => {
|
||||
</Box>
|
||||
<Spacer height="15px" />
|
||||
<Box>
|
||||
<Typography sx={{
|
||||
fontWeight: 'bold',
|
||||
fontSize: '16px',
|
||||
userSelect: 'none'
|
||||
}}>{categoriesDisplay}</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
fontSize: "16px",
|
||||
userSelect: "none",
|
||||
}}
|
||||
>
|
||||
{categoriesDisplay}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Spacer height="15px" />
|
||||
<Box
|
||||
@ -380,10 +422,16 @@ export const VideoContent = () => {
|
||||
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 && (
|
||||
<Box
|
||||
@ -402,95 +450,101 @@ export const VideoContent = () => {
|
||||
/>
|
||||
)}
|
||||
<Box
|
||||
ref={contentRef}
|
||||
ref={contentRef}
|
||||
sx={{
|
||||
height: !descriptionHeight ? 'auto' : isExpandedDescription ? "auto" : "100px",
|
||||
height: !descriptionHeight
|
||||
? "auto"
|
||||
: isExpandedDescription
|
||||
? "auto"
|
||||
: "100px",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{videoData?.htmlDescription ? (
|
||||
<DisplayHtml html={videoData?.htmlDescription} />
|
||||
{fileData?.htmlDescription ? (
|
||||
<DisplayHtml html={fileData?.htmlDescription} />
|
||||
) : (
|
||||
<VideoDescription variant="body1" color="textPrimary" sx={{
|
||||
cursor: 'default'
|
||||
}}>
|
||||
{videoData?.fullDescription}
|
||||
</VideoDescription>
|
||||
<FileDescription
|
||||
variant="body1"
|
||||
color="textPrimary"
|
||||
sx={{
|
||||
cursor: "default",
|
||||
}}
|
||||
>
|
||||
{fileData?.fullDescription}
|
||||
</FileDescription>
|
||||
)}
|
||||
</Box>
|
||||
{descriptionHeight && (
|
||||
<Typography
|
||||
onClick={() => {
|
||||
setIsExpandedDescription((prev) => !prev);
|
||||
}}
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
fontSize: "16px",
|
||||
cursor: "pointer",
|
||||
paddingLeft: "15px",
|
||||
paddingTop: "15px",
|
||||
}}
|
||||
>
|
||||
{isExpandedDescription ? "Show less" : "...more"}
|
||||
</Typography>
|
||||
<Typography
|
||||
onClick={() => {
|
||||
setIsExpandedDescription(prev => !prev);
|
||||
}}
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
fontSize: "16px",
|
||||
cursor: "pointer",
|
||||
paddingLeft: "15px",
|
||||
paddingTop: "15px",
|
||||
}}
|
||||
>
|
||||
{isExpandedDescription ? "Show less" : "...more"}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
</Box>
|
||||
<Box sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
flexDirection: 'column',
|
||||
gap: '25px',
|
||||
marginTop: '25px'
|
||||
}}>
|
||||
{videoData?.files?.map((file)=> {
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
flexDirection: "column",
|
||||
gap: "25px",
|
||||
marginTop: "25px",
|
||||
}}
|
||||
>
|
||||
{fileData?.files?.map((file, index) => {
|
||||
return (
|
||||
<FileAttachmentContainer sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between'
|
||||
}}>
|
||||
|
||||
<FileAttachmentFont>
|
||||
{file.filename}
|
||||
</FileAttachmentFont>
|
||||
<Box sx={{
|
||||
|
||||
display: 'flex',
|
||||
gap: '25px',
|
||||
alignItems: 'center',
|
||||
|
||||
}}>
|
||||
|
||||
|
||||
<FileAttachmentFont>
|
||||
{formatBytes(file?.size || 0)}
|
||||
</FileAttachmentFont>
|
||||
<FileElement
|
||||
fileInfo={{...file,
|
||||
filename: file?.filename,
|
||||
mimeType: file?.mimetype
|
||||
|
||||
}}
|
||||
jsonId={id}
|
||||
title={file?.filename}
|
||||
customStyles={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<DownloadIcon />
|
||||
</FileElement>
|
||||
</Box>
|
||||
</FileAttachmentContainer>
|
||||
)
|
||||
<FileAttachmentContainer
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
key={file.toString() + index}
|
||||
>
|
||||
<FileAttachmentFont>{file.filename}</FileAttachmentFont>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "25px",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<FileAttachmentFont>
|
||||
{formatBytes(file?.size || 0)}
|
||||
</FileAttachmentFont>
|
||||
<FileElement
|
||||
fileInfo={{
|
||||
...file,
|
||||
filename: file?.filename,
|
||||
mimeType: file?.mimetype,
|
||||
}}
|
||||
jsonId={id}
|
||||
title={file?.filename}
|
||||
customStyles={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<DownloadIcon />
|
||||
</FileElement>
|
||||
</Box>
|
||||
</FileAttachmentContainer>
|
||||
);
|
||||
})}
|
||||
|
||||
</Box>
|
||||
</VideoPlayerContainer>
|
||||
|
||||
</Box>
|
||||
</FilePlayerContainer>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
@ -1,77 +1,79 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { RootState } from '../../state/store'
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "../../state/store";
|
||||
import { Avatar, Box, Button, Typography, useTheme } from "@mui/material";
|
||||
import { useFetchFiles } from "../../hooks/useFetchFiles.tsx";
|
||||
import LazyLoad from "../../components/common/LazyLoad";
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
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 { ChannelCard, ChannelTitle } from './Home-styles'
|
||||
BottomParent,
|
||||
NameContainer,
|
||||
VideoCard,
|
||||
VideoCardName,
|
||||
VideoCardTitle,
|
||||
FileContainer,
|
||||
VideoUploadDate,
|
||||
} from "./FileList-styles.tsx";
|
||||
import ResponsiveImage from "../../components/ResponsiveImage";
|
||||
import { formatDate, formatTimestampSeconds } from "../../utils/time";
|
||||
import { ChannelCard, ChannelTitle } from "./Home-styles";
|
||||
|
||||
interface VideoListProps {
|
||||
mode?: string
|
||||
mode?: string;
|
||||
}
|
||||
export const Channels = ({ mode }: VideoListProps) => {
|
||||
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 (
|
||||
<Box sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
minHeight: '50vh'
|
||||
}}>
|
||||
<VideoContainer>
|
||||
{publishNames && publishNames?.slice(0, 10).map((name)=> {
|
||||
let avatarUrl = ''
|
||||
if(userAvatarHash[name]){
|
||||
avatarUrl = userAvatarHash[name]
|
||||
}
|
||||
return (
|
||||
<Box
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flex: 0,
|
||||
alignItems: 'center',
|
||||
width: 'auto',
|
||||
position: 'relative',
|
||||
' @media (max-width: 450px)': {
|
||||
width: '100%'
|
||||
}
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
minHeight: "50vh",
|
||||
}}
|
||||
key={name}
|
||||
>
|
||||
<ChannelCard
|
||||
<FileContainer>
|
||||
{publishNames &&
|
||||
publishNames?.slice(0, 10).map(name => {
|
||||
let avatarUrl = "";
|
||||
if (userAvatarHash[name]) {
|
||||
avatarUrl = userAvatarHash[name];
|
||||
}
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flex: 0,
|
||||
alignItems: "center",
|
||||
width: "auto",
|
||||
position: "relative",
|
||||
" @media (max-width: 450px)": {
|
||||
width: "100%",
|
||||
},
|
||||
}}
|
||||
key={name}
|
||||
>
|
||||
<ChannelCard
|
||||
onClick={() => {
|
||||
navigate(`/channel/${name}`)
|
||||
navigate(`/channel/${name}`);
|
||||
}}
|
||||
>
|
||||
<ChannelTitle>{name}</ChannelTitle>
|
||||
<ResponsiveImage src={avatarUrl} width={50} height={50}/>
|
||||
</ChannelCard>
|
||||
>
|
||||
<ChannelTitle>{name}</ChannelTitle>
|
||||
<ResponsiveImage src={avatarUrl} width={50} height={50} />
|
||||
</ChannelCard>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</FileContainer>
|
||||
</Box>
|
||||
)
|
||||
})}
|
||||
</VideoContainer>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
);
|
||||
};
|
||||
|
@ -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)",
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
|
@ -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
|
||||
export const FileList = ({ files }: FileListProps) => {
|
||||
const hashMapFiles = useSelector(
|
||||
(state: RootState) => state.file.hashMapFiles
|
||||
);
|
||||
const filterValue = useSelector(
|
||||
(state: RootState) => state.video.filterValue
|
||||
);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(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
|
||||
);
|
||||
|
||||
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 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<any>(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<string>
|
||||
) => {
|
||||
const optionId = event.target.value;
|
||||
const selectedOption = categories.find((option) => option.id === +optionId);
|
||||
setSelectedCategoryVideos(selectedOption || null);
|
||||
};
|
||||
const handleOptionSubCategoryChangeVideos = (
|
||||
event: SelectChangeEvent<string>,
|
||||
subcategories: any[]
|
||||
) => {
|
||||
const optionId = event.target.value;
|
||||
const selectedOption = subcategories.find(
|
||||
(option) => option.id === +optionId
|
||||
);
|
||||
setSelectedSubCategoryVideos(selectedOption || null);
|
||||
};
|
||||
const handleOptionSubCategoryChangeVideos2 = (
|
||||
event: SelectChangeEvent<string>,
|
||||
subcategories: any[]
|
||||
) => {
|
||||
const optionId = event.target.value;
|
||||
const selectedOption = subcategories.find(
|
||||
(option) => option.id === +optionId
|
||||
);
|
||||
setSelectedSubCategoryVideos2(selectedOption || null);
|
||||
};
|
||||
const handleOptionSubCategoryChangeVideos3 = (
|
||||
event: SelectChangeEvent<string>,
|
||||
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 (
|
||||
<Grid container sx={{ width: "100%" }}>
|
||||
<FiltersCol item xs={12} md={2} sm={3}>
|
||||
<FiltersContainer>
|
||||
<Input
|
||||
id="standard-adornment-name"
|
||||
onChange={(e) => {
|
||||
setFilterSearch(e.target.value);
|
||||
}}
|
||||
onKeyDown={searchOnEnter}
|
||||
value={filterSearch}
|
||||
placeholder="Search"
|
||||
<FileContainer>
|
||||
{files.map((file: any, index: number) => {
|
||||
const existingFile = hashMapFiles[file?.id];
|
||||
let hasHash = false;
|
||||
let fileObj = file;
|
||||
if (existingFile) {
|
||||
fileObj = existingFile;
|
||||
hasHash = true;
|
||||
}
|
||||
const icon = getIconsFromObject(fileObj);
|
||||
return (
|
||||
<Box
|
||||
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",
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
id="standard-adornment-name"
|
||||
onChange={(e) => {
|
||||
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",
|
||||
}}
|
||||
/>
|
||||
<FiltersTitle>
|
||||
Categories
|
||||
<ExpandMoreSVG
|
||||
color={theme.palette.text.primary}
|
||||
height={"22"}
|
||||
width={"22"}
|
||||
/>
|
||||
</FiltersTitle>
|
||||
<FiltersSubContainer>
|
||||
<FormControl sx={{ width: "100%" }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "20px",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<FormControl fullWidth sx={{ marginBottom: 1 }}>
|
||||
<InputLabel
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
}}
|
||||
id="Category"
|
||||
>
|
||||
Category
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="Category"
|
||||
input={<OutlinedInput label="Category" />}
|
||||
value={selectedCategoryVideos?.id || ""}
|
||||
onChange={handleOptionCategoryChangeVideos}
|
||||
sx={{
|
||||
// Target the input field
|
||||
".MuiSelect-select": {
|
||||
fontSize: "16px", // Change font size for the selected value
|
||||
padding: "10px 5px 15px 15px;",
|
||||
},
|
||||
// Target the dropdown icon
|
||||
".MuiSelect-icon": {
|
||||
fontSize: "20px", // Adjust if needed
|
||||
},
|
||||
// Target the dropdown menu
|
||||
"& .MuiMenu-paper": {
|
||||
".MuiMenuItem-root": {
|
||||
fontSize: "14px", // Change font size for the menu items
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{categories.map((option) => (
|
||||
<MenuItem key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
{selectedCategoryVideos &&
|
||||
subCategories[selectedCategoryVideos?.id] && (
|
||||
<FormControl fullWidth sx={{ marginBottom: 2 }}>
|
||||
<InputLabel
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
}}
|
||||
id="Sub-Category"
|
||||
>
|
||||
Sub-Category
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="Sub-Category"
|
||||
input={<OutlinedInput label="Sub-Category" />}
|
||||
value={selectedSubCategoryVideos?.id || ""}
|
||||
onChange={(e) =>
|
||||
handleOptionSubCategoryChangeVideos(
|
||||
e,
|
||||
subCategories[selectedCategoryVideos?.id]
|
||||
)
|
||||
}
|
||||
sx={{
|
||||
// Target the input field
|
||||
".MuiSelect-select": {
|
||||
fontSize: "16px", // Change font size for the selected value
|
||||
padding: "10px 5px 15px 15px;",
|
||||
},
|
||||
// Target the dropdown icon
|
||||
".MuiSelect-icon": {
|
||||
fontSize: "20px", // Adjust if needed
|
||||
},
|
||||
// Target the dropdown menu
|
||||
"& .MuiMenu-paper": {
|
||||
".MuiMenuItem-root": {
|
||||
fontSize: "14px", // Change font size for the menu items
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{subCategories[selectedCategoryVideos.id].map(
|
||||
(option) => (
|
||||
<MenuItem key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</MenuItem>
|
||||
)
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
{selectedSubCategoryVideos &&
|
||||
subCategories2[selectedSubCategoryVideos?.id] && (
|
||||
<FormControl fullWidth sx={{ marginBottom: 2 }}>
|
||||
<InputLabel
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
}}
|
||||
id="Sub-2x-Category"
|
||||
>
|
||||
Sub-2x-Category
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="Sub-2x-Category"
|
||||
input={<OutlinedInput label="Sub-2x-Category" />}
|
||||
value={selectedSubCategoryVideos2?.id || ""}
|
||||
onChange={(e) =>
|
||||
handleOptionSubCategoryChangeVideos2(
|
||||
e,
|
||||
subCategories2[selectedSubCategoryVideos?.id]
|
||||
)
|
||||
}
|
||||
sx={{
|
||||
// Target the input field
|
||||
".MuiSelect-select": {
|
||||
fontSize: "16px", // Change font size for the selected value
|
||||
padding: "10px 5px 15px 15px;",
|
||||
},
|
||||
// Target the dropdown icon
|
||||
".MuiSelect-icon": {
|
||||
fontSize: "20px", // Adjust if needed
|
||||
},
|
||||
// Target the dropdown menu
|
||||
"& .MuiMenu-paper": {
|
||||
".MuiMenuItem-root": {
|
||||
fontSize: "14px", // Change font size for the menu items
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{subCategories2[selectedSubCategoryVideos.id].map(
|
||||
(option) => (
|
||||
<MenuItem key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</MenuItem>
|
||||
)
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
{selectedSubCategoryVideos2 &&
|
||||
subCategories3[selectedSubCategoryVideos2?.id] && (
|
||||
<FormControl fullWidth sx={{ marginBottom: 2 }}>
|
||||
<InputLabel
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
}}
|
||||
id="Sub-2x-Category"
|
||||
>
|
||||
Sub-3x-Category
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="Sub-3x-Category"
|
||||
input={<OutlinedInput label="Sub-sx-Category" />}
|
||||
value={selectedSubCategoryVideos3?.id || ""}
|
||||
onChange={(e) =>
|
||||
handleOptionSubCategoryChangeVideos3(
|
||||
e,
|
||||
subCategories3[selectedSubCategoryVideos2?.id]
|
||||
)
|
||||
}
|
||||
sx={{
|
||||
// Target the input field
|
||||
".MuiSelect-select": {
|
||||
fontSize: "16px", // Change font size for the selected value
|
||||
padding: "10px 5px 15px 15px;",
|
||||
},
|
||||
// Target the dropdown icon
|
||||
".MuiSelect-icon": {
|
||||
fontSize: "20px", // Adjust if needed
|
||||
},
|
||||
// Target the dropdown menu
|
||||
"& .MuiMenu-paper": {
|
||||
".MuiMenuItem-root": {
|
||||
fontSize: "14px", // Change font size for the menu items
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{subCategories3[selectedSubCategoryVideos2.id].map(
|
||||
(option) => (
|
||||
<MenuItem key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</MenuItem>
|
||||
)
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
</Box>
|
||||
</FormControl>
|
||||
</FiltersSubContainer>
|
||||
{/* <FiltersTitle>
|
||||
Type
|
||||
<ExpandMoreSVG
|
||||
color={theme.palette.text.primary}
|
||||
height={"22"}
|
||||
width={"22"}
|
||||
/>
|
||||
</FiltersTitle>
|
||||
<FiltersSubContainer>
|
||||
<FiltersRow>
|
||||
Videos
|
||||
<FiltersCheckbox
|
||||
checked={filterType === "videos"}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFilterType("videos");
|
||||
}}
|
||||
inputProps={{ "aria-label": "controlled" }}
|
||||
/>
|
||||
</FiltersRow>
|
||||
<FiltersRow>
|
||||
Playlists
|
||||
<FiltersCheckbox
|
||||
checked={filterType === "playlists"}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFilterType("playlists");
|
||||
}}
|
||||
inputProps={{ "aria-label": "controlled" }}
|
||||
/>
|
||||
</FiltersRow>
|
||||
</FiltersSubContainer> */}
|
||||
<Button
|
||||
onClick={() => {
|
||||
filtersToDefault();
|
||||
}}
|
||||
sx={{
|
||||
marginTop: "20px",
|
||||
}}
|
||||
variant="contained"
|
||||
>
|
||||
reset
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
getFilesHandler(true);
|
||||
}}
|
||||
sx={{
|
||||
marginTop: "20px",
|
||||
}}
|
||||
variant="contained"
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
</FiltersContainer>
|
||||
</FiltersCol>
|
||||
<Grid item xs={12} md={10} sm={9}>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
marginTop: "20px",
|
||||
}}
|
||||
>
|
||||
<SubtitleContainer
|
||||
sx={{
|
||||
justifyContent: "flex-start",
|
||||
paddingLeft: "15px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
maxWidth: "1400px",
|
||||
height: "75px",
|
||||
position: "relative",
|
||||
}}
|
||||
key={fileObj.id}
|
||||
onMouseEnter={() => setShowIcons(fileObj.id)}
|
||||
onMouseLeave={() => setShowIcons(null)}
|
||||
>
|
||||
|
||||
</SubtitleContainer>
|
||||
|
||||
<VideoContainer>
|
||||
{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 (
|
||||
<Box
|
||||
{hasHash ? (
|
||||
<>
|
||||
<IconsBox
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
height: "75px",
|
||||
position:"relative"
|
||||
|
||||
opacity: showIcons === fileObj.id ? 1 : 0,
|
||||
zIndex: 2,
|
||||
}}
|
||||
key={videoObj.id}
|
||||
onMouseEnter={() => setShowIcons(videoObj.id)}
|
||||
onMouseLeave={() => setShowIcons(null)}
|
||||
>
|
||||
{hasHash ? (
|
||||
<>
|
||||
<IconsBox
|
||||
sx={{
|
||||
opacity: showIcons === videoObj.id ? 1 : 0,
|
||||
zIndex: 2,
|
||||
}}
|
||||
>
|
||||
{videoObj?.user === username && (
|
||||
<Tooltip title="Edit video properties" placement="top">
|
||||
<BlockIconContainer>
|
||||
<EditIcon
|
||||
|
||||
onClick={() => {
|
||||
dispatch(setEditVideo(videoObj));
|
||||
}}
|
||||
/>
|
||||
</BlockIconContainer>
|
||||
</Tooltip>
|
||||
|
||||
)}
|
||||
|
||||
<Tooltip title="Block user content" placement="top">
|
||||
{fileObj?.user === username && (
|
||||
<Tooltip title="Edit video properties" placement="top">
|
||||
<BlockIconContainer>
|
||||
<BlockIcon
|
||||
<EditIcon
|
||||
onClick={() => {
|
||||
blockUserFunc(videoObj?.user);
|
||||
dispatch(setEditFile(fileObj));
|
||||
}}
|
||||
/>
|
||||
</BlockIconContainer>
|
||||
</Tooltip>
|
||||
</IconsBox>
|
||||
<VideoCard
|
||||
onClick={() => {
|
||||
navigate(`/share/${videoObj?.user}/${videoObj?.id}`);
|
||||
}}
|
||||
)}
|
||||
|
||||
<Tooltip title="Block user content" placement="top">
|
||||
<BlockIconContainer>
|
||||
<BlockIcon
|
||||
onClick={() => {
|
||||
blockUserFunc(fileObj?.user);
|
||||
}}
|
||||
/>
|
||||
</BlockIconContainer>
|
||||
</Tooltip>
|
||||
</IconsBox>
|
||||
<VideoCard
|
||||
onClick={() => {
|
||||
navigate(`/share/${fileObj?.user}/${fileObj?.id}`);
|
||||
}}
|
||||
sx={{
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
gap: "25px",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
gap: '25px',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between'
|
||||
display: "flex",
|
||||
gap: "25px",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
|
||||
<Box sx={{
|
||||
|
||||
display: 'flex',
|
||||
gap: '25px',
|
||||
alignItems: 'center'
|
||||
}}>
|
||||
{icon ? <img src={icon} width="50px" style={{
|
||||
borderRadius: '5px'
|
||||
}}/> : (
|
||||
<AttachFileIcon />
|
||||
{icon ? (
|
||||
<img
|
||||
src={icon}
|
||||
width="50px"
|
||||
style={{
|
||||
borderRadius: "5px",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<AttachFileIcon />
|
||||
)}
|
||||
|
||||
<VideoCardTitle
|
||||
sx={{
|
||||
width: "100px",
|
||||
}}
|
||||
>
|
||||
{formatBytes(
|
||||
fileObj?.files.reduce(
|
||||
(acc, cur) => acc + (cur?.size || 0),
|
||||
0
|
||||
)
|
||||
)}
|
||||
|
||||
<VideoCardTitle sx={{
|
||||
width: '100px'
|
||||
}}>
|
||||
{formatBytes(videoObj?.files.reduce((acc, cur) => acc + (cur?.size || 0), 0))}
|
||||
</VideoCardTitle>
|
||||
<VideoCardTitle>{videoObj.title}</VideoCardTitle>
|
||||
|
||||
|
||||
|
||||
|
||||
</Box>
|
||||
<BottomParent>
|
||||
<NameContainer
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate(`/channel/${videoObj?.user}`);
|
||||
</VideoCardTitle>
|
||||
<VideoCardTitle>{fileObj.title}</VideoCardTitle>
|
||||
</Box>
|
||||
<BottomParent>
|
||||
<NameContainer
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
navigate(`/channel/${fileObj?.user}`);
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
sx={{ height: 24, width: 24 }}
|
||||
src={`/arbitrary/THUMBNAIL/${fileObj?.user}/qortal_avatar`}
|
||||
alt={`${fileObj?.user}'s avatar`}
|
||||
/>
|
||||
<VideoCardName
|
||||
sx={{
|
||||
":hover": {
|
||||
textDecoration: "underline",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
sx={{ height: 24, width: 24 }}
|
||||
src={`/arbitrary/THUMBNAIL/${videoObj?.user}/qortal_avatar`}
|
||||
alt={`${videoObj?.user}'s avatar`}
|
||||
/>
|
||||
<VideoCardName
|
||||
sx={{
|
||||
":hover": {
|
||||
textDecoration: "underline",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{videoObj?.user}
|
||||
</VideoCardName>
|
||||
</NameContainer>
|
||||
{fileObj?.user}
|
||||
</VideoCardName>
|
||||
</NameContainer>
|
||||
|
||||
{videoObj?.created && (
|
||||
<VideoUploadDate>
|
||||
{formatDate(videoObj.created)}
|
||||
</VideoUploadDate>
|
||||
)}
|
||||
</BottomParent>
|
||||
</VideoCard>
|
||||
</>
|
||||
) : (
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
paddingBottom: "10px",
|
||||
objectFit: "contain",
|
||||
visibility: "visible",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</VideoContainer>
|
||||
|
||||
<LazyLoad
|
||||
onLoadMore={getFilesHandler}
|
||||
isLoading={isLoading}
|
||||
></LazyLoad>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{fileObj?.created && (
|
||||
<VideoUploadDate>
|
||||
{formatDate(fileObj.created)}
|
||||
</VideoUploadDate>
|
||||
)}
|
||||
</BottomParent>
|
||||
</VideoCard>
|
||||
</>
|
||||
) : (
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
paddingBottom: "10px",
|
||||
objectFit: "contain",
|
||||
visibility: "visible",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</FileContainer>
|
||||
);
|
||||
};
|
||||
|
@ -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<boolean>(true)
|
||||
const { name: paramName } = useParams();
|
||||
const theme = useTheme();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(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<Video[]>([])
|
||||
|
||||
const navigate = useNavigate()
|
||||
const {
|
||||
getVideo,
|
||||
getNewFiles,
|
||||
checkNewFiles,
|
||||
checkAndUpdateVideo
|
||||
} = useFetchFiles()
|
||||
(state: RootState) => state.file.hashMapFiles
|
||||
);
|
||||
|
||||
const [videos, setVideos] = React.useState<Video[]>([]);
|
||||
|
||||
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 (
|
||||
<Box sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center'
|
||||
}}>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<FileContainer>
|
||||
{videos.map((file: any, index: number) => {
|
||||
const existingFile = hashMapVideos[file?.id];
|
||||
let hasHash = false;
|
||||
let fileObj = file;
|
||||
if (existingFile) {
|
||||
fileObj = existingFile;
|
||||
hasHash = true;
|
||||
}
|
||||
|
||||
<VideoContainer>
|
||||
{videos.map((video: any, index: number) => {
|
||||
const existingVideo = hashMapVideos[video?.id];
|
||||
let hasHash = false;
|
||||
let videoObj = video;
|
||||
if (existingVideo) {
|
||||
videoObj = existingVideo;
|
||||
hasHash = true;
|
||||
}
|
||||
const icon = getIconsFromObject(fileObj);
|
||||
|
||||
|
||||
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 (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
height: "75px",
|
||||
position:"relative"
|
||||
|
||||
}}
|
||||
key={videoObj.id}
|
||||
|
||||
>
|
||||
{hasHash ? (
|
||||
<>
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
height: "75px",
|
||||
position: "relative",
|
||||
}}
|
||||
key={fileObj.id}
|
||||
>
|
||||
{hasHash ? (
|
||||
<>
|
||||
<VideoCard
|
||||
onClick={() => {
|
||||
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",
|
||||
}}
|
||||
>
|
||||
|
||||
<Box sx={{
|
||||
|
||||
display: 'flex',
|
||||
gap: '25px',
|
||||
alignItems: 'center'
|
||||
}}>
|
||||
{icon ? <img src={icon} width="50px" style={{
|
||||
borderRadius: '5px'
|
||||
}}/> : (
|
||||
<AttachFileIcon />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "25px",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{icon ? (
|
||||
<img
|
||||
src={icon}
|
||||
width="50px"
|
||||
style={{
|
||||
borderRadius: "5px",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<AttachFileIcon />
|
||||
)}
|
||||
<VideoCardTitle sx={{
|
||||
width: '100px'
|
||||
}}>
|
||||
{formatBytes(videoObj?.files.reduce((acc, cur) => acc + (cur?.size || 0), 0))}
|
||||
</VideoCardTitle>
|
||||
<VideoCardTitle>{videoObj.title}</VideoCardTitle>
|
||||
|
||||
|
||||
|
||||
|
||||
<VideoCardTitle
|
||||
sx={{
|
||||
width: "100px",
|
||||
}}
|
||||
>
|
||||
{formatBytes(
|
||||
fileObj?.files.reduce(
|
||||
(acc, cur) => acc + (cur?.size || 0),
|
||||
0
|
||||
)
|
||||
)}
|
||||
</VideoCardTitle>
|
||||
<VideoCardTitle>{fileObj.title}</VideoCardTitle>
|
||||
</Box>
|
||||
<BottomParent>
|
||||
<NameContainer
|
||||
onClick={(e) => {
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
navigate(`/channel/${videoObj?.user}`);
|
||||
navigate(`/channel/${fileObj?.user}`);
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
sx={{ height: 24, width: 24 }}
|
||||
src={`/arbitrary/THUMBNAIL/${videoObj?.user}/qortal_avatar`}
|
||||
alt={`${videoObj?.user}'s avatar`}
|
||||
src={`/arbitrary/THUMBNAIL/${fileObj?.user}/qortal_avatar`}
|
||||
alt={`${fileObj?.user}'s avatar`}
|
||||
/>
|
||||
<VideoCardName
|
||||
sx={{
|
||||
@ -239,39 +232,36 @@ export const FileListComponentLevel = ({ mode }: VideoListProps) => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
{videoObj?.user}
|
||||
{fileObj?.user}
|
||||
</VideoCardName>
|
||||
</NameContainer>
|
||||
|
||||
{videoObj?.created && (
|
||||
{fileObj?.created && (
|
||||
<VideoUploadDate>
|
||||
{formatDate(videoObj.created)}
|
||||
{formatDate(fileObj.created)}
|
||||
</VideoUploadDate>
|
||||
)}
|
||||
</BottomParent>
|
||||
</VideoCard>
|
||||
</>
|
||||
) : (
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
paddingBottom: "10px",
|
||||
objectFit: "contain",
|
||||
visibility: "visible",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</VideoContainer>
|
||||
</>
|
||||
) : (
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
paddingBottom: "10px",
|
||||
objectFit: "contain",
|
||||
visibility: "visible",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</FileContainer>
|
||||
<LazyLoad onLoadMore={getVideosHandler} isLoading={isLoading}></LazyLoad>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
);
|
||||
};
|
||||
|
@ -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<CategoryListRef>(null);
|
||||
const isFiltering = useSelector((state: RootState) => state.file.isFiltering);
|
||||
const filterValue = useSelector((state: RootState) => state.file.filterValue);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(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
|
||||
);
|
||||
|
||||
export const Home = () => {
|
||||
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]);
|
||||
|
||||
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<any>(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 (
|
||||
<>
|
||||
<FileList />
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
<Grid container sx={{ width: "100%" }}>
|
||||
<FiltersCol item xs={12} md={2} sm={3}>
|
||||
<FiltersContainer>
|
||||
<StatsData />
|
||||
<Input
|
||||
id="standard-adornment-name"
|
||||
onChange={e => {
|
||||
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",
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
id="standard-adornment-name"
|
||||
onChange={e => {
|
||||
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",
|
||||
}}
|
||||
/>
|
||||
<CategoryList categoryData={allCategoryData} ref={categoryListRef} />
|
||||
|
||||
<Button
|
||||
onClick={() => {
|
||||
filtersToDefault();
|
||||
}}
|
||||
sx={{
|
||||
marginTop: "20px",
|
||||
}}
|
||||
variant="contained"
|
||||
>
|
||||
reset
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
getFilesHandler(true);
|
||||
}}
|
||||
sx={{
|
||||
marginTop: "20px",
|
||||
}}
|
||||
variant="contained"
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
</FiltersContainer>
|
||||
</FiltersCol>
|
||||
<Grid item xs={12} md={10} sm={9}>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
marginTop: "20px",
|
||||
}}
|
||||
>
|
||||
<SubtitleContainer
|
||||
sx={{
|
||||
justifyContent: "flex-start",
|
||||
paddingLeft: "15px",
|
||||
width: "100%",
|
||||
maxWidth: "1400px",
|
||||
}}
|
||||
></SubtitleContainer>
|
||||
<FileList files={videos} />
|
||||
<LazyLoad
|
||||
onLoadMore={getFilesHandler}
|
||||
isLoading={isLoading}
|
||||
></LazyLoad>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
@ -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 (
|
||||
<ProfileContainer>
|
||||
<HeaderContainer>
|
||||
<Box sx={{
|
||||
cursor: 'pointer'
|
||||
}} >
|
||||
<Box
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<StyledCardHeaderComment
|
||||
sx={{
|
||||
'& .MuiCardHeader-content': {
|
||||
overflow: 'hidden'
|
||||
}
|
||||
"& .MuiCardHeader-content": {
|
||||
overflow: "hidden",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Avatar src={`/arbitrary/THUMBNAIL/${paramName}/qortal_avatar`} alt={`${paramName}'s avatar`} />
|
||||
<Avatar
|
||||
src={`/arbitrary/THUMBNAIL/${paramName}/qortal_avatar`}
|
||||
alt={`${paramName}'s avatar`}
|
||||
/>
|
||||
</Box>
|
||||
<StyledCardColComment>
|
||||
<AuthorTextComment
|
||||
color={
|
||||
theme.palette.mode === 'light'
|
||||
theme.palette.mode === "light"
|
||||
? theme.palette.text.secondary
|
||||
: '#d6e8ff'
|
||||
: "#d6e8ff"
|
||||
}
|
||||
>
|
||||
{paramName}
|
||||
</AuthorTextComment>
|
||||
</StyledCardColComment>
|
||||
|
||||
</StyledCardHeaderComment>
|
||||
</Box>
|
||||
</HeaderContainer>
|
||||
<FileListComponentLevel />
|
||||
</ProfileContainer>
|
||||
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -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 <Box sx={{
|
||||
height: height
|
||||
}} />
|
||||
}
|
||||
|
||||
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'
|
||||
}));
|
188
src/state/features/fileSlice.ts
Normal file
188
src/state/features/fileSlice.ts
Normal file
@ -0,0 +1,188 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { RootState } from "../store";
|
||||
|
||||
interface GlobalState {
|
||||
files: Video[];
|
||||
filteredFiles: Video[];
|
||||
hashMapFiles: Record<string, Video>;
|
||||
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;
|
@ -1,54 +1,68 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
interface GlobalState {
|
||||
isLoadingGlobal: boolean
|
||||
downloads: any
|
||||
userAvatarHash: Record<string, string>
|
||||
publishNames: string[] | null
|
||||
videoPlaying: any | null
|
||||
isLoadingGlobal: boolean;
|
||||
downloads: any;
|
||||
userAvatarHash: Record<string, string>;
|
||||
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;
|
||||
|
@ -1,216 +0,0 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { RootState } from '../store'
|
||||
|
||||
|
||||
interface GlobalState {
|
||||
videos: Video[]
|
||||
filteredVideos: Video[]
|
||||
hashMapVideos: Record<string, Video>
|
||||
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
|
||||
|
@ -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<typeof store.getState>
|
||||
export type RootState = ReturnType<typeof store.getState>;
|
||||
|
||||
// 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;
|
||||
|
20
src/utils/utilFunctions.ts
Normal file
20
src/utils/utilFunctions.ts
Normal file
@ -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);
|
||||
};
|
Loading…
Reference in New Issue
Block a user