mirror of
https://github.com/Qortal/q-share.git
synced 2025-01-30 06:42:22 +00:00
commit
a3bce74ad8
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,66 +1,32 @@
|
||||
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,
|
||||
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 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 { 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 +70,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 +87,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 +118,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 +152,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 +191,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 +217,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 +231,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 +254,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 +283,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 +314,7 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
|
||||
<NewCrowdfundTitle>Share</NewCrowdfundTitle>
|
||||
|
||||
<NewCrowdfundTitle>Share</NewCrowdfundTitle>
|
||||
</Box>
|
||||
|
||||
{step === "videos" && (
|
||||
@ -449,7 +347,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 +361,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 +399,12 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||
</Typography>
|
||||
<TextEditor
|
||||
inlineContent={description}
|
||||
setInlineContent={(value) => {
|
||||
setInlineContent={value => {
|
||||
setDescription(value);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
</>
|
||||
)}
|
||||
<CrowdfundActionButtonRow>
|
||||
@ -664,16 +440,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 +462,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,71 +1,60 @@
|
||||
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, { 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, Box, Skeleton, useTheme } from "@mui/material";
|
||||
import { useFetchFiles } from "../../hooks/useFetchFiles.tsx";
|
||||
import LazyLoad from "../../components/common/LazyLoad";
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
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";
|
||||
BottomParent,
|
||||
FileContainer,
|
||||
NameContainer,
|
||||
VideoCard,
|
||||
VideoCardName,
|
||||
VideoCardTitle,
|
||||
VideoUploadDate,
|
||||
} from "./FileList-styles.tsx";
|
||||
import { formatDate } 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 { 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 +65,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 +211,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