3
0
mirror of https://github.com/Qortal/q-share.git synced 2025-01-30 14:52:20 +00:00

Index.ts refactored into Identifiers.ts and Categories.ts for more clarity

Some references to Q-Tube in code replaced with Q-Share

Characters allowed in Publish Titles added to constants/Misc.ts, more characters are allowed than before

User Search Label now says "User's Exact Name" to communicate that users must be precise about what names they search for

Search and Name Search now can perform searches by hitting enter instead of clicking on Search Button

Phil's MultiplePublishAll.tsx component used

Added "Other" main Category

Categories are sorted by name, "Other" is always last
This commit is contained in:
Qortal Dev 2024-01-15 14:53:32 -07:00
parent 66b9fb8b6b
commit 350d2ff052
6 changed files with 283 additions and 166 deletions

View File

@ -29,7 +29,7 @@ import {objectToBase64} from "../../utils/toBase64";
import {RootState} from "../../state/store"; import {RootState} from "../../state/store";
import {setEditVideo, updateInHashMap, updateVideo,} from "../../state/features/videoSlice"; import {setEditVideo, updateInHashMap, updateVideo,} from "../../state/features/videoSlice";
import {QSHARE_FILE_BASE,} from "../../constants/Identifiers.ts"; import {QSHARE_FILE_BASE,} from "../../constants/Identifiers.ts";
import {MultiplePublish} from "../common/MultiplePublish/MultiplePublish"; import {MultiplePublish} from "../common/MultiplePublish/MultiplePublishAll";
import {TextEditor} from "../common/TextEditor/TextEditor"; import {TextEditor} from "../common/TextEditor/TextEditor";
import {extractTextFromHTML} from "../common/TextEditor/utils"; import {extractTextFromHTML} from "../common/TextEditor/utils";
import {categories, subCategories, subCategories2, subCategories3} from "../../constants/Categories.ts"; import {categories, subCategories, subCategories2, subCategories3} from "../../constants/Categories.ts";
@ -65,7 +65,7 @@ export const EditFile = () => {
const editVideoProperties = useSelector( const editVideoProperties = useSelector(
(state: RootState) => state.video.editVideoProperties (state: RootState) => state.video.editVideoProperties
); );
const [publishes, setPublishes] = useState<any[]>([]); const [publishes, setPublishes] = useState<any>(null);
const [isOpenMultiplePublish, setIsOpenMultiplePublish] = useState(false); const [isOpenMultiplePublish, setIsOpenMultiplePublish] = useState(false);
const [videoPropertiesToSetToRedux, setVideoPropertiesToSetToRedux] = const [videoPropertiesToSetToRedux, setVideoPropertiesToSetToRedux] =
useState(null); useState(null);
@ -378,7 +378,11 @@ export const EditFile = () => {
}; };
listOfPublishes.push(requestBodyJson); listOfPublishes.push(requestBodyJson);
setPublishes(listOfPublishes); const multiplePublish = {
action: "PUBLISH_MULTIPLE_QDN_RESOURCES",
resources: [...listOfPublishes],
};
setPublishes(multiplePublish);
setIsOpenMultiplePublish(true); setIsOpenMultiplePublish(true);
setVideoPropertiesToSetToRedux({ setVideoPropertiesToSetToRedux({
...editVideoProperties, ...editVideoProperties,
@ -726,6 +730,18 @@ export const EditFile = () => {
{isOpenMultiplePublish && ( {isOpenMultiplePublish && (
<MultiplePublish <MultiplePublish
isOpen={isOpenMultiplePublish} isOpen={isOpenMultiplePublish}
onError={(messageNotification)=> {
setIsOpenMultiplePublish(false);
setPublishes(null)
if(messageNotification){
dispatch(
setNotification({
msg: messageNotification,
alertType: 'error'
})
)
}
}}
onSubmit={() => { onSubmit={() => {
setIsOpenMultiplePublish(false); setIsOpenMultiplePublish(false);
const clonedCopy = structuredClone(videoPropertiesToSetToRedux); const clonedCopy = structuredClone(videoPropertiesToSetToRedux);

View File

@ -51,7 +51,7 @@ import {
} from "../../constants/Identifiers.ts"; } from "../../constants/Identifiers.ts";
import { MultiplePublish } from "../common/MultiplePublish/MultiplePublish"; import { MultiplePublish } from "../common/MultiplePublish/MultiplePublishAll";
import { import {
CrowdfundSubTitle, CrowdfundSubTitle,
CrowdfundSubTitleRow, CrowdfundSubTitleRow,
@ -114,7 +114,7 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
useState<any>(null); useState<any>(null);
const [playlistSetting, setPlaylistSetting] = useState<null | string>(null); const [playlistSetting, setPlaylistSetting] = useState<null | string>(null);
const [publishes, setPublishes] = useState<any[]>([]); const [publishes, setPublishes] = useState<any>(null);
const { getRootProps, getInputProps } = useDropzone({ const { getRootProps, getInputProps } = useDropzone({
maxFiles: 10, maxFiles: 10,
maxSize: 419430400, // 400 MB in bytes maxSize: 419430400, // 400 MB in bytes
@ -308,8 +308,14 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
filename: `video_metadata.json`, filename: `video_metadata.json`,
}; };
listOfPublishes.push(requestBodyJson); listOfPublishes.push(requestBodyJson);
setPublishes(listOfPublishes);
const multiplePublish = {
action: "PUBLISH_MULTIPLE_QDN_RESOURCES",
resources: [...listOfPublishes],
};
setPublishes(multiplePublish);
setIsOpenMultiplePublish(true); setIsOpenMultiplePublish(true);
} catch (error: any) { } catch (error: any) {
let notificationObj: any = null; let notificationObj: any = null;
if (typeof error === "string") { if (typeof error === "string") {
@ -658,6 +664,18 @@ export const PublishFile = ({ editId, editContent }: NewCrowdfundProps) => {
{isOpenMultiplePublish && ( {isOpenMultiplePublish && (
<MultiplePublish <MultiplePublish
isOpen={isOpenMultiplePublish} isOpen={isOpenMultiplePublish}
onError={(messageNotification)=> {
setIsOpenMultiplePublish(false);
setPublishes(null)
if(messageNotification){
dispatch(
setNotification({
msg: messageNotification,
alertType: 'error'
})
)
}
}}
onSubmit={() => { onSubmit={() => {
setIsOpenMultiplePublish(false); setIsOpenMultiplePublish(false);
setIsOpen(false); setIsOpen(false);

View File

@ -1,136 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {
Box,
Button,
CircularProgress,
Modal,
Typography,
useTheme,
} from "@mui/material";
import React, { useCallback, useEffect, useState, useRef } from "react";
import { ModalBody } from "../../PublishFile/Upload-styles.tsx";
import { CircleSVG } from "../../../assets/svgs/CircleSVG";
import { EmptyCircleSVG } from "../../../assets/svgs/EmptyCircleSVG";
export const MultiplePublish = ({ publishes, isOpen, onSubmit }) => {
const theme = useTheme();
const listOfSuccessfulPublishesRef = useRef([])
const [listOfSuccessfulPublishes, setListOfSuccessfulPublishes] = useState<
any[]
>([]);
const [currentlyInPublish, setCurrentlyInPublish] = useState(null);
const hasStarted = useRef(false);
const publish = useCallback(async (pub: any) => {
await qortalRequest(pub);
}, []);
const [isPublishing, setIsPublishing] = useState(true)
const handlePublish = useCallback(
async (pub: any) => {
try {
setCurrentlyInPublish(pub?.identifier);
await publish(pub);
setListOfSuccessfulPublishes((prev: any) => [...prev, pub?.identifier]);
listOfSuccessfulPublishesRef.current = [...listOfSuccessfulPublishesRef.current, pub?.identifier]
} catch (error) {
console.log({ error });
await new Promise<void>((res) => {
setTimeout(() => {
res();
}, 5000);
});
// await handlePublish(pub);
}
},
[publish]
);
const startPublish = useCallback(
async (pubs: any) => {
setIsPublishing(true)
const filterPubs = pubs.filter((pub)=> !listOfSuccessfulPublishesRef.current.includes(pub.identifier))
for (const pub of filterPubs) {
await handlePublish(pub);
}
if(listOfSuccessfulPublishesRef.current.length === pubs.length){
onSubmit()
}
setIsPublishing(false)
},
[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.map((publish: any) => {
return (
<Box
sx={{
display: "flex",
gap: "20px",
justifyContent: "space-between",
alignItems: "center",
}}
>
<Typography>{publish?.title}</Typography>
{publish?.identifier === currentlyInPublish ? (
<CircularProgress
size={20}
thickness={2}
sx={{
color: theme.palette.secondary.main,
}}
/>
) : listOfSuccessfulPublishes.includes(publish.identifier) ? (
<CircleSVG
color={theme.palette.text.primary}
height="24px"
width="24px"
/>
) : (
<EmptyCircleSVG
color={theme.palette.text.primary}
height="24px"
width="24px"
/>
)}
</Box>
);
})}
{!isPublishing && listOfSuccessfulPublishes.length !== publishes.length && (
<>
<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 onClick={()=> {
startPublish(publishes)
}}>Try again</Button>
</>
)}
</ModalBody>
</Modal>
);
};

View File

@ -0,0 +1,211 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {
Box,
Button,
CircularProgress,
Modal,
Typography,
useTheme,
} from "@mui/material";
import React, { useCallback, useEffect, useState, useRef } from "react";
import { CircleSVG } from "../../../assets/svgs/CircleSVG";
import { EmptyCircleSVG } from "../../../assets/svgs/EmptyCircleSVG";
import { styled } from "@mui/system";
interface Publish {
resources: any[];
action: string;
}
interface MultiplePublishProps {
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)
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);
}
}, [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",
}}
>
<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",
},
}));

View File

@ -14,43 +14,50 @@ interface Categories {
[key: number]: SubCategory[]; [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 = [ export const categories = [
{"id": 1, "name": "Software"}, {"id": 1, "name": "Software"},
{"id": 2, "name": "Gaming"}, {"id": 2, "name": "Gaming"},
{"id": 3, "name": "Media"} {"id": 3, "name": "Media"},
]; {"id": 4, "name": "Other"}
].sort(sortCategory);
export const subCategories: Categories = { export const subCategories: Categories = {
1: [ 1: [
{"id": 101, "name": "OS"}, {"id": 101, "name": "OS"},
{"id": 102, "name": "Application"}, {"id": 102, "name": "Application"},
{"id": 103, "name": "Source Code"}, {"id": 103, "name": "Source Code"},
{"id": 104, "name": "Other"} {"id": 104, "name": "Other"}
], ].sort(sortCategory),
2: [ 2: [
{"id": 201, "name": "NES"}, {"id": 201, "name": "NES"},
{"id": 202, "name": "SNES"}, {"id": 202, "name": "SNES"},
{"id": 203, "name": "PC"}, {"id": 203, "name": "PC"},
{"id": 204, "name": "Other Gaming Systems"} {"id": 204, "name": "Other"}
], ].sort(sortCategory),
3: [ 3: [
{"id": 301, "name": "Audio"}, {"id": 301, "name": "Audio"},
{"id": 302, "name": "Video"}, {"id": 302, "name": "Video"},
{"id": 303, "name": "Image"}, {"id": 303, "name": "Image"},
{"id": 304, "name": "Document"}, {"id": 304, "name": "Document"},
{"id": 305, "name": "Other Media Formats"} {"id": 305, "name": "Other"}
] ].sort(sortCategory)
}; };
export const subCategories2: Categories = {
201: [ // NES const gamingSystems = [
{"id": 20101, "name": "ROM"}, {"id": 20101, "name": "ROM"},
{"id": 20102, "name": "Romhack"}, {"id": 20102, "name": "Romhack"},
{"id": 20103, "name": "Emulator"}, {"id": 20103, "name": "Emulator"},
], {"id": 20104, "name": "Guide"},
202: [ // SNES {"id": 20105, "name": "Other"},
{"id": 20201, "name": "ROM"}, ].sort(sortCategory)
{"id": 20202, "name": "Romhack"}, export const subCategories2: Categories = {
{"id": 20203, "name": "Emulator"}, 201: gamingSystems, // NES
], 202: gamingSystems, // SNES
301: [ // Audio 301: [ // Audio
{"id": 30101, "name": "Music"}, {"id": 30101, "name": "Music"},
{"id": 30102, "name": "Podcasts"}, {"id": 30102, "name": "Podcasts"},
@ -67,7 +74,7 @@ export const subCategories2: Categories = {
{"id": 30113, "name": "Nature Sounds"}, {"id": 30113, "name": "Nature Sounds"},
{"id": 30114, "name": "Soundtracks"}, {"id": 30114, "name": "Soundtracks"},
{"id": 30115, "name": "Interviews"} {"id": 30115, "name": "Interviews"}
], ].sort(sortCategory),
302: [ // Under Video 302: [ // Under Video
{"id": 30201, "name": "Movies"}, {"id": 30201, "name": "Movies"},
{"id": 30202, "name": "Series"}, {"id": 30202, "name": "Series"},
@ -92,7 +99,7 @@ export const subCategories2: Categories = {
{"id": 30221, "name": "Personal Development"}, {"id": 30221, "name": "Personal Development"},
{"id": 30222, "name": "Other"}, {"id": 30222, "name": "Other"},
{"id": 30223, "name": "History"} {"id": 30223, "name": "History"}
], ].sort(sortCategory),
303: [ // Image 303: [ // Image
{"id": 30301, "name": "Nature"}, {"id": 30301, "name": "Nature"},
{"id": 30302, "name": "Urban & Cityscapes"}, {"id": 30302, "name": "Urban & Cityscapes"},
@ -114,14 +121,14 @@ export const subCategories2: Categories = {
{"id": 30318, "name": "Still Life & Objects"}, {"id": 30318, "name": "Still Life & Objects"},
{"id": 30319, "name": "Architecture & Buildings"}, {"id": 30319, "name": "Architecture & Buildings"},
{"id": 30320, "name": "Landscapes & Seascapes"} {"id": 30320, "name": "Landscapes & Seascapes"}
], ].sort(sortCategory),
304: [ // Document 304: [ // Document
{"id": 30401, "name": "PDF"}, {"id": 30401, "name": "PDF"},
{"id": 30402, "name": "Word Document"}, {"id": 30402, "name": "Word Document"},
{"id": 30403, "name": "Spreadsheet"}, {"id": 30403, "name": "Spreadsheet"},
{"id": 30404, "name": "Powerpoint"}, {"id": 30404, "name": "Powerpoint"},
{"id": 30405, "name": "Books"} {"id": 30405, "name": "Books"}
] ].sort(sortCategory)
}; };
export const subCategories3: Categories = { export const subCategories3: Categories = {
30201: [ // Under Movies 30201: [ // Under Movies
@ -141,7 +148,7 @@ export const subCategories3: Categories = {
{"id": 3020114, "name": "International Films"}, {"id": 3020114, "name": "International Films"},
{"id": 3020115, "name": "Biographies & True Stories"}, {"id": 3020115, "name": "Biographies & True Stories"},
{"id": 3020116, "name": "Other"} {"id": 3020116, "name": "Other"}
], ].sort(sortCategory),
30202: [ // Under Series 30202: [ // Under Series
{"id": 3020201, "name": "Dramas"}, {"id": 3020201, "name": "Dramas"},
{"id": 3020202, "name": "Comedies"}, {"id": 3020202, "name": "Comedies"},
@ -159,7 +166,7 @@ export const subCategories3: Categories = {
{"id": 3020214, "name": "International Series"}, {"id": 3020214, "name": "International Series"},
{"id": 3020215, "name": "Miniseries"}, {"id": 3020215, "name": "Miniseries"},
{"id": 3020216, "name": "Other"} {"id": 3020216, "name": "Other"}
], ].sort(sortCategory),
30405: [ // Under Books 30405: [ // Under Books
{"id": 3040501, "name": "Fiction"}, {"id": 3040501, "name": "Fiction"},
{"id": 3040502, "name": "Non-Fiction"}, {"id": 3040502, "name": "Non-Fiction"},
@ -177,7 +184,7 @@ export const subCategories3: Categories = {
{"id": 3040514, "name": "Travel"}, {"id": 3040514, "name": "Travel"},
{"id": 3040515, "name": "Comics & Graphic Novels"}, {"id": 3040515, "name": "Comics & Graphic Novels"},
], ].sort(sortCategory),
30101: [ // Under Music 30101: [ // Under Music
{"id": 3010101, "name": "Rock"}, {"id": 3010101, "name": "Rock"},
{"id": 3010102, "name": "Pop"}, {"id": 3010102, "name": "Pop"},
@ -199,7 +206,7 @@ export const subCategories3: Categories = {
{"id": 3010118, "name": "Children's Music"}, {"id": 3010118, "name": "Children's Music"},
{"id": 3010119, "name": "New Age"}, {"id": 3010119, "name": "New Age"},
{"id": 3010120, "name": "Classical Crossover"} {"id": 3010120, "name": "Classical Crossover"}
] ].sort(sortCategory)
}; };
@ -207,6 +214,7 @@ export const icons = {
1: softwareIcon, 1: softwareIcon,
2: gamingIcon, 2: gamingIcon,
3: mediaIcon, 3: mediaIcon,
4: softwareIcon,
302: videoIcon, 302: videoIcon,
301: audioIcon, 301: audioIcon,
304: documentIcon 304: documentIcon

View File

@ -376,7 +376,7 @@ export const FileList = ({ mode }: VideoListProps) => {
}} }}
onKeyDown={searchOnEnter} onKeyDown={searchOnEnter}
value={filterName} value={filterName}
placeholder="User's Exact Name" placeholder="User's Name (Exact)"
sx={{ sx={{
marginTop: "20px", marginTop: "20px",
borderBottom: "1px solid white", borderBottom: "1px solid white",