forked from Qortal/q-support
		
	Merge pull request #2 from QortalSeth/main
Small bugfixes and QoL changes
This commit is contained in:
		@@ -4,7 +4,7 @@
 | 
			
		||||
    <meta charset="UTF-8" />
 | 
			
		||||
    <link rel="icon" type="image/x-icon" href="/favicon.ico" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
			
		||||
    <title>Q-Share</title>
 | 
			
		||||
    <title>Q-Support</title>
 | 
			
		||||
  </head>
 | 
			
		||||
  <body>
 | 
			
		||||
    <div id="root"></div>
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ function App() {
 | 
			
		||||
            <CssBaseline />
 | 
			
		||||
            <Routes>
 | 
			
		||||
              <Route path="/" element={<Home />} />
 | 
			
		||||
              <Route path="/share/:name/:id" element={<IssueContent />} />
 | 
			
		||||
              <Route path="/issue/:name/:id" element={<IssueContent />} />
 | 
			
		||||
              <Route path="/channel/:name" element={<IndividualProfile />} />
 | 
			
		||||
            </Routes>
 | 
			
		||||
          </GlobalWrapper>
 | 
			
		||||
 
 | 
			
		||||
@@ -144,7 +144,6 @@ export const EditIssue = () => {
 | 
			
		||||
  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 (!categoryList[0]) throw new Error("Please select a category");
 | 
			
		||||
      if (!editFileProperties) return;
 | 
			
		||||
@@ -173,10 +172,6 @@ export const EditIssue = () => {
 | 
			
		||||
        );
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      let fileReferences = [];
 | 
			
		||||
 | 
			
		||||
      let listOfPublishes = [];
 | 
			
		||||
      const fullDescription = extractTextFromHTML(description);
 | 
			
		||||
 | 
			
		||||
      const sanitizeTitle = title
 | 
			
		||||
        .replace(/[^a-zA-Z0-9\s-]/g, "")
 | 
			
		||||
@@ -185,6 +180,12 @@ export const EditIssue = () => {
 | 
			
		||||
        .trim()
 | 
			
		||||
        .toLowerCase();
 | 
			
		||||
 | 
			
		||||
      if (!sanitizeTitle) throw new Error("Please enter a title");
 | 
			
		||||
      let fileReferences = [];
 | 
			
		||||
 | 
			
		||||
      let listOfPublishes = [];
 | 
			
		||||
      const fullDescription = extractTextFromHTML(description);
 | 
			
		||||
 | 
			
		||||
      for (const publish of files) {
 | 
			
		||||
        if (publish?.identifier) {
 | 
			
		||||
          fileReferences.push(publish);
 | 
			
		||||
 
 | 
			
		||||
@@ -219,7 +219,6 @@ export const EditPlaylist = () => {
 | 
			
		||||
 | 
			
		||||
  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");
 | 
			
		||||
@@ -249,6 +248,16 @@ export const EditPlaylist = () => {
 | 
			
		||||
        );
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const sanitizeTitle = title
 | 
			
		||||
        .replace(/[^a-zA-Z0-9\s-]/g, "")
 | 
			
		||||
        .replace(/\s+/g, "-")
 | 
			
		||||
        .replace(/-+/g, "-")
 | 
			
		||||
        .trim()
 | 
			
		||||
        .toLowerCase();
 | 
			
		||||
 | 
			
		||||
      if (!sanitizeTitle) throw new Error("Please enter a title");
 | 
			
		||||
 | 
			
		||||
      const category = selectedCategoryVideos.id;
 | 
			
		||||
      const subcategory = selectedSubCategoryVideos?.id || "";
 | 
			
		||||
 | 
			
		||||
@@ -308,12 +317,7 @@ export const EditPlaylist = () => {
 | 
			
		||||
      // Description is obtained from raw data
 | 
			
		||||
 | 
			
		||||
      let identifier = editVideoProperties?.id;
 | 
			
		||||
      const sanitizeTitle = title
 | 
			
		||||
        .replace(/[^a-zA-Z0-9\s-]/g, "")
 | 
			
		||||
        .replace(/\s+/g, "-")
 | 
			
		||||
        .replace(/-+/g, "-")
 | 
			
		||||
        .trim()
 | 
			
		||||
        .toLowerCase();
 | 
			
		||||
 | 
			
		||||
      if (isNew) {
 | 
			
		||||
        identifier = `${QSUPPORT_PLAYLIST_BASE}${sanitizeTitle.slice(0, 30)}_${id}`;
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -128,8 +128,6 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
 | 
			
		||||
    try {
 | 
			
		||||
      if (!categoryListRef.current) throw new Error("No CategoryListRef found");
 | 
			
		||||
      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 (!categoryListRef.current?.getSelectedCategories()[0])
 | 
			
		||||
        throw new Error("Please select a category");
 | 
			
		||||
@@ -157,18 +155,19 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      let fileReferences = [];
 | 
			
		||||
 | 
			
		||||
      let listOfPublishes = [];
 | 
			
		||||
 | 
			
		||||
      const fullDescription = extractTextFromHTML(description);
 | 
			
		||||
 | 
			
		||||
      const sanitizeTitle = title
 | 
			
		||||
        .replace(/[^a-zA-Z0-9\s-]/g, "")
 | 
			
		||||
        .replace(/\s+/g, "-")
 | 
			
		||||
        .replace(/-+/g, "-")
 | 
			
		||||
        .trim()
 | 
			
		||||
        .toLowerCase();
 | 
			
		||||
      if (!sanitizeTitle) throw new Error("Please enter a title");
 | 
			
		||||
 | 
			
		||||
      let fileReferences = [];
 | 
			
		||||
 | 
			
		||||
      let listOfPublishes = [];
 | 
			
		||||
 | 
			
		||||
      const fullDescription = extractTextFromHTML(description);
 | 
			
		||||
 | 
			
		||||
      for (const publish of files) {
 | 
			
		||||
        const file = publish.file;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,21 @@
 | 
			
		||||
import { styled } from '@mui/system';
 | 
			
		||||
import {
 | 
			
		||||
  Box,
 | 
			
		||||
  Modal,
 | 
			
		||||
  Typography
 | 
			
		||||
} from '@mui/material';
 | 
			
		||||
import { styled } from "@mui/system";
 | 
			
		||||
import { Box, Modal, Typography } from "@mui/material";
 | 
			
		||||
 | 
			
		||||
export const StyledModal = styled(Modal)(({ theme }) => ({
 | 
			
		||||
  display: 'flex',
 | 
			
		||||
  alignItems: 'center',
 | 
			
		||||
  justifyContent: 'center'
 | 
			
		||||
}))
 | 
			
		||||
  display: "flex",
 | 
			
		||||
  alignItems: "center",
 | 
			
		||||
  justifyContent: "center",
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
export const ModalContent = styled(Box)(({ theme }) => ({
 | 
			
		||||
  backgroundColor: theme.palette.primary.main,
 | 
			
		||||
  backgroundColor: theme.palette.background.default,
 | 
			
		||||
  padding: theme.spacing(4),
 | 
			
		||||
  borderRadius: theme.spacing(1),
 | 
			
		||||
  width: '40%',
 | 
			
		||||
  '&:focus': {
 | 
			
		||||
    outline: 'none'
 | 
			
		||||
  }
 | 
			
		||||
}))
 | 
			
		||||
  width: "40%",
 | 
			
		||||
  "&:focus": {
 | 
			
		||||
    outline: "none",
 | 
			
		||||
  },
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
export const ModalText = styled(Typography)(({ theme }) => ({
 | 
			
		||||
  fontFamily: "Raleway",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,9 @@
 | 
			
		||||
import React, { useState } from "react";
 | 
			
		||||
import { Button, List, ListItem, Typography, useTheme } from "@mui/material";
 | 
			
		||||
import {
 | 
			
		||||
  Box,
 | 
			
		||||
  Button,
 | 
			
		||||
  Modal,
 | 
			
		||||
  Typography,
 | 
			
		||||
  SelectChangeEvent,
 | 
			
		||||
  ListItem,
 | 
			
		||||
  List,
 | 
			
		||||
  useTheme
 | 
			
		||||
} from "@mui/material";
 | 
			
		||||
import {
 | 
			
		||||
  StyledModal,
 | 
			
		||||
  ModalContent,
 | 
			
		||||
  ModalText
 | 
			
		||||
  ModalText,
 | 
			
		||||
  StyledModal,
 | 
			
		||||
} from "./BlockedNamesModal-styles";
 | 
			
		||||
 | 
			
		||||
interface PostModalProps {
 | 
			
		||||
@@ -22,7 +13,7 @@ interface PostModalProps {
 | 
			
		||||
 | 
			
		||||
export const BlockedNamesModal: React.FC<PostModalProps> = ({
 | 
			
		||||
  open,
 | 
			
		||||
  onClose
 | 
			
		||||
  onClose,
 | 
			
		||||
}) => {
 | 
			
		||||
  const [blockedNames, setBlockedNames] = useState<string[]>([]);
 | 
			
		||||
  const theme = useTheme();
 | 
			
		||||
@@ -31,7 +22,7 @@ export const BlockedNamesModal: React.FC<PostModalProps> = ({
 | 
			
		||||
      const listName = `blockedNames`;
 | 
			
		||||
      const response = await qortalRequest({
 | 
			
		||||
        action: "GET_LIST_ITEMS",
 | 
			
		||||
        list_name: listName
 | 
			
		||||
        list_name: listName,
 | 
			
		||||
      });
 | 
			
		||||
      setBlockedNames(response);
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
@@ -48,11 +39,11 @@ export const BlockedNamesModal: React.FC<PostModalProps> = ({
 | 
			
		||||
      const response = await qortalRequest({
 | 
			
		||||
        action: "DELETE_LIST_ITEM",
 | 
			
		||||
        list_name: "blockedNames",
 | 
			
		||||
        item: name
 | 
			
		||||
        item: name,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      if (response === true) {
 | 
			
		||||
        setBlockedNames((prev) => prev.filter((n) => n !== name));
 | 
			
		||||
        setBlockedNames(prev => prev.filter(n => n !== name));
 | 
			
		||||
      }
 | 
			
		||||
    } catch (error) {}
 | 
			
		||||
  };
 | 
			
		||||
@@ -67,22 +58,22 @@ export const BlockedNamesModal: React.FC<PostModalProps> = ({
 | 
			
		||||
            display: "flex",
 | 
			
		||||
            flexDirection: "column",
 | 
			
		||||
            flex: "1",
 | 
			
		||||
            overflow: "auto"
 | 
			
		||||
            overflow: "auto",
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          {blockedNames.map((name, index) => (
 | 
			
		||||
            <ListItem
 | 
			
		||||
              key={name + index}
 | 
			
		||||
              sx={{
 | 
			
		||||
                display: "flex"
 | 
			
		||||
                display: "flex",
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              <Typography>{name}</Typography>
 | 
			
		||||
              <Button
 | 
			
		||||
                sx={{
 | 
			
		||||
                  backgroundColor: theme.palette.primary.light,
 | 
			
		||||
                  backgroundColor: theme.palette.primary.main,
 | 
			
		||||
                  color: theme.palette.text.primary,
 | 
			
		||||
                  fontFamily: "Raleway"
 | 
			
		||||
                  fontFamily: "Raleway",
 | 
			
		||||
                }}
 | 
			
		||||
                onClick={() => removeFromBlockList(name)}
 | 
			
		||||
              >
 | 
			
		||||
@@ -91,7 +82,15 @@ export const BlockedNamesModal: React.FC<PostModalProps> = ({
 | 
			
		||||
            </ListItem>
 | 
			
		||||
          ))}
 | 
			
		||||
        </List>
 | 
			
		||||
        <Button variant="contained" color="primary" onClick={onClose}>
 | 
			
		||||
        <Button
 | 
			
		||||
          variant="contained"
 | 
			
		||||
          onClick={onClose}
 | 
			
		||||
          sx={{
 | 
			
		||||
            backgroundColor: theme.palette.primary.light,
 | 
			
		||||
            color: theme.palette.text.primary,
 | 
			
		||||
            fontFamily: "Raleway",
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          Close
 | 
			
		||||
        </Button>
 | 
			
		||||
      </ModalContent>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,8 +7,9 @@ import DialogContentText from "@mui/material/DialogContentText";
 | 
			
		||||
import DialogTitle from "@mui/material/DialogTitle";
 | 
			
		||||
import localForage from "localforage";
 | 
			
		||||
import { useTheme } from "@mui/material";
 | 
			
		||||
 | 
			
		||||
const generalLocal = localForage.createInstance({
 | 
			
		||||
  name: "q-share-general",
 | 
			
		||||
  name: "q-support-general",
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default function ConsentModal() {
 | 
			
		||||
@@ -44,13 +45,15 @@ export default function ConsentModal() {
 | 
			
		||||
        <DialogTitle id="alert-dialog-title">Welcome</DialogTitle>
 | 
			
		||||
        <DialogContent>
 | 
			
		||||
          <DialogContentText id="alert-dialog-description">
 | 
			
		||||
            Q-Share is currently in its first version and as such there could be
 | 
			
		||||
            some bugs. The Qortal community, along with its development team and
 | 
			
		||||
            the creators of this application, cannot be held accountable for any
 | 
			
		||||
            content published or displayed. Also, they are not responsible for
 | 
			
		||||
            any loss of coin due to either bad actors or bugs in the
 | 
			
		||||
            Q-Support is currently in its first version and as such there could
 | 
			
		||||
            be some bugs. The Qortal community, along with its development team
 | 
			
		||||
            and the creators of this application, cannot be held accountable for
 | 
			
		||||
            any content published or displayed. Also, they are not responsible
 | 
			
		||||
            for any loss of coin due to either bad actors or bugs in the
 | 
			
		||||
            application. Furthermore, they bear no responsibility for any data
 | 
			
		||||
            loss that may occur as a result of using this application. Finally, they bear no responsibility for any of the content uploaded by users.
 | 
			
		||||
            loss that may occur as a result of using this application. Finally,
 | 
			
		||||
            they bear no responsibility for any of the content uploaded by
 | 
			
		||||
            users.
 | 
			
		||||
          </DialogContentText>
 | 
			
		||||
        </DialogContent>
 | 
			
		||||
        <DialogActions>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,5 @@
 | 
			
		||||
import React, { useState, useEffect } from 'react'
 | 
			
		||||
import React, { useEffect, useState } from "react";
 | 
			
		||||
import {
 | 
			
		||||
  Accordion,
 | 
			
		||||
  AccordionDetails,
 | 
			
		||||
  AccordionSummary,
 | 
			
		||||
  Box,
 | 
			
		||||
  Button,
 | 
			
		||||
  LinearProgress,
 | 
			
		||||
@@ -11,30 +8,26 @@ import {
 | 
			
		||||
  ListItemIcon,
 | 
			
		||||
  Popover,
 | 
			
		||||
  Typography,
 | 
			
		||||
  useTheme
 | 
			
		||||
} from '@mui/material'
 | 
			
		||||
import { Movie } from '@mui/icons-material'
 | 
			
		||||
import {  useSelector } from 'react-redux'
 | 
			
		||||
import { RootState } from '../../state/store'
 | 
			
		||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
 | 
			
		||||
import { useLocation, useNavigate } from 'react-router-dom'
 | 
			
		||||
import { DownloadingLight } from '../../assets/svgs/DownloadingLight'
 | 
			
		||||
import { DownloadedLight } from '../../assets/svgs/DownloadedLight'
 | 
			
		||||
  useTheme,
 | 
			
		||||
} from "@mui/material";
 | 
			
		||||
import { useSelector } from "react-redux";
 | 
			
		||||
import { RootState } from "../../state/store";
 | 
			
		||||
import { useLocation, useNavigate } from "react-router-dom";
 | 
			
		||||
import { DownloadingLight } from "../../assets/svgs/DownloadingLight";
 | 
			
		||||
import { DownloadedLight } from "../../assets/svgs/DownloadedLight";
 | 
			
		||||
import AttachFileIcon from "@mui/icons-material/AttachFile";
 | 
			
		||||
 | 
			
		||||
export const DownloadTaskManager: React.FC = () => {
 | 
			
		||||
  const { downloads } = useSelector((state: RootState) => state.global)
 | 
			
		||||
  const location = useLocation()
 | 
			
		||||
  const theme = useTheme()
 | 
			
		||||
  const [visible, setVisible] = useState(false)
 | 
			
		||||
  const [hidden, setHidden] = useState(true)
 | 
			
		||||
  const navigate = useNavigate()
 | 
			
		||||
  const { downloads } = useSelector((state: RootState) => state.global);
 | 
			
		||||
  const location = useLocation();
 | 
			
		||||
  const theme = useTheme();
 | 
			
		||||
  const [visible, setVisible] = useState(false);
 | 
			
		||||
  const [hidden, setHidden] = useState(true);
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
  const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  const [openDownload, setOpenDownload] = useState<boolean>(false);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  const handleClick = (event?: React.MouseEvent<HTMLDivElement>) => {
 | 
			
		||||
    const target = event?.currentTarget as unknown as HTMLButtonElement | null;
 | 
			
		||||
    setAnchorEl(target);
 | 
			
		||||
@@ -50,44 +43,48 @@ export const DownloadTaskManager: React.FC = () => {
 | 
			
		||||
 | 
			
		||||
    if (visible) {
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
        setHidden(true)
 | 
			
		||||
        setVisible(false)
 | 
			
		||||
      }, 3000)
 | 
			
		||||
        setHidden(true);
 | 
			
		||||
        setVisible(false);
 | 
			
		||||
      }, 3000);
 | 
			
		||||
    }
 | 
			
		||||
  }, [visible])
 | 
			
		||||
 | 
			
		||||
  }, [visible]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (Object.keys(downloads).length === 0) return
 | 
			
		||||
    setVisible(true)
 | 
			
		||||
    setHidden(false)
 | 
			
		||||
  }, [downloads])
 | 
			
		||||
    if (Object.keys(downloads).length === 0) return;
 | 
			
		||||
    setVisible(true);
 | 
			
		||||
    setHidden(false);
 | 
			
		||||
  }, [downloads]);
 | 
			
		||||
 | 
			
		||||
  if (!downloads || Object.keys(downloads).length === 0) return null;
 | 
			
		||||
 | 
			
		||||
  let downloadInProgress = false;
 | 
			
		||||
  if (
 | 
			
		||||
    !downloads ||
 | 
			
		||||
    Object.keys(downloads).length === 0
 | 
			
		||||
    Object.keys(downloads).find(
 | 
			
		||||
      key =>
 | 
			
		||||
        downloads[key]?.status?.status !== "READY" &&
 | 
			
		||||
        downloads[key]?.status?.status !== "DOWNLOADED"
 | 
			
		||||
    )
 | 
			
		||||
    return null
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  let downloadInProgress = false
 | 
			
		||||
  if(Object.keys(downloads).find((key)=> (downloads[key]?.status?.status !== 'READY' && downloads[key]?.status?.status !== 'DOWNLOADED'))){
 | 
			
		||||
    downloadInProgress = true
 | 
			
		||||
  ) {
 | 
			
		||||
    downloadInProgress = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Box>
 | 
			
		||||
              <Button  onClick={(e: any) => {
 | 
			
		||||
      <Button
 | 
			
		||||
        onClick={(e: any) => {
 | 
			
		||||
          handleClick(e);
 | 
			
		||||
          setOpenDownload(true);
 | 
			
		||||
              }}>
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        {downloadInProgress ? (
 | 
			
		||||
                   <DownloadingLight height='24px' width='24px' className='download-icon' />
 | 
			
		||||
          <DownloadingLight
 | 
			
		||||
            height="24px"
 | 
			
		||||
            width="24px"
 | 
			
		||||
            className="download-icon"
 | 
			
		||||
          />
 | 
			
		||||
        ) : (
 | 
			
		||||
                  <DownloadedLight height='24px' width='24px'  />
 | 
			
		||||
          <DownloadedLight height="24px" width="24px" />
 | 
			
		||||
        )}
 | 
			
		||||
                
 | 
			
		||||
      </Button>
 | 
			
		||||
 | 
			
		||||
      <Popover
 | 
			
		||||
@@ -97,108 +94,99 @@ export const DownloadTaskManager: React.FC = () => {
 | 
			
		||||
        onClose={handleCloseDownload}
 | 
			
		||||
        anchorOrigin={{
 | 
			
		||||
          vertical: "bottom",
 | 
			
		||||
            horizontal: "left"
 | 
			
		||||
          horizontal: "left",
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        <List
 | 
			
		||||
          sx={{
 | 
			
		||||
              maxHeight: '50vh',
 | 
			
		||||
              overflow: 'auto',
 | 
			
		||||
              width: '250px',
 | 
			
		||||
              gap: '5px',
 | 
			
		||||
              display: 'flex',
 | 
			
		||||
              flexDirection: 'column',
 | 
			
		||||
          
 | 
			
		||||
            maxHeight: "50vh",
 | 
			
		||||
            overflow: "auto",
 | 
			
		||||
            width: "250px",
 | 
			
		||||
            gap: "5px",
 | 
			
		||||
            display: "flex",
 | 
			
		||||
            flexDirection: "column",
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
            {Object.keys(downloads)
 | 
			
		||||
              .map((download: any) => {
 | 
			
		||||
                const downloadObj = downloads[download]
 | 
			
		||||
                const progress = downloads[download]?.status?.percentLoaded || 0
 | 
			
		||||
                const status = downloads[download]?.status?.status
 | 
			
		||||
                const service = downloads[download]?.service
 | 
			
		||||
          {Object.keys(downloads).map((download: any) => {
 | 
			
		||||
            const downloadObj = downloads[download];
 | 
			
		||||
            const progress = downloads[download]?.status?.percentLoaded || 0;
 | 
			
		||||
            const status = downloads[download]?.status?.status;
 | 
			
		||||
            const service = downloads[download]?.service;
 | 
			
		||||
            return (
 | 
			
		||||
              <ListItem
 | 
			
		||||
                key={downloadObj?.identifier}
 | 
			
		||||
                sx={{
 | 
			
		||||
                      display: 'flex',
 | 
			
		||||
                      flexDirection: 'column',
 | 
			
		||||
                      width: '100%',
 | 
			
		||||
                      justifyContent: 'center',
 | 
			
		||||
                  display: "flex",
 | 
			
		||||
                  flexDirection: "column",
 | 
			
		||||
                  width: "100%",
 | 
			
		||||
                  justifyContent: "center",
 | 
			
		||||
                  background: theme.palette.primary.main,
 | 
			
		||||
                  color: theme.palette.text.primary,
 | 
			
		||||
                      cursor: 'pointer',
 | 
			
		||||
                      padding: '2px',
 | 
			
		||||
                      
 | 
			
		||||
                  cursor: "pointer",
 | 
			
		||||
                  padding: "2px",
 | 
			
		||||
                }}
 | 
			
		||||
                onClick={() => {
 | 
			
		||||
                      const id = downloadObj?.properties?.jsonId
 | 
			
		||||
                      if (!id) return
 | 
			
		||||
                  const id = downloadObj?.properties?.jsonId;
 | 
			
		||||
                  if (!id) return;
 | 
			
		||||
 | 
			
		||||
                      navigate(
 | 
			
		||||
                        `/share/${downloadObj?.properties?.name}/${id}`
 | 
			
		||||
                      )
 | 
			
		||||
                  navigate(`/issue/${downloadObj?.properties?.name}/${id}`);
 | 
			
		||||
                }}
 | 
			
		||||
              >
 | 
			
		||||
                <Box
 | 
			
		||||
                  sx={{
 | 
			
		||||
                        width: '100%',
 | 
			
		||||
                        display: 'flex',
 | 
			
		||||
                        alignItems: 'center',
 | 
			
		||||
                        justifyContent: 'space-between'
 | 
			
		||||
                    width: "100%",
 | 
			
		||||
                    display: "flex",
 | 
			
		||||
                    alignItems: "center",
 | 
			
		||||
                    justifyContent: "space-between",
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  <ListItemIcon>
 | 
			
		||||
                       
 | 
			
		||||
                          <AttachFileIcon sx={{ color: theme.palette.text.primary }} />
 | 
			
		||||
                       
 | 
			
		||||
                    <AttachFileIcon
 | 
			
		||||
                      sx={{ color: theme.palette.text.primary }}
 | 
			
		||||
                    />
 | 
			
		||||
                  </ListItemIcon>
 | 
			
		||||
 | 
			
		||||
                      <Box
 | 
			
		||||
                        sx={{ width: '100px', marginLeft: 1, marginRight: 1 }}
 | 
			
		||||
                      >
 | 
			
		||||
                  <Box sx={{ width: "100px", marginLeft: 1, marginRight: 1 }}>
 | 
			
		||||
                    <LinearProgress
 | 
			
		||||
                      variant="determinate"
 | 
			
		||||
                      value={progress}
 | 
			
		||||
                      sx={{
 | 
			
		||||
                            borderRadius: '5px',
 | 
			
		||||
                            color: theme.palette.secondary.main
 | 
			
		||||
                        borderRadius: "5px",
 | 
			
		||||
                        color: theme.palette.secondary.main,
 | 
			
		||||
                      }}
 | 
			
		||||
                    />
 | 
			
		||||
                  </Box>
 | 
			
		||||
                  <Typography
 | 
			
		||||
                    sx={{
 | 
			
		||||
                          fontFamily: 'Arial',
 | 
			
		||||
                          color: theme.palette.text.primary
 | 
			
		||||
                      fontFamily: "Arial",
 | 
			
		||||
                      color: theme.palette.text.primary,
 | 
			
		||||
                    }}
 | 
			
		||||
                    variant="caption"
 | 
			
		||||
                  >
 | 
			
		||||
                        {`${progress?.toFixed(0)}%`}{' '}
 | 
			
		||||
                        {status && status === 'REFETCHING' && '- refetching'}
 | 
			
		||||
                        {status && status === 'DOWNLOADED' && '- building'}
 | 
			
		||||
                    {`${progress?.toFixed(0)}%`}{" "}
 | 
			
		||||
                    {status && status === "REFETCHING" && "- refetching"}
 | 
			
		||||
                    {status && status === "DOWNLOADED" && "- building"}
 | 
			
		||||
                  </Typography>
 | 
			
		||||
                </Box>
 | 
			
		||||
                <Typography
 | 
			
		||||
                  sx={{
 | 
			
		||||
                        fontSize: '10px',
 | 
			
		||||
                        width: '100%',
 | 
			
		||||
                        textAlign: 'end',
 | 
			
		||||
                        fontFamily: 'Arial',
 | 
			
		||||
                    fontSize: "10px",
 | 
			
		||||
                    width: "100%",
 | 
			
		||||
                    textAlign: "end",
 | 
			
		||||
                    fontFamily: "Arial",
 | 
			
		||||
                    color: theme.palette.text.primary,
 | 
			
		||||
                        textOverflow: 'ellipsis',
 | 
			
		||||
                        whiteSpace: 'nowrap',
 | 
			
		||||
                        overflow: 'hidden',
 | 
			
		||||
                    textOverflow: "ellipsis",
 | 
			
		||||
                    whiteSpace: "nowrap",
 | 
			
		||||
                    overflow: "hidden",
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  {downloadObj?.identifier}
 | 
			
		||||
                </Typography>
 | 
			
		||||
              </ListItem>
 | 
			
		||||
                )
 | 
			
		||||
            );
 | 
			
		||||
          })}
 | 
			
		||||
        </List>
 | 
			
		||||
      </Popover>
 | 
			
		||||
           
 | 
			
		||||
    </Box>
 | 
			
		||||
     
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
const useTestIdentifiers = false;
 | 
			
		||||
const useTestIdentifiers = true;
 | 
			
		||||
 | 
			
		||||
export const QSUPPORT_FILE_BASE = useTestIdentifiers
 | 
			
		||||
  ? "MYTEST_support_issue_"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,10 @@
 | 
			
		||||
import React, { useCallback, useEffect, useRef, useState } from "react";
 | 
			
		||||
import React 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 {
 | 
			
		||||
  BottomParent,
 | 
			
		||||
  NameContainer,
 | 
			
		||||
  VideoCard,
 | 
			
		||||
  VideoCardName,
 | 
			
		||||
  VideoCardTitle,
 | 
			
		||||
  FileContainer,
 | 
			
		||||
  VideoUploadDate,
 | 
			
		||||
} from "./FileList-styles.tsx";
 | 
			
		||||
import { Box, useTheme } from "@mui/material";
 | 
			
		||||
import { FileContainer } from "./IssueList-styles.tsx";
 | 
			
		||||
import ResponsiveImage from "../../components/ResponsiveImage";
 | 
			
		||||
import { formatDate, formatTimestampSeconds } from "../../utils/time";
 | 
			
		||||
import { ChannelCard, ChannelTitle } from "./Home-styles";
 | 
			
		||||
 | 
			
		||||
interface VideoListProps {
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ import {
 | 
			
		||||
  VideoCardName,
 | 
			
		||||
  VideoCardTitle,
 | 
			
		||||
  VideoUploadDate,
 | 
			
		||||
} from "./FileList-styles.tsx";
 | 
			
		||||
} from "./IssueList-styles.tsx";
 | 
			
		||||
import { formatDate } from "../../utils/time";
 | 
			
		||||
import { Video } from "../../state/features/fileSlice.ts";
 | 
			
		||||
import { queue } from "../../wrappers/GlobalWrapper";
 | 
			
		||||
@@ -149,7 +149,7 @@ export const FileListComponentLevel = ({ mode }: VideoListProps) => {
 | 
			
		||||
                <>
 | 
			
		||||
                  <VideoCard
 | 
			
		||||
                    onClick={() => {
 | 
			
		||||
                      navigate(`/share/${fileObj?.user}/${fileObj?.id}`);
 | 
			
		||||
                      navigate(`/issue/${fileObj?.user}/${fileObj?.id}`);
 | 
			
		||||
                    }}
 | 
			
		||||
                    sx={{
 | 
			
		||||
                      height: "100%",
 | 
			
		||||
 
 | 
			
		||||
@@ -2,11 +2,11 @@ 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 { IssueList } from "./IssueList.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 { FiltersCol, FiltersContainer } from "./IssueList-styles.tsx";
 | 
			
		||||
import { SubtitleContainer } from "./Home-styles";
 | 
			
		||||
import {
 | 
			
		||||
  changefilterName,
 | 
			
		||||
@@ -315,7 +315,7 @@ export const Home = ({ mode }: HomeProps) => {
 | 
			
		||||
              maxWidth: "1400px",
 | 
			
		||||
            }}
 | 
			
		||||
          ></SubtitleContainer>
 | 
			
		||||
          <FileList files={videos} />
 | 
			
		||||
          <IssueList files={videos} />
 | 
			
		||||
          <LazyLoad
 | 
			
		||||
            onLoadMore={getFilesHandler}
 | 
			
		||||
            isLoading={isLoading}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,12 @@
 | 
			
		||||
import { styled } from "@mui/system";
 | 
			
		||||
import {
 | 
			
		||||
  Box,
 | 
			
		||||
  Grid,
 | 
			
		||||
  Typography,
 | 
			
		||||
  Checkbox,
 | 
			
		||||
  TextField,
 | 
			
		||||
  InputLabel,
 | 
			
		||||
  Autocomplete,
 | 
			
		||||
  Box,
 | 
			
		||||
  Checkbox,
 | 
			
		||||
  Grid,
 | 
			
		||||
  InputLabel,
 | 
			
		||||
  TextField,
 | 
			
		||||
  Typography,
 | 
			
		||||
} from "@mui/material";
 | 
			
		||||
 | 
			
		||||
export const FileContainer = styled(Box)(({ theme }) => ({
 | 
			
		||||
@@ -283,6 +283,7 @@ export const BlockIconContainer = styled(Box)({
 | 
			
		||||
  padding: "2px",
 | 
			
		||||
  borderRadius: "3px",
 | 
			
		||||
  transition: "all 0.3s ease-in-out",
 | 
			
		||||
  fontSize: "18px",
 | 
			
		||||
  "&:hover": {
 | 
			
		||||
    cursor: "pointer",
 | 
			
		||||
    transform: "scale(1.1)",
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import { Avatar, Box, Skeleton, Tooltip } from "@mui/material";
 | 
			
		||||
import { Avatar, Box, Skeleton } from "@mui/material";
 | 
			
		||||
import {
 | 
			
		||||
  BlockIconContainer,
 | 
			
		||||
  BottomParent,
 | 
			
		||||
@@ -9,7 +9,7 @@ import {
 | 
			
		||||
  VideoCardName,
 | 
			
		||||
  VideoCardTitle,
 | 
			
		||||
  VideoUploadDate,
 | 
			
		||||
} from "./FileList-styles.tsx";
 | 
			
		||||
} from "./IssueList-styles.tsx";
 | 
			
		||||
import EditIcon from "@mui/icons-material/Edit";
 | 
			
		||||
import {
 | 
			
		||||
  blockUser,
 | 
			
		||||
@@ -29,7 +29,7 @@ import { getIconsFromObject } from "../../constants/Categories/CategoryFunctions
 | 
			
		||||
interface FileListProps {
 | 
			
		||||
  files: Video[];
 | 
			
		||||
}
 | 
			
		||||
export const FileList = ({ files }: FileListProps) => {
 | 
			
		||||
export const IssueList = ({ files }: FileListProps) => {
 | 
			
		||||
  const hashMapFiles = useSelector(
 | 
			
		||||
    (state: RootState) => state.file.hashMapFiles
 | 
			
		||||
  );
 | 
			
		||||
@@ -40,7 +40,7 @@ export const FileList = ({ files }: FileListProps) => {
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
 | 
			
		||||
  const blockUserFunc = async (user: string) => {
 | 
			
		||||
    if (user === "Q-Share") return;
 | 
			
		||||
    if (user === "Q-Support") return;
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const response = await qortalRequest({
 | 
			
		||||
@@ -88,30 +88,30 @@ export const FileList = ({ files }: FileListProps) => {
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  {fileObj?.user === username && (
 | 
			
		||||
                    <Tooltip title="Edit Issue Properties" placement="top">
 | 
			
		||||
                      <BlockIconContainer>
 | 
			
		||||
                        <EditIcon
 | 
			
		||||
                    <BlockIconContainer
 | 
			
		||||
                      onClick={() => {
 | 
			
		||||
                        dispatch(setEditFile(fileObj));
 | 
			
		||||
                      }}
 | 
			
		||||
                        />
 | 
			
		||||
                    >
 | 
			
		||||
                      <EditIcon />
 | 
			
		||||
                      Edit Issue
 | 
			
		||||
                    </BlockIconContainer>
 | 
			
		||||
                    </Tooltip>
 | 
			
		||||
                  )}
 | 
			
		||||
 | 
			
		||||
                  <Tooltip title="Block user content" placement="top">
 | 
			
		||||
                    <BlockIconContainer>
 | 
			
		||||
                      <BlockIcon
 | 
			
		||||
                  {fileObj?.user !== username && (
 | 
			
		||||
                    <BlockIconContainer
 | 
			
		||||
                      onClick={() => {
 | 
			
		||||
                        blockUserFunc(fileObj?.user);
 | 
			
		||||
                      }}
 | 
			
		||||
                      />
 | 
			
		||||
                    >
 | 
			
		||||
                      <BlockIcon />
 | 
			
		||||
                      Block User
 | 
			
		||||
                    </BlockIconContainer>
 | 
			
		||||
                  </Tooltip>
 | 
			
		||||
                  )}
 | 
			
		||||
                </IconsBox>
 | 
			
		||||
                <VideoCard
 | 
			
		||||
                  onClick={() => {
 | 
			
		||||
                    navigate(`/share/${fileObj?.user}/${fileObj?.id}`);
 | 
			
		||||
                    navigate(`/issue/${fileObj?.user}/${fileObj?.id}`);
 | 
			
		||||
                  }}
 | 
			
		||||
                  sx={{
 | 
			
		||||
                    height: "100%",
 | 
			
		||||
		Reference in New Issue
	
	Block a user