mirror of
https://github.com/Qortal/q-support.git
synced 2025-02-11 09:45:50 +00:00
Q-Support 1.0
This commit is contained in:
parent
9a7480aeb3
commit
48ce1f8068
32
.eslintrc.js
32
.eslintrc.js
@ -1,16 +1,16 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
env: { browser: true, es2020: true },
|
env: { browser: true, es2020: true },
|
||||||
extends: [
|
extends: [
|
||||||
'eslint:recommended',
|
'eslint:recommended',
|
||||||
'plugin:@typescript-eslint/recommended',
|
'plugin:@typescript-eslint/recommended',
|
||||||
'plugin:react-hooks/recommended',
|
'plugin:react-hooks/recommended',
|
||||||
],
|
],
|
||||||
parser: '@typescript-eslint/parser',
|
parser: '@typescript-eslint/parser',
|
||||||
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
|
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
|
||||||
plugins: ['react-refresh'],
|
plugins: ['react-refresh'],
|
||||||
rules: {
|
rules: {
|
||||||
'react-refresh/only-export-components': 'warn',
|
'react-refresh/only-export-components': 'warn',
|
||||||
'@typescript-eslint/no-explicit-any': "off"
|
'@typescript-eslint/no-explicit-any': "off"
|
||||||
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
49
.gitignore
vendored
49
.gitignore
vendored
@ -1,25 +1,26 @@
|
|||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
pnpm-debug.log*
|
pnpm-debug.log*
|
||||||
lerna-debug.log*
|
lerna-debug.log*
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
src/assets/icons/*
|
||||||
!.vscode/extensions.json
|
.vscode/*
|
||||||
.idea
|
!.vscode/extensions.json
|
||||||
.DS_Store
|
.idea
|
||||||
*.suo
|
.DS_Store
|
||||||
*.ntvs*
|
*.suo
|
||||||
*.njsproj
|
*.ntvs*
|
||||||
*.sln
|
*.njsproj
|
||||||
*.sw?
|
*.sln
|
||||||
|
*.sw?
|
||||||
*.zip
|
*.zip
|
20
.prettierrc
20
.prettierrc
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"printWidth": 80,
|
"printWidth": 80,
|
||||||
"singleQuote": false,
|
"singleQuote": false,
|
||||||
"trailingComma": "es5",
|
"trailingComma": "es5",
|
||||||
"bracketSpacing": true,
|
"bracketSpacing": true,
|
||||||
"jsxBracketSameLine": false,
|
"jsxBracketSameLine": false,
|
||||||
"arrowParens": "avoid",
|
"arrowParens": "avoid",
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"semi": true
|
"semi": true
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
# q-support
|
# q-support
|
||||||
This Q-App lets users submit issues involving the Qortal Core, UI, and Q-Apps to the Qortal Dev team.
|
This Q-App lets users submit issues involving the Qortal Core, UI, and Q-Apps to the Qortal Dev team.
|
||||||
|
26
index.html
26
index.html
@ -1,13 +1,13 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Q-Support</title>
|
<title>Q-Support</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
15060
package-lock.json
generated
15060
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
96
package.json
96
package.json
@ -1,48 +1,48 @@
|
|||||||
{
|
{
|
||||||
"name": "qsupport",
|
"name": "qsupport",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.10.6",
|
"@emotion/react": "^11.10.6",
|
||||||
"@emotion/styled": "^11.10.6",
|
"@emotion/styled": "^11.10.6",
|
||||||
"@mui/icons-material": "^5.11.11",
|
"@mui/icons-material": "^5.11.11",
|
||||||
"@mui/material": "^5.11.13",
|
"@mui/material": "^5.11.13",
|
||||||
"@reduxjs/toolkit": "^1.9.3",
|
"@reduxjs/toolkit": "^1.9.3",
|
||||||
"compressorjs": "^1.2.1",
|
"compressorjs": "^1.2.1",
|
||||||
"dompurify": "^3.0.6",
|
"dompurify": "^3.0.6",
|
||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"prettier": "^3.2.4",
|
"prettier": "^3.2.4",
|
||||||
"quill-image-resize-module-react": "^3.0.0",
|
"quill-image-resize-module-react": "^3.0.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
"react-intersection-observer": "^9.4.3",
|
"react-intersection-observer": "^9.4.3",
|
||||||
"react-quill": "^2.0.0",
|
"react-quill": "^2.0.0",
|
||||||
"react-redux": "^8.0.5",
|
"react-redux": "^8.0.5",
|
||||||
"react-rnd": "^10.4.1",
|
"react-rnd": "^10.4.1",
|
||||||
"react-router-dom": "^6.9.0",
|
"react-router-dom": "^6.9.0",
|
||||||
"react-toastify": "^9.1.2",
|
"react-toastify": "^9.1.2",
|
||||||
"short-unique-id": "^4.4.4",
|
"short-unique-id": "^4.4.4",
|
||||||
"ts-key-enum": "^2.0.12"
|
"ts-key-enum": "^2.0.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.0.28",
|
"@types/react": "^18.0.28",
|
||||||
"@types/react-dom": "^18.0.11",
|
"@types/react-dom": "^18.0.11",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.57.1",
|
"@typescript-eslint/eslint-plugin": "^5.57.1",
|
||||||
"@typescript-eslint/parser": "^5.57.1",
|
"@typescript-eslint/parser": "^5.57.1",
|
||||||
"@vitejs/plugin-react": "^4.0.0",
|
"@vitejs/plugin-react": "^4.0.0",
|
||||||
"eslint": "^8.38.0",
|
"eslint": "^8.38.0",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-react-refresh": "^0.3.4",
|
"eslint-plugin-react-refresh": "^0.3.4",
|
||||||
"typescript": "^5.0.2",
|
"typescript": "^5.0.2",
|
||||||
"vite": "6.0.0-alpha.1"
|
"vite": "6.0.0-alpha.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
86
src/App.css
86
src/App.css
@ -1,43 +1,43 @@
|
|||||||
#root {
|
#root {
|
||||||
max-width: 1280px;
|
max-width: 1280px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
height: 6em;
|
height: 6em;
|
||||||
padding: 1.5em;
|
padding: 1.5em;
|
||||||
will-change: filter;
|
will-change: filter;
|
||||||
transition: filter 300ms;
|
transition: filter 300ms;
|
||||||
}
|
}
|
||||||
.logo:hover {
|
.logo:hover {
|
||||||
filter: drop-shadow(0 0 2em #646cffaa);
|
filter: drop-shadow(0 0 2em #646cffaa);
|
||||||
}
|
}
|
||||||
.logo.react:hover {
|
.logo.react:hover {
|
||||||
filter: drop-shadow(0 0 2em #61dafbaa);
|
filter: drop-shadow(0 0 2em #61dafbaa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes logo-spin {
|
@keyframes logo-spin {
|
||||||
from {
|
from {
|
||||||
transform: rotate(0deg);
|
transform: rotate(0deg);
|
||||||
}
|
}
|
||||||
to {
|
to {
|
||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
a:nth-of-type(2) .logo {
|
a:nth-of-type(2) .logo {
|
||||||
animation: logo-spin infinite 20s linear;
|
animation: logo-spin infinite 20s linear;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
padding: 2em;
|
padding: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.read-the-docs {
|
.read-the-docs {
|
||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Route, Routes } from "react-router-dom";
|
import { Route, Routes } from "react-router-dom";
|
||||||
import { ThemeProvider } from "@mui/material/styles";
|
import { ThemeProvider } from "@mui/material/styles";
|
||||||
import { CssBaseline } from "@mui/material";
|
import { CssBaseline } from "@mui/material";
|
||||||
@ -11,12 +11,16 @@ import { Home } from "./pages/Home/Home";
|
|||||||
import { IssueContent } from "./pages/IssueContent/IssueContent.tsx";
|
import { IssueContent } from "./pages/IssueContent/IssueContent.tsx";
|
||||||
import DownloadWrapper from "./wrappers/DownloadWrapper";
|
import DownloadWrapper from "./wrappers/DownloadWrapper";
|
||||||
import { IndividualProfile } from "./pages/IndividualProfile/IndividualProfile";
|
import { IndividualProfile } from "./pages/IndividualProfile/IndividualProfile";
|
||||||
|
import { fetchFeesRedux } from "./constants/PublishFees/FeePricePublish/FeePricePublish.ts";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
// const themeColor = window._qdnTheme
|
// const themeColor = window._qdnTheme
|
||||||
|
|
||||||
const [theme, setTheme] = useState("dark");
|
const [theme, setTheme] = useState("dark");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchFeesRedux();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ThemeProvider theme={theme === "light" ? lightTheme : darkTheme}>
|
<ThemeProvider theme={theme === "light" ? lightTheme : darkTheme}>
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 14 KiB |
Binary file not shown.
Before Width: | Height: | Size: 132 KiB |
Binary file not shown.
Before Width: | Height: | Size: 7.1 KiB |
Binary file not shown.
Before Width: | Height: | Size: 13 KiB |
BIN
src/assets/img/Q-SupportIcon(AlphaX).webp
Normal file
BIN
src/assets/img/Q-SupportIcon(AlphaX).webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 73 KiB |
Binary file not shown.
Before Width: | Height: | Size: 39 KiB |
@ -1,25 +1,25 @@
|
|||||||
interface AccountCircleSVGProps {
|
interface AccountCircleSVGProps {
|
||||||
color: string
|
color: string
|
||||||
height: string
|
height: string
|
||||||
width: string
|
width: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AccountCircleSVG: React.FC<AccountCircleSVGProps> = ({
|
export const AccountCircleSVG: React.FC<AccountCircleSVGProps> = ({
|
||||||
color,
|
color,
|
||||||
height,
|
height,
|
||||||
width
|
width
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
height={height}
|
height={height}
|
||||||
viewBox="0 96 960 960"
|
viewBox="0 96 960 960"
|
||||||
width={width}
|
width={width}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill={color}
|
fill={color}
|
||||||
d="M222 801q63-44 125-67.5T480 710q71 0 133.5 23.5T739 801q44-54 62.5-109T820 576q0-145-97.5-242.5T480 236q-145 0-242.5 97.5T140 576q0 61 19 116t63 109Zm257.814-195Q422 606 382.5 566.314q-39.5-39.686-39.5-97.5t39.686-97.314q39.686-39.5 97.5-39.5t97.314 39.686q39.5 39.686 39.5 97.5T577.314 566.5q-39.686 39.5-97.5 39.5Zm.654 370Q398 976 325 944.5q-73-31.5-127.5-86t-86-127.266Q80 658.468 80 575.734T111.5 420.5q31.5-72.5 86-127t127.266-86q72.766-31.5 155.5-31.5T635.5 207.5q72.5 31.5 127 86t86 127.032q31.5 72.532 31.5 155T848.5 731q-31.5 73-86 127.5t-127.032 86q-72.532 31.5-155 31.5ZM480 916q55 0 107.5-16T691 844q-51-36-104-55t-107-19q-54 0-107 19t-104 55q51 40 103.5 56T480 916Zm0-370q34 0 55.5-21.5T557 469q0-34-21.5-55.5T480 392q-34 0-55.5 21.5T403 469q0 34 21.5 55.5T480 546Zm0-77Zm0 374Z"
|
d="M222 801q63-44 125-67.5T480 710q71 0 133.5 23.5T739 801q44-54 62.5-109T820 576q0-145-97.5-242.5T480 236q-145 0-242.5 97.5T140 576q0 61 19 116t63 109Zm257.814-195Q422 606 382.5 566.314q-39.5-39.686-39.5-97.5t39.686-97.314q39.686-39.5 97.5-39.5t97.314 39.686q39.5 39.686 39.5 97.5T577.314 566.5q-39.686 39.5-97.5 39.5Zm.654 370Q398 976 325 944.5q-73-31.5-127.5-86t-86-127.266Q80 658.468 80 575.734T111.5 420.5q31.5-72.5 86-127t127.266-86q72.766-31.5 155.5-31.5T635.5 207.5q72.5 31.5 127 86t86 127.032q31.5 72.532 31.5 155T848.5 731q-31.5 73-86 127.5t-127.032 86q-72.532 31.5-155 31.5ZM480 916q55 0 107.5-16T691 844q-51-36-104-55t-107-19q-54 0-107 19t-104 55q51 40 103.5 56T480 916Zm0-370q34 0 55.5-21.5T557 469q0-34-21.5-55.5T480 392q-34 0-55.5 21.5T403 469q0 34 21.5 55.5T480 546Zm0-77Zm0 374Z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
import { IconTypes } from "./IconTypes";
|
import { IconTypes } from "./IconTypes";
|
||||||
|
|
||||||
export const CircleSVG: React.FC<IconTypes> = ({
|
export const CircleSVG: React.FC<IconTypes> = ({
|
||||||
color,
|
color,
|
||||||
height,
|
height,
|
||||||
width,
|
width,
|
||||||
className,
|
className,
|
||||||
onClickFunc,
|
onClickFunc,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
onClick={onClickFunc}
|
onClick={onClickFunc}
|
||||||
className={className}
|
className={className}
|
||||||
fill={color}
|
fill={color}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
height={height}
|
height={height}
|
||||||
viewBox="0 -960 960 960"
|
viewBox="0 -960 960 960"
|
||||||
width={width}
|
width={width}
|
||||||
>
|
>
|
||||||
<path d="m424-296 282-282-56-56-226 226-114-114-56 56 170 170Zm56 216q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z" />
|
<path d="m424-296 282-282-56-56-226 226-114-114-56 56 170 170Zm56 216q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z" />
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
import { IconTypes } from './IconTypes'
|
import { IconTypes } from './IconTypes'
|
||||||
|
|
||||||
export const DarkModeSVG: React.FC<IconTypes> = ({
|
export const DarkModeSVG: React.FC<IconTypes> = ({
|
||||||
color,
|
color,
|
||||||
height,
|
height,
|
||||||
width,
|
width,
|
||||||
className,
|
className,
|
||||||
onClickFunc
|
onClickFunc
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
className={className}
|
className={className}
|
||||||
onClick={onClickFunc}
|
onClick={onClickFunc}
|
||||||
fill={color}
|
fill={color}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
height={height}
|
height={height}
|
||||||
viewBox="0 96 960 960"
|
viewBox="0 96 960 960"
|
||||||
width={width}
|
width={width}
|
||||||
>
|
>
|
||||||
<path d="M480 936q-150 0-255-105T120 576q0-150 105-255t255-105q8 0 17 .5t23 1.5q-36 32-56 79t-20 99q0 90 63 153t153 63q52 0 99-18.5t79-51.5q1 12 1.5 19.5t.5 14.5q0 150-105 255T480 936Zm0-60q109 0 190-67.5T771 650q-25 11-53.667 16.5Q688.667 672 660 672q-114.689 0-195.345-80.655Q384 510.689 384 396q0-24 5-51.5t18-62.5q-98 27-162.5 109.5T180 576q0 125 87.5 212.5T480 876Zm-4-297Z" />
|
<path d="M480 936q-150 0-255-105T120 576q0-150 105-255t255-105q8 0 17 .5t23 1.5q-36 32-56 79t-20 99q0 90 63 153t153 63q52 0 99-18.5t79-51.5q1 12 1.5 19.5t.5 14.5q0 150-105 255T480 936Zm0-60q109 0 190-67.5T771 650q-25 11-53.667 16.5Q688.667 672 660 672q-114.689 0-195.345-80.655Q384 510.689 384 396q0-24 5-51.5t18-62.5q-98 27-162.5 109.5T180 576q0 125 87.5 212.5T480 876Zm-4-297Z" />
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { IconTypes } from './IconTypes'
|
import { IconTypes } from './IconTypes'
|
||||||
|
|
||||||
export const DownloadedLight: React.FC<IconTypes> = ({
|
export const DownloadedLight: React.FC<IconTypes> = ({
|
||||||
color,
|
color,
|
||||||
height,
|
height,
|
||||||
width,
|
width,
|
||||||
className,
|
className,
|
||||||
onClickFunc
|
onClickFunc
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<svg className={className} xmlns="http://www.w3.org/2000/svg" height={height} viewBox="0 0 24 24" width={width} fill="#FFFFFF"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M5 18h14v2H5v-2zm4.6-2.7L5 10.7l2-1.9 2.6 2.6L17 4l2 2-9.4 9.3z"/></svg>
|
<svg className={className} xmlns="http://www.w3.org/2000/svg" height={height} viewBox="0 0 24 24" width={width} fill="#FFFFFF"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M5 18h14v2H5v-2zm4.6-2.7L5 10.7l2-1.9 2.6 2.6L17 4l2 2-9.4 9.3z"/></svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { IconTypes } from './IconTypes'
|
import { IconTypes } from './IconTypes'
|
||||||
|
|
||||||
export const DownloadingLight: React.FC<IconTypes> = ({
|
export const DownloadingLight: React.FC<IconTypes> = ({
|
||||||
color,
|
color,
|
||||||
height,
|
height,
|
||||||
width,
|
width,
|
||||||
className,
|
className,
|
||||||
onClickFunc
|
onClickFunc
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<svg className={className} xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height={height} viewBox="0 0 24 24" width={width} fill="#FFFFFF"><g><rect fill="none" /></g><g><g><path d="M18.32,4.26C16.84,3.05,15.01,2.25,13,2.05v2.02c1.46,0.18,2.79,0.76,3.9,1.62L18.32,4.26z M19.93,11h2.02 c-0.2-2.01-1-3.84-2.21-5.32L18.31,7.1C19.17,8.21,19.75,9.54,19.93,11z M18.31,16.9l1.43,1.43c1.21-1.48,2.01-3.32,2.21-5.32 h-2.02C19.75,14.46,19.17,15.79,18.31,16.9z M13,19.93v2.02c2.01-0.2,3.84-1,5.32-2.21l-1.43-1.43 C15.79,19.17,14.46,19.75,13,19.93z M13,12V7h-2v5H7l5,5l5-5H13z M11,19.93v2.02c-5.05-0.5-9-4.76-9-9.95s3.95-9.45,9-9.95v2.02 C7.05,4.56,4,7.92,4,12S7.05,19.44,11,19.93z"/></g></g></svg>
|
<svg className={className} xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height={height} viewBox="0 0 24 24" width={width} fill="#FFFFFF"><g><rect fill="none" /></g><g><g><path d="M18.32,4.26C16.84,3.05,15.01,2.25,13,2.05v2.02c1.46,0.18,2.79,0.76,3.9,1.62L18.32,4.26z M19.93,11h2.02 c-0.2-2.01-1-3.84-2.21-5.32L18.31,7.1C19.17,8.21,19.75,9.54,19.93,11z M18.31,16.9l1.43,1.43c1.21-1.48,2.01-3.32,2.21-5.32 h-2.02C19.75,14.46,19.17,15.79,18.31,16.9z M13,19.93v2.02c2.01-0.2,3.84-1,5.32-2.21l-1.43-1.43 C15.79,19.17,14.46,19.75,13,19.93z M13,12V7h-2v5H7l5,5l5-5H13z M11,19.93v2.02c-5.05-0.5-9-4.76-9-9.95s3.95-9.45,9-9.95v2.02 C7.05,4.56,4,7.92,4,12S7.05,19.44,11,19.93z"/></g></g></svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
import { IconTypes } from "./IconTypes";
|
import { IconTypes } from "./IconTypes";
|
||||||
|
|
||||||
export const EmptyCircleSVG: React.FC<IconTypes> = ({
|
export const EmptyCircleSVG: React.FC<IconTypes> = ({
|
||||||
color,
|
color,
|
||||||
height,
|
height,
|
||||||
width,
|
width,
|
||||||
className,
|
className,
|
||||||
onClickFunc,
|
onClickFunc,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<svg onClick={onClickFunc}
|
<svg onClick={onClickFunc}
|
||||||
className={className}
|
className={className}
|
||||||
fill={color}
|
fill={color}
|
||||||
|
|
||||||
height={height}
|
height={height}
|
||||||
|
|
||||||
width={width} xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" ><path d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>
|
width={width} xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" ><path d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
import { IconTypes } from "./IconTypes";
|
import { IconTypes } from "./IconTypes";
|
||||||
export const ExpandMoreSVG: React.FC<IconTypes> = ({
|
export const ExpandMoreSVG: React.FC<IconTypes> = ({
|
||||||
color,
|
color,
|
||||||
height,
|
height,
|
||||||
width,
|
width,
|
||||||
className,
|
className,
|
||||||
onClickFunc
|
onClickFunc
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
onClick={onClickFunc}
|
onClick={onClickFunc}
|
||||||
height={height}
|
height={height}
|
||||||
width={width}
|
width={width}
|
||||||
fill={color}
|
fill={color}
|
||||||
className={className}
|
className={className}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 -960 960 960"
|
viewBox="0 -960 960 960"
|
||||||
>
|
>
|
||||||
<path d="M480-345 240-585l43-43 197 198 197-197 43 43-240 239Z" />
|
<path d="M480-345 240-585l43-43 197 198 197-197 43 43-240 239Z" />
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
export interface IconTypes {
|
export interface IconTypes {
|
||||||
color?: string;
|
color?: string;
|
||||||
height: string;
|
height: string;
|
||||||
width: string;
|
width: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
onClickFunc?: (e?: any) => void;
|
onClickFunc?: (e?: any) => void;
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
import { IconTypes } from './IconTypes'
|
import { IconTypes } from './IconTypes'
|
||||||
|
|
||||||
export const LightModeSVG: React.FC<IconTypes> = ({
|
export const LightModeSVG: React.FC<IconTypes> = ({
|
||||||
color,
|
color,
|
||||||
height,
|
height,
|
||||||
width,
|
width,
|
||||||
className,
|
className,
|
||||||
onClickFunc
|
onClickFunc
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
className={className}
|
className={className}
|
||||||
onClick={onClickFunc}
|
onClick={onClickFunc}
|
||||||
fill={color}
|
fill={color}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
height={height}
|
height={height}
|
||||||
viewBox="0 96 960 960"
|
viewBox="0 96 960 960"
|
||||||
width={width}
|
width={width}
|
||||||
>
|
>
|
||||||
<path d="M479.765 716Q538 716 579 675.235q41-40.764 41-99Q620 518 579.235 477q-40.764-41-99-41Q422 436 381 476.765q-41 40.764-41 99Q340 634 380.765 675q40.764 41 99 41Zm.235 60q-83 0-141.5-58.5T280 576q0-83 58.5-141.5T480 376q83 0 141.5 58.5T680 576q0 83-58.5 141.5T480 776ZM70 606q-12.75 0-21.375-8.675Q40 588.649 40 575.825 40 563 48.625 554.5T70 546h100q12.75 0 21.375 8.675 8.625 8.676 8.625 21.5 0 12.825-8.625 21.325T170 606H70Zm720 0q-12.75 0-21.375-8.675-8.625-8.676-8.625-21.5 0-12.825 8.625-21.325T790 546h100q12.75 0 21.375 8.675 8.625 8.676 8.625 21.5 0 12.825-8.625 21.325T890 606H790ZM479.825 296Q467 296 458.5 287.375T450 266V166q0-12.75 8.675-21.375 8.676-8.625 21.5-8.625 12.825 0 21.325 8.625T510 166v100q0 12.75-8.675 21.375-8.676 8.625-21.5 8.625Zm0 720q-12.825 0-21.325-8.62-8.5-8.63-8.5-21.38V886q0-12.75 8.675-21.375 8.676-8.625 21.5-8.625 12.825 0 21.325 8.625T510 886v100q0 12.75-8.675 21.38-8.676 8.62-21.5 8.62ZM240 378l-57-56q-9-9-8.629-21.603.37-12.604 8.526-21.5 8.896-8.897 21.5-8.897Q217 270 226 279l56 57q8 9 8 21t-8 20.5q-8 8.5-20.5 8.5t-21.5-8Zm494 495-56-57q-8-9-8-21.375T678.5 774q8.5-9 20.5-9t21 9l57 56q9 9 8.629 21.603-.37 12.604-8.526 21.5-8.896 8.897-21.5 8.897Q743 882 734 873Zm-56-495q-9-9-9-21t9-21l56-57q9-9 21.603-8.629 12.604.37 21.5 8.526 8.897 8.896 8.897 21.5Q786 313 777 322l-57 56q-8 8-20.364 8-12.363 0-21.636-8ZM182.897 873.103q-8.897-8.896-8.897-21.5Q174 839 183 830l57-56q8.8-9 20.9-9 12.1 0 20.709 9Q291 783 291 795t-9 21l-56 57q-9 9-21.603 8.629-12.604-.37-21.5-8.526ZM480 576Z" />
|
<path d="M479.765 716Q538 716 579 675.235q41-40.764 41-99Q620 518 579.235 477q-40.764-41-99-41Q422 436 381 476.765q-41 40.764-41 99Q340 634 380.765 675q40.764 41 99 41Zm.235 60q-83 0-141.5-58.5T280 576q0-83 58.5-141.5T480 376q83 0 141.5 58.5T680 576q0 83-58.5 141.5T480 776ZM70 606q-12.75 0-21.375-8.675Q40 588.649 40 575.825 40 563 48.625 554.5T70 546h100q12.75 0 21.375 8.675 8.625 8.676 8.625 21.5 0 12.825-8.625 21.325T170 606H70Zm720 0q-12.75 0-21.375-8.675-8.625-8.676-8.625-21.5 0-12.825 8.625-21.325T790 546h100q12.75 0 21.375 8.675 8.625 8.676 8.625 21.5 0 12.825-8.625 21.325T890 606H790ZM479.825 296Q467 296 458.5 287.375T450 266V166q0-12.75 8.675-21.375 8.676-8.625 21.5-8.625 12.825 0 21.325 8.625T510 166v100q0 12.75-8.675 21.375-8.676 8.625-21.5 8.625Zm0 720q-12.825 0-21.325-8.62-8.5-8.63-8.5-21.38V886q0-12.75 8.675-21.375 8.676-8.625 21.5-8.625 12.825 0 21.325 8.625T510 886v100q0 12.75-8.675 21.38-8.676 8.62-21.5 8.62ZM240 378l-57-56q-9-9-8.629-21.603.37-12.604 8.526-21.5 8.896-8.897 21.5-8.897Q217 270 226 279l56 57q8 9 8 21t-8 20.5q-8 8.5-20.5 8.5t-21.5-8Zm494 495-56-57q-8-9-8-21.375T678.5 774q8.5-9 20.5-9t21 9l57 56q9 9 8.629 21.603-.37 12.604-8.526 21.5-8.896 8.897-21.5 8.897Q743 882 734 873Zm-56-495q-9-9-9-21t9-21l56-57q9-9 21.603-8.629 12.604.37 21.5 8.526 8.897 8.896 8.897 21.5Q786 313 777 322l-57 56q-8 8-20.364 8-12.363 0-21.636-8ZM182.897 873.103q-8.897-8.896-8.897-21.5Q174 839 183 830l57-56q8.8-9 20.9-9 12.1 0 20.709 9Q291 783 291 795t-9 21l-56 57q-9 9-21.603 8.629-12.604-.37-21.5-8.526ZM480 576Z" />
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import { IconTypes } from "./IconTypes";
|
import { IconTypes } from "./IconTypes";
|
||||||
|
|
||||||
export const PlaylistSVG: React.FC<IconTypes> = ({
|
export const PlaylistSVG: React.FC<IconTypes> = ({
|
||||||
color,
|
color,
|
||||||
height,
|
height,
|
||||||
width,
|
width,
|
||||||
className,
|
className,
|
||||||
onClickFunc
|
onClickFunc
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<svg onClick={onClickFunc}
|
<svg onClick={onClickFunc}
|
||||||
className={className}
|
className={className}
|
||||||
fill={color}
|
fill={color}
|
||||||
height={height}
|
height={height}
|
||||||
width={width} xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" ><path d="M120-320v-80h320v80H120Zm0-160v-80h480v80H120Zm0-160v-80h480v80H120Zm520 520v-320l240 160-240 160Z"/></svg>
|
width={width} xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" ><path d="M120-320v-80h320v80H120Zm0-160v-80h480v80H120Zm0-160v-80h480v80H120Zm520 520v-320l240 160-240 160Z"/></svg>
|
||||||
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
import { IconTypes } from "./IconTypes";
|
import { IconTypes } from "./IconTypes";
|
||||||
|
|
||||||
export const TimesSVG: React.FC<IconTypes> = ({
|
export const TimesSVG: React.FC<IconTypes> = ({
|
||||||
color,
|
color,
|
||||||
height,
|
height,
|
||||||
width,
|
width,
|
||||||
className,
|
className,
|
||||||
onClickFunc
|
onClickFunc
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
onClick={onClickFunc}
|
onClick={onClickFunc}
|
||||||
className={className}
|
className={className}
|
||||||
fill={color}
|
fill={color}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
height={height}
|
height={height}
|
||||||
viewBox="0 -960 960 960"
|
viewBox="0 -960 960 960"
|
||||||
width={width}
|
width={width}
|
||||||
>
|
>
|
||||||
<path d="m249-207-42-42 231-231-231-231 42-42 231 231 231-231 42 42-231 231 231 231-42 42-231-231-231 231Z" />
|
<path d="m249-207-42-42 231-231-231-231 42-42 231 231 231-231 42 42-231 231 231 231-42 42-231-231-231 231Z" />
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -25,8 +25,8 @@ import { QSUPPORT_FILE_BASE } from "../../constants/Identifiers.ts";
|
|||||||
import { MultiplePublish } from "../common/MultiplePublish/MultiplePublishAll";
|
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 { allCategoryData } from "../../constants/Categories/1stCategories.ts";
|
import { allCategoryData } from "../../constants/Categories/Categories.ts";
|
||||||
import { titleFormatter } from "../../constants/Misc.ts";
|
import { log, titleFormatter } from "../../constants/Misc.ts";
|
||||||
import {
|
import {
|
||||||
CategoryList,
|
CategoryList,
|
||||||
CategoryListRef,
|
CategoryListRef,
|
||||||
@ -37,6 +37,13 @@ import {
|
|||||||
ImagePublisherRef,
|
ImagePublisherRef,
|
||||||
} from "../common/ImagePublisher/ImagePublisher.tsx";
|
} from "../common/ImagePublisher/ImagePublisher.tsx";
|
||||||
import { ThemeButtonBright } from "../../pages/Home/Home-styles.tsx";
|
import { ThemeButtonBright } from "../../pages/Home/Home-styles.tsx";
|
||||||
|
import {
|
||||||
|
AutocompleteQappNames,
|
||||||
|
QappNamesRef,
|
||||||
|
} from "../common/AutocompleteQappNames.tsx";
|
||||||
|
import { payPublishFeeQORT } from "../../constants/PublishFees/SendFeeFunctions.ts";
|
||||||
|
import { feeAmountBase } from "../../constants/PublishFees/FeeData.tsx";
|
||||||
|
import { verifyPayment } from "../../constants/PublishFees/VerifyPayment.ts";
|
||||||
|
|
||||||
const uid = new ShortUniqueId();
|
const uid = new ShortUniqueId();
|
||||||
const shortuid = new ShortUniqueId({ length: 5 });
|
const shortuid = new ShortUniqueId({ length: 5 });
|
||||||
@ -58,6 +65,7 @@ interface VideoFile {
|
|||||||
identifier?: string;
|
identifier?: string;
|
||||||
filename?: string;
|
filename?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditIssue = () => {
|
export const EditIssue = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -65,9 +73,13 @@ export const EditIssue = () => {
|
|||||||
const userAddress = useSelector(
|
const userAddress = useSelector(
|
||||||
(state: RootState) => state.auth?.user?.address
|
(state: RootState) => state.auth?.user?.address
|
||||||
);
|
);
|
||||||
const editFileProperties = useSelector(
|
const editIssueProperties = useSelector(
|
||||||
(state: RootState) => state.file.editFileProperties
|
(state: RootState) => state.file.editFileProperties
|
||||||
);
|
);
|
||||||
|
const QappNames = useSelector(
|
||||||
|
(state: RootState) => state.file.publishedQappNames
|
||||||
|
);
|
||||||
|
|
||||||
const [publishes, setPublishes] = useState<any>(null);
|
const [publishes, setPublishes] = useState<any>(null);
|
||||||
const [isOpenMultiplePublish, setIsOpenMultiplePublish] = useState(false);
|
const [isOpenMultiplePublish, setIsOpenMultiplePublish] = useState(false);
|
||||||
const [videoPropertiesToSetToRedux, setVideoPropertiesToSetToRedux] =
|
const [videoPropertiesToSetToRedux, setVideoPropertiesToSetToRedux] =
|
||||||
@ -78,9 +90,11 @@ export const EditIssue = () => {
|
|||||||
const [coverImage, setCoverImage] = useState<string>("");
|
const [coverImage, setCoverImage] = useState<string>("");
|
||||||
const [file, setFile] = useState(null);
|
const [file, setFile] = useState(null);
|
||||||
const [files, setFiles] = useState<VideoFile[]>([]);
|
const [files, setFiles] = useState<VideoFile[]>([]);
|
||||||
const [editCategories, setEditCategories] = useState<string[]>([]);
|
const [selectedCategories, setSelectedCategories] = useState<string[]>([]);
|
||||||
|
const [isIssuePaid, setIsIssuePaid] = useState<boolean>(true);
|
||||||
const categoryListRef = useRef<CategoryListRef>(null);
|
const categoryListRef = useRef<CategoryListRef>(null);
|
||||||
const imagePublisherRef = useRef<ImagePublisherRef>(null);
|
const imagePublisherRef = useRef<ImagePublisherRef>(null);
|
||||||
|
const autocompleteRef = useRef<QappNamesRef>(null);
|
||||||
|
|
||||||
const { getRootProps, getInputProps } = useDropzone({
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
maxFiles: 10,
|
maxFiles: 10,
|
||||||
@ -118,21 +132,25 @@ export const EditIssue = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editFileProperties) {
|
if (editIssueProperties) {
|
||||||
setTitle(editFileProperties?.title || "");
|
setTitle(editIssueProperties?.title || "");
|
||||||
setFiles(editFileProperties?.files || []);
|
setFiles(editIssueProperties?.files || []);
|
||||||
if (editFileProperties?.htmlDescription) {
|
if (editIssueProperties?.htmlDescription) {
|
||||||
setDescription(editFileProperties?.htmlDescription);
|
setDescription(editIssueProperties?.htmlDescription);
|
||||||
} else if (editFileProperties?.fullDescription) {
|
} else if (editIssueProperties?.fullDescription) {
|
||||||
const paragraph = `<p>${editFileProperties?.fullDescription}</p>`;
|
const paragraph = `<p>${editIssueProperties?.fullDescription}</p>`;
|
||||||
setDescription(paragraph);
|
setDescription(paragraph);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
verifyPayment(editIssueProperties).then(isIssuePaid => {
|
||||||
|
setIsIssuePaid(isIssuePaid);
|
||||||
|
});
|
||||||
const categoriesFromEditFile =
|
const categoriesFromEditFile =
|
||||||
getCategoriesFromObject(editFileProperties);
|
getCategoriesFromObject(editIssueProperties);
|
||||||
setEditCategories(categoriesFromEditFile);
|
setSelectedCategories(categoriesFromEditFile);
|
||||||
}
|
}
|
||||||
}, [editFileProperties]);
|
}, [editIssueProperties]);
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
dispatch(setEditFile(null));
|
dispatch(setEditFile(null));
|
||||||
setVideoPropertiesToSetToRedux(null);
|
setVideoPropertiesToSetToRedux(null);
|
||||||
@ -142,14 +160,22 @@ export const EditIssue = () => {
|
|||||||
setCoverImage("");
|
setCoverImage("");
|
||||||
};
|
};
|
||||||
|
|
||||||
async function publishQDNResource() {
|
async function publishQDNResource(payFee: boolean) {
|
||||||
try {
|
try {
|
||||||
const categoryList = categoryListRef.current?.getSelectedCategories();
|
if (!categoryListRef.current) throw new Error("No CategoryListRef found");
|
||||||
if (!description) throw new Error("Please enter a description");
|
|
||||||
if (!categoryList[0]) throw new Error("Please select a category");
|
|
||||||
if (!editFileProperties) return;
|
|
||||||
if (!userAddress) throw new Error("Unable to locate user address");
|
if (!userAddress) throw new Error("Unable to locate user address");
|
||||||
|
if (!description) throw new Error("Please enter a description");
|
||||||
|
const allCategoriesSelected = !selectedCategories.includes("");
|
||||||
|
if (!allCategoriesSelected)
|
||||||
|
throw new Error("All Categories must be selected");
|
||||||
|
|
||||||
|
console.log("categories", selectedCategories);
|
||||||
|
const QappsCategoryID = "3";
|
||||||
|
if (
|
||||||
|
selectedCategories[0] === QappsCategoryID &&
|
||||||
|
!autocompleteRef?.current?.getSelectedValue()
|
||||||
|
)
|
||||||
|
throw new Error("Select a published Q-App");
|
||||||
let errorMsg = "";
|
let errorMsg = "";
|
||||||
let name = "";
|
let name = "";
|
||||||
if (username) {
|
if (username) {
|
||||||
@ -160,7 +186,7 @@ export const EditIssue = () => {
|
|||||||
"Cannot publish without access to your name. Please authenticate.";
|
"Cannot publish without access to your name. Please authenticate.";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (editFileProperties?.user !== username) {
|
if (editIssueProperties?.user !== username) {
|
||||||
errorMsg = "Cannot publish another user's resource";
|
errorMsg = "Cannot publish another user's resource";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,9 +249,8 @@ export const EditIssue = () => {
|
|||||||
filename = alphanumericString;
|
filename = alphanumericString;
|
||||||
}
|
}
|
||||||
|
|
||||||
let metadescription =
|
const categoryString = `**${categoryListRef.current?.getSelectedCategories()}**`;
|
||||||
`**${categoryListRef.current?.getCategoriesFetchString()}**` +
|
let metadescription = categoryString + fullDescription.slice(0, 150);
|
||||||
fullDescription.slice(0, 150);
|
|
||||||
|
|
||||||
const requestBodyVideo: any = {
|
const requestBodyVideo: any = {
|
||||||
action: "PUBLISH_QDN_RESOURCE",
|
action: "PUBLISH_QDN_RESOURCE",
|
||||||
@ -248,23 +273,50 @@ export const EditIssue = () => {
|
|||||||
size: file.size,
|
size: file.size,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const selectedQappName = autocompleteRef?.current?.getSelectedValue();
|
||||||
|
|
||||||
const fileObject: any = {
|
const issueObject: any = {
|
||||||
title,
|
title,
|
||||||
version: editFileProperties.version,
|
version: editIssueProperties.version,
|
||||||
fullDescription,
|
fullDescription,
|
||||||
htmlDescription: description,
|
htmlDescription: description,
|
||||||
commentsId: editFileProperties.commentsId,
|
commentsId: editIssueProperties.commentsId,
|
||||||
...categoryListRef.current?.categoriesToObject(),
|
...categoryListRef.current?.categoriesToObject(),
|
||||||
files: fileReferences,
|
files: fileReferences,
|
||||||
images: imagePublisherRef?.current?.getImageArray(),
|
images: imagePublisherRef?.current?.getImageArray(),
|
||||||
|
QappName: selectedQappName,
|
||||||
|
feeData: editIssueProperties?.feeData,
|
||||||
};
|
};
|
||||||
|
if (payFee) {
|
||||||
|
const publishFeeResponse = await payPublishFeeQORT(feeAmountBase);
|
||||||
|
if (!publishFeeResponse) {
|
||||||
|
dispatch(
|
||||||
|
setNotification({
|
||||||
|
msg: "Fee publish rejected by user.",
|
||||||
|
alertType: "error",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (log) console.log("feeResponse: ", publishFeeResponse);
|
||||||
|
|
||||||
let metadescription =
|
issueObject.feeData = { signature: publishFeeResponse };
|
||||||
`**${categoryListRef.current?.getCategoriesFetchString()}**` +
|
dispatch(updateInHashMap(issueObject)); // shows issue as paid right away?
|
||||||
fullDescription.slice(0, 150);
|
}
|
||||||
|
|
||||||
const fileObjectToBase64 = await objectToBase64(fileObject);
|
const QappNameString = autocompleteRef?.current?.getQappNameFetchString();
|
||||||
|
const categoryString =
|
||||||
|
categoryListRef.current?.getCategoriesFetchString(selectedCategories);
|
||||||
|
const metaDataString = `**${categoryString + QappNameString}**`;
|
||||||
|
|
||||||
|
let metadescription = metaDataString + fullDescription.slice(0, 150);
|
||||||
|
if (log) console.log("description is: ", metadescription);
|
||||||
|
if (log) console.log("description length is: ", metadescription.length);
|
||||||
|
if (log) console.log("characters left:", 240 - metadescription.length);
|
||||||
|
if (log)
|
||||||
|
console.log("% of characters used:", metadescription.length / 240);
|
||||||
|
|
||||||
|
const fileObjectToBase64 = await objectToBase64(issueObject);
|
||||||
// Description is obtained from raw data
|
// Description is obtained from raw data
|
||||||
|
|
||||||
const requestBodyJson: any = {
|
const requestBodyJson: any = {
|
||||||
@ -274,7 +326,7 @@ export const EditIssue = () => {
|
|||||||
data64: fileObjectToBase64,
|
data64: fileObjectToBase64,
|
||||||
title: title.slice(0, 50),
|
title: title.slice(0, 50),
|
||||||
description: metadescription,
|
description: metadescription,
|
||||||
identifier: editFileProperties.id,
|
identifier: editIssueProperties.id,
|
||||||
tag1: QSUPPORT_FILE_BASE,
|
tag1: QSUPPORT_FILE_BASE,
|
||||||
filename: `video_metadata.json`,
|
filename: `video_metadata.json`,
|
||||||
};
|
};
|
||||||
@ -287,10 +339,13 @@ export const EditIssue = () => {
|
|||||||
setPublishes(multiplePublish);
|
setPublishes(multiplePublish);
|
||||||
setIsOpenMultiplePublish(true);
|
setIsOpenMultiplePublish(true);
|
||||||
setVideoPropertiesToSetToRedux({
|
setVideoPropertiesToSetToRedux({
|
||||||
...editFileProperties,
|
...editIssueProperties,
|
||||||
...fileObject,
|
...issueObject,
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
console.log("error is: ", error);
|
||||||
|
if (error === "User declined request") return;
|
||||||
|
|
||||||
let notificationObj: any = null;
|
let notificationObj: any = null;
|
||||||
if (typeof error === "string") {
|
if (typeof error === "string") {
|
||||||
notificationObj = {
|
notificationObj = {
|
||||||
@ -315,26 +370,15 @@ export const EditIssue = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleOnchange = (index: number, type: string, value: string) => {
|
const isShowQappNameTextField = () => {
|
||||||
// setFiles((prev) => {
|
const QappID = "3";
|
||||||
// let formattedValue = value
|
return selectedCategories[0] === QappID;
|
||||||
// console.log({type})
|
|
||||||
// if(type === 'title'){
|
|
||||||
// formattedValue = value.replace(/[^a-zA-Z0-9\s]/g, "")
|
|
||||||
// }
|
|
||||||
// const copyFiles = [...prev];
|
|
||||||
// copyFiles[index] = {
|
|
||||||
// ...copyFiles[index],
|
|
||||||
// [type]: formattedValue,
|
|
||||||
// };
|
|
||||||
// return copyFiles;
|
|
||||||
// });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Modal
|
||||||
open={!!editFileProperties}
|
open={!!editIssueProperties}
|
||||||
aria-labelledby="modal-title"
|
aria-labelledby="modal-title"
|
||||||
aria-describedby="modal-description"
|
aria-describedby="modal-description"
|
||||||
>
|
>
|
||||||
@ -410,15 +454,26 @@ export const EditIssue = () => {
|
|||||||
>
|
>
|
||||||
<CategoryList
|
<CategoryList
|
||||||
categoryData={allCategoryData}
|
categoryData={allCategoryData}
|
||||||
initialCategories={editCategories}
|
initialCategories={selectedCategories}
|
||||||
columns={3}
|
columns={3}
|
||||||
ref={categoryListRef}
|
ref={categoryListRef}
|
||||||
|
showEmptyItem={false}
|
||||||
|
afterChange={newSelectedCategories => {
|
||||||
|
setSelectedCategories(newSelectedCategories);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
{isShowQappNameTextField() && (
|
||||||
|
<AutocompleteQappNames
|
||||||
|
ref={autocompleteRef}
|
||||||
|
namesList={QappNames}
|
||||||
|
initialSelection={editIssueProperties?.QappName}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<ImagePublisher
|
<ImagePublisher
|
||||||
ref={imagePublisherRef}
|
ref={imagePublisherRef}
|
||||||
initialImages={editFileProperties?.images}
|
initialImages={editIssueProperties?.images}
|
||||||
/>
|
/>
|
||||||
<CustomInputField
|
<CustomInputField
|
||||||
name="title"
|
name="title"
|
||||||
@ -466,10 +521,27 @@ export const EditIssue = () => {
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{isIssuePaid === false && (
|
||||||
|
<ThemeButtonBright
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => {
|
||||||
|
publishQDNResource(true);
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Montserrat",
|
||||||
|
fontSize: "16px",
|
||||||
|
fontWeight: 400,
|
||||||
|
letterSpacing: "0.2px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Publish Edit with Fee
|
||||||
|
</ThemeButtonBright>
|
||||||
|
)}
|
||||||
|
|
||||||
<ThemeButtonBright
|
<ThemeButtonBright
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
publishQDNResource();
|
publishQDNResource(false);
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
fontFamily: "Montserrat",
|
fontFamily: "Montserrat",
|
||||||
@ -478,7 +550,7 @@ export const EditIssue = () => {
|
|||||||
letterSpacing: "0.2px",
|
letterSpacing: "0.2px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Publish
|
Publish Edit
|
||||||
</ThemeButtonBright>
|
</ThemeButtonBright>
|
||||||
</Box>
|
</Box>
|
||||||
</CrowdfundActionButtonRow>
|
</CrowdfundActionButtonRow>
|
||||||
@ -506,7 +578,7 @@ export const EditIssue = () => {
|
|||||||
dispatch(updateInHashMap(clonedCopy));
|
dispatch(updateInHashMap(clonedCopy));
|
||||||
dispatch(
|
dispatch(
|
||||||
setNotification({
|
setNotification({
|
||||||
msg: "File updated",
|
msg: "Issue updated",
|
||||||
alertType: "success",
|
alertType: "success",
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -43,9 +43,9 @@ import { PlaylistListEdit } from "../PlaylistListEdit/PlaylistListEdit";
|
|||||||
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 {
|
import {
|
||||||
firstCategories,
|
issueLocation,
|
||||||
secondCategories,
|
thirdCategories,
|
||||||
} from "../../constants/Categories/1stCategories.ts";
|
} from "../../constants/Categories/Categories.ts";
|
||||||
|
|
||||||
const uid = new ShortUniqueId();
|
const uid = new ShortUniqueId();
|
||||||
const shortuid = new ShortUniqueId({ length: 5 });
|
const shortuid = new ShortUniqueId({ length: 5 });
|
||||||
@ -183,7 +183,7 @@ export const EditPlaylist = () => {
|
|||||||
setVideos(editVideoProperties?.videos || []);
|
setVideos(editVideoProperties?.videos || []);
|
||||||
|
|
||||||
if (editVideoProperties?.category) {
|
if (editVideoProperties?.category) {
|
||||||
const selectedOption = firstCategories.find(
|
const selectedOption = issueLocation.find(
|
||||||
option => option.id === +editVideoProperties.category
|
option => option.id === +editVideoProperties.category
|
||||||
);
|
);
|
||||||
setSelectedCategoryVideos(selectedOption || null);
|
setSelectedCategoryVideos(selectedOption || null);
|
||||||
@ -192,9 +192,9 @@ export const EditPlaylist = () => {
|
|||||||
if (
|
if (
|
||||||
editVideoProperties?.category &&
|
editVideoProperties?.category &&
|
||||||
editVideoProperties?.subcategory &&
|
editVideoProperties?.subcategory &&
|
||||||
secondCategories[+editVideoProperties?.category]
|
thirdCategories[+editVideoProperties?.category]
|
||||||
) {
|
) {
|
||||||
const selectedOption = secondCategories[
|
const selectedOption = thirdCategories[
|
||||||
+editVideoProperties?.category
|
+editVideoProperties?.category
|
||||||
]?.find(option => option.id === +editVideoProperties.subcategory);
|
]?.find(option => option.id === +editVideoProperties.subcategory);
|
||||||
setSelectedSubCategoryVideos(selectedOption || null);
|
setSelectedSubCategoryVideos(selectedOption || null);
|
||||||
@ -405,7 +405,7 @@ export const EditPlaylist = () => {
|
|||||||
event: SelectChangeEvent<string>
|
event: SelectChangeEvent<string>
|
||||||
) => {
|
) => {
|
||||||
const optionId = event.target.value;
|
const optionId = event.target.value;
|
||||||
const selectedOption = firstCategories.find(
|
const selectedOption = issueLocation.find(
|
||||||
option => option.id === +optionId
|
option => option.id === +optionId
|
||||||
);
|
);
|
||||||
setSelectedCategoryVideos(selectedOption || null);
|
setSelectedCategoryVideos(selectedOption || null);
|
||||||
@ -479,7 +479,7 @@ export const EditPlaylist = () => {
|
|||||||
value={selectedCategoryVideos?.id || ""}
|
value={selectedCategoryVideos?.id || ""}
|
||||||
onChange={handleOptionCategoryChangeVideos}
|
onChange={handleOptionCategoryChangeVideos}
|
||||||
>
|
>
|
||||||
{firstCategories.map(option => (
|
{issueLocation.map(option => (
|
||||||
<MenuItem key={option.id} value={option.id}>
|
<MenuItem key={option.id} value={option.id}>
|
||||||
{option.name}
|
{option.name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@ -487,7 +487,7 @@ export const EditPlaylist = () => {
|
|||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
{selectedCategoryVideos &&
|
{selectedCategoryVideos &&
|
||||||
secondCategories[selectedCategoryVideos?.id] && (
|
thirdCategories[selectedCategoryVideos?.id] && (
|
||||||
<FormControl fullWidth sx={{ marginBottom: 2 }}>
|
<FormControl fullWidth sx={{ marginBottom: 2 }}>
|
||||||
<InputLabel id="Category">Select a Sub-Category</InputLabel>
|
<InputLabel id="Category">Select a Sub-Category</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
@ -497,11 +497,11 @@ export const EditPlaylist = () => {
|
|||||||
onChange={e =>
|
onChange={e =>
|
||||||
handleOptionSubCategoryChangeVideos(
|
handleOptionSubCategoryChangeVideos(
|
||||||
e,
|
e,
|
||||||
secondCategories[selectedCategoryVideos?.id]
|
thirdCategories[selectedCategoryVideos?.id]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{secondCategories[selectedCategoryVideos.id].map(
|
{thirdCategories[selectedCategoryVideos.id].map(
|
||||||
option => (
|
option => (
|
||||||
<MenuItem key={option.id} value={option.id}>
|
<MenuItem key={option.id} value={option.id}>
|
||||||
{option.name}
|
{option.name}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -3,6 +3,7 @@ import {
|
|||||||
Accordion,
|
Accordion,
|
||||||
AccordionDetails,
|
AccordionDetails,
|
||||||
AccordionSummary,
|
AccordionSummary,
|
||||||
|
Autocomplete,
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Grid,
|
Grid,
|
||||||
@ -11,8 +12,10 @@ import {
|
|||||||
TextField,
|
TextField,
|
||||||
Typography,
|
Typography,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
||||||
import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternate";
|
import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternate";
|
||||||
import { TimesSVG } from "../../assets/svgs/TimesSVG";
|
import { TimesSVG } from "../../assets/svgs/TimesSVG";
|
||||||
|
import { fontSizeMedium } from "../../constants/Misc.ts";
|
||||||
|
|
||||||
export const DoubleLine = styled(Typography)`
|
export const DoubleLine = styled(Typography)`
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
@ -59,7 +62,7 @@ export const ModalBody = styled(Box)(({ theme }) => ({
|
|||||||
left: "50%",
|
left: "50%",
|
||||||
transform: "translate(-50%, -50%)",
|
transform: "translate(-50%, -50%)",
|
||||||
width: "75%",
|
width: "75%",
|
||||||
maxWidth: "900px",
|
maxWidth: "1000px",
|
||||||
padding: "15px 35px",
|
padding: "15px 35px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
@ -113,50 +116,59 @@ export const NewCrowdfundTimeDescription = styled(Typography)(({ theme }) => ({
|
|||||||
textDecoration: "underline",
|
textDecoration: "underline",
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const getInputFieldStyles = (theme: any) => {
|
||||||
|
return {
|
||||||
|
fontFamily: "Mulish",
|
||||||
|
letterSpacing: "0px",
|
||||||
|
fontWeight: 400,
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
backgroundColor: theme.palette.background.default,
|
||||||
|
borderColor: theme.palette.background.paper,
|
||||||
|
"& label": {
|
||||||
|
color: theme.palette.mode === "light" ? "#808183" : "#edeef0",
|
||||||
|
fontFamily: "Mulish",
|
||||||
|
fontSize: fontSizeMedium,
|
||||||
|
letterSpacing: "0px",
|
||||||
|
fontWeight: 400,
|
||||||
|
},
|
||||||
|
"& label.Mui-focused": {
|
||||||
|
color: theme.palette.mode === "light" ? "#A0AAB4" : "#d7d8da",
|
||||||
|
},
|
||||||
|
"& .MuiInput-underline:after": {
|
||||||
|
borderBottomColor: theme.palette.mode === "light" ? "#B2BAC2" : "#c9cccf",
|
||||||
|
},
|
||||||
|
"& .MuiOutlinedInput-root": {
|
||||||
|
"& fieldset": {
|
||||||
|
borderColor: "#E0E3E7",
|
||||||
|
},
|
||||||
|
"&:hover fieldset": {
|
||||||
|
borderColor: "#B2BAC2",
|
||||||
|
},
|
||||||
|
"&.Mui-focused fieldset": {
|
||||||
|
borderColor: "#6F7E8C",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"& .MuiInputBase-root": {
|
||||||
|
fontFamily: "Mulish",
|
||||||
|
fontSize: "25px",
|
||||||
|
letterSpacing: "0px",
|
||||||
|
fontWeight: 400,
|
||||||
|
},
|
||||||
|
"& [class$='-MuiFilledInput-root']": {
|
||||||
|
padding: "30px 12px 8px",
|
||||||
|
},
|
||||||
|
"& .MuiFilledInput-root:after": {
|
||||||
|
borderBottomColor: theme.palette.secondary.main,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const CustomInputField = styled(TextField)(({ theme }) => ({
|
export const CustomInputField = styled(TextField)(({ theme }) => ({
|
||||||
fontFamily: "Mulish",
|
...getInputFieldStyles(theme),
|
||||||
fontSize: "19px",
|
}));
|
||||||
letterSpacing: "0px",
|
|
||||||
fontWeight: 400,
|
export const CustomAutoCompleteField = styled(Autocomplete)(({ theme }) => ({
|
||||||
color: theme.palette.text.primary,
|
...getInputFieldStyles(theme),
|
||||||
backgroundColor: theme.palette.background.default,
|
|
||||||
borderColor: theme.palette.background.paper,
|
|
||||||
"& label": {
|
|
||||||
color: theme.palette.mode === "light" ? "#808183" : "#edeef0",
|
|
||||||
fontFamily: "Mulish",
|
|
||||||
fontSize: "19px",
|
|
||||||
letterSpacing: "0px",
|
|
||||||
fontWeight: 400,
|
|
||||||
},
|
|
||||||
"& label.Mui-focused": {
|
|
||||||
color: theme.palette.mode === "light" ? "#A0AAB4" : "#d7d8da",
|
|
||||||
},
|
|
||||||
"& .MuiInput-underline:after": {
|
|
||||||
borderBottomColor: theme.palette.mode === "light" ? "#B2BAC2" : "#c9cccf",
|
|
||||||
},
|
|
||||||
"& .MuiOutlinedInput-root": {
|
|
||||||
"& fieldset": {
|
|
||||||
borderColor: "#E0E3E7",
|
|
||||||
},
|
|
||||||
"&:hover fieldset": {
|
|
||||||
borderColor: "#B2BAC2",
|
|
||||||
},
|
|
||||||
"&.Mui-focused fieldset": {
|
|
||||||
borderColor: "#6F7E8C",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"& .MuiInputBase-root": {
|
|
||||||
fontFamily: "Mulish",
|
|
||||||
fontSize: "19px",
|
|
||||||
letterSpacing: "0px",
|
|
||||||
fontWeight: 400,
|
|
||||||
},
|
|
||||||
"& [class$='-MuiFilledInput-root']": {
|
|
||||||
padding: "30px 12px 8px",
|
|
||||||
},
|
|
||||||
"& .MuiFilledInput-root:after": {
|
|
||||||
borderBottomColor: theme.palette.secondary.main,
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const CrowdfundTitle = styled(Typography)(({ theme }) => ({
|
export const CrowdfundTitle = styled(Typography)(({ theme }) => ({
|
||||||
|
@ -21,19 +21,34 @@ import { QSUPPORT_FILE_BASE } from "../../constants/Identifiers.ts";
|
|||||||
import { MultiplePublish } from "../common/MultiplePublish/MultiplePublishAll";
|
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 { allCategoryData } from "../../constants/Categories/1stCategories.ts";
|
import { allCategoryData } from "../../constants/Categories/Categories.ts";
|
||||||
import { titleFormatter } from "../../constants/Misc.ts";
|
import {
|
||||||
|
fontSizeLarge,
|
||||||
|
fontSizeSmall,
|
||||||
|
log,
|
||||||
|
titleFormatter,
|
||||||
|
} from "../../constants/Misc.ts";
|
||||||
import {
|
import {
|
||||||
appendCategoryToList,
|
|
||||||
CategoryList,
|
CategoryList,
|
||||||
CategoryListRef,
|
CategoryListRef,
|
||||||
} from "../common/CategoryList/CategoryList.tsx";
|
} from "../common/CategoryList/CategoryList.tsx";
|
||||||
import { SupportState } from "../../constants/Categories/2ndCategories.ts";
|
|
||||||
import {
|
import {
|
||||||
ImagePublisher,
|
ImagePublisher,
|
||||||
ImagePublisherRef,
|
ImagePublisherRef,
|
||||||
} from "../common/ImagePublisher/ImagePublisher.tsx";
|
} from "../common/ImagePublisher/ImagePublisher.tsx";
|
||||||
import { ThemeButtonBright } from "../../pages/Home/Home-styles.tsx";
|
import { ThemeButtonBright } from "../../pages/Home/Home-styles.tsx";
|
||||||
|
import {
|
||||||
|
AutocompleteQappNames,
|
||||||
|
QappNamesRef,
|
||||||
|
} from "../common/AutocompleteQappNames.tsx";
|
||||||
|
import {
|
||||||
|
feeAmountBase,
|
||||||
|
feeDisclaimer,
|
||||||
|
} from "../../constants/PublishFees/FeeData.tsx";
|
||||||
|
import {
|
||||||
|
payPublishFeeQORT,
|
||||||
|
PublishFeeData,
|
||||||
|
} from "../../constants/PublishFees/SendFeeFunctions.ts";
|
||||||
|
|
||||||
const uid = new ShortUniqueId();
|
const uid = new ShortUniqueId();
|
||||||
const shortuid = new ShortUniqueId({ length: 5 });
|
const shortuid = new ShortUniqueId({ length: 5 });
|
||||||
@ -53,14 +68,21 @@ interface VideoFile {
|
|||||||
description: string;
|
description: string;
|
||||||
coverImage?: string;
|
coverImage?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [isOpenMultiplePublish, setIsOpenMultiplePublish] = useState(false);
|
const [isOpenMultiplePublish, setIsOpenMultiplePublish] = useState(false);
|
||||||
|
const [QappName, setQappName] = useState<string>("");
|
||||||
|
|
||||||
|
const [selectedCategories, setSelectedCategories] = useState<string[]>([]);
|
||||||
const username = useSelector((state: RootState) => state.auth?.user?.name);
|
const username = useSelector((state: RootState) => state.auth?.user?.name);
|
||||||
const userAddress = useSelector(
|
const userAddress = useSelector(
|
||||||
(state: RootState) => state.auth?.user?.address
|
(state: RootState) => state.auth?.user?.address
|
||||||
);
|
);
|
||||||
|
const QappNames = useSelector(
|
||||||
|
(state: RootState) => state.file.publishedQappNames
|
||||||
|
);
|
||||||
const [files, setFiles] = useState<VideoFile[]>([]);
|
const [files, setFiles] = useState<VideoFile[]>([]);
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||||
@ -77,8 +99,11 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
|
|
||||||
const [playlistSetting, setPlaylistSetting] = useState<null | string>(null);
|
const [playlistSetting, setPlaylistSetting] = useState<null | string>(null);
|
||||||
const [publishes, setPublishes] = useState<any>(null);
|
const [publishes, setPublishes] = useState<any>(null);
|
||||||
|
|
||||||
const categoryListRef = useRef<CategoryListRef>(null);
|
const categoryListRef = useRef<CategoryListRef>(null);
|
||||||
const imagePublisherRef = useRef<ImagePublisherRef>(null);
|
const imagePublisherRef = useRef<ImagePublisherRef>(null);
|
||||||
|
const autocompleteRef = useRef<QappNamesRef>(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
|
||||||
@ -128,8 +153,18 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
if (!categoryListRef.current) throw new Error("No CategoryListRef found");
|
if (!categoryListRef.current) throw new Error("No CategoryListRef found");
|
||||||
if (!userAddress) throw new Error("Unable to locate user address");
|
if (!userAddress) throw new Error("Unable to locate user address");
|
||||||
if (!description) throw new Error("Please enter a description");
|
if (!description) throw new Error("Please enter a description");
|
||||||
if (!categoryListRef.current?.getSelectedCategories()[0])
|
|
||||||
throw new Error("Please select a category");
|
const allCategoriesSelected =
|
||||||
|
selectedCategories && selectedCategories[0] && selectedCategories[1];
|
||||||
|
if (!allCategoriesSelected)
|
||||||
|
throw new Error("All Categories must be selected");
|
||||||
|
|
||||||
|
const QappsCategoryID = "3";
|
||||||
|
if (
|
||||||
|
selectedCategories[0] === QappsCategoryID &&
|
||||||
|
!autocompleteRef?.current?.getSelectedValue()
|
||||||
|
)
|
||||||
|
throw new Error("Select a published Q-App");
|
||||||
let errorMsg = "";
|
let errorMsg = "";
|
||||||
let name = "";
|
let name = "";
|
||||||
if (username) {
|
if (username) {
|
||||||
@ -200,14 +235,10 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
filename = alphanumericString;
|
filename = alphanumericString;
|
||||||
}
|
}
|
||||||
|
|
||||||
const categoryList = appendCategoryToList(
|
const categoryString = `**${categoryListRef.current?.getSelectedCategories()}**`;
|
||||||
categoryListRef.current?.getSelectedCategories(),
|
|
||||||
"101"
|
|
||||||
);
|
|
||||||
const categoryString = `**${categoryListRef.current?.getCategoriesFetchString(categoryList)}**`;
|
|
||||||
let metadescription = categoryString + fullDescription.slice(0, 150);
|
let metadescription = categoryString + fullDescription.slice(0, 150);
|
||||||
|
|
||||||
const requestBodyVideo: any = {
|
const requestBodyFile: any = {
|
||||||
action: "PUBLISH_QDN_RESOURCE",
|
action: "PUBLISH_QDN_RESOURCE",
|
||||||
name: name,
|
name: name,
|
||||||
service: "FILE",
|
service: "FILE",
|
||||||
@ -218,7 +249,7 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
filename,
|
filename,
|
||||||
tag1: QSUPPORT_FILE_BASE,
|
tag1: QSUPPORT_FILE_BASE,
|
||||||
};
|
};
|
||||||
listOfPublishes.push(requestBodyVideo);
|
listOfPublishes.push(requestBodyFile);
|
||||||
fileReferences.push({
|
fileReferences.push({
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
identifier,
|
identifier,
|
||||||
@ -232,12 +263,19 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
const idMeta = uid();
|
const idMeta = uid();
|
||||||
const identifier = `${QSUPPORT_FILE_BASE}${sanitizeTitle.slice(0, 30)}_${idMeta}`;
|
const identifier = `${QSUPPORT_FILE_BASE}${sanitizeTitle.slice(0, 30)}_${idMeta}`;
|
||||||
|
|
||||||
const categoryList = appendCategoryToList(
|
const categoryList = categoryListRef.current?.getSelectedCategories();
|
||||||
categoryListRef.current?.getSelectedCategories(),
|
|
||||||
"101"
|
|
||||||
);
|
|
||||||
|
|
||||||
const fileObject: any = {
|
const selectedQappName = autocompleteRef?.current?.getSelectedValue();
|
||||||
|
|
||||||
|
const publishFeeResponse = await payPublishFeeQORT(feeAmountBase);
|
||||||
|
if (log) console.log("feeResponse: ", publishFeeResponse);
|
||||||
|
|
||||||
|
const feeData: PublishFeeData = {
|
||||||
|
signature: publishFeeResponse,
|
||||||
|
senderName: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const issueObject: any = {
|
||||||
title,
|
title,
|
||||||
version: 1,
|
version: 1,
|
||||||
fullDescription,
|
fullDescription,
|
||||||
@ -246,12 +284,24 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
...categoryListRef.current?.categoriesToObject(categoryList),
|
...categoryListRef.current?.categoriesToObject(categoryList),
|
||||||
files: fileReferences,
|
files: fileReferences,
|
||||||
images: imagePublisherRef?.current?.getImageArray(),
|
images: imagePublisherRef?.current?.getImageArray(),
|
||||||
|
QappName: selectedQappName,
|
||||||
|
feeData,
|
||||||
};
|
};
|
||||||
|
|
||||||
const categoryString = `**${categoryListRef.current?.getCategoriesFetchString(categoryList)}**`;
|
const QappNameString = autocompleteRef?.current?.getQappNameFetchString();
|
||||||
let metadescription = categoryString + fullDescription.slice(0, 150);
|
const categoryString =
|
||||||
|
categoryListRef.current?.getCategoriesFetchString(categoryList);
|
||||||
|
const metaDataString = `**${categoryString + QappNameString}**`;
|
||||||
|
|
||||||
const fileObjectToBase64 = await objectToBase64(fileObject);
|
let metadescription = metaDataString + fullDescription.slice(0, 150);
|
||||||
|
|
||||||
|
if (log) console.log("description is: ", metadescription);
|
||||||
|
if (log) console.log("description length is: ", metadescription.length);
|
||||||
|
if (log) console.log("characters left:", 240 - metadescription.length);
|
||||||
|
if (log)
|
||||||
|
console.log("% of characters used:", metadescription.length / 240);
|
||||||
|
|
||||||
|
const fileObjectToBase64 = await objectToBase64(issueObject);
|
||||||
// Description is obtained from raw data
|
// Description is obtained from raw data
|
||||||
const requestBodyJson: any = {
|
const requestBodyJson: any = {
|
||||||
action: "PUBLISH_QDN_RESOURCE",
|
action: "PUBLISH_QDN_RESOURCE",
|
||||||
@ -295,6 +345,11 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isShowQappNameTextField = () => {
|
||||||
|
const QappID = "3";
|
||||||
|
return selectedCategories[0] === QappID;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{username && (
|
{username && (
|
||||||
@ -386,13 +441,29 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
categoryData={allCategoryData}
|
categoryData={allCategoryData}
|
||||||
ref={categoryListRef}
|
ref={categoryListRef}
|
||||||
columns={3}
|
columns={3}
|
||||||
excludeCategories={SupportState}
|
afterChange={newSelectedCategories => {
|
||||||
|
if (
|
||||||
|
newSelectedCategories[0] &&
|
||||||
|
newSelectedCategories[1] &&
|
||||||
|
!newSelectedCategories[2]
|
||||||
|
) {
|
||||||
|
newSelectedCategories[2] = "101";
|
||||||
|
}
|
||||||
|
setSelectedCategories(newSelectedCategories);
|
||||||
|
}}
|
||||||
|
showEmptyItem={false}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
{isShowQappNameTextField() && (
|
||||||
|
<AutocompleteQappNames
|
||||||
|
ref={autocompleteRef}
|
||||||
|
namesList={QappNames}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<ImagePublisher ref={imagePublisherRef} />
|
<ImagePublisher ref={imagePublisherRef} />
|
||||||
<CustomInputField
|
<CustomInputField
|
||||||
name="title"
|
name="title"
|
||||||
label="Title of Issue"
|
label="Title"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
value={title}
|
value={title}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
@ -400,15 +471,15 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
const formattedValue = value.replace(titleFormatter, "");
|
const formattedValue = value.replace(titleFormatter, "");
|
||||||
setTitle(formattedValue);
|
setTitle(formattedValue);
|
||||||
}}
|
}}
|
||||||
inputProps={{ maxLength: 180 }}
|
inputProps={{ maxLength: 60 }}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: "18px",
|
fontSize: fontSizeLarge,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Description of Issue
|
Description
|
||||||
</Typography>
|
</Typography>
|
||||||
<TextEditor
|
<TextEditor
|
||||||
inlineContent={description}
|
inlineContent={description}
|
||||||
@ -426,7 +497,10 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
}}
|
}}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="error"
|
color="error"
|
||||||
sx={{ color: theme.palette.text.primary }}
|
sx={{
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
fontSize: fontSizeSmall,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
@ -439,13 +513,10 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
>
|
>
|
||||||
<ThemeButtonBright
|
<ThemeButtonBright
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={() => {
|
onClick={publishQDNResource}
|
||||||
publishQDNResource();
|
|
||||||
}}
|
|
||||||
sx={{
|
sx={{
|
||||||
fontFamily: "Montserrat",
|
fontFamily: "Montserrat",
|
||||||
fontSize: "16px",
|
fontWeight: "400",
|
||||||
fontWeight: 400,
|
|
||||||
letterSpacing: "0.2px",
|
letterSpacing: "0.2px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -453,6 +524,7 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
</ThemeButtonBright>
|
</ThemeButtonBright>
|
||||||
</Box>
|
</Box>
|
||||||
</ActionButtonRow>
|
</ActionButtonRow>
|
||||||
|
{feeDisclaimer}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
@ -1,109 +1,109 @@
|
|||||||
import React, { useState, useEffect, CSSProperties } from 'react'
|
import React, { useState, useEffect, CSSProperties } from 'react'
|
||||||
import Skeleton from '@mui/material/Skeleton'
|
import Skeleton from '@mui/material/Skeleton'
|
||||||
import { Box } from '@mui/material'
|
import { Box } from '@mui/material'
|
||||||
|
|
||||||
interface ResponsiveImageProps {
|
interface ResponsiveImageProps {
|
||||||
src: string
|
src: string
|
||||||
width: number
|
width: number
|
||||||
height: number
|
height: number
|
||||||
alt?: string
|
alt?: string
|
||||||
className?: string
|
className?: string
|
||||||
style?: CSSProperties
|
style?: CSSProperties
|
||||||
}
|
}
|
||||||
|
|
||||||
const ResponsiveImage: React.FC<ResponsiveImageProps> = ({
|
const ResponsiveImage: React.FC<ResponsiveImageProps> = ({
|
||||||
src,
|
src,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
alt,
|
alt,
|
||||||
className,
|
className,
|
||||||
style
|
style
|
||||||
}) => {
|
}) => {
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const aspectRatio = (height / width) * 100
|
const aspectRatio = (height / width) * 100
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const imageStyle: CSSProperties = {
|
const imageStyle: CSSProperties = {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
objectFit: 'cover'
|
objectFit: 'cover'
|
||||||
}
|
}
|
||||||
|
|
||||||
const wrapperStyle: CSSProperties = {
|
const wrapperStyle: CSSProperties = {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
paddingBottom: `${aspectRatio}%`,
|
paddingBottom: `${aspectRatio}%`,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
...style
|
...style
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
padding: '2px',
|
padding: '2px',
|
||||||
maxHeight: '50%'
|
maxHeight: '50%'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
||||||
{loading && (
|
{loading && (
|
||||||
<Skeleton
|
<Skeleton
|
||||||
variant="rectangular"
|
variant="rectangular"
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: 0,
|
height: 0,
|
||||||
paddingBottom: `${(height / width) * 100}%`,
|
paddingBottom: `${(height / width) * 100}%`,
|
||||||
objectFit: 'contain',
|
objectFit: 'contain',
|
||||||
visibility: loading ? 'visible' : 'hidden',
|
visibility: loading ? 'visible' : 'hidden',
|
||||||
borderRadius: '8px'
|
borderRadius: '8px'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<img
|
<img
|
||||||
onLoad={() => setLoading(false)}
|
onLoad={() => setLoading(false)}
|
||||||
src={src}
|
src={src}
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
visibility: loading ? 'hidden' : 'visible',
|
visibility: loading ? 'hidden' : 'visible',
|
||||||
position: loading ? 'absolute' : 'unset',
|
position: loading ? 'absolute' : 'unset',
|
||||||
objectFit: 'contain'
|
objectFit: 'contain'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={wrapperStyle} className={className}>
|
<div style={wrapperStyle} className={className}>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Skeleton
|
<Skeleton
|
||||||
variant="rectangular"
|
variant="rectangular"
|
||||||
sx={{
|
sx={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: 0
|
bottom: 0
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<img
|
<img
|
||||||
src={src}
|
src={src}
|
||||||
alt={alt}
|
alt={alt}
|
||||||
style={{
|
style={{
|
||||||
...imageStyle,
|
...imageStyle,
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0
|
left: 0
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ResponsiveImage
|
export default ResponsiveImage
|
||||||
|
@ -15,14 +15,14 @@ export const StatsData = () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getFiles,
|
getIssues,
|
||||||
checkAndUpdateFile,
|
checkAndUpdateIssue,
|
||||||
getFile,
|
getIssue,
|
||||||
hashMapFiles,
|
hashMapFiles,
|
||||||
getNewFiles,
|
getNewIssues,
|
||||||
checkNewFiles,
|
checkNewIssues,
|
||||||
getFilesFiltered,
|
getIssuesFiltered,
|
||||||
getFilesCount,
|
getIssuesCount,
|
||||||
} = useFetchIssues();
|
} = useFetchIssues();
|
||||||
|
|
||||||
const totalIssuesPublished = useSelector(
|
const totalIssuesPublished = useSelector(
|
||||||
@ -36,8 +36,8 @@ export const StatsData = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getFilesCount();
|
getIssuesCount();
|
||||||
}, [getFilesCount]);
|
}, [getIssuesCount]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
totalIssuesPublished > 0 && (
|
totalIssuesPublished > 0 && (
|
||||||
|
135
src/components/common/AutocompleteQappNames.tsx
Normal file
135
src/components/common/AutocompleteQappNames.tsx
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
import React, { useEffect, useImperativeHandle, useState } from "react";
|
||||||
|
import { Autocomplete, SxProps, Theme } from "@mui/material";
|
||||||
|
import { CustomInputField } from "../PublishIssue/PublishIssue-styles.tsx";
|
||||||
|
import { log } from "../../constants/Misc.ts";
|
||||||
|
|
||||||
|
interface AutoCompleteQappNamesProps {
|
||||||
|
namesList?: string[];
|
||||||
|
afterChange?: (selectedName: string) => void;
|
||||||
|
sx?: SxProps<Theme>;
|
||||||
|
required?: boolean;
|
||||||
|
initialSelection?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type QappNamesRef = {
|
||||||
|
getSelectedValue: () => string;
|
||||||
|
setSelectedValue: (selectedValue: string) => void;
|
||||||
|
getQappNameFetchString: () => string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AutocompleteQappNames = React.forwardRef<
|
||||||
|
QappNamesRef,
|
||||||
|
AutoCompleteQappNamesProps
|
||||||
|
>(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
namesList,
|
||||||
|
afterChange,
|
||||||
|
sx,
|
||||||
|
required = true,
|
||||||
|
initialSelection = null,
|
||||||
|
}: AutoCompleteQappNamesProps,
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
const [QappNamesList, setQappNamesList] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const [selectedQappName, setSelectedQappName] = useState<string>(
|
||||||
|
initialSelection || null
|
||||||
|
);
|
||||||
|
|
||||||
|
if (log) console.log("initial selection: ", initialSelection);
|
||||||
|
useEffect(() => {
|
||||||
|
if (namesList) {
|
||||||
|
if (log) console.log("prop namesList: ", namesList);
|
||||||
|
setQappNamesList(namesList);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPublishedQappNames().then((names: string[]) => {
|
||||||
|
setQappNamesList(names);
|
||||||
|
if (log) console.log("QappNames set manually");
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedQappName(initialSelection || null);
|
||||||
|
}, [initialSelection]);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
getSelectedValue: () => {
|
||||||
|
return selectedQappName;
|
||||||
|
},
|
||||||
|
setSelectedValue: (selectedValue: string) => {
|
||||||
|
setSelectedQappName(selectedValue);
|
||||||
|
},
|
||||||
|
getQappNameFetchString: () => {
|
||||||
|
return getQappNameFetchString(selectedQappName);
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
return (
|
||||||
|
<Autocomplete
|
||||||
|
options={QappNamesList}
|
||||||
|
value={selectedQappName}
|
||||||
|
onChange={(e, newValue) => {
|
||||||
|
setSelectedQappName(newValue);
|
||||||
|
if (afterChange) afterChange(newValue || null);
|
||||||
|
}}
|
||||||
|
sx={{ height: "100px", ...sx }}
|
||||||
|
renderInput={params => (
|
||||||
|
<CustomInputField
|
||||||
|
{...params}
|
||||||
|
label={"Q-App/Website Name"}
|
||||||
|
value={selectedQappName}
|
||||||
|
variant={"filled"}
|
||||||
|
required={required}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface MetaData {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
tags: string[];
|
||||||
|
mimeType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchResourcesResponse {
|
||||||
|
name: string;
|
||||||
|
service: string;
|
||||||
|
identifier: string;
|
||||||
|
metadata?: MetaData;
|
||||||
|
size: number;
|
||||||
|
created: number;
|
||||||
|
updated: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchService = (service: string) => {
|
||||||
|
return qortalRequest({
|
||||||
|
action: "SEARCH_QDN_RESOURCES",
|
||||||
|
service: service,
|
||||||
|
limit: 0,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPublishedQappNames = async () => {
|
||||||
|
const QappList: Promise<SearchResourcesResponse[]> = searchService("APP");
|
||||||
|
const siteList: Promise<SearchResourcesResponse[]> = searchService("WEBSITE");
|
||||||
|
|
||||||
|
const responses = await Promise.all([QappList, siteList]);
|
||||||
|
const processedQappList = responses[0].map(value => value.name);
|
||||||
|
const processedWebsiteList = responses[1].map(value => value.name);
|
||||||
|
|
||||||
|
const removedDuplicates = Array.from(
|
||||||
|
new Set<string>([...processedQappList, ...processedWebsiteList])
|
||||||
|
);
|
||||||
|
return removedDuplicates.sort((a, b) => {
|
||||||
|
return a.localeCompare(b);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getQappNameFetchString = (selectedQappName: string) => {
|
||||||
|
return selectedQappName ? `Qapp:${selectedQappName};` : "";
|
||||||
|
};
|
@ -12,13 +12,15 @@ import {
|
|||||||
|
|
||||||
import React, { useEffect, useImperativeHandle, useState } from "react";
|
import React, { useEffect, useImperativeHandle, useState } from "react";
|
||||||
import { CategoryContainer } from "./CategoryList-styles.tsx";
|
import { CategoryContainer } from "./CategoryList-styles.tsx";
|
||||||
import { allCategoryData } from "../../../constants/Categories/1stCategories.ts";
|
import { allCategoryData } from "../../../constants/Categories/Categories.ts";
|
||||||
import { log } from "../../../constants/Misc.ts";
|
import { log } from "../../../constants/Misc.ts";
|
||||||
|
import { findCategoryData } from "../../../constants/Categories/CategoryFunctions.ts";
|
||||||
|
|
||||||
export interface Category {
|
export interface Category {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
label?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Categories {
|
export interface Categories {
|
||||||
@ -29,8 +31,6 @@ export interface CategoryData {
|
|||||||
subCategories: Categories[];
|
subCategories: Categories[];
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListDirection = "column" | "row";
|
|
||||||
|
|
||||||
interface CategoryListProps {
|
interface CategoryListProps {
|
||||||
sx?: SxProps<Theme>;
|
sx?: SxProps<Theme>;
|
||||||
categoryData: CategoryData;
|
categoryData: CategoryData;
|
||||||
@ -38,6 +38,7 @@ interface CategoryListProps {
|
|||||||
columns?: number;
|
columns?: number;
|
||||||
afterChange?: (categories: string[]) => void;
|
afterChange?: (categories: string[]) => void;
|
||||||
excludeCategories?: Category[];
|
excludeCategories?: Category[];
|
||||||
|
showEmptyItem?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CategoryListRef = {
|
export type CategoryListRef = {
|
||||||
@ -60,6 +61,7 @@ export const CategoryList = React.forwardRef<
|
|||||||
columns = 1,
|
columns = 1,
|
||||||
afterChange,
|
afterChange,
|
||||||
excludeCategories,
|
excludeCategories,
|
||||||
|
showEmptyItem = true,
|
||||||
}: CategoryListProps,
|
}: CategoryListProps,
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
@ -127,7 +129,8 @@ export const CategoryList = React.forwardRef<
|
|||||||
const newSelectedCategories: string[] = selectedCategories.map(
|
const newSelectedCategories: string[] = selectedCategories.map(
|
||||||
(category, categoryIndex) => {
|
(category, categoryIndex) => {
|
||||||
if (index > categoryIndex) return category;
|
if (index > categoryIndex) return category;
|
||||||
else if (index === categoryIndex) return selectedOption.id.toString();
|
else if (index === categoryIndex)
|
||||||
|
return selectedOption?.id?.toString();
|
||||||
else return "";
|
else return "";
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -140,23 +143,31 @@ export const CategoryList = React.forwardRef<
|
|||||||
};
|
};
|
||||||
|
|
||||||
const categorySelectSX = {
|
const categorySelectSX = {
|
||||||
// Target the input field
|
// // Target the input field
|
||||||
".MuiSelect-select": {
|
// ".MuiSelect-select": {
|
||||||
fontSize: "16px", // Change font size for the selected value
|
// padding: "10px 5px 15px 15px;",
|
||||||
padding: "10px 5px 15px 15px;",
|
// },
|
||||||
},
|
// // Target the dropdown icon
|
||||||
// Target the dropdown icon
|
// ".MuiSelect-icon": {
|
||||||
".MuiSelect-icon": {
|
// fontSize: "20px", // Adjust if needed
|
||||||
fontSize: "20px", // Adjust if needed
|
// },
|
||||||
},
|
// // Target the dropdown menu
|
||||||
// Target the dropdown menu
|
// "& .MuiMenu-paper": {
|
||||||
"& .MuiMenu-paper": {
|
// ".MuiMenuItem-root": {
|
||||||
".MuiMenuItem-root": {
|
// fontSize: "14px", // Change font size for the menu items
|
||||||
fontSize: "14px", // Change font size for the menu items
|
// },
|
||||||
},
|
// },
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const emptyMenuItem = (
|
||||||
|
<MenuItem
|
||||||
|
key={""}
|
||||||
|
value={""}
|
||||||
|
sx={{
|
||||||
|
"@media (min-width: 600px)": { minHeight: "46.5px" },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
const fillMenu = (category: Categories, index: number) => {
|
const fillMenu = (category: Categories, index: number) => {
|
||||||
const subCategoryIndex = selectedCategories[index];
|
const subCategoryIndex = selectedCategories[index];
|
||||||
if (log) console.log("selected categories: ", selectedCategories);
|
if (log) console.log("selected categories: ", selectedCategories);
|
||||||
@ -171,12 +182,23 @@ export const CategoryList = React.forwardRef<
|
|||||||
if (log) console.log("categoryData: ", categoryData);
|
if (log) console.log("categoryData: ", categoryData);
|
||||||
|
|
||||||
const menuToFill = category[subCategoryIndex];
|
const menuToFill = category[subCategoryIndex];
|
||||||
if (menuToFill)
|
if (menuToFill) {
|
||||||
return menuToFill.map(option => (
|
const menuItems = [];
|
||||||
<MenuItem key={option.id} value={option.id}>
|
|
||||||
{option.name}
|
if (showEmptyItem) menuItems.push(emptyMenuItem);
|
||||||
</MenuItem>
|
|
||||||
));
|
menuToFill.map(option =>
|
||||||
|
menuItems.push(
|
||||||
|
<MenuItem key={option.id} value={option.id}>
|
||||||
|
{option.name}
|
||||||
|
</MenuItem>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (log) console.log(" returning menuItems: ", menuItems);
|
||||||
|
|
||||||
|
return menuItems;
|
||||||
|
}
|
||||||
|
if (log) console.log("not returning menuItems");
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasSubCategory = (category: Categories, index: number) => {
|
const hasSubCategory = (category: Categories, index: number) => {
|
||||||
@ -202,11 +224,11 @@ export const CategoryList = React.forwardRef<
|
|||||||
<FormControl fullWidth sx={{ marginBottom: 1 }}>
|
<FormControl fullWidth sx={{ marginBottom: 1 }}>
|
||||||
<InputLabel
|
<InputLabel
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: "16px",
|
fontSize: "24px",
|
||||||
}}
|
}}
|
||||||
id="Category-1"
|
id="Category-1"
|
||||||
>
|
>
|
||||||
Category
|
{categoryData.category[0]?.label || "Category"}
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
labelId="Category 1"
|
labelId="Category 1"
|
||||||
@ -217,6 +239,7 @@ export const CategoryList = React.forwardRef<
|
|||||||
}}
|
}}
|
||||||
sx={categorySelectSX}
|
sx={categorySelectSX}
|
||||||
>
|
>
|
||||||
|
{showEmptyItem && emptyMenuItem}
|
||||||
{categoryData.category.map(option => (
|
{categoryData.category.map(option => (
|
||||||
<MenuItem key={option.id} value={option.id}>
|
<MenuItem key={option.id} value={option.id}>
|
||||||
{option.name}
|
{option.name}
|
||||||
@ -237,11 +260,14 @@ export const CategoryList = React.forwardRef<
|
|||||||
>
|
>
|
||||||
<InputLabel
|
<InputLabel
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: "16px",
|
fontSize: "24px",
|
||||||
}}
|
}}
|
||||||
id={`Category-${index + 2}`}
|
id={`Category-${index + 2}`}
|
||||||
>
|
>
|
||||||
{`Category-${index + 2}`}
|
{findCategoryData(+selectedCategories[index + 1])
|
||||||
|
?.label ||
|
||||||
|
category[selectedCategories[index]][0]?.label ||
|
||||||
|
`Category-${index + 2}`}
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
labelId={`Category ${index + 2}`}
|
labelId={`Category ${index + 2}`}
|
||||||
@ -250,24 +276,6 @@ export const CategoryList = React.forwardRef<
|
|||||||
onChange={e => {
|
onChange={e => {
|
||||||
selectCategoryEvent(e, index + 1);
|
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)}
|
{fillMenu(category, index)}
|
||||||
</Select>
|
</Select>
|
||||||
@ -285,9 +293,9 @@ export const getCategoriesFetchString = (categories: string[]) => {
|
|||||||
let fetchString = "";
|
let fetchString = "";
|
||||||
categories.map((category, index) => {
|
categories.map((category, index) => {
|
||||||
if (category) {
|
if (category) {
|
||||||
if (index === 0) fetchString += `cat:${category}`;
|
if (index === 0 && category) fetchString += `cat:${category};`;
|
||||||
else if (index === 1) fetchString += `;sub:${category}`;
|
else if (index === 1 && category) fetchString += `sub:${category};`;
|
||||||
else fetchString += `;sub${index}:${category}`;
|
else if (category) fetchString += `sub${index}:${category};`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (log) console.log("categoriesAsDescription: ", fetchString);
|
if (log) console.log("categoriesAsDescription: ", fetchString);
|
||||||
@ -318,3 +326,16 @@ export const getCategoriesFromObject = (editFileProperties: any) => {
|
|||||||
}
|
}
|
||||||
return categoryList;
|
return categoryList;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getCategoriesLength = categoryList => {
|
||||||
|
return categoryList.filter(category => category !== "").length;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hasCategories = (categories: string[]) => {
|
||||||
|
return categories.findIndex(category => category !== "") >= 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const appendCategory = (categoryList: string[], category: string) => {
|
||||||
|
const nextIndex = categoryList.findIndex(category => category === "");
|
||||||
|
categoryList[nextIndex] = category;
|
||||||
|
};
|
||||||
|
@ -0,0 +1,178 @@
|
|||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
InputLabel,
|
||||||
|
MenuItem,
|
||||||
|
OutlinedInput,
|
||||||
|
Select,
|
||||||
|
SxProps,
|
||||||
|
Theme,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
import React, { useEffect, useImperativeHandle, useState } from "react";
|
||||||
|
import { CategoryContainer } from "./CategoryList-styles.tsx";
|
||||||
|
import { log } from "../../../constants/Misc.ts";
|
||||||
|
|
||||||
|
export interface Category {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
icon?: string;
|
||||||
|
label?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Categories {
|
||||||
|
[key: number]: Category[];
|
||||||
|
}
|
||||||
|
export interface CategoryData {
|
||||||
|
category: Category[];
|
||||||
|
subCategories: Categories[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CategoryListProps {
|
||||||
|
sx?: SxProps<Theme>;
|
||||||
|
categoryData: Category[];
|
||||||
|
initialCategory?: string;
|
||||||
|
afterChange?: (category: string) => void;
|
||||||
|
showEmptyItem?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CategorySelectRef = {
|
||||||
|
getSelectedCategory: () => string;
|
||||||
|
setSelectedCategory: (arr: string) => void;
|
||||||
|
clearCategory: () => void;
|
||||||
|
getCategoryFetchString: (categories?: string) => string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CategorySelect = React.forwardRef<
|
||||||
|
CategorySelectRef,
|
||||||
|
CategoryListProps
|
||||||
|
>(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
sx,
|
||||||
|
categoryData,
|
||||||
|
initialCategory,
|
||||||
|
afterChange,
|
||||||
|
showEmptyItem = true,
|
||||||
|
}: CategoryListProps,
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
const [selectedCategory, setSelectedCategory] = useState<string>(
|
||||||
|
initialCategory || ""
|
||||||
|
);
|
||||||
|
useEffect(() => {
|
||||||
|
if (initialCategory) setSelectedCategory(initialCategory);
|
||||||
|
}, [initialCategory]);
|
||||||
|
|
||||||
|
const updateCategory = (category: string) => {
|
||||||
|
if (log) console.log("updateCategory ID: ", category);
|
||||||
|
setSelectedCategory(category);
|
||||||
|
if (afterChange) afterChange(category);
|
||||||
|
};
|
||||||
|
const categoryToObject = (category: string) => {
|
||||||
|
let categoryObject = {};
|
||||||
|
categoryObject["category"] = category;
|
||||||
|
if (log) console.log("categoryObject is: ", categoryObject);
|
||||||
|
return categoryObject;
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearCategory = () => {
|
||||||
|
updateCategory("");
|
||||||
|
};
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
getSelectedCategory: () => {
|
||||||
|
return selectedCategory;
|
||||||
|
},
|
||||||
|
setSelectedCategory: category => {
|
||||||
|
if (log) console.log("setSelectedCategory: ", category);
|
||||||
|
updateCategory(category);
|
||||||
|
},
|
||||||
|
clearCategory,
|
||||||
|
getCategoryFetchString: (category?: string) =>
|
||||||
|
getCategoryFetchString(category || selectedCategory),
|
||||||
|
categoriesToObject: (category?: string) =>
|
||||||
|
categoryToObject(category || selectedCategory),
|
||||||
|
}));
|
||||||
|
|
||||||
|
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 emptyMenuItem = (
|
||||||
|
<MenuItem
|
||||||
|
key={""}
|
||||||
|
value={""}
|
||||||
|
// sx={{
|
||||||
|
// "& .MuiButtonBase-root-MuiMenuItem-root": {
|
||||||
|
// minHeight: "50px",
|
||||||
|
// },
|
||||||
|
sx={{
|
||||||
|
"@media (min-width: 600px)": { minHeight: "46.5px" },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const fillMenu = () => {
|
||||||
|
const menuItems = [];
|
||||||
|
if (showEmptyItem) menuItems.push(emptyMenuItem);
|
||||||
|
|
||||||
|
categoryData.map(option =>
|
||||||
|
menuItems.push(
|
||||||
|
<MenuItem key={option.id} value={option.id}>
|
||||||
|
{option.name}
|
||||||
|
</MenuItem>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return menuItems;
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<CategoryContainer sx={{ width: "100%", ...sx }}>
|
||||||
|
<FormControl fullWidth sx={{ marginBottom: 1 }}>
|
||||||
|
<InputLabel
|
||||||
|
sx={{
|
||||||
|
fontSize: "24px",
|
||||||
|
}}
|
||||||
|
id="Category-1"
|
||||||
|
>
|
||||||
|
{categoryData[0]?.label || "Category"}
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="Category 1"
|
||||||
|
input={<OutlinedInput label="Category 1" />}
|
||||||
|
value={selectedCategory || ""}
|
||||||
|
onChange={e => {
|
||||||
|
updateCategory(e.target.value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{fillMenu()}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</CategoryContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getCategoryFetchString = (category: string) => {
|
||||||
|
return `cat:${category}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCategoryFromObject = (editFileProperties: any) => {
|
||||||
|
const categoryList: string[] = [];
|
||||||
|
if (editFileProperties.category)
|
||||||
|
categoryList.push(editFileProperties.category);
|
||||||
|
return categoryList;
|
||||||
|
};
|
@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Box,
|
Box,
|
||||||
Button,
|
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@ -9,26 +8,26 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import React, { useCallback, useState, useEffect } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { CommentEditor } from "./CommentEditor";
|
import { CommentEditor } from "./CommentEditor";
|
||||||
import {
|
import {
|
||||||
|
AuthorTextComment,
|
||||||
CardContentContainerComment,
|
CardContentContainerComment,
|
||||||
CommentActionButtonRow,
|
CommentActionButtonRow,
|
||||||
CommentDateText,
|
CommentDateText,
|
||||||
EditReplyButton,
|
EditReplyButton,
|
||||||
StyledCardComment,
|
|
||||||
} from "./Comments-styles";
|
|
||||||
import { StyledCardHeaderComment } from "./Comments-styles";
|
|
||||||
import { StyledCardColComment } from "./Comments-styles";
|
|
||||||
import { AuthorTextComment } from "./Comments-styles";
|
|
||||||
import {
|
|
||||||
StyledCardContentComment,
|
|
||||||
LoadMoreCommentsButton as CommentActionButton,
|
LoadMoreCommentsButton as CommentActionButton,
|
||||||
|
StyledCardColComment,
|
||||||
|
StyledCardComment,
|
||||||
|
StyledCardContentComment,
|
||||||
|
StyledCardHeaderComment,
|
||||||
} from "./Comments-styles";
|
} from "./Comments-styles";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { RootState } from "../../../state/store";
|
import { RootState } from "../../../state/store";
|
||||||
import Portal from "../Portal";
|
import Portal from "../Portal";
|
||||||
import { formatDate } from "../../../utils/time";
|
import { formatDate } from "../../../utils/time";
|
||||||
|
import { ThemeButton } from "../../../pages/Home/Home-styles.tsx";
|
||||||
|
|
||||||
interface CommentProps {
|
interface CommentProps {
|
||||||
comment: any;
|
comment: any;
|
||||||
postId: string;
|
postId: string;
|
||||||
@ -69,12 +68,15 @@ export const Comment = ({
|
|||||||
onClose={() => setCurrentEdit(null)}
|
onClose={() => setCurrentEdit(null)}
|
||||||
aria-labelledby="alert-dialog-title"
|
aria-labelledby="alert-dialog-title"
|
||||||
aria-describedby="alert-dialog-description"
|
aria-describedby="alert-dialog-description"
|
||||||
|
maxWidth={false}
|
||||||
>
|
>
|
||||||
<DialogTitle id="alert-dialog-title"></DialogTitle>
|
<DialogTitle id="alert-dialog-title" sx={{ fontSize: "30px" }}>
|
||||||
|
Edit Comment
|
||||||
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
width: "300px",
|
width: "1000px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
}}
|
}}
|
||||||
@ -90,9 +92,12 @@ export const Comment = ({
|
|||||||
</Box>
|
</Box>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button variant="contained" onClick={() => setCurrentEdit(null)}>
|
<ThemeButton
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => setCurrentEdit(null)}
|
||||||
|
>
|
||||||
Close
|
Close
|
||||||
</Button>
|
</ThemeButton>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Portal>
|
</Portal>
|
||||||
@ -125,13 +130,15 @@ export const Comment = ({
|
|||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
<CommentActionButtonRow>
|
<CommentActionButtonRow>
|
||||||
<CommentActionButton
|
{user?.name !== comment?.name && (
|
||||||
size="small"
|
<CommentActionButton
|
||||||
variant="contained"
|
size="small"
|
||||||
onClick={() => setIsReplying(true)}
|
variant="contained"
|
||||||
>
|
onClick={() => setIsReplying(true)}
|
||||||
reply
|
>
|
||||||
</CommentActionButton>
|
reply
|
||||||
|
</CommentActionButton>
|
||||||
|
)}
|
||||||
{user?.name === comment?.name && (
|
{user?.name === comment?.name && (
|
||||||
<CommentActionButton
|
<CommentActionButton
|
||||||
size="small"
|
size="small"
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import { Box, Button, TextField } from "@mui/material";
|
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { RootState } from "../../../state/store";
|
import { RootState } from "../../../state/store";
|
||||||
import ShortUniqueId from "short-unique-id";
|
import ShortUniqueId from "short-unique-id";
|
||||||
import { setNotification } from "../../../state/features/notificationsSlice";
|
import { setNotification } from "../../../state/features/notificationsSlice";
|
||||||
import { toBase64 } from "../../../utils/toBase64";
|
|
||||||
import localforage from "localforage";
|
import localforage from "localforage";
|
||||||
import {
|
import {
|
||||||
CommentInput,
|
CommentInput,
|
||||||
@ -12,6 +10,9 @@ import {
|
|||||||
SubmitCommentButton,
|
SubmitCommentButton,
|
||||||
} from "./Comments-styles";
|
} from "./Comments-styles";
|
||||||
import { QSUPPORT_COMMENT_BASE } from "../../../constants/Identifiers.ts";
|
import { QSUPPORT_COMMENT_BASE } from "../../../constants/Identifiers.ts";
|
||||||
|
import { sendQchatDM } from "../../../utils/qortalRequests.ts";
|
||||||
|
import { maxCommentLength } from "../../../constants/Misc.ts";
|
||||||
|
|
||||||
const uid = new ShortUniqueId();
|
const uid = new ShortUniqueId();
|
||||||
|
|
||||||
const notification = localforage.createInstance({
|
const notification = localforage.createInstance({
|
||||||
@ -123,7 +124,6 @@ export const CommentEditor = ({
|
|||||||
let address;
|
let address;
|
||||||
let name;
|
let name;
|
||||||
let errorMsg = "";
|
let errorMsg = "";
|
||||||
|
|
||||||
address = user?.address;
|
address = user?.address;
|
||||||
name = user?.name || "";
|
name = user?.name || "";
|
||||||
|
|
||||||
@ -134,8 +134,8 @@ export const CommentEditor = ({
|
|||||||
errorMsg = "Cannot post without a name";
|
errorMsg = "Cannot post without a name";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.length > 200) {
|
if (value.length > maxCommentLength) {
|
||||||
errorMsg = "Comment needs to be under 200 characters";
|
errorMsg = `Comment needs to be under ${maxCommentLength} characters`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorMsg) {
|
if (errorMsg) {
|
||||||
@ -157,6 +157,7 @@ export const CommentEditor = ({
|
|||||||
data64: base64,
|
data64: base64,
|
||||||
identifier: identifier,
|
identifier: identifier,
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
setNotification({
|
setNotification({
|
||||||
msg: "Comment successfully published",
|
msg: "Comment successfully published",
|
||||||
@ -171,7 +172,19 @@ export const CommentEditor = ({
|
|||||||
postName: postName,
|
postName: postName,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (!isReply && !isEdit) {
|
||||||
|
// const notificationMessage = `This is an automated Q-Support notification indicating that someone has commented on your issue here:
|
||||||
|
// qortal://APP/Q-Support/issue/${postName}/${postId}
|
||||||
|
//
|
||||||
|
// Here are the first ${maxNotificationLength} characters of the comment:
|
||||||
|
//
|
||||||
|
// ${value.substring(0, maxNotificationLength)}`;
|
||||||
|
|
||||||
|
const notificationMessage = `This is an automated Q-Support notification indicating that someone has commented on your issue here:
|
||||||
|
qortal://APP/Q-Support/issue/${postName}/${postId}`;
|
||||||
|
|
||||||
|
await sendQchatDM(postName, notificationMessage);
|
||||||
|
}
|
||||||
return resourceResponse;
|
return resourceResponse;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
let notificationObj: any = null;
|
let notificationObj: any = null;
|
||||||
@ -236,11 +249,11 @@ export const CommentEditor = ({
|
|||||||
id="standard-multiline-flexible"
|
id="standard-multiline-flexible"
|
||||||
label="Your comment"
|
label="Your comment"
|
||||||
multiline
|
multiline
|
||||||
maxRows={4}
|
maxRows={10}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
value={value}
|
value={value}
|
||||||
inputProps={{
|
inputProps={{
|
||||||
maxLength: 200,
|
maxLength: maxCommentLength,
|
||||||
}}
|
}}
|
||||||
InputLabelProps={{ style: { fontSize: "18px" } }}
|
InputLabelProps={{ style: { fontSize: "18px" } }}
|
||||||
onChange={e => setValue(e.target.value)}
|
onChange={e => setValue(e.target.value)}
|
||||||
@ -252,3 +265,5 @@ export const CommentEditor = ({
|
|||||||
</CommentInputContainer>
|
</CommentInputContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sendDMwithComment = () => {};
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { styled } from "@mui/system";
|
import { styled } from "@mui/system";
|
||||||
import { Card, Box, Typography, Button, TextField } from "@mui/material";
|
import { Box, Button, Card, TextField, Typography } from "@mui/material";
|
||||||
|
import { ThemeButton } from "../../../pages/Home/Home-styles.tsx";
|
||||||
|
|
||||||
export const StyledCard = styled(Card)(({ theme }) => ({
|
export const StyledCard = styled(Card)(({ theme }) => ({
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
@ -93,7 +94,7 @@ export const StyledCardComment = styled(Typography)(({ theme }) => ({
|
|||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
fontSize: "19px",
|
fontSize: "19px",
|
||||||
wordBreak: "break-word"
|
wordBreak: "break-word",
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const TitleText = styled(Typography)({
|
export const TitleText = styled(Typography)({
|
||||||
@ -200,13 +201,10 @@ export const EditReplyButton = styled(Button)(({ theme }) => ({
|
|||||||
color: "#ffffff",
|
color: "#ffffff",
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const LoadMoreCommentsButton = styled(Button)(({ theme }) => ({
|
export const LoadMoreCommentsButton = styled(ThemeButton)(({ theme }) => ({
|
||||||
fontFamily: "Montserrat",
|
fontFamily: "Montserrat",
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
letterSpacing: "0.2px",
|
letterSpacing: "0.2px",
|
||||||
fontSize: "15px",
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
color: "#ffffff",
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const CommentActionButtonRow = styled(Box)({
|
export const CommentActionButtonRow = styled(Box)({
|
||||||
@ -234,8 +232,7 @@ export const CommentInputContainer = styled(Box)({
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
marginTop: "15px",
|
marginTop: "15px",
|
||||||
width: "90%",
|
width: "100%",
|
||||||
maxWidth: "1000px",
|
|
||||||
borderRadius: "8px",
|
borderRadius: "8px",
|
||||||
gap: "10px",
|
gap: "10px",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@ -270,12 +267,9 @@ export const CommentInput = styled(TextField)(({ theme }) => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const SubmitCommentButton = styled(Button)(({ theme }) => ({
|
export const SubmitCommentButton = styled(ThemeButton)(({ theme }) => ({
|
||||||
fontFamily: "Montserrat",
|
fontFamily: "Montserrat",
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
letterSpacing: "0.2px",
|
letterSpacing: "0.2px",
|
||||||
fontSize: "15px",
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
color: "#ffffff",
|
|
||||||
width: "75%",
|
width: "75%",
|
||||||
}));
|
}));
|
||||||
|
@ -1,446 +1,446 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { styled, useTheme } from "@mui/material/styles";
|
import { styled, useTheme } from "@mui/material/styles";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { CircularProgress } from "@mui/material";
|
import { CircularProgress } from "@mui/material";
|
||||||
import AttachFileIcon from "@mui/icons-material/AttachFile";
|
import AttachFileIcon from "@mui/icons-material/AttachFile";
|
||||||
import { MyContext } from "../../wrappers/DownloadWrapper";
|
import { MyContext } from "../../wrappers/DownloadWrapper";
|
||||||
import { RootState } from "../../state/store";
|
import { RootState } from "../../state/store";
|
||||||
import { setNotification } from "../../state/features/notificationsSlice";
|
import { setNotification } from "../../state/features/notificationsSlice";
|
||||||
|
|
||||||
const Widget = styled("div")(({ theme }) => ({
|
const Widget = styled("div")(({ theme }) => ({
|
||||||
padding: 8,
|
padding: 8,
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
maxWidth: 350,
|
maxWidth: 350,
|
||||||
position: "relative",
|
position: "relative",
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
backdropFilter: "blur(40px)",
|
backdropFilter: "blur(40px)",
|
||||||
background: "skyblue",
|
background: "skyblue",
|
||||||
transition: "0.2s all",
|
transition: "0.2s all",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
opacity: 0.75,
|
opacity: 0.75,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const CoverImage = styled("div")({
|
const CoverImage = styled("div")({
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 40,
|
height: 40,
|
||||||
objectFit: "cover",
|
objectFit: "cover",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
backgroundColor: "rgba(0,0,0,0.08)",
|
backgroundColor: "rgba(0,0,0,0.08)",
|
||||||
"& > img": {
|
"& > img": {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
interface IAudioElement {
|
interface IAudioElement {
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
author?: string;
|
author?: string;
|
||||||
fileInfo?: any;
|
fileInfo?: any;
|
||||||
postId?: string;
|
postId?: string;
|
||||||
user?: string;
|
user?: string;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
mimeType?: string;
|
mimeType?: string;
|
||||||
disable?: boolean;
|
disable?: boolean;
|
||||||
mode?: string;
|
mode?: string;
|
||||||
otherUser?: string;
|
otherUser?: string;
|
||||||
customStyles?: any;
|
customStyles?: any;
|
||||||
jsonId:string;
|
jsonId:string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CustomWindow extends Window {
|
interface CustomWindow extends Window {
|
||||||
showSaveFilePicker: any; // Replace 'any' with the appropriate type if you know it
|
showSaveFilePicker: any; // Replace 'any' with the appropriate type if you know it
|
||||||
}
|
}
|
||||||
|
|
||||||
const customWindow = window as unknown as CustomWindow;
|
const customWindow = window as unknown as CustomWindow;
|
||||||
|
|
||||||
export default function FileElement({
|
export default function FileElement({
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
author,
|
author,
|
||||||
fileInfo,
|
fileInfo,
|
||||||
children,
|
children,
|
||||||
mimeType,
|
mimeType,
|
||||||
disable,
|
disable,
|
||||||
customStyles,
|
customStyles,
|
||||||
jsonId
|
jsonId
|
||||||
}: IAudioElement) {
|
}: IAudioElement) {
|
||||||
const { downloadVideo } = React.useContext(MyContext);
|
const { downloadVideo } = React.useContext(MyContext);
|
||||||
const [startedDownload, setStartedDownload] = React.useState<boolean>(false)
|
const [startedDownload, setStartedDownload] = React.useState<boolean>(false)
|
||||||
const [isLoading, setIsLoading] = React.useState<boolean>(false);
|
const [isLoading, setIsLoading] = React.useState<boolean>(false);
|
||||||
const [fileProperties, setFileProperties] = React.useState<any>(null);
|
const [fileProperties, setFileProperties] = React.useState<any>(null);
|
||||||
const [downloadLoader, setDownloadLoader] = React.useState<any>(false);
|
const [downloadLoader, setDownloadLoader] = React.useState<any>(false);
|
||||||
const downloads = useSelector((state: RootState) => state.global?.downloads);
|
const downloads = useSelector((state: RootState) => state.global?.downloads);
|
||||||
const status = React.useRef<null | string>(null)
|
const status = React.useRef<null | string>(null)
|
||||||
|
|
||||||
const hasCommencedDownload = React.useRef(false);
|
const hasCommencedDownload = React.useRef(false);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const reDownload = React.useRef<boolean>(false)
|
const reDownload = React.useRef<boolean>(false)
|
||||||
const isFetchingProperties = React.useRef<boolean>(false)
|
const isFetchingProperties = React.useRef<boolean>(false)
|
||||||
const download = React.useMemo(() => {
|
const download = React.useMemo(() => {
|
||||||
if (!downloads || !fileInfo?.identifier) return {};
|
if (!downloads || !fileInfo?.identifier) return {};
|
||||||
const findDownload = downloads[fileInfo?.identifier];
|
const findDownload = downloads[fileInfo?.identifier];
|
||||||
|
|
||||||
if (!findDownload) return {};
|
if (!findDownload) return {};
|
||||||
return findDownload;
|
return findDownload;
|
||||||
}, [downloads, fileInfo]);
|
}, [downloads, fileInfo]);
|
||||||
|
|
||||||
const resourceStatus = React.useMemo(() => {
|
const resourceStatus = React.useMemo(() => {
|
||||||
return download?.status || {};
|
return download?.status || {};
|
||||||
}, [download]);
|
}, [download]);
|
||||||
|
|
||||||
const retryDownload = React.useRef(0);
|
const retryDownload = React.useRef(0);
|
||||||
|
|
||||||
const handlePlay = async () => {
|
const handlePlay = async () => {
|
||||||
if (disable) return;
|
if (disable) return;
|
||||||
hasCommencedDownload.current = true;
|
hasCommencedDownload.current = true;
|
||||||
setStartedDownload(true)
|
setStartedDownload(true)
|
||||||
if (
|
if (
|
||||||
resourceStatus?.status === "READY"
|
resourceStatus?.status === "READY"
|
||||||
) {
|
) {
|
||||||
if (downloadLoader) return;
|
if (downloadLoader) return;
|
||||||
|
|
||||||
setDownloadLoader(true);
|
setDownloadLoader(true);
|
||||||
let filename = download?.properties?.filename
|
let filename = download?.properties?.filename
|
||||||
let mimeType = download?.properties?.type
|
let mimeType = download?.properties?.type
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { name, service, identifier } = fileInfo;
|
const { name, service, identifier } = fileInfo;
|
||||||
|
|
||||||
const res = await qortalRequest({
|
const res = await qortalRequest({
|
||||||
action: "GET_QDN_RESOURCE_PROPERTIES",
|
action: "GET_QDN_RESOURCE_PROPERTIES",
|
||||||
name: name,
|
name: name,
|
||||||
service: service,
|
service: service,
|
||||||
identifier: identifier,
|
identifier: identifier,
|
||||||
});
|
});
|
||||||
filename = res?.filename || filename;
|
filename = res?.filename || filename;
|
||||||
mimeType = res?.mimeType || mimeType;
|
mimeType = res?.mimeType || mimeType;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const { name, service, identifier } = fileInfo;
|
const { name, service, identifier } = fileInfo;
|
||||||
|
|
||||||
const url = `/arbitrary/${service}/${name}/${identifier}`;
|
const url = `/arbitrary/${service}/${name}/${identifier}`;
|
||||||
fetch(url)
|
fetch(url)
|
||||||
.then(response => response.blob())
|
.then(response => response.blob())
|
||||||
.then(async blob => {
|
.then(async blob => {
|
||||||
await qortalRequest({
|
await qortalRequest({
|
||||||
action: "SAVE_FILE",
|
action: "SAVE_FILE",
|
||||||
blob,
|
blob,
|
||||||
filename: filename,
|
filename: filename,
|
||||||
mimeType,
|
mimeType,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error("Error fetching the video:", error);
|
console.error("Error fetching the video:", error);
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
let notificationObj: any = null;
|
let notificationObj: any = null;
|
||||||
if (typeof error === "string") {
|
if (typeof error === "string") {
|
||||||
notificationObj = {
|
notificationObj = {
|
||||||
msg: error || "Failed to send message",
|
msg: error || "Failed to send message",
|
||||||
alertType: "error",
|
alertType: "error",
|
||||||
};
|
};
|
||||||
} else if (typeof error?.error === "string") {
|
} else if (typeof error?.error === "string") {
|
||||||
notificationObj = {
|
notificationObj = {
|
||||||
msg: error?.error || "Failed to send message",
|
msg: error?.error || "Failed to send message",
|
||||||
alertType: "error",
|
alertType: "error",
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
notificationObj = {
|
notificationObj = {
|
||||||
msg: error?.message || "Failed to send message",
|
msg: error?.message || "Failed to send message",
|
||||||
alertType: "error",
|
alertType: "error",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (!notificationObj) return;
|
if (!notificationObj) return;
|
||||||
dispatch(setNotification(notificationObj));
|
dispatch(setNotification(notificationObj));
|
||||||
} finally {
|
} finally {
|
||||||
setDownloadLoader(false);
|
setDownloadLoader(false);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name, service, identifier } = fileInfo;
|
const { name, service, identifier } = fileInfo;
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
downloadVideo({
|
downloadVideo({
|
||||||
name,
|
name,
|
||||||
service,
|
service,
|
||||||
identifier,
|
identifier,
|
||||||
properties: {
|
properties: {
|
||||||
...fileInfo,
|
...fileInfo,
|
||||||
jsonId
|
jsonId
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const refetch = React.useCallback(async () => {
|
const refetch = React.useCallback(async () => {
|
||||||
if (!fileInfo) return
|
if (!fileInfo) return
|
||||||
try {
|
try {
|
||||||
const { name, service, identifier } = fileInfo;
|
const { name, service, identifier } = fileInfo;
|
||||||
isFetchingProperties.current = true
|
isFetchingProperties.current = true
|
||||||
await qortalRequest({
|
await qortalRequest({
|
||||||
action: 'GET_QDN_RESOURCE_PROPERTIES',
|
action: 'GET_QDN_RESOURCE_PROPERTIES',
|
||||||
name,
|
name,
|
||||||
service,
|
service,
|
||||||
identifier
|
identifier
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
isFetchingProperties.current = false
|
isFetchingProperties.current = false
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [fileInfo])
|
}, [fileInfo])
|
||||||
|
|
||||||
const refetchInInterval = ()=> {
|
const refetchInInterval = ()=> {
|
||||||
try {
|
try {
|
||||||
const interval = setInterval(()=> {
|
const interval = setInterval(()=> {
|
||||||
if(status?.current === 'DOWNLOADED'){
|
if(status?.current === 'DOWNLOADED'){
|
||||||
refetch()
|
refetch()
|
||||||
}
|
}
|
||||||
if(status?.current === 'READY'){
|
if(status?.current === 'READY'){
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
}, 7500)
|
}, 7500)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if(resourceStatus?.status){
|
if(resourceStatus?.status){
|
||||||
status.current = resourceStatus?.status
|
status.current = resourceStatus?.status
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
resourceStatus?.status === 'DOWNLOADED' &&
|
resourceStatus?.status === 'DOWNLOADED' &&
|
||||||
reDownload?.current === false
|
reDownload?.current === false
|
||||||
) {
|
) {
|
||||||
refetchInInterval()
|
refetchInInterval()
|
||||||
reDownload.current = true
|
reDownload.current = true
|
||||||
}
|
}
|
||||||
}, [resourceStatus])
|
}, [resourceStatus])
|
||||||
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (
|
if (
|
||||||
resourceStatus?.status === "READY" &&
|
resourceStatus?.status === "READY" &&
|
||||||
download?.url &&
|
download?.url &&
|
||||||
download?.properties?.filename &&
|
download?.properties?.filename &&
|
||||||
hasCommencedDownload.current
|
hasCommencedDownload.current
|
||||||
) {
|
) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
dispatch(
|
dispatch(
|
||||||
setNotification({
|
setNotification({
|
||||||
msg: "Download completed. Click to save file",
|
msg: "Download completed. Click to save file",
|
||||||
alertType: "info",
|
alertType: "info",
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [resourceStatus, download]);
|
}, [resourceStatus, download]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
onClick={handlePlay}
|
onClick={handlePlay}
|
||||||
sx={{
|
sx={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
...(customStyles || {}),
|
...(customStyles || {}),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children && (
|
{children && (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
gap: "7px",
|
gap: "7px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}{" "}
|
{children}{" "}
|
||||||
{((resourceStatus.status && resourceStatus?.status !== "READY") ||
|
{((resourceStatus.status && resourceStatus?.status !== "READY") ||
|
||||||
isLoading) && startedDownload ? (
|
isLoading) && startedDownload ? (
|
||||||
<>
|
<>
|
||||||
<CircularProgress color="secondary" size={14} />
|
<CircularProgress color="secondary" size={14} />
|
||||||
<Typography variant="body2">{`${Math.round(
|
<Typography variant="body2">{`${Math.round(
|
||||||
resourceStatus?.percentLoaded || 0
|
resourceStatus?.percentLoaded || 0
|
||||||
).toFixed(0)}% loaded`}</Typography>
|
).toFixed(0)}% loaded`}</Typography>
|
||||||
</>
|
</>
|
||||||
) : resourceStatus?.status === "READY" ? (
|
) : resourceStatus?.status === "READY" ? (
|
||||||
<>
|
<>
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: "14px",
|
fontSize: "14px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Ready to save: click here
|
Ready to save: click here
|
||||||
</Typography>
|
</Typography>
|
||||||
{downloadLoader && (
|
{downloadLoader && (
|
||||||
<CircularProgress color="secondary" size={14} />
|
<CircularProgress color="secondary" size={14} />
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{!children && (
|
{!children && (
|
||||||
<Widget>
|
<Widget>
|
||||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
<CoverImage>
|
<CoverImage>
|
||||||
<AttachFileIcon
|
<AttachFileIcon
|
||||||
sx={{
|
sx={{
|
||||||
width: "90%",
|
width: "90%",
|
||||||
height: "auto",
|
height: "auto",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</CoverImage>
|
</CoverImage>
|
||||||
<Box sx={{ ml: 1.5, minWidth: 0 }}>
|
<Box sx={{ ml: 1.5, minWidth: 0 }}>
|
||||||
<Typography
|
<Typography
|
||||||
variant="caption"
|
variant="caption"
|
||||||
color="text.secondary"
|
color="text.secondary"
|
||||||
fontWeight={500}
|
fontWeight={500}
|
||||||
>
|
>
|
||||||
{author}
|
{author}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
noWrap
|
noWrap
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: "16px",
|
fontSize: "16px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<b>{title}</b>
|
<b>{title}</b>
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
noWrap
|
noWrap
|
||||||
letterSpacing={-0.25}
|
letterSpacing={-0.25}
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: "14px",
|
fontSize: "14px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{description}
|
{description}
|
||||||
</Typography>
|
</Typography>
|
||||||
{mimeType && (
|
{mimeType && (
|
||||||
<Typography
|
<Typography
|
||||||
noWrap
|
noWrap
|
||||||
letterSpacing={-0.25}
|
letterSpacing={-0.25}
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: "12px",
|
fontSize: "12px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{mimeType}
|
{mimeType}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
{((resourceStatus.status && resourceStatus?.status !== "READY") ||
|
{((resourceStatus.status && resourceStatus?.status !== "READY") ||
|
||||||
isLoading) && startedDownload && (
|
isLoading) && startedDownload && (
|
||||||
<Box
|
<Box
|
||||||
position="absolute"
|
position="absolute"
|
||||||
top={0}
|
top={0}
|
||||||
left={0}
|
left={0}
|
||||||
right={0}
|
right={0}
|
||||||
bottom={0}
|
bottom={0}
|
||||||
display="flex"
|
display="flex"
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
zIndex={4999}
|
zIndex={4999}
|
||||||
bgcolor="rgba(0, 0, 0, 0.6)"
|
bgcolor="rgba(0, 0, 0, 0.6)"
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
gap: "10px",
|
gap: "10px",
|
||||||
padding: "8px",
|
padding: "8px",
|
||||||
borderRadius: "10px",
|
borderRadius: "10px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CircularProgress color="secondary" />
|
<CircularProgress color="secondary" />
|
||||||
{resourceStatus && (
|
{resourceStatus && (
|
||||||
<Typography
|
<Typography
|
||||||
variant="subtitle2"
|
variant="subtitle2"
|
||||||
component="div"
|
component="div"
|
||||||
sx={{
|
sx={{
|
||||||
color: "white",
|
color: "white",
|
||||||
fontSize: "14px",
|
fontSize: "14px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{resourceStatus?.status === "REFETCHING" ? (
|
{resourceStatus?.status === "REFETCHING" ? (
|
||||||
<>
|
<>
|
||||||
<>
|
<>
|
||||||
{(
|
{(
|
||||||
(resourceStatus?.localChunkCount /
|
(resourceStatus?.localChunkCount /
|
||||||
resourceStatus?.totalChunkCount) *
|
resourceStatus?.totalChunkCount) *
|
||||||
100
|
100
|
||||||
)?.toFixed(0)}
|
)?.toFixed(0)}
|
||||||
%
|
%
|
||||||
</>
|
</>
|
||||||
|
|
||||||
<> Refetching in 2 minutes</>
|
<> Refetching in 2 minutes</>
|
||||||
</>
|
</>
|
||||||
) : resourceStatus?.status === "DOWNLOADED" ? (
|
) : resourceStatus?.status === "DOWNLOADED" ? (
|
||||||
<>Download Completed: building file...</>
|
<>Download Completed: building file...</>
|
||||||
) : resourceStatus?.status !== "READY" ? (
|
) : resourceStatus?.status !== "READY" ? (
|
||||||
<>
|
<>
|
||||||
{(
|
{(
|
||||||
(resourceStatus?.localChunkCount /
|
(resourceStatus?.localChunkCount /
|
||||||
resourceStatus?.totalChunkCount) *
|
resourceStatus?.totalChunkCount) *
|
||||||
100
|
100
|
||||||
)?.toFixed(0)}
|
)?.toFixed(0)}
|
||||||
%
|
%
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>Download Completed: fetching file...</>
|
<>Download Completed: fetching file...</>
|
||||||
)}
|
)}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{resourceStatus?.status === "READY" &&
|
{resourceStatus?.status === "READY" &&
|
||||||
download?.url &&
|
download?.url &&
|
||||||
download?.properties?.filename && (
|
download?.properties?.filename && (
|
||||||
<Box
|
<Box
|
||||||
position="absolute"
|
position="absolute"
|
||||||
top={0}
|
top={0}
|
||||||
left={0}
|
left={0}
|
||||||
right={0}
|
right={0}
|
||||||
bottom={0}
|
bottom={0}
|
||||||
display="flex"
|
display="flex"
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
zIndex={4999}
|
zIndex={4999}
|
||||||
bgcolor="rgba(0, 0, 0, 0.6)"
|
bgcolor="rgba(0, 0, 0, 0.6)"
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
gap: "10px",
|
gap: "10px",
|
||||||
padding: "8px",
|
padding: "8px",
|
||||||
borderRadius: "10px",
|
borderRadius: "10px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
<Typography
|
||||||
variant="subtitle2"
|
variant="subtitle2"
|
||||||
component="div"
|
component="div"
|
||||||
sx={{
|
sx={{
|
||||||
color: "white",
|
color: "white",
|
||||||
fontSize: "14px",
|
fontSize: "14px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Ready to save: click here
|
Ready to save: click here
|
||||||
</Typography>
|
</Typography>
|
||||||
{downloadLoader && (
|
{downloadLoader && (
|
||||||
<CircularProgress color="secondary" size={14} />
|
<CircularProgress color="secondary" size={14} />
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Widget>
|
</Widget>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ export const AddCoverImageButton = styled(Button)(({ theme }) => ({
|
|||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
letterSpacing: "0.2px",
|
letterSpacing: "0.2px",
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
|
width: "170px",
|
||||||
backgroundColor: "#44c4ff",
|
backgroundColor: "#44c4ff",
|
||||||
"&:hover": { backgroundColor: "#01a9e9" },
|
"&:hover": { backgroundColor: "#01a9e9" },
|
||||||
gap: "5px",
|
gap: "5px",
|
||||||
|
@ -98,6 +98,7 @@ export const ImageUploader: React.FC<ImageUploaderProps> = ({
|
|||||||
{...getRootProps()}
|
{...getRootProps()}
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
width: "170px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<input {...getInputProps()} />
|
<input {...getInputProps()} />
|
||||||
|
61
src/components/common/IssueIcon.tsx
Normal file
61
src/components/common/IssueIcon.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import AttachFileIcon from "@mui/icons-material/AttachFile";
|
||||||
|
import React, { CSSProperties } from "react";
|
||||||
|
|
||||||
|
interface IssueIconProps {
|
||||||
|
iconSrc: string;
|
||||||
|
showBackupIcon?: boolean;
|
||||||
|
style?: CSSProperties;
|
||||||
|
}
|
||||||
|
export const IssueIcon = ({
|
||||||
|
iconSrc,
|
||||||
|
showBackupIcon = true,
|
||||||
|
style,
|
||||||
|
}: IssueIconProps) => {
|
||||||
|
const displayFileIcon = !iconSrc && showBackupIcon;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{iconSrc && (
|
||||||
|
<img
|
||||||
|
src={iconSrc}
|
||||||
|
width="50px"
|
||||||
|
height="50px"
|
||||||
|
style={{
|
||||||
|
borderRadius: "5px",
|
||||||
|
...style,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{displayFileIcon && (
|
||||||
|
<AttachFileIcon
|
||||||
|
sx={{
|
||||||
|
...style,
|
||||||
|
width: "40px",
|
||||||
|
height: "40px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IssueIconsProps {
|
||||||
|
iconSources: string[];
|
||||||
|
showBackupIcon?: boolean;
|
||||||
|
style?: CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IssueIcons = ({
|
||||||
|
iconSources,
|
||||||
|
showBackupIcon = true,
|
||||||
|
style,
|
||||||
|
}: IssueIconsProps) => {
|
||||||
|
return iconSources.map((icon, index) => (
|
||||||
|
<IssueIcon
|
||||||
|
key={icon + index}
|
||||||
|
iconSrc={icon}
|
||||||
|
style={{ ...style }}
|
||||||
|
showBackupIcon={showBackupIcon}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
};
|
@ -1,48 +1,48 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react'
|
import React, { useState, useEffect, useRef } from 'react'
|
||||||
import { useInView } from 'react-intersection-observer'
|
import { useInView } from 'react-intersection-observer'
|
||||||
import CircularProgress from '@mui/material/CircularProgress'
|
import CircularProgress from '@mui/material/CircularProgress'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onLoadMore: () => Promise<void>
|
onLoadMore: () => Promise<void>
|
||||||
isLoading?: boolean
|
isLoading?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const LazyLoad: React.FC<Props> = ({ onLoadMore, isLoading }) => {
|
const LazyLoad: React.FC<Props> = ({ onLoadMore, isLoading }) => {
|
||||||
const [isFetching, setIsFetching] = useState<boolean>(false)
|
const [isFetching, setIsFetching] = useState<boolean>(false)
|
||||||
|
|
||||||
const firstLoad = useRef(false)
|
const firstLoad = useRef(false)
|
||||||
const [ref, inView] = useInView({
|
const [ref, inView] = useInView({
|
||||||
threshold: 0.7
|
threshold: 0.7
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (inView) {
|
if (inView) {
|
||||||
setIsFetching(true)
|
setIsFetching(true)
|
||||||
onLoadMore().finally(() => {
|
onLoadMore().finally(() => {
|
||||||
setIsFetching(false)
|
setIsFetching(false)
|
||||||
firstLoad.current = true
|
firstLoad.current = true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [inView])
|
}, [inView])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
minHeight: '25px'
|
minHeight: '25px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
visibility: (isFetching || isLoading) ? 'visible' : 'hidden'
|
visibility: (isFetching || isLoading) ? 'visible' : 'hidden'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CircularProgress />
|
<CircularProgress />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LazyLoad
|
export default LazyLoad
|
||||||
|
@ -1,86 +1,86 @@
|
|||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import { toast, ToastContainer, Zoom, Slide } from 'react-toastify'
|
import { toast, ToastContainer, Zoom, Slide } from 'react-toastify'
|
||||||
import { removeNotification } from '../../../state/features/notificationsSlice'
|
import { removeNotification } from '../../../state/features/notificationsSlice'
|
||||||
import 'react-toastify/dist/ReactToastify.css'
|
import 'react-toastify/dist/ReactToastify.css'
|
||||||
import { RootState } from '../../../state/store'
|
import { RootState } from '../../../state/store'
|
||||||
|
|
||||||
const Notification = () => {
|
const Notification = () => {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
const { alertTypes } = useSelector((state: RootState) => state.notifications)
|
const { alertTypes } = useSelector((state: RootState) => state.notifications)
|
||||||
|
|
||||||
if (alertTypes.alertError) {
|
if (alertTypes.alertError) {
|
||||||
toast.error(`❌ ${alertTypes?.alertError}`, {
|
toast.error(`❌ ${alertTypes?.alertError}`, {
|
||||||
position: 'bottom-right',
|
position: 'bottom-right',
|
||||||
autoClose: 4000,
|
autoClose: 4000,
|
||||||
hideProgressBar: false,
|
hideProgressBar: false,
|
||||||
closeOnClick: true,
|
closeOnClick: true,
|
||||||
pauseOnHover: true,
|
pauseOnHover: true,
|
||||||
draggable: true,
|
draggable: true,
|
||||||
progress: undefined,
|
progress: undefined,
|
||||||
icon: false
|
icon: false
|
||||||
})
|
})
|
||||||
dispatch(removeNotification())
|
dispatch(removeNotification())
|
||||||
}
|
}
|
||||||
if (alertTypes.alertSuccess) {
|
if (alertTypes.alertSuccess) {
|
||||||
toast.success(`✔️ ${alertTypes?.alertSuccess}`, {
|
toast.success(`✔️ ${alertTypes?.alertSuccess}`, {
|
||||||
position: 'bottom-right',
|
position: 'bottom-right',
|
||||||
autoClose: 4000,
|
autoClose: 4000,
|
||||||
hideProgressBar: false,
|
hideProgressBar: false,
|
||||||
closeOnClick: true,
|
closeOnClick: true,
|
||||||
pauseOnHover: true,
|
pauseOnHover: true,
|
||||||
draggable: true,
|
draggable: true,
|
||||||
progress: undefined,
|
progress: undefined,
|
||||||
icon: false
|
icon: false
|
||||||
})
|
})
|
||||||
dispatch(removeNotification())
|
dispatch(removeNotification())
|
||||||
}
|
}
|
||||||
if (alertTypes.alertInfo) {
|
if (alertTypes.alertInfo) {
|
||||||
toast.info(`${alertTypes?.alertInfo}`, {
|
toast.info(`${alertTypes?.alertInfo}`, {
|
||||||
position: 'top-right',
|
position: 'top-right',
|
||||||
autoClose: 1300,
|
autoClose: 1300,
|
||||||
hideProgressBar: false,
|
hideProgressBar: false,
|
||||||
closeOnClick: true,
|
closeOnClick: true,
|
||||||
pauseOnHover: true,
|
pauseOnHover: true,
|
||||||
draggable: true,
|
draggable: true,
|
||||||
progress: undefined,
|
progress: undefined,
|
||||||
theme: 'light'
|
theme: 'light'
|
||||||
})
|
})
|
||||||
dispatch(removeNotification())
|
dispatch(removeNotification())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alertTypes.alertInfo) {
|
if (alertTypes.alertInfo) {
|
||||||
return (
|
return (
|
||||||
<ToastContainer
|
<ToastContainer
|
||||||
position="top-right"
|
position="top-right"
|
||||||
autoClose={2000}
|
autoClose={2000}
|
||||||
hideProgressBar={false}
|
hideProgressBar={false}
|
||||||
newestOnTop={false}
|
newestOnTop={false}
|
||||||
closeOnClick
|
closeOnClick
|
||||||
rtl={false}
|
rtl={false}
|
||||||
pauseOnFocusLoss
|
pauseOnFocusLoss
|
||||||
draggable
|
draggable
|
||||||
pauseOnHover
|
pauseOnHover
|
||||||
theme="light"
|
theme="light"
|
||||||
toastStyle={{ fontSize: '16px' }}
|
toastStyle={{ fontSize: '16px' }}
|
||||||
transition={Slide}
|
transition={Slide}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToastContainer
|
<ToastContainer
|
||||||
transition={Zoom}
|
transition={Zoom}
|
||||||
position="bottom-right"
|
position="bottom-right"
|
||||||
autoClose={false}
|
autoClose={false}
|
||||||
hideProgressBar={false}
|
hideProgressBar={false}
|
||||||
newestOnTop={false}
|
newestOnTop={false}
|
||||||
closeOnClick
|
closeOnClick
|
||||||
rtl={false}
|
rtl={false}
|
||||||
draggable
|
draggable
|
||||||
pauseOnHover
|
pauseOnHover
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Notification
|
export default Notification
|
||||||
|
@ -1,43 +1,43 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import CircularProgress from '@mui/material/CircularProgress';
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
import Box from '@mui/system/Box';
|
import Box from '@mui/system/Box';
|
||||||
import { useTheme } from '@mui/material'
|
import { useTheme } from '@mui/material'
|
||||||
|
|
||||||
interface PageLoaderProps {
|
interface PageLoaderProps {
|
||||||
size?: number
|
size?: number
|
||||||
thickness?: number
|
thickness?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const PageLoader: React.FC<PageLoaderProps> = ({
|
const PageLoader: React.FC<PageLoaderProps> = ({
|
||||||
size = 40,
|
size = 40,
|
||||||
thickness = 5
|
thickness = 5
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
height: '100vh',
|
height: '100vh',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.25)',
|
backgroundColor: 'rgba(255, 255, 255, 0.25)',
|
||||||
zIndex: 1000
|
zIndex: 1000
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CircularProgress
|
<CircularProgress
|
||||||
size={size}
|
size={size}
|
||||||
thickness={thickness}
|
thickness={thickness}
|
||||||
sx={{
|
sx={{
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PageLoader;
|
export default PageLoader;
|
||||||
|
@ -1,25 +1,25 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
|
|
||||||
interface PortalProps {
|
interface PortalProps {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
const Portal: React.FC<PortalProps> = ({ children }) => {
|
const Portal: React.FC<PortalProps> = ({ children }) => {
|
||||||
const [mounted, setMounted] = useState(false)
|
const [mounted, setMounted] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMounted(true)
|
setMounted(true)
|
||||||
|
|
||||||
return () => setMounted(false)
|
return () => setMounted(false)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return mounted
|
return mounted
|
||||||
? createPortal(
|
? createPortal(
|
||||||
children,
|
children,
|
||||||
document.querySelector('#modal-root') as HTMLElement
|
document.querySelector('#modal-root') as HTMLElement
|
||||||
)
|
)
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Portal
|
export default Portal
|
||||||
|
@ -1,40 +1,40 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import DOMPurify from "dompurify";
|
import DOMPurify from "dompurify";
|
||||||
import "react-quill/dist/quill.snow.css";
|
import "react-quill/dist/quill.snow.css";
|
||||||
import "react-quill/dist/quill.core.css";
|
import "react-quill/dist/quill.core.css";
|
||||||
import "react-quill/dist/quill.bubble.css";
|
import "react-quill/dist/quill.bubble.css";
|
||||||
import { convertQortalLinks } from "./utils";
|
import { convertQortalLinks } from "./utils";
|
||||||
import { Box, styled } from "@mui/material";
|
import { Box, styled } from "@mui/material";
|
||||||
|
|
||||||
|
|
||||||
const CrowdfundInlineContent = styled(Box)(({ theme }) => ({
|
const CrowdfundInlineContent = styled(Box)(({ theme }) => ({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
fontFamily: "Mulish",
|
fontFamily: "Mulish",
|
||||||
fontSize: "19px",
|
fontSize: "19px",
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
letterSpacing: 0,
|
letterSpacing: 0,
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
width: '100%'
|
width: '100%'
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const DisplayHtml = ({ html }) => {
|
export const DisplayHtml = ({ html }) => {
|
||||||
const cleanContent = useMemo(() => {
|
const cleanContent = useMemo(() => {
|
||||||
if (!html) return null;
|
if (!html) return null;
|
||||||
|
|
||||||
const sanitize: string = DOMPurify.sanitize(html, {
|
const sanitize: string = DOMPurify.sanitize(html, {
|
||||||
USE_PROFILES: { html: true },
|
USE_PROFILES: { html: true },
|
||||||
});
|
});
|
||||||
const anchorQortal = convertQortalLinks(sanitize);
|
const anchorQortal = convertQortalLinks(sanitize);
|
||||||
return anchorQortal;
|
return anchorQortal;
|
||||||
}, [html]);
|
}, [html]);
|
||||||
|
|
||||||
if (!cleanContent) return null;
|
if (!cleanContent) return null;
|
||||||
return (
|
return (
|
||||||
<CrowdfundInlineContent>
|
<CrowdfundInlineContent>
|
||||||
<div
|
<div
|
||||||
className="ql-editor"
|
className="ql-editor"
|
||||||
dangerouslySetInnerHTML={{ __html: cleanContent }}
|
dangerouslySetInnerHTML={{ __html: cleanContent }}
|
||||||
/>
|
/>
|
||||||
</CrowdfundInlineContent>
|
</CrowdfundInlineContent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
export function convertQortalLinks(inputHtml) {
|
export function convertQortalLinks(inputHtml) {
|
||||||
// Regular expression to match 'qortal://...' URLs.
|
// Regular expression to match 'qortal://...' URLs.
|
||||||
// This will stop at the first whitespace, comma, or HTML tag
|
// This will stop at the first whitespace, comma, or HTML tag
|
||||||
var regex = /(qortal:\/\/[^\s,<]+)/g;
|
var regex = /(qortal:\/\/[^\s,<]+)/g;
|
||||||
|
|
||||||
// Replace matches in inputHtml with formatted anchor tag
|
// Replace matches in inputHtml with formatted anchor tag
|
||||||
var outputHtml = inputHtml.replace(regex, function (match) {
|
var outputHtml = inputHtml.replace(regex, function (match) {
|
||||||
return `<a href="${match}" className="qortal-link">${match}</a>`;
|
return `<a href="${match}" className="qortal-link">${match}</a>`;
|
||||||
});
|
});
|
||||||
|
|
||||||
return outputHtml;
|
return outputHtml;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractTextFromHTML(htmlString: any, length = 150) {
|
export function extractTextFromHTML(htmlString: any, length = 150) {
|
||||||
// Create a temporary DOM element
|
// Create a temporary DOM element
|
||||||
const tempDiv = document.createElement("div");
|
const tempDiv = document.createElement("div");
|
||||||
// Replace br tags and block-level tags with a space before setting the HTML content
|
// Replace br tags and block-level tags with a space before setting the HTML content
|
||||||
const htmlWithSpaces = htmlString.replace(/<\/?(br|p|div|h[1-6]|ul|ol|li|blockquote)[^>]*>/gi, ' ');
|
const htmlWithSpaces = htmlString.replace(/<\/?(br|p|div|h[1-6]|ul|ol|li|blockquote)[^>]*>/gi, ' ');
|
||||||
tempDiv.innerHTML = htmlWithSpaces;
|
tempDiv.innerHTML = htmlWithSpaces;
|
||||||
// Extract the text content
|
// Extract the text content
|
||||||
let text = tempDiv.textContent || tempDiv.innerText || "";
|
let text = tempDiv.textContent || tempDiv.innerText || "";
|
||||||
// Replace multiple spaces with a single space and trim
|
// Replace multiple spaces with a single space and trim
|
||||||
text = text.replace(/\s+/g, ' ').trim();
|
text = text.replace(/\s+/g, ' ').trim();
|
||||||
// Slice the text to the desired length
|
// Slice the text to the desired length
|
||||||
return text.slice(0, length);
|
return text.slice(0, length);
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -22,7 +22,7 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import SearchIcon from "@mui/icons-material/Search";
|
import SearchIcon from "@mui/icons-material/Search";
|
||||||
|
|
||||||
import { DownloadTaskManager } from "../../common/DownloadTaskManager";
|
import { DownloadTaskManager } from "../../common/DownloadTaskManager";
|
||||||
import QSupportLogo from "../../../assets/img/Q-SupportIcon.webp";
|
import QSupportIcon from "../../../assets/img/Q-SupportIcon(AlphaX).webp";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import {
|
import {
|
||||||
addFilteredFiles,
|
addFilteredFiles,
|
||||||
@ -32,6 +32,7 @@ import {
|
|||||||
import { RootState } from "../../../state/store";
|
import { RootState } from "../../../state/store";
|
||||||
import { useWindowSize } from "../../../hooks/useWindowSize";
|
import { useWindowSize } from "../../../hooks/useWindowSize";
|
||||||
import { PublishIssue } from "../../PublishIssue/PublishIssue.tsx";
|
import { PublishIssue } from "../../PublishIssue/PublishIssue.tsx";
|
||||||
|
import { FeeHistoryModal } from "../../../constants/PublishFees/FeePricePublish/FeeHistoryModal.tsx";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean;
|
||||||
@ -114,7 +115,7 @@ const NavBar: React.FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={QSupportLogo}
|
src={QSupportIcon}
|
||||||
style={{
|
style={{
|
||||||
width: "auto",
|
width: "auto",
|
||||||
height: "100px",
|
height: "100px",
|
||||||
@ -238,6 +239,7 @@ const NavBar: React.FC<Props> = ({
|
|||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
<DownloadTaskManager />
|
<DownloadTaskManager />
|
||||||
|
<FeeHistoryModal />
|
||||||
{theme.palette.mode === "dark" ? (
|
{theme.palette.mode === "dark" ? (
|
||||||
<LightModeIcon
|
<LightModeIcon
|
||||||
onClickFunc={() => setTheme("light")}
|
onClickFunc={() => setTheme("light")}
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
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 {
|
|
||||||
Categories,
|
|
||||||
Category,
|
|
||||||
CategoryData,
|
|
||||||
} from "../../components/common/CategoryList/CategoryList.tsx";
|
|
||||||
import {
|
|
||||||
getAllCategoriesWithIcons,
|
|
||||||
sortCategory,
|
|
||||||
} from "./CategoryFunctions.ts";
|
|
||||||
import { QappCategories, SupportState } from "./2ndCategories.ts";
|
|
||||||
|
|
||||||
export const firstCategories: Category[] = [
|
|
||||||
{ id: 1, name: "Core" },
|
|
||||||
{ id: 2, name: "UI" },
|
|
||||||
{ id: 3, name: "Q-Apps" },
|
|
||||||
{ id: 4, name: "Website" },
|
|
||||||
{ id: 5, name: "Marketing" },
|
|
||||||
{ id: 99, name: "Other" },
|
|
||||||
];
|
|
||||||
export const secondCategories: Categories = {
|
|
||||||
1: SupportState,
|
|
||||||
2: SupportState,
|
|
||||||
3: QappCategories,
|
|
||||||
4: SupportState,
|
|
||||||
5: SupportState,
|
|
||||||
99: SupportState,
|
|
||||||
};
|
|
||||||
|
|
||||||
export let thirdCategories: Categories = {};
|
|
||||||
QappCategories.map(
|
|
||||||
supportStateCategory =>
|
|
||||||
(thirdCategories[supportStateCategory.id] = SupportState)
|
|
||||||
);
|
|
||||||
export const allCategoryData: CategoryData = {
|
|
||||||
category: firstCategories,
|
|
||||||
subCategories: [secondCategories, thirdCategories],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const iconCategories = getAllCategoriesWithIcons();
|
|
@ -1,23 +0,0 @@
|
|||||||
import OpenIcon from "../../assets/icons/OpenIcon.png";
|
|
||||||
import ClosedIcon from "../../assets/icons/ClosedIcon.png";
|
|
||||||
import InProgressIcon from "../../assets/icons/InProgressIcon.png";
|
|
||||||
import CompleteIcon from "../../assets/icons/CompleteIcon.png";
|
|
||||||
|
|
||||||
export const SupportState = [
|
|
||||||
{ id: 101, name: "Open", icon: OpenIcon },
|
|
||||||
{ id: 102, name: "Closed", icon: ClosedIcon },
|
|
||||||
{ id: 103, name: "In Progress", icon: InProgressIcon },
|
|
||||||
{ id: 104, name: "Complete", icon: CompleteIcon },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const QappCategories = [
|
|
||||||
{ id: 301, name: "Q-Blog" },
|
|
||||||
{ id: 302, name: "Q-Mail" },
|
|
||||||
{ id: 303, name: "Q-Shop" },
|
|
||||||
{ id: 304, name: "Q-Fund" },
|
|
||||||
{ id: 305, name: "Ear-Bump" },
|
|
||||||
{ id: 306, name: "Q-Tube" },
|
|
||||||
{ id: 307, name: "Q-Share" },
|
|
||||||
{ id: 308, name: "Q-Support" },
|
|
||||||
{ id: 399, name: "Other" },
|
|
||||||
];
|
|
67
src/constants/Categories/Categories.ts
Normal file
67
src/constants/Categories/Categories.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import {
|
||||||
|
Categories,
|
||||||
|
Category,
|
||||||
|
CategoryData,
|
||||||
|
} from "../../components/common/CategoryList/CategoryList.tsx";
|
||||||
|
import { getAllCategoriesWithIcons } from "./CategoryFunctions.ts";
|
||||||
|
import CoreIcon from "../../assets/icons/Qortal-Core-Icon.webp";
|
||||||
|
import UIicon from "../../assets/icons/Qortal-UI-Icon.webp";
|
||||||
|
import QappIcon from "../../assets/icons/Q-App-Icon.webp";
|
||||||
|
import UnknownIcon from "../../assets/icons/unknown.webp";
|
||||||
|
|
||||||
|
import BugReportIcon from "../../assets/icons/Bug-Report-Icon.webp";
|
||||||
|
import FeatureRequestIcon from "../../assets/icons/Feature-Request-Icon.webp";
|
||||||
|
import TechSupportIcon from "../../assets/icons/Tech-Support-Icon.webp";
|
||||||
|
|
||||||
|
import OpenIcon from "../../assets/icons/Open-Icon.webp";
|
||||||
|
import ClosedIcon from "../../assets/icons/Closed-Icon.webp";
|
||||||
|
import InProgressIcon from "../../assets/icons/In-Progress-Icon.webp";
|
||||||
|
import CompleteIcon from "../../assets/icons/Complete-Icon.webp";
|
||||||
|
|
||||||
|
const issueLocationLabel = "Issue Location";
|
||||||
|
export const issueLocation: Category[] = [
|
||||||
|
{ id: 1, name: "Core", icon: CoreIcon, label: issueLocationLabel },
|
||||||
|
{ id: 2, name: "UI", icon: UIicon, label: issueLocationLabel },
|
||||||
|
{ id: 3, name: "Q-Apps/Websites", icon: QappIcon, label: issueLocationLabel },
|
||||||
|
{ id: 99, name: "Other", icon: UnknownIcon, label: issueLocationLabel },
|
||||||
|
];
|
||||||
|
|
||||||
|
const issueTypeLabel = "Issue Type";
|
||||||
|
export const issueType = [
|
||||||
|
{ id: 11, name: "Bug Report", icon: BugReportIcon, label: issueTypeLabel },
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
name: "Feature Request",
|
||||||
|
icon: FeatureRequestIcon,
|
||||||
|
label: issueTypeLabel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 13,
|
||||||
|
name: "Tech Support",
|
||||||
|
icon: TechSupportIcon,
|
||||||
|
label: issueTypeLabel,
|
||||||
|
},
|
||||||
|
{ id: 19, name: "Other", icon: UnknownIcon, label: issueTypeLabel },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const secondCategories: Categories = {};
|
||||||
|
issueLocation.map(c => (secondCategories[c.id] = issueType));
|
||||||
|
|
||||||
|
const issueLabel = "Issue State";
|
||||||
|
export const IssueState = [
|
||||||
|
{ id: 101, name: "Open", icon: OpenIcon, label: issueLabel },
|
||||||
|
{ id: 102, name: "Closed", icon: ClosedIcon, label: issueLabel },
|
||||||
|
{ id: 103, name: "In Progress", icon: InProgressIcon, label: issueLabel },
|
||||||
|
{ id: 104, name: "Complete", icon: CompleteIcon, label: issueLabel },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const thirdCategories: Categories = {};
|
||||||
|
|
||||||
|
issueType.map(issueType => (thirdCategories[issueType.id] = IssueState));
|
||||||
|
|
||||||
|
export const allCategoryData: CategoryData = {
|
||||||
|
category: issueLocation,
|
||||||
|
subCategories: [secondCategories, thirdCategories],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const iconCategories = getAllCategoriesWithIcons();
|
@ -2,7 +2,7 @@ import {
|
|||||||
Category,
|
Category,
|
||||||
getCategoriesFromObject,
|
getCategoriesFromObject,
|
||||||
} from "../../components/common/CategoryList/CategoryList.tsx";
|
} from "../../components/common/CategoryList/CategoryList.tsx";
|
||||||
import { allCategoryData, iconCategories } from "./1stCategories.ts";
|
import { allCategoryData, iconCategories } from "./Categories.ts";
|
||||||
|
|
||||||
export const sortCategory = (a: Category, b: Category) => {
|
export const sortCategory = (a: Category, b: Category) => {
|
||||||
if (a.name === "Other") return 1;
|
if (a.name === "Other") return 1;
|
||||||
@ -81,11 +81,9 @@ export const getAllCategoriesWithIcons = () => {
|
|||||||
|
|
||||||
export const getIconsFromObject = (fileObj: any) => {
|
export const getIconsFromObject = (fileObj: any) => {
|
||||||
const categories = getCategoriesFromObject(fileObj);
|
const categories = getCategoriesFromObject(fileObj);
|
||||||
const icons = categories
|
const icons = categories.map(categoryID => {
|
||||||
.map(categoryID => {
|
return iconCategories.find(category => category.id === +categoryID)?.icon;
|
||||||
return iconCategories.find(category => category.id === +categoryID)?.icon;
|
});
|
||||||
})
|
|
||||||
.reverse();
|
|
||||||
|
|
||||||
return icons.find(icon => icon !== undefined);
|
return icons.filter(icon => icon !== undefined);
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const useTestIdentifiers = true;
|
export const useTestIdentifiers = false;
|
||||||
|
|
||||||
export const QSUPPORT_FILE_BASE = useTestIdentifiers
|
export const QSUPPORT_FILE_BASE = useTestIdentifiers
|
||||||
? "MYTEST_support_issue_"
|
? "MYTEST_support_issue_"
|
||||||
|
@ -3,3 +3,10 @@ export const titleFormatter = /[^a-zA-Z0-9\s-_!?()&'",.;:|—~@#$%^*+=<>]/g;
|
|||||||
export const titleFormatterOnSave = /[^a-zA-Z0-9\s-_!()&',.;—~@#$%^+=]/g;
|
export const titleFormatterOnSave = /[^a-zA-Z0-9\s-_!()&',.;—~@#$%^+=]/g;
|
||||||
|
|
||||||
export const log = false;
|
export const log = false;
|
||||||
|
|
||||||
|
export const fontSizeSmall = "80%";
|
||||||
|
export const fontSizeMedium = "100%";
|
||||||
|
export const fontSizeLarge = "120%";
|
||||||
|
export const fontSizeExLarge = "150%";
|
||||||
|
export const maxCommentLength = 10_000;
|
||||||
|
export const maxNotificationLength = 2000;
|
||||||
|
29
src/constants/PublishFees/FeeData.tsx
Normal file
29
src/constants/PublishFees/FeeData.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { Box } from "@mui/material";
|
||||||
|
import React from "react";
|
||||||
|
import { useTestIdentifiers } from "../Identifiers.ts";
|
||||||
|
|
||||||
|
export const appName = "Q-Support";
|
||||||
|
export const feeDestinationName = "Q-Support";
|
||||||
|
|
||||||
|
export const feeAmountBase = useTestIdentifiers ? 0.000001 : 0.25;
|
||||||
|
export const FEE_BASE = useTestIdentifiers
|
||||||
|
? "MYTEST_support_fees"
|
||||||
|
: "q_support_fees";
|
||||||
|
|
||||||
|
export const maxFeePublishTimeDiff = 10; // time in minutes before/after publish when fee is considered valid
|
||||||
|
export type FeeType = "default" | "comment" | "like" | "dislike" | "superlike";
|
||||||
|
|
||||||
|
export const feeDisclaimerString = `When Publishing (but not editing) Issues ${feeAmountBase} \n
|
||||||
|
QORT is requested to fund continued development of Q-Support.`;
|
||||||
|
|
||||||
|
export const feeDisclaimer = (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
fontSize: "28px",
|
||||||
|
color: "#f44336",
|
||||||
|
fontWeight: 600,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{feeDisclaimerString}
|
||||||
|
</Box>
|
||||||
|
);
|
60
src/constants/PublishFees/FeePricePublish/DataTable.tsx
Normal file
60
src/constants/PublishFees/FeePricePublish/DataTable.tsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
} from "@mui/material";
|
||||||
|
import React from "react";
|
||||||
|
import { SxProps } from "@mui/material/styles";
|
||||||
|
|
||||||
|
export interface DataTableProps {
|
||||||
|
columnNames: string[];
|
||||||
|
data: string[][];
|
||||||
|
sx?: SxProps;
|
||||||
|
}
|
||||||
|
export const DataTable = ({ columnNames, data, sx }: DataTableProps) => {
|
||||||
|
return (
|
||||||
|
<TableContainer sx={{ ...sx }}>
|
||||||
|
<Table align="center" stickyHeader>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
{columnNames.map((columnName, index) => (
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
fontSize: "30px",
|
||||||
|
textAlign: "center",
|
||||||
|
fontWeight: "bold",
|
||||||
|
}}
|
||||||
|
key={columnName + index}
|
||||||
|
>
|
||||||
|
{columnName}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{data.map((tableRow, index) => {
|
||||||
|
return (
|
||||||
|
<TableRow key={tableRow.toString() + index}>
|
||||||
|
{tableRow.map((tableCell, index) => (
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
fontSize: index === 0 ? "30px" : "25px",
|
||||||
|
fontWeight: index === 0 ? "bold" : "normal",
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
key={tableCell + index}
|
||||||
|
>
|
||||||
|
{tableCell}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,48 @@
|
|||||||
|
import { Button, Modal, useTheme } from "@mui/material";
|
||||||
|
import { ThemeButton } from "../../../pages/Home/Home-styles.tsx";
|
||||||
|
import { appName } from "../FeeData.tsx";
|
||||||
|
import { ModalBody } from "./FeePricePublish-styles.tsx";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { userHasName } from "../VerifyPayment-Functions.ts";
|
||||||
|
import { FeeHistoryTable } from "./FeeHistoryTable.tsx";
|
||||||
|
|
||||||
|
export const FeeHistoryModal = () => {
|
||||||
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
const [userOwnsApp, setUserOwnsApp] = useState<boolean>(false);
|
||||||
|
const theme = useTheme();
|
||||||
|
useEffect(() => {
|
||||||
|
userHasName(appName).then(userHasName => setUserOwnsApp(userHasName));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const buttonSX = {
|
||||||
|
fontSize: "20px",
|
||||||
|
color: theme.palette.secondary.main,
|
||||||
|
fontWeight: "bold",
|
||||||
|
};
|
||||||
|
if (theme.palette.mode === "light")
|
||||||
|
buttonSX["&:hover"] = { backgroundColor: theme.palette.primary.dark };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ThemeButton
|
||||||
|
sx={{ height: "40px", marginRight: "5px" }}
|
||||||
|
onClick={() => setOpen(true)}
|
||||||
|
>
|
||||||
|
{appName} Fees
|
||||||
|
</ThemeButton>
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
aria-labelledby="modal-title"
|
||||||
|
aria-describedby="modal-description"
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
>
|
||||||
|
<ModalBody sx={{ width: "75vw", maxWidth: "75vw" }}>
|
||||||
|
<FeeHistoryTable />
|
||||||
|
<Button sx={buttonSX} onClick={() => setOpen(false)}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</ModalBody>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,49 @@
|
|||||||
|
import { DataTable } from "./DataTable.tsx";
|
||||||
|
import { FeePrice, fetchFees } from "./FeePricePublish.ts";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export interface FeeHistoryProps {
|
||||||
|
showFeeType?: boolean;
|
||||||
|
showCoinType?: boolean;
|
||||||
|
filterData?: () => string[][];
|
||||||
|
}
|
||||||
|
export const FeeHistoryTable = ({
|
||||||
|
showFeeType = true,
|
||||||
|
showCoinType = true,
|
||||||
|
filterData,
|
||||||
|
}: FeeHistoryProps) => {
|
||||||
|
const [feeData, setFeeData] = useState<FeePrice[]>([]);
|
||||||
|
|
||||||
|
const fetchFeesOnStartup = () => {
|
||||||
|
fetchFees().then(feeResponse => {
|
||||||
|
setFeeData(filterData ? feeData.filter(filterData) : feeResponse);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(fetchFeesOnStartup, []);
|
||||||
|
|
||||||
|
const columnNames = ["ID", "Date", "Fee Amount"];
|
||||||
|
if (showFeeType) columnNames.push("Fee Type");
|
||||||
|
if (showCoinType) columnNames.push("Coin Type");
|
||||||
|
|
||||||
|
const data: string[][] = [];
|
||||||
|
|
||||||
|
const getRowData = (row: FeePrice, index: number) => {
|
||||||
|
const rowData: string[] = [];
|
||||||
|
rowData.push(
|
||||||
|
index.toString(),
|
||||||
|
new Date(row.time).toDateString(),
|
||||||
|
row.feeAmount.toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (showFeeType) rowData.push(row.feeType);
|
||||||
|
if (showCoinType) rowData.push(row.coinType);
|
||||||
|
|
||||||
|
return rowData;
|
||||||
|
};
|
||||||
|
|
||||||
|
feeData.map((row, index) => {
|
||||||
|
data.push(getRowData(row, index + 1));
|
||||||
|
});
|
||||||
|
return <DataTable columnNames={columnNames} data={data} />;
|
||||||
|
};
|
@ -0,0 +1,43 @@
|
|||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { styled } from "@mui/system";
|
||||||
|
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
}));
|
90
src/constants/PublishFees/FeePricePublish/FeePricePublish.ts
Normal file
90
src/constants/PublishFees/FeePricePublish/FeePricePublish.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import { appName, FEE_BASE, feeAmountBase, FeeType } from "../FeeData.tsx";
|
||||||
|
import { objectToBase64 } from "../../../utils/toBase64.ts";
|
||||||
|
import { store } from "../../../state/store.ts";
|
||||||
|
import { setFeeData } from "../../../state/features/globalSlice.ts";
|
||||||
|
import { useTestIdentifiers } from "../../Identifiers.ts";
|
||||||
|
|
||||||
|
export type CoinType = "QORT" | "BTC" | "LTC" | "DOGE" | "DGB" | "RVN" | "ARRR";
|
||||||
|
|
||||||
|
export interface FeePrice {
|
||||||
|
time: number;
|
||||||
|
feeAmount: number;
|
||||||
|
feeType: FeeType; // used to differentiate different types of fees such as comments, likes, data, etc.
|
||||||
|
coinType: CoinType;
|
||||||
|
}
|
||||||
|
|
||||||
|
const feesPublishService = "DOCUMENT";
|
||||||
|
|
||||||
|
export const fetchFees = async () => {
|
||||||
|
const feeData = store.getState().global.feeData;
|
||||||
|
if (feeData.length > 0) {
|
||||||
|
return feeData;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await qortalRequest({
|
||||||
|
action: "FETCH_QDN_RESOURCE",
|
||||||
|
identifier: FEE_BASE,
|
||||||
|
name: "Q-Support",
|
||||||
|
service: feesPublishService,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (await response) as FeePrice[];
|
||||||
|
} catch (e) {
|
||||||
|
console.log("fetch current fees error: ", e);
|
||||||
|
return [] as FeePrice[];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchFeesRedux = () => {
|
||||||
|
const feeData = store.getState().global.feeData;
|
||||||
|
if (feeData.length > 0) {
|
||||||
|
return feeData;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchFees().then(feeData => store.dispatch(setFeeData(feeData)));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addFeePrice = async (
|
||||||
|
feeAmount = feeAmountBase,
|
||||||
|
feeType: FeeType = "default",
|
||||||
|
coinType: CoinType = "QORT"
|
||||||
|
) => {
|
||||||
|
let fees = await fetchFees();
|
||||||
|
|
||||||
|
fees.push({
|
||||||
|
time: Date.now(),
|
||||||
|
feeAmount,
|
||||||
|
feeType,
|
||||||
|
coinType,
|
||||||
|
});
|
||||||
|
|
||||||
|
const feesBase64 = await objectToBase64(fees);
|
||||||
|
console.log("fees are: ", fees);
|
||||||
|
await qortalRequest({
|
||||||
|
action: "PUBLISH_QDN_RESOURCE",
|
||||||
|
name: appName,
|
||||||
|
identifier: FEE_BASE,
|
||||||
|
service: feesPublishService,
|
||||||
|
data64: feesBase64,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const feeFilter = (fee: FeePrice, feeToVerify: FeePrice) => {
|
||||||
|
const nameCheck = fee.feeType === feeToVerify.feeType;
|
||||||
|
const coinTypeCheck = fee.coinType === feeToVerify.coinType;
|
||||||
|
const timeCheck = feeToVerify.time <= feeToVerify.time;
|
||||||
|
|
||||||
|
return nameCheck && coinTypeCheck && timeCheck;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const verifyFeeAmount = async (feeToVerify: FeePrice) => {
|
||||||
|
if (useTestIdentifiers) return true;
|
||||||
|
|
||||||
|
const fees = await fetchFees();
|
||||||
|
const filteredFees = fees.filter(fee => feeFilter(fee, feeToVerify));
|
||||||
|
if (filteredFees.length === 0) return false;
|
||||||
|
|
||||||
|
const feeToCheck = filteredFees[filteredFees.length - 1]; // gets fee that applies at the time of feeToVerify
|
||||||
|
return feeToVerify.feeAmount >= feeToCheck.feeAmount;
|
||||||
|
};
|
79
src/constants/PublishFees/SendFeeFunctions.ts
Normal file
79
src/constants/PublishFees/SendFeeFunctions.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { feeDestinationName, FeeType } from "./FeeData.tsx";
|
||||||
|
import { CoinType } from "./FeePricePublish/FeePricePublish.ts";
|
||||||
|
|
||||||
|
export interface NameData {
|
||||||
|
name: string;
|
||||||
|
reducedName: string;
|
||||||
|
owner: string;
|
||||||
|
data: string;
|
||||||
|
registered: number;
|
||||||
|
isForSale: boolean;
|
||||||
|
}
|
||||||
|
export const getNameData = async (name: string) => {
|
||||||
|
return qortalRequest({
|
||||||
|
action: "GET_NAME_DATA",
|
||||||
|
name: name,
|
||||||
|
}) as Promise<NameData>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface SendCoinResponse {
|
||||||
|
amount: number;
|
||||||
|
approvalStatus: string;
|
||||||
|
fee: string;
|
||||||
|
recipient: string;
|
||||||
|
reference: string;
|
||||||
|
senderPublicKey: string;
|
||||||
|
signature: string;
|
||||||
|
timestamp: number;
|
||||||
|
txGroupId: number;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sendCoin = async (
|
||||||
|
address: string,
|
||||||
|
amount: number,
|
||||||
|
coin: CoinType
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
return (await qortalRequest({
|
||||||
|
action: "SEND_COIN",
|
||||||
|
coin,
|
||||||
|
destinationAddress: address,
|
||||||
|
amount,
|
||||||
|
})) as SendCoinResponse;
|
||||||
|
} catch (e) {
|
||||||
|
console.log("sendCoin refused", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendQORT = async (address: string, amount: number) => {
|
||||||
|
return await sendCoin(address, amount, "QORT");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendQORTtoName = async (name: string, amount: number) => {
|
||||||
|
const address = await getNameData(name);
|
||||||
|
if (address) return await sendQORT(address.owner, amount);
|
||||||
|
else throw Error("Name Not Found");
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface PublishFeeData {
|
||||||
|
signature: string;
|
||||||
|
senderName: string;
|
||||||
|
createdTimestamp?: number; //timestamp of the metadata publish, NOT the send feeAmount publish, added after publish is fetched
|
||||||
|
updatedTimestamp?: number;
|
||||||
|
feeType?: FeeType;
|
||||||
|
coinType?: CoinType;
|
||||||
|
isPaid?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CommentType = "reply" | "edit" | "comment";
|
||||||
|
|
||||||
|
export interface CommentObject {
|
||||||
|
text: string;
|
||||||
|
feeData: PublishFeeData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const payPublishFeeQORT = async (feeAmount: number) => {
|
||||||
|
const publish = await sendQORTtoName(feeDestinationName, feeAmount);
|
||||||
|
return publish?.signature;
|
||||||
|
};
|
85
src/constants/PublishFees/VerifyPayment-Functions.ts
Normal file
85
src/constants/PublishFees/VerifyPayment-Functions.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { Issue } from "../../state/features/fileSlice.ts";
|
||||||
|
import { PublishFeeData } from "./SendFeeFunctions.ts";
|
||||||
|
|
||||||
|
export type AccountName = { name: string; owner: string };
|
||||||
|
|
||||||
|
export interface GetRequestData {
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
reverse?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface getTransactionBySignatureResponse {
|
||||||
|
type: "string";
|
||||||
|
timestamp: number;
|
||||||
|
reference: string;
|
||||||
|
fee: number;
|
||||||
|
signature: string;
|
||||||
|
txGroupId: number;
|
||||||
|
recipient: string;
|
||||||
|
blockHeight: number;
|
||||||
|
approvalStatus: string;
|
||||||
|
creatorAddress: string;
|
||||||
|
senderPublicKey: string;
|
||||||
|
amount: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const stringIsEmpty = (value: string) => {
|
||||||
|
return value === "";
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAccountNames = async (
|
||||||
|
address: string,
|
||||||
|
params?: GetRequestData
|
||||||
|
) => {
|
||||||
|
const names = (await qortalRequest({
|
||||||
|
action: "GET_ACCOUNT_NAMES",
|
||||||
|
address: address,
|
||||||
|
...params,
|
||||||
|
})) as AccountName[];
|
||||||
|
|
||||||
|
const namelessAddress = { name: "", owner: address };
|
||||||
|
const emptyNamesFilled = names.map(({ name, owner }) => {
|
||||||
|
return stringIsEmpty(name) ? namelessAddress : { name, owner };
|
||||||
|
});
|
||||||
|
|
||||||
|
const returnValue =
|
||||||
|
emptyNamesFilled.length > 0 ? emptyNamesFilled : [namelessAddress];
|
||||||
|
return returnValue as AccountName[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUserAccountNames = async () => {
|
||||||
|
const account = await getUserAccount();
|
||||||
|
return await getAccountNames(account.address);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const userHasName = async (name: string) => {
|
||||||
|
const userAccountNames = await getUserAccountNames();
|
||||||
|
const userNames = userAccountNames.map(userName => userName.name);
|
||||||
|
return userNames.includes(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const objectToPublishFeeData = (object: Issue) => {
|
||||||
|
const createdTimestamp = +object?.created || 0;
|
||||||
|
const updatedTimestamp = +object?.updated || 0;
|
||||||
|
return {
|
||||||
|
signature: object?.feeData?.signature,
|
||||||
|
createdTimestamp,
|
||||||
|
updatedTimestamp,
|
||||||
|
feeType: object?.feeData?.feeType || "default",
|
||||||
|
coinType: object?.feeData?.coinType || "QORT",
|
||||||
|
senderName: object?.user,
|
||||||
|
isPaid: object?.feeData?.isPaid || false,
|
||||||
|
} as PublishFeeData;
|
||||||
|
};
|
||||||
|
export const objectHasNullValues = (object: object) => {
|
||||||
|
const objectAsArray = Object.values(object);
|
||||||
|
return objectAsArray.some(value => value == null);
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AccountInfo = { address: string; publicKey: string };
|
||||||
|
export const getUserAccount = async () => {
|
||||||
|
return (await qortalRequest({
|
||||||
|
action: "GET_USER_ACCOUNT",
|
||||||
|
})) as AccountInfo;
|
||||||
|
};
|
120
src/constants/PublishFees/VerifyPayment.ts
Normal file
120
src/constants/PublishFees/VerifyPayment.ts
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import { feeDestinationName, maxFeePublishTimeDiff } from "./FeeData.tsx";
|
||||||
|
import {
|
||||||
|
getAccountNames,
|
||||||
|
getTransactionBySignatureResponse,
|
||||||
|
objectHasNullValues,
|
||||||
|
objectToPublishFeeData,
|
||||||
|
} from "./VerifyPayment-Functions.ts";
|
||||||
|
import { verifyFeeAmount } from "./FeePricePublish/FeePricePublish.ts";
|
||||||
|
import { getNameData, PublishFeeData } from "./SendFeeFunctions.ts";
|
||||||
|
import { Issue } from "../../state/features/fileSlice.ts";
|
||||||
|
|
||||||
|
const getSignature = async (signature: string) => {
|
||||||
|
const url = "/transactions/signature/" + signature;
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (await response.json()) as getTransactionBySignatureResponse;
|
||||||
|
};
|
||||||
|
|
||||||
|
const verifySignature = async (feeData: PublishFeeData) => {
|
||||||
|
const {
|
||||||
|
signature,
|
||||||
|
createdTimestamp,
|
||||||
|
updatedTimestamp,
|
||||||
|
feeType,
|
||||||
|
coinType,
|
||||||
|
senderName,
|
||||||
|
} = feeData;
|
||||||
|
|
||||||
|
const [signatureData, accountData] = await Promise.all([
|
||||||
|
getSignature(signature),
|
||||||
|
getNameData(senderName),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const namesofFeeRecipient = await getAccountNames(signatureData.recipient);
|
||||||
|
const doesFeeAmountMatch = await verifyFeeAmount({
|
||||||
|
time: signatureData.timestamp,
|
||||||
|
feeAmount: +signatureData.amount,
|
||||||
|
feeType,
|
||||||
|
coinType,
|
||||||
|
});
|
||||||
|
|
||||||
|
const signatureTime = signatureData.timestamp;
|
||||||
|
let doesTimeMatch: boolean = false;
|
||||||
|
if (!updatedTimestamp) {
|
||||||
|
const timeDiff = createdTimestamp - signatureTime;
|
||||||
|
const timeDiffMinutes = Math.abs(timeDiff) / 1000 / 60;
|
||||||
|
|
||||||
|
doesTimeMatch = timeDiffMinutes <= maxFeePublishTimeDiff;
|
||||||
|
} else {
|
||||||
|
const minutesPublishDiff = 1000 * 60 * maxFeePublishTimeDiff;
|
||||||
|
const startTime = createdTimestamp - minutesPublishDiff;
|
||||||
|
const endTime = updatedTimestamp;
|
||||||
|
|
||||||
|
const sigTimeAfterStartTime = signatureTime > startTime;
|
||||||
|
const sigTimeBeforeEndTime = signatureTime < endTime;
|
||||||
|
|
||||||
|
doesTimeMatch = sigTimeAfterStartTime && sigTimeBeforeEndTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
const doesSignatureMatch = signature === signatureData?.signature;
|
||||||
|
|
||||||
|
const doesSenderMatch = signatureData.creatorAddress === accountData.owner;
|
||||||
|
|
||||||
|
const doesFeeRecipientNameMatch =
|
||||||
|
namesofFeeRecipient.findIndex(
|
||||||
|
nameData => nameData?.name === feeDestinationName
|
||||||
|
) >= 0;
|
||||||
|
|
||||||
|
if (!doesTimeMatch) console.log("Time does not match");
|
||||||
|
if (!doesSignatureMatch) console.log("Signature does not match");
|
||||||
|
if (!doesSenderMatch) console.log("Sender does not match");
|
||||||
|
if (!doesFeeRecipientNameMatch) console.log("Recipient does not match");
|
||||||
|
if (!doesFeeAmountMatch) console.log("FeeAmount does not match");
|
||||||
|
return (
|
||||||
|
doesTimeMatch &&
|
||||||
|
doesSignatureMatch &&
|
||||||
|
doesSenderMatch &&
|
||||||
|
doesFeeRecipientNameMatch &&
|
||||||
|
doesFeeAmountMatch
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const verifyPayment = async (publishToVerify: Issue) => {
|
||||||
|
if (!publishToVerify) return false;
|
||||||
|
|
||||||
|
const publishFeeData = objectToPublishFeeData(publishToVerify);
|
||||||
|
|
||||||
|
if (objectHasNullValues(publishFeeData)) return false;
|
||||||
|
|
||||||
|
const verifyFunctionsList: Promise<boolean>[] = [];
|
||||||
|
|
||||||
|
verifyFunctionsList.push(verifySignature(publishFeeData));
|
||||||
|
|
||||||
|
const paymentChecks = await Promise.all(verifyFunctionsList);
|
||||||
|
return paymentChecks.every(check => check === true);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const appendIsPaidToFeeData = (issue: Issue, isPaid: boolean): Issue => {
|
||||||
|
return {
|
||||||
|
...issue,
|
||||||
|
feeData: {
|
||||||
|
...(issue?.feeData || { signature: undefined, senderName: "" }),
|
||||||
|
isPaid,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export const verifyAllPayments = async (issues: Issue[]) => {
|
||||||
|
const verifiedPayments = await Promise.all(
|
||||||
|
issues.map(issue => verifyPayment(issue))
|
||||||
|
);
|
||||||
|
|
||||||
|
return issues.map((issue, index) => {
|
||||||
|
return appendIsPaidToFeeData(issue, verifiedPayments[index]);
|
||||||
|
});
|
||||||
|
};
|
87
src/global.d.ts
vendored
87
src/global.d.ts
vendored
@ -1,55 +1,56 @@
|
|||||||
// src/global.d.ts
|
// src/global.d.ts
|
||||||
interface QortalRequestOptions {
|
interface QortalRequestOptions {
|
||||||
action: string
|
action: string;
|
||||||
name?: string
|
name?: string;
|
||||||
service?: string
|
service?: string;
|
||||||
data64?: string
|
data64?: string;
|
||||||
title?: string
|
title?: string;
|
||||||
description?: string
|
description?: string;
|
||||||
category?: string
|
category?: string;
|
||||||
tags?: string[]
|
tags?: string[];
|
||||||
identifier?: string
|
identifier?: string;
|
||||||
address?: string
|
address?: string;
|
||||||
metaData?: string
|
metaData?: string;
|
||||||
encoding?: string
|
encoding?: string;
|
||||||
includeMetadata?: boolean
|
includeMetadata?: boolean;
|
||||||
limit?: numebr
|
limit?: numebr;
|
||||||
offset?: number
|
offset?: number;
|
||||||
reverse?: boolean
|
reverse?: boolean;
|
||||||
resources?: any[]
|
resources?: any[];
|
||||||
filename?: string
|
filename?: string;
|
||||||
list_name?: string
|
list_name?: string;
|
||||||
item?: string
|
item?: string;
|
||||||
items?: strings[]
|
items?: strings[];
|
||||||
tag1?: string
|
tag1?: string;
|
||||||
tag2?: string
|
tag2?: string;
|
||||||
tag3?: string
|
tag3?: string;
|
||||||
tag4?: string
|
tag4?: string;
|
||||||
tag5?: string
|
tag5?: string;
|
||||||
coin?: string
|
coin?: string;
|
||||||
destinationAddress?: string
|
destinationAddress?: string;
|
||||||
amount?: number
|
amount?: number;
|
||||||
blob?: Blob
|
blob?: Blob;
|
||||||
mimeType?: string
|
mimeType?: string;
|
||||||
file?: File
|
file?: File;
|
||||||
encryptedData?: string
|
encryptedData?: string;
|
||||||
name?: string
|
name?: string;
|
||||||
mode?: string
|
mode?: string;
|
||||||
query?: string
|
query?: string;
|
||||||
excludeBlocked?: boolean
|
excludeBlocked?: boolean;
|
||||||
exactMatchNames?: boolean
|
exactMatchNames?: boolean;
|
||||||
|
message?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare function qortalRequest(options: QortalRequestOptions): Promise<any>
|
declare function qortalRequest(options: QortalRequestOptions): Promise<any>;
|
||||||
declare function qortalRequestWithTimeout(
|
declare function qortalRequestWithTimeout(
|
||||||
options: QortalRequestOptions,
|
options: QortalRequestOptions,
|
||||||
time: number
|
time: number
|
||||||
): Promise<any>
|
): Promise<any>;
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
_qdnBase: any // Replace 'any' with the appropriate type if you know it
|
_qdnBase: any; // Replace 'any' with the appropriate type if you know it
|
||||||
_qdnTheme: string
|
_qdnTheme: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,6 +58,6 @@ declare global {
|
|||||||
interface Window {
|
interface Window {
|
||||||
showSaveFilePicker: (
|
showSaveFilePicker: (
|
||||||
options?: SaveFilePickerOptions
|
options?: SaveFilePickerOptions
|
||||||
) => Promise<FileSystemFileHandle>
|
) => Promise<FileSystemFileHandle>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,12 @@ import { useDispatch, useSelector } from "react-redux";
|
|||||||
import {
|
import {
|
||||||
addFiles,
|
addFiles,
|
||||||
addToHashMap,
|
addToHashMap,
|
||||||
|
Issue,
|
||||||
removeFromHashMap,
|
removeFromHashMap,
|
||||||
setCountNewFiles,
|
setCountNewFiles,
|
||||||
upsertFiles,
|
upsertFiles,
|
||||||
upsertFilesBeginning,
|
upsertFilesBeginning,
|
||||||
upsertFilteredFiles,
|
upsertFilteredFiles,
|
||||||
Video,
|
|
||||||
} from "../state/features/fileSlice.ts";
|
} from "../state/features/fileSlice.ts";
|
||||||
import {
|
import {
|
||||||
setFilesPerNamePublished,
|
setFilesPerNamePublished,
|
||||||
@ -24,7 +24,8 @@ import {
|
|||||||
QSUPPORT_PLAYLIST_BASE,
|
QSUPPORT_PLAYLIST_BASE,
|
||||||
} from "../constants/Identifiers.ts";
|
} from "../constants/Identifiers.ts";
|
||||||
import { queue } from "../wrappers/GlobalWrapper";
|
import { queue } from "../wrappers/GlobalWrapper";
|
||||||
import { getCategoriesFetchString } from "../components/common/CategoryList/CategoryList.tsx";
|
import { log } from "../constants/Misc.ts";
|
||||||
|
import { verifyAllPayments } from "../constants/PublishFees/VerifyPayment.ts";
|
||||||
|
|
||||||
export const useFetchIssues = () => {
|
export const useFetchIssues = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -50,7 +51,7 @@ export const useFetchIssues = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const checkAndUpdateIssue = React.useCallback(
|
const checkAndUpdateIssue = React.useCallback(
|
||||||
(video: Video) => {
|
(video: Issue) => {
|
||||||
const existingVideo = hashMapFiles[video.id];
|
const existingVideo = hashMapFiles[video.id];
|
||||||
if (!existingVideo) {
|
if (!existingVideo) {
|
||||||
return true;
|
return true;
|
||||||
@ -97,10 +98,10 @@ export const useFetchIssues = () => {
|
|||||||
videoId: issueID,
|
videoId: issueID,
|
||||||
content,
|
content,
|
||||||
});
|
});
|
||||||
console.log("response is: ", res);
|
|
||||||
res?.isValid
|
res?.isValid
|
||||||
? dispatch(addToHashMap(res))
|
? dispatch(addToHashMap(res))
|
||||||
: dispatch(removeFromHashMap(issueID));
|
: dispatch(removeFromHashMap(issueID));
|
||||||
|
return res;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
retries = retries + 1;
|
retries = retries + 1;
|
||||||
if (retries < 2) {
|
if (retries < 2) {
|
||||||
@ -112,7 +113,7 @@ export const useFetchIssues = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getNewFiles = React.useCallback(async () => {
|
const getNewIssues = React.useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
dispatch(setIsLoadingGlobal(true));
|
dispatch(setIsLoadingGlobal(true));
|
||||||
|
|
||||||
@ -149,7 +150,7 @@ export const useFetchIssues = () => {
|
|||||||
fetchAll = responseData.slice(0, findVideo);
|
fetchAll = responseData.slice(0, findVideo);
|
||||||
}
|
}
|
||||||
|
|
||||||
const structureData = fetchAll.map((video: any): Video => {
|
const structureData = fetchAll.map((video: any): Issue => {
|
||||||
return {
|
return {
|
||||||
title: video?.metadata?.title,
|
title: video?.metadata?.title,
|
||||||
category: video?.metadata?.category,
|
category: video?.metadata?.category,
|
||||||
@ -186,20 +187,21 @@ export const useFetchIssues = () => {
|
|||||||
}
|
}
|
||||||
}, [videos, hashMapFiles]);
|
}, [videos, hashMapFiles]);
|
||||||
|
|
||||||
const getFiles = React.useCallback(
|
const getIssues = React.useCallback(
|
||||||
async (
|
async (
|
||||||
filters = {},
|
filters = {},
|
||||||
reset?: boolean,
|
reset?: boolean,
|
||||||
resetFilers?: boolean,
|
resetFilters?: boolean,
|
||||||
limit?: number
|
limit?: number
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
name = "",
|
name = "",
|
||||||
categories = [],
|
categories = "",
|
||||||
|
QappName = "",
|
||||||
keywords = "",
|
keywords = "",
|
||||||
type = "",
|
type = "",
|
||||||
}: any = resetFilers ? {} : filters;
|
}: any = resetFilters ? {} : filters;
|
||||||
let offset = videos.length;
|
let offset = videos.length;
|
||||||
if (reset) {
|
if (reset) {
|
||||||
offset = 0;
|
offset = 0;
|
||||||
@ -211,10 +213,17 @@ export const useFetchIssues = () => {
|
|||||||
defaultUrl += `&name=${name}`;
|
defaultUrl += `&name=${name}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (categories.length > 0) {
|
if (categories) {
|
||||||
defaultUrl += "&description=" + getCategoriesFetchString(categories);
|
defaultUrl += "&description=";
|
||||||
}
|
if (log) console.log("categories: ", categories);
|
||||||
|
if (categories) defaultUrl += categories;
|
||||||
|
|
||||||
|
if (log) console.log("description: ", defaultUrl);
|
||||||
|
}
|
||||||
|
if (QappName) {
|
||||||
|
defaultUrl += `&query=${QappName}`;
|
||||||
|
}
|
||||||
|
if (log) console.log("defaultURL: ", defaultUrl);
|
||||||
if (keywords) {
|
if (keywords) {
|
||||||
defaultUrl = defaultUrl + `&query=${keywords}`;
|
defaultUrl = defaultUrl + `&query=${keywords}`;
|
||||||
}
|
}
|
||||||
@ -236,47 +245,48 @@ export const useFetchIssues = () => {
|
|||||||
});
|
});
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
|
|
||||||
// const responseData = await qortalRequest({
|
let structureData = responseData.map((issue: any): Issue => {
|
||||||
// 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 {
|
return {
|
||||||
title: video?.metadata?.title,
|
title: issue?.metadata?.title,
|
||||||
service: video?.service,
|
service: issue?.service,
|
||||||
category: video?.metadata?.category,
|
category: issue?.metadata?.category,
|
||||||
categoryName: video?.metadata?.categoryName,
|
categoryName: issue?.metadata?.categoryName,
|
||||||
tags: video?.metadata?.tags || [],
|
tags: issue?.metadata?.tags || [],
|
||||||
description: video?.metadata?.description,
|
description: issue?.metadata?.description,
|
||||||
created: video?.created,
|
created: issue?.created,
|
||||||
updated: video?.updated,
|
updated: issue?.updated,
|
||||||
user: video.name,
|
user: issue.name,
|
||||||
videoImage: "",
|
videoImage: "",
|
||||||
id: video.identifier,
|
id: issue.identifier,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
if (reset) {
|
const verifiedIssuePromises: Promise<Issue>[] = [];
|
||||||
dispatch(addFiles(structureData));
|
|
||||||
} else {
|
|
||||||
dispatch(upsertFiles(structureData));
|
|
||||||
}
|
|
||||||
for (const content of structureData) {
|
for (const content of structureData) {
|
||||||
if (content.user && content.id) {
|
if (content.user && content.id) {
|
||||||
const res = checkAndUpdateIssue(content);
|
const res = checkAndUpdateIssue(content);
|
||||||
|
const issue: Promise<Issue> = getIssue(
|
||||||
|
content.user,
|
||||||
|
content.id,
|
||||||
|
content
|
||||||
|
);
|
||||||
|
verifiedIssuePromises.push(issue);
|
||||||
if (res) {
|
if (res) {
|
||||||
queue.push(() => getIssue(content.user, content.id, content));
|
queue.push(() => issue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const issues = await Promise.all(verifiedIssuePromises);
|
||||||
|
const verifiedIssues = await verifyAllPayments(issues);
|
||||||
|
structureData = structureData.map((issue, index) => {
|
||||||
|
return {
|
||||||
|
...issue,
|
||||||
|
feeData: verifiedIssues[index]?.feeData,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (reset) dispatch(addFiles(structureData));
|
||||||
|
else dispatch(upsertFiles(structureData));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log({ error });
|
console.log({ error });
|
||||||
} finally {
|
} finally {
|
||||||
@ -285,7 +295,7 @@ export const useFetchIssues = () => {
|
|||||||
[videos, hashMapFiles]
|
[videos, hashMapFiles]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getFilesFiltered = React.useCallback(
|
const getIssuesFiltered = React.useCallback(
|
||||||
async (filterValue: string) => {
|
async (filterValue: string) => {
|
||||||
try {
|
try {
|
||||||
const offset = filteredVideos.length;
|
const offset = filteredVideos.length;
|
||||||
@ -314,7 +324,7 @@ export const useFetchIssues = () => {
|
|||||||
// exactMatchNames: true,
|
// exactMatchNames: true,
|
||||||
// name: names
|
// name: names
|
||||||
// })
|
// })
|
||||||
const structureData = responseData.map((video: any): Video => {
|
const structureData = responseData.map((video: any): Issue => {
|
||||||
return {
|
return {
|
||||||
title: video?.metadata?.title,
|
title: video?.metadata?.title,
|
||||||
category: video?.metadata?.category,
|
category: video?.metadata?.category,
|
||||||
@ -345,7 +355,7 @@ export const useFetchIssues = () => {
|
|||||||
[filteredVideos, hashMapFiles]
|
[filteredVideos, hashMapFiles]
|
||||||
);
|
);
|
||||||
|
|
||||||
const checkNewFiles = React.useCallback(async () => {
|
const checkNewIssues = React.useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QSUPPORT_FILE_BASE}&limit=20&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true`;
|
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QSUPPORT_FILE_BASE}&limit=20&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true`;
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
@ -382,7 +392,7 @@ export const useFetchIssues = () => {
|
|||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
}, [videos]);
|
}, [videos]);
|
||||||
|
|
||||||
const getFilesCount = React.useCallback(async () => {
|
const getIssuesCount = React.useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
let url = `/arbitrary/resources/search?mode=ALL&includemetadata=false&limit=0&service=DOCUMENT&identifier=${QSUPPORT_FILE_BASE}`;
|
let url = `/arbitrary/resources/search?mode=ALL&includemetadata=false&limit=0&service=DOCUMENT&identifier=${QSUPPORT_FILE_BASE}`;
|
||||||
|
|
||||||
@ -411,13 +421,13 @@ export const useFetchIssues = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getFiles,
|
getIssues,
|
||||||
checkAndUpdateFile: checkAndUpdateIssue,
|
checkAndUpdateIssue,
|
||||||
getFile: getIssue,
|
getIssue,
|
||||||
hashMapFiles,
|
hashMapFiles,
|
||||||
getNewFiles,
|
getNewIssues,
|
||||||
checkNewFiles,
|
checkNewIssues,
|
||||||
getFilesFiltered,
|
getIssuesFiltered,
|
||||||
getFilesCount,
|
getIssuesCount,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,25 +1,25 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
export function useWindowSize() {
|
export function useWindowSize() {
|
||||||
const [windowSize, setWindowSize] = useState<any>({
|
const [windowSize, setWindowSize] = useState<any>({
|
||||||
width: undefined,
|
width: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleResize() {
|
function handleResize() {
|
||||||
setWindowSize({
|
setWindowSize({
|
||||||
width: window.innerWidth,
|
width: window.innerWidth,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("resize", handleResize);
|
window.addEventListener("resize", handleResize);
|
||||||
|
|
||||||
// Call handler right away so state gets updated with initial window size
|
// Call handler right away so state gets updated with initial window size
|
||||||
handleResize();
|
handleResize();
|
||||||
|
|
||||||
// Remove event listener on cleanup
|
// Remove event listener on cleanup
|
||||||
return () => window.removeEventListener("resize", handleResize);
|
return () => window.removeEventListener("resize", handleResize);
|
||||||
}, []); // Empty array means that effect doesn't depend on any values from props or state, so it runs once when the component mounts, and never re-runs.
|
}, []); // Empty array means that effect doesn't depend on any values from props or state, so it runs once when the component mounts, and never re-runs.
|
||||||
|
|
||||||
return windowSize;
|
return windowSize;
|
||||||
}
|
}
|
||||||
|
456
src/index.css
456
src/index.css
@ -1,229 +1,229 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Cambon Light';
|
font-family: 'Cambon Light';
|
||||||
src: url("./styles/fonts/Cambon-Light.ttf") format("truetype");
|
src: url("./styles/fonts/Cambon-Light.ttf") format("truetype");
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Merriweather Sans';
|
font-family: 'Merriweather Sans';
|
||||||
src: url("./styles/fonts/Merriweather Sans.ttf") format("truetype");
|
src: url("./styles/fonts/Merriweather Sans.ttf") format("truetype");
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Karla';
|
font-family: 'Karla';
|
||||||
src: url("./styles/fonts/Karla.ttf") format("truetype");
|
src: url("./styles/fonts/Karla.ttf") format("truetype");
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Proxima Nova';
|
font-family: 'Proxima Nova';
|
||||||
src: url("./styles/fonts/ProximaNova.otf") format("opentype");
|
src: url("./styles/fonts/ProximaNova.otf") format("opentype");
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Raleway';
|
font-family: 'Raleway';
|
||||||
src: url("./styles/fonts/Raleway.ttf") format("truetype");
|
src: url("./styles/fonts/Raleway.ttf") format("truetype");
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Catamaran';
|
font-family: 'Catamaran';
|
||||||
src: url("./styles/fonts/Catamaran.ttf") format("truetype");
|
src: url("./styles/fonts/Catamaran.ttf") format("truetype");
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Oxygen';
|
font-family: 'Oxygen';
|
||||||
src: url("./styles/fonts/Oxygen.ttf") format("truetype");
|
src: url("./styles/fonts/Oxygen.ttf") format("truetype");
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Cairo';
|
font-family: 'Cairo';
|
||||||
src: url("./styles/fonts/Cairo.ttf") format("truetype");
|
src: url("./styles/fonts/Cairo.ttf") format("truetype");
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.line-clamp {
|
.line-clamp {
|
||||||
height: 100px;
|
height: 100px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: 5; /* number of lines to show */
|
-webkit-line-clamp: 5; /* number of lines to show */
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-btn:hover {
|
.edit-btn:hover {
|
||||||
opacity: .75;
|
opacity: .75;
|
||||||
transition: .2s all;
|
transition: .2s all;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-image {
|
.post-image {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.test-grid {
|
.test-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
grid-template-columns: repeat(4, 1fr);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
min-height: 25px;
|
min-height: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.test-grid-item {
|
.test-grid-item {
|
||||||
border: 1px solid powderblue;
|
border: 1px solid powderblue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
body::-webkit-scrollbar-track {
|
body::-webkit-scrollbar-track {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
body::-webkit-scrollbar-track:hover {
|
body::-webkit-scrollbar-track:hover {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
body::-webkit-scrollbar {
|
body::-webkit-scrollbar {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
body::-webkit-scrollbar-thumb {
|
body::-webkit-scrollbar-thumb {
|
||||||
background-color: #838eee;
|
background-color: #838eee;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background-clip: content-box;
|
background-clip: content-box;
|
||||||
border: 4px solid transparent;
|
border: 4px solid transparent;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body::-webkit-scrollbar-thumb:hover {
|
body::-webkit-scrollbar-thumb:hover {
|
||||||
background-color: #6270f0;
|
background-color: #6270f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.MuiList-root::-webkit-scrollbar-track {
|
.MuiList-root::-webkit-scrollbar-track {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
.MuiList-root::-webkit-scrollbar-track:hover {
|
.MuiList-root::-webkit-scrollbar-track:hover {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.MuiList-root::-webkit-scrollbar {
|
.MuiList-root::-webkit-scrollbar {
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.MuiList-root::-webkit-scrollbar-thumb {
|
.MuiList-root::-webkit-scrollbar-thumb {
|
||||||
background-color: lightgray;
|
background-color: lightgray;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background-clip: content-box;
|
background-clip: content-box;
|
||||||
border: 4px solid transparent;
|
border: 4px solid transparent;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.MuiList-root::-webkit-scrollbar-thumb:hover {
|
.MuiList-root::-webkit-scrollbar-thumb:hover {
|
||||||
background-color: lightslategray;
|
background-color: lightslategray;
|
||||||
}
|
}
|
||||||
|
|
||||||
.my-masonry-grid {
|
.my-masonry-grid {
|
||||||
display: -webkit-box; /* Not needed if autoprefixing */
|
display: -webkit-box; /* Not needed if autoprefixing */
|
||||||
display: -ms-flexbox; /* Not needed if autoprefixing */
|
display: -ms-flexbox; /* Not needed if autoprefixing */
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-left: -20px; /* gutter size offset */
|
margin-left: -20px; /* gutter size offset */
|
||||||
width: auto;
|
width: auto;
|
||||||
padding: 15px 20px;
|
padding: 15px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.my-masonry-grid_column {
|
.my-masonry-grid_column {
|
||||||
padding-left: 20px; /* gutter size */
|
padding-left: 20px; /* gutter size */
|
||||||
background-clip: padding-box;
|
background-clip: padding-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Style your items */
|
/* Style your items */
|
||||||
.my-masonry-grid_column > li { /* change div to reference your elements you put in <Masonry> */
|
.my-masonry-grid_column > li { /* change div to reference your elements you put in <Masonry> */
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.my-svg path {
|
.my-svg path {
|
||||||
fill: red
|
fill: red
|
||||||
}
|
}
|
||||||
|
|
||||||
.qortal-link {
|
.qortal-link {
|
||||||
text-decoration: none; /* Removes the underline */
|
text-decoration: none; /* Removes the underline */
|
||||||
color: inherit; /* Inherits the color of the parent element */
|
color: inherit; /* Inherits the color of the parent element */
|
||||||
}
|
}
|
||||||
.qortal-link:hover, a:focus {
|
.qortal-link:hover, a:focus {
|
||||||
text-decoration: underline; /* Adds underline on hover and focus for accessibility */
|
text-decoration: underline; /* Adds underline on hover and focus for accessibility */
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-icon {
|
.download-icon {
|
||||||
transition: all 0.5s ease-in-out;
|
transition: all 0.5s ease-in-out;
|
||||||
animation: downloadIconAnimation 2s infinite;
|
animation: downloadIconAnimation 2s infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes downloadIconAnimation {
|
@keyframes downloadIconAnimation {
|
||||||
0% { transform: scale(1); fill: #fff; }
|
0% { transform: scale(1); fill: #fff; }
|
||||||
50% { transform: scale(1.2); fill: #3498db; }
|
50% { transform: scale(1.2); fill: #3498db; }
|
||||||
100% { transform: scale(1); fill: #fff; }
|
100% { transform: scale(1); fill: #fff; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.closePlayer {
|
.closePlayer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
z-index: 8000;
|
z-index: 8000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* When the screen is 600px or less, display .myClassUnder600 and hide .myClassOver600 */
|
/* When the screen is 600px or less, display .myClassUnder600 and hide .myClassOver600 */
|
||||||
@media screen and (max-width: 600px) {
|
@media screen and (max-width: 600px) {
|
||||||
.myClassUnder600 {
|
.myClassUnder600 {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 601px) {
|
@media screen and (min-width: 601px) {
|
||||||
.myClassOver600 {
|
.myClassOver600 {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ql-editor {
|
.ql-editor {
|
||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
width: 100%
|
width: 100%
|
||||||
}
|
}
|
||||||
|
|
||||||
.ql-editor img {
|
.ql-editor img {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ql-container {
|
.ql-container {
|
||||||
font-size: 16px
|
font-size: 16px
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover-click {
|
.hover-click {
|
||||||
transition: opacity 0.2s;
|
transition: opacity 0.2s;
|
||||||
}
|
}
|
||||||
.hover-click:hover {
|
.hover-click:hover {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
34
src/main.tsx
34
src/main.tsx
@ -1,17 +1,17 @@
|
|||||||
import ReactDOM from 'react-dom/client'
|
import ReactDOM from 'react-dom/client'
|
||||||
import App from './App'
|
import App from './App'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
import { BrowserRouter } from 'react-router-dom'
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
interface CustomWindow extends Window {
|
interface CustomWindow extends Window {
|
||||||
_qdnBase: string
|
_qdnBase: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const customWindow = window as unknown as CustomWindow
|
const customWindow = window as unknown as CustomWindow
|
||||||
|
|
||||||
const baseUrl = customWindow?._qdnBase || ''
|
const baseUrl = customWindow?._qdnBase || ''
|
||||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||||
<BrowserRouter basename={baseUrl}>
|
<BrowserRouter basename={baseUrl}>
|
||||||
<App />
|
<App />
|
||||||
<div id="modal-root" />
|
<div id="modal-root" />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
)
|
)
|
||||||
|
@ -3,7 +3,7 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { RootState } from "../../state/store";
|
import { RootState } from "../../state/store";
|
||||||
import { Box, useTheme } from "@mui/material";
|
import { Box, useTheme } from "@mui/material";
|
||||||
import { FileContainer } from "./IssueList-styles.tsx";
|
import { IssueContainer } from "./IssueList-styles.tsx";
|
||||||
import ResponsiveImage from "../../components/ResponsiveImage";
|
import ResponsiveImage from "../../components/ResponsiveImage";
|
||||||
import { ChannelCard, ChannelTitle } from "./Home-styles";
|
import { ChannelCard, ChannelTitle } from "./Home-styles";
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ export const Channels = ({ mode }: VideoListProps) => {
|
|||||||
minHeight: "50vh",
|
minHeight: "50vh",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FileContainer>
|
<IssueContainer>
|
||||||
{publishNames &&
|
{publishNames &&
|
||||||
publishNames?.slice(0, 10).map(name => {
|
publishNames?.slice(0, 10).map(name => {
|
||||||
let avatarUrl = "";
|
let avatarUrl = "";
|
||||||
@ -62,7 +62,7 @@ export const Channels = ({ mode }: VideoListProps) => {
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</FileContainer>
|
</IssueContainer>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { styled } from "@mui/system";
|
import { styled } from "@mui/system";
|
||||||
import { Box, Button, Grid, Typography } from "@mui/material";
|
import { Box, Button, Grid, Typography } from "@mui/material";
|
||||||
|
import { fontSizeSmall } from "../../constants/Misc.ts";
|
||||||
|
|
||||||
export const SubtitleContainer = styled(Box)(({ theme }) => ({
|
export const SubtitleContainer = styled(Box)(({ theme }) => ({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@ -88,12 +89,12 @@ export const ThemeButton = styled(Button)(({ theme }) => ({
|
|||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
backgroundColor: "#01a9e9",
|
backgroundColor: "#01a9e9",
|
||||||
fontSize: "18px",
|
fontSize: "18px",
|
||||||
"&:hover": { backgroundColor: "#3e74c1" },
|
"&:hover": { backgroundColor: "#008fcd" },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const ThemeButtonBright = styled(Button)(({ theme }) => ({
|
export const ThemeButtonBright = styled(Button)(({ theme }) => ({
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
backgroundColor: "#44c4ff",
|
backgroundColor: "#44c4ff",
|
||||||
fontSize: "18px",
|
fontSize: fontSizeSmall,
|
||||||
"&:hover": { backgroundColor: "#01a9e9" },
|
"&:hover": { backgroundColor: "#01a9e9" },
|
||||||
}));
|
}));
|
||||||
|
@ -12,40 +12,48 @@ import {
|
|||||||
changefilterName,
|
changefilterName,
|
||||||
changefilterSearch,
|
changefilterSearch,
|
||||||
changeFilterType,
|
changeFilterType,
|
||||||
|
setQappNames,
|
||||||
} from "../../state/features/fileSlice.ts";
|
} from "../../state/features/fileSlice.ts";
|
||||||
import { allCategoryData } from "../../constants/Categories/1stCategories.ts";
|
import {
|
||||||
|
allCategoryData,
|
||||||
|
IssueState,
|
||||||
|
} from "../../constants/Categories/Categories.ts";
|
||||||
import {
|
import {
|
||||||
CategoryList,
|
CategoryList,
|
||||||
CategoryListRef,
|
CategoryListRef,
|
||||||
|
getCategoriesFetchString,
|
||||||
} from "../../components/common/CategoryList/CategoryList.tsx";
|
} from "../../components/common/CategoryList/CategoryList.tsx";
|
||||||
import { StatsData } from "../../components/StatsData.tsx";
|
import { StatsData } from "../../components/StatsData.tsx";
|
||||||
|
import {
|
||||||
|
CategorySelect,
|
||||||
|
CategorySelectRef,
|
||||||
|
} from "../../components/common/CategoryList/CategorySelect.tsx";
|
||||||
|
import {
|
||||||
|
AutocompleteQappNames,
|
||||||
|
getPublishedQappNames,
|
||||||
|
QappNamesRef,
|
||||||
|
} from "../../components/common/AutocompleteQappNames.tsx";
|
||||||
|
|
||||||
interface HomeProps {
|
interface HomeProps {
|
||||||
mode?: string;
|
mode?: string;
|
||||||
}
|
}
|
||||||
export const Home = ({ mode }: HomeProps) => {
|
export const Home = ({ mode }: HomeProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const prevVal = useRef("");
|
|
||||||
const categoryListRef = useRef<CategoryListRef>(null);
|
|
||||||
const isFiltering = useSelector((state: RootState) => state.file.isFiltering);
|
const isFiltering = useSelector((state: RootState) => state.file.isFiltering);
|
||||||
const filterValue = useSelector((state: RootState) => state.file.filterValue);
|
const filterValue = useSelector((state: RootState) => state.file.filterValue);
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
const filterType = useSelector((state: RootState) => state.file.filterType);
|
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 => {
|
const setFilterType = payload => {
|
||||||
dispatch(changeFilterType(payload));
|
dispatch(changeFilterType(payload));
|
||||||
};
|
};
|
||||||
const filterSearch = useSelector(
|
const filterSearch = useSelector(
|
||||||
(state: RootState) => state.file.filterSearch
|
(state: RootState) => state.file.filterSearch
|
||||||
);
|
);
|
||||||
|
const QappNames = useSelector(
|
||||||
|
(state: RootState) => state.file.publishedQappNames
|
||||||
|
);
|
||||||
|
const autocompleteRef = useRef<QappNamesRef>(null);
|
||||||
|
|
||||||
const setFilterSearch = payload => {
|
const setFilterSearch = payload => {
|
||||||
dispatch(changefilterSearch(payload));
|
dispatch(changefilterSearch(payload));
|
||||||
@ -59,60 +67,66 @@ export const Home = ({ mode }: HomeProps) => {
|
|||||||
const isFilterMode = useRef(false);
|
const isFilterMode = useRef(false);
|
||||||
const firstFetch = useRef(false);
|
const firstFetch = useRef(false);
|
||||||
const afterFetch = useRef(false);
|
const afterFetch = useRef(false);
|
||||||
const isFetchingFiltered = useRef(false);
|
|
||||||
const isFetching = useRef(false);
|
const isFetching = useRef(false);
|
||||||
|
const prevVal = useRef("");
|
||||||
|
const categoryListRef = useRef<CategoryListRef>(null);
|
||||||
|
const categorySelectRef = useRef<CategorySelectRef>(null);
|
||||||
|
|
||||||
const countNewFiles = useSelector(
|
const [showCategoryList, setShowCategoryList] = useState<boolean>(true);
|
||||||
(state: RootState) => state.file.countNewFiles
|
const [showCategorySelect, setShowCategorySelect] = useState<boolean>(true);
|
||||||
);
|
|
||||||
const userAvatarHash = useSelector(
|
|
||||||
(state: RootState) => state.global.userAvatarHash
|
|
||||||
);
|
|
||||||
|
|
||||||
const { files: globalVideos } = useSelector((state: RootState) => state.file);
|
const { files: globalVideos } = useSelector((state: RootState) => state.file);
|
||||||
|
|
||||||
const setSelectedCategoryFiles = payload => {};
|
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const filteredFiles = useSelector(
|
const filteredFiles = useSelector(
|
||||||
(state: RootState) => state.file.filteredFiles
|
(state: RootState) => state.file.filteredFiles
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [QappNamesParam, setQappNamesParam] = useState<string[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getPublishedQappNames().then(QappNamesResult => {
|
||||||
|
dispatch(setQappNames(QappNamesResult));
|
||||||
|
setQappNamesParam(QappNamesResult);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getFiles,
|
getIssues,
|
||||||
checkAndUpdateFile,
|
checkAndUpdateIssue,
|
||||||
getFile,
|
getIssue,
|
||||||
hashMapFiles,
|
hashMapFiles,
|
||||||
getNewFiles,
|
getNewIssues,
|
||||||
checkNewFiles,
|
checkNewIssues,
|
||||||
getFilesFiltered,
|
getIssuesFiltered,
|
||||||
getFilesCount,
|
getIssuesCount,
|
||||||
} = useFetchIssues();
|
} = useFetchIssues();
|
||||||
|
|
||||||
const getFilesHandler = React.useCallback(
|
const getIssuesHandler = React.useCallback(
|
||||||
async (reset?: boolean, resetFilers?: boolean) => {
|
async (reset?: boolean, resetFilters?: boolean) => {
|
||||||
if (!firstFetch.current || !afterFetch.current) return;
|
if (!firstFetch.current || !afterFetch.current) return;
|
||||||
if (isFetching.current) return;
|
if (isFetching.current) return;
|
||||||
isFetching.current = true;
|
isFetching.current = true;
|
||||||
const selectedCategories =
|
const selectedCategories =
|
||||||
categoryListRef.current.getSelectedCategories() || [];
|
categoryListRef.current?.getSelectedCategories() || [];
|
||||||
|
const issueType = categorySelectRef?.current?.getSelectedCategory();
|
||||||
await getFiles(
|
if (issueType) selectedCategories[2] = issueType;
|
||||||
|
await getIssues(
|
||||||
{
|
{
|
||||||
name: filterName,
|
name: filterName,
|
||||||
categories: selectedCategories,
|
categories: getCategoriesFetchString(selectedCategories),
|
||||||
|
QappName: autocompleteRef?.current?.getQappNameFetchString(),
|
||||||
keywords: filterSearch,
|
keywords: filterSearch,
|
||||||
type: filterType,
|
type: filterType,
|
||||||
},
|
},
|
||||||
reset,
|
reset,
|
||||||
resetFilers
|
resetFilters
|
||||||
);
|
);
|
||||||
isFetching.current = false;
|
isFetching.current = false;
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
getFiles,
|
getIssues,
|
||||||
filterValue,
|
filterValue,
|
||||||
getFilesFiltered,
|
getIssuesFiltered,
|
||||||
isFiltering,
|
isFiltering,
|
||||||
filterName,
|
filterName,
|
||||||
filterSearch,
|
filterSearch,
|
||||||
@ -122,33 +136,33 @@ export const Home = ({ mode }: HomeProps) => {
|
|||||||
|
|
||||||
const searchOnEnter = e => {
|
const searchOnEnter = e => {
|
||||||
if (e.keyCode == 13) {
|
if (e.keyCode == 13) {
|
||||||
getFilesHandler(true);
|
getIssuesHandler(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isFiltering && filterValue !== prevVal?.current) {
|
if (isFiltering && filterValue !== prevVal?.current) {
|
||||||
prevVal.current = filterValue;
|
prevVal.current = filterValue;
|
||||||
getFilesHandler();
|
getIssuesHandler();
|
||||||
}
|
}
|
||||||
}, [filterValue, isFiltering, filteredFiles, getFilesCount]);
|
}, [filterValue, isFiltering, filteredFiles, getIssuesCount]);
|
||||||
|
|
||||||
const getFilesHandlerMount = React.useCallback(async () => {
|
const getFilesHandlerMount = React.useCallback(async () => {
|
||||||
if (firstFetch.current) return;
|
if (firstFetch.current) return;
|
||||||
firstFetch.current = true;
|
firstFetch.current = true;
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
await getFiles();
|
await getIssues();
|
||||||
afterFetch.current = true;
|
afterFetch.current = true;
|
||||||
isFetching.current = false;
|
isFetching.current = false;
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}, [getFiles]);
|
}, [getIssues]);
|
||||||
|
|
||||||
let videos = globalVideos;
|
let issues = globalVideos;
|
||||||
|
|
||||||
if (isFiltering) {
|
if (isFiltering) {
|
||||||
videos = filteredFiles;
|
issues = filteredFiles;
|
||||||
isFilterMode.current = true;
|
isFilterMode.current = true;
|
||||||
} else {
|
} else {
|
||||||
isFilterMode.current = false;
|
isFilterMode.current = false;
|
||||||
@ -199,9 +213,10 @@ export const Home = ({ mode }: HomeProps) => {
|
|||||||
setFilterSearch("");
|
setFilterSearch("");
|
||||||
setFilterName("");
|
setFilterName("");
|
||||||
categoryListRef.current?.clearCategories();
|
categoryListRef.current?.clearCategories();
|
||||||
|
categorySelectRef.current?.clearCategory();
|
||||||
|
autocompleteRef.current?.setSelectedValue(null);
|
||||||
ReactDOM.flushSync(() => {
|
ReactDOM.flushSync(() => {
|
||||||
getFilesHandler(true, true);
|
getIssuesHandler(true, true);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -268,7 +283,43 @@ export const Home = ({ mode }: HomeProps) => {
|
|||||||
fontSize: "20px",
|
fontSize: "20px",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<CategoryList categoryData={allCategoryData} ref={categoryListRef} />
|
{showCategoryList && (
|
||||||
|
<CategoryList
|
||||||
|
categoryData={allCategoryData}
|
||||||
|
ref={categoryListRef}
|
||||||
|
afterChange={value => {
|
||||||
|
setShowCategorySelect(!value[0]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{showCategorySelect && (
|
||||||
|
<CategorySelect
|
||||||
|
categoryData={IssueState}
|
||||||
|
ref={categorySelectRef}
|
||||||
|
sx={{ marginTop: "20px" }}
|
||||||
|
afterChange={value => {
|
||||||
|
setShowCategoryList(!value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{QappNamesParam.length > 0 && (
|
||||||
|
<AutocompleteQappNames
|
||||||
|
ref={autocompleteRef}
|
||||||
|
namesList={QappNamesParam}
|
||||||
|
sx={{ marginTop: "20px" }}
|
||||||
|
required={false}
|
||||||
|
afterChange={() => {
|
||||||
|
const currentSelectedCategories =
|
||||||
|
categoryListRef?.current?.getSelectedCategories();
|
||||||
|
categoryListRef?.current?.setSelectedCategories([
|
||||||
|
"3",
|
||||||
|
currentSelectedCategories[1],
|
||||||
|
currentSelectedCategories[2],
|
||||||
|
]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<ThemeButton
|
<ThemeButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -284,7 +335,7 @@ export const Home = ({ mode }: HomeProps) => {
|
|||||||
</ThemeButton>
|
</ThemeButton>
|
||||||
<ThemeButton
|
<ThemeButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
getFilesHandler(true);
|
getIssuesHandler(true);
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
marginTop: "20px",
|
marginTop: "20px",
|
||||||
@ -314,9 +365,9 @@ export const Home = ({ mode }: HomeProps) => {
|
|||||||
maxWidth: "1400px",
|
maxWidth: "1400px",
|
||||||
}}
|
}}
|
||||||
></SubtitleContainer>
|
></SubtitleContainer>
|
||||||
<IssueList issues={videos} />
|
<IssueList issues={issues} />
|
||||||
<LazyLoad
|
<LazyLoad
|
||||||
onLoadMore={getFilesHandler}
|
onLoadMore={getIssuesHandler}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
></LazyLoad>
|
></LazyLoad>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -8,8 +8,9 @@ import {
|
|||||||
TextField,
|
TextField,
|
||||||
Typography,
|
Typography,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
import { fontSizeMedium, fontSizeSmall } from "../../constants/Misc.ts";
|
||||||
|
|
||||||
export const FileContainer = styled(Box)(({ theme }) => ({
|
export const IssueContainer = styled(Box)(({ theme }) => ({
|
||||||
position: "relative",
|
position: "relative",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
padding: "15px",
|
padding: "15px",
|
||||||
@ -33,7 +34,7 @@ export const StoresRow = styled(Grid)(({ theme }) => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const VideoCard = styled(Grid)(({ theme }) => ({
|
export const IssueCard = styled(Grid)(({ theme }) => ({
|
||||||
position: "relative",
|
position: "relative",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
@ -89,14 +90,14 @@ const DoubleLine = styled(Typography)`
|
|||||||
|
|
||||||
export const VideoCardTitle = styled(DoubleLine)(({ theme }) => ({
|
export const VideoCardTitle = styled(DoubleLine)(({ theme }) => ({
|
||||||
fontFamily: "Cairo",
|
fontFamily: "Cairo",
|
||||||
fontSize: "16px",
|
fontSize: fontSizeMedium,
|
||||||
letterSpacing: "0.4px",
|
letterSpacing: "0.4px",
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
userSelect: "none",
|
userSelect: "none",
|
||||||
}));
|
}));
|
||||||
export const VideoCardName = styled(Typography)(({ theme }) => ({
|
export const VideoCardName = styled(Typography)(({ theme }) => ({
|
||||||
fontFamily: "Cairo",
|
fontFamily: "Cairo",
|
||||||
fontSize: "14px",
|
fontSize: fontSizeSmall,
|
||||||
letterSpacing: "0.4px",
|
letterSpacing: "0.4px",
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
userSelect: "none",
|
userSelect: "none",
|
||||||
@ -107,14 +108,16 @@ export const VideoCardName = styled(Typography)(({ theme }) => ({
|
|||||||
}));
|
}));
|
||||||
export const VideoUploadDate = styled(Typography)(({ theme }) => ({
|
export const VideoUploadDate = styled(Typography)(({ theme }) => ({
|
||||||
fontFamily: "Cairo",
|
fontFamily: "Cairo",
|
||||||
fontSize: "12px",
|
display: "span",
|
||||||
|
fontSize: fontSizeSmall,
|
||||||
letterSpacing: "0.4px",
|
letterSpacing: "0.4px",
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
userSelect: "none",
|
userSelect: "none",
|
||||||
}));
|
}));
|
||||||
export const BottomParent = styled(Box)(({ theme }) => ({
|
export const BottomParent = styled(Box)(({ theme }) => ({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "flex-start",
|
justifyItems: "flex-end",
|
||||||
|
alignItems: "flex-end",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
}));
|
}));
|
||||||
export const VideoCardDescription = styled(Typography)(({ theme }) => ({
|
export const VideoCardDescription = styled(Typography)(({ theme }) => ({
|
||||||
@ -155,11 +158,11 @@ export const MyStoresRow = styled(Grid)(({ theme }) => ({
|
|||||||
width: "100%",
|
width: "100%",
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const NameContainer = styled(Box)(({ theme }) => ({
|
export const NameAndDateContainer = styled(Box)(({ theme }) => ({
|
||||||
display: "flex",
|
display: "grid",
|
||||||
flexDirection: "row",
|
gridTemplateRows: "1fr 1fr",
|
||||||
justifyContent: "flex-start",
|
justifyContent: "end",
|
||||||
alignItems: "center",
|
alignContent: "center",
|
||||||
gap: "10px",
|
gap: "10px",
|
||||||
marginBottom: "10px",
|
marginBottom: "10px",
|
||||||
}));
|
}));
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import { Avatar, Box, Skeleton } from "@mui/material";
|
import { Avatar, Box, Skeleton } from "@mui/material";
|
||||||
import {
|
import {
|
||||||
BlockIconContainer,
|
BlockIconContainer,
|
||||||
BottomParent,
|
|
||||||
FileContainer,
|
|
||||||
IconsBox,
|
IconsBox,
|
||||||
NameContainer,
|
IssueCard,
|
||||||
VideoCard,
|
IssueContainer,
|
||||||
|
NameAndDateContainer,
|
||||||
VideoCardName,
|
VideoCardName,
|
||||||
VideoCardTitle,
|
VideoCardTitle,
|
||||||
VideoUploadDate,
|
VideoUploadDate,
|
||||||
@ -13,11 +12,10 @@ import {
|
|||||||
import EditIcon from "@mui/icons-material/Edit";
|
import EditIcon from "@mui/icons-material/Edit";
|
||||||
import {
|
import {
|
||||||
blockUser,
|
blockUser,
|
||||||
|
Issue,
|
||||||
setEditFile,
|
setEditFile,
|
||||||
Video,
|
|
||||||
} from "../../state/features/fileSlice.ts";
|
} from "../../state/features/fileSlice.ts";
|
||||||
import BlockIcon from "@mui/icons-material/Block";
|
import BlockIcon from "@mui/icons-material/Block";
|
||||||
import AttachFileIcon from "@mui/icons-material/AttachFile";
|
|
||||||
import { formatBytes } from "../IssueContent/IssueContent.tsx";
|
import { formatBytes } from "../IssueContent/IssueContent.tsx";
|
||||||
import { formatDate } from "../../utils/time.ts";
|
import { formatDate } from "../../utils/time.ts";
|
||||||
import React, { useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
@ -26,8 +24,12 @@ import { RootState } from "../../state/store.ts";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { getIconsFromObject } from "../../constants/Categories/CategoryFunctions.ts";
|
import { getIconsFromObject } from "../../constants/Categories/CategoryFunctions.ts";
|
||||||
|
|
||||||
|
import { IssueIcon, IssueIcons } from "../../components/common/IssueIcon.tsx";
|
||||||
|
import QORTicon from "../../assets/icons/qort.png";
|
||||||
|
import { fontSizeMedium } from "../../constants/Misc.ts";
|
||||||
|
|
||||||
interface FileListProps {
|
interface FileListProps {
|
||||||
issues: Video[];
|
issues: Issue[];
|
||||||
}
|
}
|
||||||
export const IssueList = ({ issues }: FileListProps) => {
|
export const IssueList = ({ issues }: FileListProps) => {
|
||||||
const hashMapIssues = useSelector(
|
const hashMapIssues = useSelector(
|
||||||
@ -60,16 +62,21 @@ export const IssueList = ({ issues }: FileListProps) => {
|
|||||||
}, [issues, hashMapIssues]);
|
}, [issues, hashMapIssues]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FileContainer>
|
<IssueContainer>
|
||||||
{filteredIssues.map((file: any, index: number) => {
|
{filteredIssues.map((issue: any, index: number) => {
|
||||||
const existingFile = hashMapIssues[file?.id];
|
const existingFile = hashMapIssues[issue?.id];
|
||||||
let hasHash = false;
|
let hasHash = false;
|
||||||
let fileObj = file;
|
let issueObj = issue;
|
||||||
if (existingFile) {
|
if (existingFile) {
|
||||||
fileObj = existingFile;
|
issueObj = existingFile;
|
||||||
hasHash = true;
|
hasHash = true;
|
||||||
}
|
}
|
||||||
const icon = getIconsFromObject(fileObj);
|
|
||||||
|
const issueIcons = getIconsFromObject(issueObj);
|
||||||
|
const fileBytes = issueObj?.files.reduce(
|
||||||
|
(acc, cur) => acc + (cur?.size || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -79,22 +86,22 @@ export const IssueList = ({ issues }: FileListProps) => {
|
|||||||
height: "75px",
|
height: "75px",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
}}
|
}}
|
||||||
key={fileObj.id}
|
key={issueObj.id}
|
||||||
onMouseEnter={() => setShowIcons(fileObj.id)}
|
onMouseEnter={() => setShowIcons(issueObj.id)}
|
||||||
onMouseLeave={() => setShowIcons(null)}
|
onMouseLeave={() => setShowIcons(null)}
|
||||||
>
|
>
|
||||||
{hasHash ? (
|
{hasHash ? (
|
||||||
<>
|
<>
|
||||||
<IconsBox
|
<IconsBox
|
||||||
sx={{
|
sx={{
|
||||||
opacity: showIcons === fileObj.id ? 1 : 0,
|
opacity: showIcons === issueObj.id ? 1 : 0,
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{fileObj?.user === username && (
|
{issueObj?.user === username && (
|
||||||
<BlockIconContainer
|
<BlockIconContainer
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
dispatch(setEditFile(fileObj));
|
dispatch(setEditFile(issueObj));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<EditIcon />
|
<EditIcon />
|
||||||
@ -102,10 +109,10 @@ export const IssueList = ({ issues }: FileListProps) => {
|
|||||||
</BlockIconContainer>
|
</BlockIconContainer>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{fileObj?.user !== username && (
|
{issueObj?.user !== username && (
|
||||||
<BlockIconContainer
|
<BlockIconContainer
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
blockUserFunc(fileObj?.user);
|
blockUserFunc(issueObj?.user);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<BlockIcon />
|
<BlockIcon />
|
||||||
@ -113,15 +120,14 @@ export const IssueList = ({ issues }: FileListProps) => {
|
|||||||
</BlockIconContainer>
|
</BlockIconContainer>
|
||||||
)}
|
)}
|
||||||
</IconsBox>
|
</IconsBox>
|
||||||
<VideoCard
|
<IssueCard
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/issue/${fileObj?.user}/${fileObj?.id}`);
|
navigate(`/issue/${issueObj?.user}/${issueObj?.id}`);
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
height: "100%",
|
height: "100%",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
gap: "25px",
|
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
@ -129,47 +135,59 @@ export const IssueList = ({ issues }: FileListProps) => {
|
|||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
gap: "25px",
|
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{icon ? (
|
<div
|
||||||
<img
|
style={{
|
||||||
src={icon}
|
display: "flex",
|
||||||
width="50px"
|
alignItems: "center",
|
||||||
style={{
|
width: "200px",
|
||||||
borderRadius: "5px",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<AttachFileIcon />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<VideoCardTitle
|
|
||||||
sx={{
|
|
||||||
width: "100px",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{formatBytes(
|
<IssueIcons
|
||||||
fileObj?.files.reduce(
|
iconSources={issueIcons}
|
||||||
(acc, cur) => acc + (cur?.size || 0),
|
style={{ marginRight: "20px" }}
|
||||||
0
|
showBackupIcon={true}
|
||||||
)
|
/>
|
||||||
)}
|
</div>
|
||||||
|
<VideoCardTitle
|
||||||
|
sx={{
|
||||||
|
width: "150px",
|
||||||
|
fontSize: fontSizeMedium,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{fileBytes > 0 && formatBytes(fileBytes)}
|
||||||
|
</VideoCardTitle>
|
||||||
|
<VideoCardTitle sx={{ fontWeight: "bold", width: "500px" }}>
|
||||||
|
{issueObj.title}
|
||||||
</VideoCardTitle>
|
</VideoCardTitle>
|
||||||
<VideoCardTitle>{fileObj.title}</VideoCardTitle>
|
|
||||||
</Box>
|
</Box>
|
||||||
<BottomParent>
|
|
||||||
<NameContainer
|
{issue?.feeData?.isPaid && (
|
||||||
onClick={e => {
|
<IssueIcon
|
||||||
e.stopPropagation();
|
iconSrc={QORTicon}
|
||||||
navigate(`/channel/${fileObj?.user}`);
|
style={{ marginRight: "20px" }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<NameAndDateContainer
|
||||||
|
sx={{ width: "200px", height: "100%" }}
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
navigate(`/channel/${issueObj?.user}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
width: "200px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
sx={{ height: 24, width: 24 }}
|
sx={{ height: 24, width: 24, marginRight: "10px" }}
|
||||||
src={`/arbitrary/THUMBNAIL/${fileObj?.user}/qortal_avatar`}
|
src={`/arbitrary/THUMBNAIL/${issueObj?.user}/qortal_avatar`}
|
||||||
alt={`${fileObj?.user}'s avatar`}
|
alt={`${issueObj?.user}'s avatar`}
|
||||||
/>
|
/>
|
||||||
<VideoCardName
|
<VideoCardName
|
||||||
sx={{
|
sx={{
|
||||||
@ -178,17 +196,17 @@ export const IssueList = ({ issues }: FileListProps) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{fileObj?.user}
|
{issueObj?.user}
|
||||||
</VideoCardName>
|
</VideoCardName>
|
||||||
</NameContainer>
|
</div>
|
||||||
|
|
||||||
{fileObj?.created && (
|
{issueObj?.created && (
|
||||||
<VideoUploadDate>
|
<VideoUploadDate>
|
||||||
{formatDate(fileObj.created)}
|
{formatDate(issueObj.created)}
|
||||||
</VideoUploadDate>
|
</VideoUploadDate>
|
||||||
)}
|
)}
|
||||||
</BottomParent>
|
</NameAndDateContainer>
|
||||||
</VideoCard>
|
</IssueCard>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Skeleton
|
<Skeleton
|
||||||
@ -206,6 +224,6 @@ export const IssueList = ({ issues }: FileListProps) => {
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</FileContainer>
|
</IssueContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -2,31 +2,33 @@ import React, { useEffect, useRef, useState } from "react";
|
|||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { RootState } from "../../state/store";
|
import { RootState } from "../../state/store";
|
||||||
import AttachFileIcon from "@mui/icons-material/AttachFile";
|
|
||||||
|
|
||||||
import { Avatar, Box, Skeleton, useTheme } from "@mui/material";
|
import { Avatar, Box, Skeleton, useTheme } from "@mui/material";
|
||||||
import { useFetchIssues } from "../../hooks/useFetchIssues.tsx";
|
import { useFetchIssues } from "../../hooks/useFetchIssues.tsx";
|
||||||
import LazyLoad from "../../components/common/LazyLoad";
|
import LazyLoad from "../../components/common/LazyLoad";
|
||||||
import {
|
import {
|
||||||
BottomParent,
|
BottomParent,
|
||||||
FileContainer,
|
IssueCard,
|
||||||
NameContainer,
|
IssueContainer,
|
||||||
VideoCard,
|
NameAndDateContainer,
|
||||||
VideoCardName,
|
VideoCardName,
|
||||||
VideoCardTitle,
|
VideoCardTitle,
|
||||||
VideoUploadDate,
|
VideoUploadDate,
|
||||||
} from "./IssueList-styles.tsx";
|
} from "./IssueList-styles.tsx";
|
||||||
import { formatDate } from "../../utils/time";
|
import { formatDate } from "../../utils/time";
|
||||||
import { Video } from "../../state/features/fileSlice.ts";
|
import { Issue } from "../../state/features/fileSlice.ts";
|
||||||
import { queue } from "../../wrappers/GlobalWrapper";
|
import { queue } from "../../wrappers/GlobalWrapper";
|
||||||
import { QSUPPORT_FILE_BASE } from "../../constants/Identifiers.ts";
|
import { QSUPPORT_FILE_BASE } from "../../constants/Identifiers.ts";
|
||||||
import { formatBytes } from "../IssueContent/IssueContent.tsx";
|
import { formatBytes } from "../IssueContent/IssueContent.tsx";
|
||||||
import { getIconsFromObject } from "../../constants/Categories/CategoryFunctions.ts";
|
import { getIconsFromObject } from "../../constants/Categories/CategoryFunctions.ts";
|
||||||
|
import { IssueIcon, IssueIcons } from "../../components/common/IssueIcon.tsx";
|
||||||
|
import QORTicon from "../../assets/icons/qort.png";
|
||||||
|
import { verifyAllPayments } from "../../constants/PublishFees/VerifyPayment.ts";
|
||||||
|
|
||||||
interface VideoListProps {
|
interface VideoListProps {
|
||||||
mode?: string;
|
mode?: string;
|
||||||
}
|
}
|
||||||
export const FileListComponentLevel = ({ mode }: VideoListProps) => {
|
export const IssueListComponentLevel = ({ mode }: VideoListProps) => {
|
||||||
const { name: paramName } = useParams();
|
const { name: paramName } = useParams();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||||
@ -37,16 +39,17 @@ export const FileListComponentLevel = ({ mode }: VideoListProps) => {
|
|||||||
(state: RootState) => state.file.hashMapFiles
|
(state: RootState) => state.file.hashMapFiles
|
||||||
);
|
);
|
||||||
|
|
||||||
const [videos, setVideos] = React.useState<Video[]>([]);
|
const [issues, setIssues] = React.useState<Issue[]>([]);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { getFile, getNewFiles, checkNewFiles, checkAndUpdateFile } =
|
const { getIssue, getNewIssues, checkNewIssues, checkAndUpdateIssue } =
|
||||||
useFetchIssues();
|
useFetchIssues();
|
||||||
|
|
||||||
const getVideos = React.useCallback(async () => {
|
const getIssues = React.useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const offset = videos.length;
|
const offset = issues.length;
|
||||||
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QSUPPORT_FILE_BASE}_&limit=50&includemetadata=false&reverse=true&excludeblocked=true&name=${paramName}&exactmatchnames=true&offset=${offset}`;
|
// `/arbitrary/resources/search?mode=ALL&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}&limit=${videoLimit}`;
|
||||||
|
const url = `/arbitrary/resources/search?mode=ALL&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}&limit=50&service=DOCUMENT&query=${QSUPPORT_FILE_BASE}_&name=${paramName}`;
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
@ -55,63 +58,69 @@ export const FileListComponentLevel = ({ mode }: VideoListProps) => {
|
|||||||
});
|
});
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
|
|
||||||
const structureData = responseData.map((video: any): Video => {
|
const structureData = responseData.map((issue: any): Issue => {
|
||||||
return {
|
return {
|
||||||
title: video?.metadata?.title,
|
title: issue?.metadata?.title,
|
||||||
category: video?.metadata?.category,
|
category: issue?.metadata?.category,
|
||||||
categoryName: video?.metadata?.categoryName,
|
categoryName: issue?.metadata?.categoryName,
|
||||||
tags: video?.metadata?.tags || [],
|
tags: issue?.metadata?.tags || [],
|
||||||
description: video?.metadata?.description,
|
description: issue?.metadata?.description,
|
||||||
created: video?.created,
|
created: issue?.created,
|
||||||
updated: video?.updated,
|
updated: issue?.updated,
|
||||||
user: video.name,
|
user: issue.name,
|
||||||
videoImage: "",
|
videoImage: "",
|
||||||
id: video.identifier,
|
id: issue.identifier,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const copiedVideos: Video[] = [...videos];
|
const copiedIssues: Issue[] = [...issues];
|
||||||
structureData.forEach((video: Video) => {
|
structureData.forEach((issue: Issue) => {
|
||||||
const index = videos.findIndex(p => p.id === video.id);
|
const index = issues.findIndex(p => p.id === issue.id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
copiedVideos[index] = video;
|
copiedIssues[index] = issue;
|
||||||
} else {
|
} else {
|
||||||
copiedVideos.push(video);
|
copiedIssues.push(issue);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setVideos(copiedVideos);
|
|
||||||
|
|
||||||
for (const content of structureData) {
|
const verifiedIssuePromises: Promise<Issue>[] = [];
|
||||||
|
|
||||||
|
for (const content of copiedIssues) {
|
||||||
if (content.user && content.id) {
|
if (content.user && content.id) {
|
||||||
const res = checkAndUpdateFile(content);
|
const res = checkAndUpdateIssue(content);
|
||||||
if (res) {
|
const getIssueData = getIssue(content.user, content.id, content);
|
||||||
queue.push(() => getFile(content.user, content.id, content));
|
if (res) queue.push(() => getIssueData);
|
||||||
}
|
|
||||||
|
verifiedIssuePromises.push(getIssueData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const issueData = await Promise.all(verifiedIssuePromises);
|
||||||
|
const verifiedIssues = await verifyAllPayments(issueData);
|
||||||
|
setIssues(verifiedIssues);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
} finally {
|
} finally {
|
||||||
}
|
}
|
||||||
}, [videos, hashMapVideos]);
|
}, [issues, hashMapVideos]);
|
||||||
|
|
||||||
const getVideosHandler = React.useCallback(async () => {
|
const getIssuesHandler = React.useCallback(async () => {
|
||||||
if (!firstFetch.current || !afterFetch.current) return;
|
if (!firstFetch.current || !afterFetch.current) return;
|
||||||
await getVideos();
|
await getIssues();
|
||||||
}, [getVideos]);
|
}, [getIssues]);
|
||||||
|
|
||||||
const getVideosHandlerMount = React.useCallback(async () => {
|
const getIssuesHandlerMount = React.useCallback(async () => {
|
||||||
if (firstFetch.current) return;
|
if (firstFetch.current) return;
|
||||||
firstFetch.current = true;
|
firstFetch.current = true;
|
||||||
await getVideos();
|
await getIssues();
|
||||||
afterFetch.current = true;
|
afterFetch.current = true;
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}, [getVideos]);
|
}, [getIssues]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!firstFetch.current) {
|
if (!firstFetch.current) {
|
||||||
getVideosHandlerMount();
|
getIssuesHandlerMount();
|
||||||
}
|
}
|
||||||
}, [getVideosHandlerMount]);
|
}, [getIssuesHandlerMount]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -122,18 +131,21 @@ export const FileListComponentLevel = ({ mode }: VideoListProps) => {
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FileContainer>
|
<IssueContainer>
|
||||||
{videos.map((file: any, index: number) => {
|
{issues.map((issue: any, index: number) => {
|
||||||
const existingFile = hashMapVideos[file?.id];
|
const existingFile = hashMapVideos[issue?.id];
|
||||||
let hasHash = false;
|
let hasHash = false;
|
||||||
let fileObj = file;
|
let issueObj = issue;
|
||||||
if (existingFile) {
|
if (existingFile) {
|
||||||
fileObj = existingFile;
|
issueObj = existingFile;
|
||||||
hasHash = true;
|
hasHash = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const icon = getIconsFromObject(fileObj);
|
const issueIcons = getIconsFromObject(issueObj);
|
||||||
|
const fileBytes = issueObj?.files.reduce(
|
||||||
|
(acc, cur) => acc + (cur?.size || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -143,13 +155,13 @@ export const FileListComponentLevel = ({ mode }: VideoListProps) => {
|
|||||||
height: "75px",
|
height: "75px",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
}}
|
}}
|
||||||
key={fileObj.id}
|
key={issueObj.id}
|
||||||
>
|
>
|
||||||
{hasHash ? (
|
{hasHash ? (
|
||||||
<>
|
<>
|
||||||
<VideoCard
|
<IssueCard
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/issue/${fileObj?.user}/${fileObj?.id}`);
|
navigate(`/issue/${issueObj?.user}/${issueObj?.id}`);
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
height: "100%",
|
height: "100%",
|
||||||
@ -167,42 +179,49 @@ export const FileListComponentLevel = ({ mode }: VideoListProps) => {
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{icon ? (
|
<div
|
||||||
<img
|
style={{
|
||||||
src={icon}
|
display: "flex",
|
||||||
width="50px"
|
alignItems: "center",
|
||||||
style={{
|
width: "200px",
|
||||||
borderRadius: "5px",
|
}}
|
||||||
}}
|
>
|
||||||
|
<IssueIcons
|
||||||
|
iconSources={issueIcons}
|
||||||
|
style={{ marginRight: "20px" }}
|
||||||
|
showBackupIcon={true}
|
||||||
/>
|
/>
|
||||||
) : (
|
</div>
|
||||||
<AttachFileIcon />
|
|
||||||
)}
|
|
||||||
<VideoCardTitle
|
<VideoCardTitle
|
||||||
sx={{
|
sx={{
|
||||||
width: "100px",
|
width: "100px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{formatBytes(
|
{fileBytes > 0 && formatBytes(fileBytes)}
|
||||||
fileObj?.files.reduce(
|
</VideoCardTitle>
|
||||||
(acc, cur) => acc + (cur?.size || 0),
|
<VideoCardTitle
|
||||||
0
|
sx={{ fontWeight: "bold", width: "500px" }}
|
||||||
)
|
>
|
||||||
)}
|
{issueObj.title}
|
||||||
</VideoCardTitle>
|
</VideoCardTitle>
|
||||||
<VideoCardTitle>{fileObj.title}</VideoCardTitle>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
{issue?.feeData?.isPaid && (
|
||||||
|
<IssueIcon
|
||||||
|
iconSrc={QORTicon}
|
||||||
|
style={{ marginRight: "20px" }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<BottomParent>
|
<BottomParent>
|
||||||
<NameContainer
|
<NameAndDateContainer
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
navigate(`/channel/${fileObj?.user}`);
|
navigate(`/channel/${issueObj?.user}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
sx={{ height: 24, width: 24 }}
|
sx={{ height: 24, width: 24 }}
|
||||||
src={`/arbitrary/THUMBNAIL/${fileObj?.user}/qortal_avatar`}
|
src={`/arbitrary/THUMBNAIL/${issueObj?.user}/qortal_avatar`}
|
||||||
alt={`${fileObj?.user}'s avatar`}
|
alt={`${issueObj?.user}'s avatar`}
|
||||||
/>
|
/>
|
||||||
<VideoCardName
|
<VideoCardName
|
||||||
sx={{
|
sx={{
|
||||||
@ -211,17 +230,17 @@ export const FileListComponentLevel = ({ mode }: VideoListProps) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{fileObj?.user}
|
{issueObj?.user}
|
||||||
</VideoCardName>
|
</VideoCardName>
|
||||||
</NameContainer>
|
</NameAndDateContainer>
|
||||||
|
|
||||||
{fileObj?.created && (
|
{issueObj?.created && (
|
||||||
<VideoUploadDate>
|
<VideoUploadDate>
|
||||||
{formatDate(fileObj.created)}
|
{formatDate(issueObj.created)}
|
||||||
</VideoUploadDate>
|
</VideoUploadDate>
|
||||||
)}
|
)}
|
||||||
</BottomParent>
|
</BottomParent>
|
||||||
</VideoCard>
|
</IssueCard>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Skeleton
|
<Skeleton
|
||||||
@ -239,8 +258,8 @@ export const FileListComponentLevel = ({ mode }: VideoListProps) => {
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</FileContainer>
|
</IssueContainer>
|
||||||
<LazyLoad onLoadMore={getVideosHandler} isLoading={isLoading}></LazyLoad>
|
<LazyLoad onLoadMore={getIssuesHandler} isLoading={isLoading}></LazyLoad>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
import { FileListComponentLevel } from "../Home/FileListComponentLevel.tsx";
|
import { IssueListComponentLevel } from "../Home/IssueListComponentLevel.tsx";
|
||||||
import { HeaderContainer, ProfileContainer } from "./Profile-styles";
|
import { HeaderContainer, ProfileContainer } from "./Profile-styles";
|
||||||
import {
|
import {
|
||||||
AuthorTextComment,
|
AuthorTextComment,
|
||||||
@ -62,7 +62,7 @@ export const IndividualProfile = () => {
|
|||||||
</StyledCardHeaderComment>
|
</StyledCardHeaderComment>
|
||||||
</Box>
|
</Box>
|
||||||
</HeaderContainer>
|
</HeaderContainer>
|
||||||
<FileListComponentLevel />
|
<IssueListComponentLevel />
|
||||||
</ProfileContainer>
|
</ProfileContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import { styled } from "@mui/system";
|
import { styled } from "@mui/system";
|
||||||
import { Box, Grid, Typography, Checkbox } from "@mui/material";
|
import { Box, Grid, Typography, Checkbox } from "@mui/material";
|
||||||
|
|
||||||
export const ProfileContainer = styled(Box)(({ theme }) => ({
|
export const ProfileContainer = styled(Box)(({ theme }) => ({
|
||||||
position: "relative",
|
position: "relative",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
flexDirection: "column"
|
flexDirection: "column"
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const HeaderContainer = styled(Box)(({ theme }) => ({
|
export const HeaderContainer = styled(Box)(({ theme }) => ({
|
||||||
position: "relative",
|
position: "relative",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
justifyContent: "center"
|
justifyContent: "center"
|
||||||
}));
|
}));
|
||||||
|
@ -5,7 +5,6 @@ import { setIsLoadingGlobal } from "../../state/features/globalSlice";
|
|||||||
import { Avatar, Box, Typography, useTheme } from "@mui/material";
|
import { Avatar, Box, Typography, useTheme } from "@mui/material";
|
||||||
import { RootState } from "../../state/store";
|
import { RootState } from "../../state/store";
|
||||||
import { addToHashMap } from "../../state/features/fileSlice.ts";
|
import { addToHashMap } from "../../state/features/fileSlice.ts";
|
||||||
import AttachFileIcon from "@mui/icons-material/AttachFile";
|
|
||||||
import DownloadIcon from "@mui/icons-material/Download";
|
import DownloadIcon from "@mui/icons-material/Download";
|
||||||
import {
|
import {
|
||||||
AuthorTextComment,
|
AuthorTextComment,
|
||||||
@ -24,14 +23,20 @@ import { CommentSection } from "../../components/common/Comments/CommentSection"
|
|||||||
import { QSUPPORT_FILE_BASE } from "../../constants/Identifiers.ts";
|
import { QSUPPORT_FILE_BASE } from "../../constants/Identifiers.ts";
|
||||||
import { DisplayHtml } from "../../components/common/TextEditor/DisplayHtml";
|
import { DisplayHtml } from "../../components/common/TextEditor/DisplayHtml";
|
||||||
import FileElement from "../../components/common/FileElement";
|
import FileElement from "../../components/common/FileElement";
|
||||||
import { allCategoryData } from "../../constants/Categories/1stCategories.ts";
|
import { allCategoryData } from "../../constants/Categories/Categories.ts";
|
||||||
import {
|
import {
|
||||||
Category,
|
Category,
|
||||||
getCategoriesFromObject,
|
getCategoriesFromObject,
|
||||||
} from "../../components/common/CategoryList/CategoryList.tsx";
|
} from "../../components/common/CategoryList/CategoryList.tsx";
|
||||||
import { getIconsFromObject } from "../../constants/Categories/CategoryFunctions.ts";
|
import { getIconsFromObject } from "../../constants/Categories/CategoryFunctions.ts";
|
||||||
|
import { IssueIcon, IssueIcons } from "../../components/common/IssueIcon.tsx";
|
||||||
|
import QORTicon from "../../assets/icons/qort.png";
|
||||||
|
import {
|
||||||
|
appendIsPaidToFeeData,
|
||||||
|
verifyPayment,
|
||||||
|
} from "../../constants/PublishFees/VerifyPayment.ts";
|
||||||
|
|
||||||
export function formatBytes(bytes, decimals = 2) {
|
export function formatBytes(bytes: number, decimals = 2) {
|
||||||
if (bytes === 0) return "0 Bytes";
|
if (bytes === 0) return "0 Bytes";
|
||||||
|
|
||||||
const k = 1024;
|
const k = 1024;
|
||||||
@ -50,7 +55,7 @@ export const IssueContent = () => {
|
|||||||
const [descriptionHeight, setDescriptionHeight] = useState<null | number>(
|
const [descriptionHeight, setDescriptionHeight] = useState<null | number>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
const [icon, setIcon] = useState<string>("");
|
const [issueIcons, setIssueIcons] = useState<string[]>([]);
|
||||||
const userAvatarHash = useSelector(
|
const userAvatarHash = useSelector(
|
||||||
(state: RootState) => state.global.userAvatarHash
|
(state: RootState) => state.global.userAvatarHash
|
||||||
);
|
);
|
||||||
@ -67,15 +72,15 @@ export const IssueContent = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const [fileData, setFileData] = useState<any>(null);
|
const [issueData, setIssueData] = useState<any>(null);
|
||||||
const [playlistData, setPlaylistData] = useState<any>(null);
|
const [playlistData, setPlaylistData] = useState<any>(null);
|
||||||
|
|
||||||
const hashMapVideos = useSelector(
|
const hashMapVideos = useSelector(
|
||||||
(state: RootState) => state.file.hashMapFiles
|
(state: RootState) => state.file.hashMapFiles
|
||||||
);
|
);
|
||||||
const videoReference = useMemo(() => {
|
const videoReference = useMemo(() => {
|
||||||
if (!fileData) return null;
|
if (!issueData) return null;
|
||||||
const { videoReference } = fileData;
|
const { videoReference } = issueData;
|
||||||
if (
|
if (
|
||||||
videoReference?.identifier &&
|
videoReference?.identifier &&
|
||||||
videoReference?.name &&
|
videoReference?.name &&
|
||||||
@ -85,13 +90,13 @@ export const IssueContent = () => {
|
|||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}, [fileData]);
|
}, [issueData]);
|
||||||
|
|
||||||
const videoCover = useMemo(() => {
|
const videoCover = useMemo(() => {
|
||||||
if (!fileData) return null;
|
if (!issueData) return null;
|
||||||
const { videoImage } = fileData;
|
const { videoImage } = issueData;
|
||||||
return videoImage || null;
|
return videoImage || null;
|
||||||
}, [fileData]);
|
}, [issueData]);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const getVideoData = React.useCallback(async (name: string, id: string) => {
|
const getVideoData = React.useCallback(async (name: string, id: string) => {
|
||||||
@ -135,9 +140,16 @@ export const IssueContent = () => {
|
|||||||
...resourceData,
|
...resourceData,
|
||||||
...responseData,
|
...responseData,
|
||||||
};
|
};
|
||||||
setFileData(combinedData);
|
|
||||||
dispatch(addToHashMap(combinedData));
|
verifyPayment(combinedData).then(feeData => {
|
||||||
checkforPlaylist(name, id, combinedData?.code);
|
console.log(
|
||||||
|
"async data: ",
|
||||||
|
appendIsPaidToFeeData(combinedData, feeData)
|
||||||
|
);
|
||||||
|
setIssueData(appendIsPaidToFeeData(combinedData, feeData));
|
||||||
|
dispatch(addToHashMap(combinedData));
|
||||||
|
checkforPlaylist(name, id, combinedData?.code);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -217,8 +229,10 @@ export const IssueContent = () => {
|
|||||||
const existingVideo = hashMapVideos[id];
|
const existingVideo = hashMapVideos[id];
|
||||||
|
|
||||||
if (existingVideo) {
|
if (existingVideo) {
|
||||||
setFileData(existingVideo);
|
verifyPayment(existingVideo).then(feeData => {
|
||||||
checkforPlaylist(name, id, existingVideo?.code);
|
setIssueData(appendIsPaidToFeeData(existingVideo, feeData));
|
||||||
|
checkforPlaylist(name, id, existingVideo?.code);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
getVideoData(name, id);
|
getVideoData(name, id);
|
||||||
}
|
}
|
||||||
@ -259,21 +273,21 @@ export const IssueContent = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (contentRef.current) {
|
if (contentRef.current) {
|
||||||
const height = contentRef.current.offsetHeight;
|
const height = contentRef.current.offsetHeight;
|
||||||
if (height > 100) {
|
const maxDescriptionHeight = 200;
|
||||||
|
if (height > maxDescriptionHeight) {
|
||||||
// Assuming 100px is your threshold
|
// Assuming 100px is your threshold
|
||||||
setDescriptionHeight(100);
|
setDescriptionHeight(maxDescriptionHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fileData) {
|
if (issueData) {
|
||||||
const icon = getIconsFromObject(fileData);
|
const icons = getIconsFromObject(issueData);
|
||||||
setIcon(icon);
|
setIssueIcons(icons);
|
||||||
}
|
}
|
||||||
}, [fileData]);
|
}, [issueData]);
|
||||||
|
|
||||||
const categoriesDisplay = useMemo(() => {
|
const categoriesDisplay = useMemo(() => {
|
||||||
if (fileData) {
|
if (issueData) {
|
||||||
const categoryList = getCategoriesFromObject(fileData);
|
const categoryList = getCategoriesFromObject(issueData);
|
||||||
|
|
||||||
const categoryNames = categoryList.map((categoryID, index) => {
|
const categoryNames = categoryList.map((categoryID, index) => {
|
||||||
let categoryName: Category;
|
let categoryName: Category;
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
@ -294,14 +308,21 @@ export const IssueContent = () => {
|
|||||||
const filteredCategoryNames = categoryNames.filter(name => name);
|
const filteredCategoryNames = categoryNames.filter(name => name);
|
||||||
let categoryDisplay = "";
|
let categoryDisplay = "";
|
||||||
const separator = " > ";
|
const separator = " > ";
|
||||||
|
const QappName = issueData?.QappName || "";
|
||||||
|
|
||||||
filteredCategoryNames.map((name, index) => {
|
filteredCategoryNames.map((name, index) => {
|
||||||
categoryDisplay +=
|
if (QappName && index === 1) {
|
||||||
index !== filteredCategoryNames.length - 1 ? name + separator : name;
|
categoryDisplay += QappName + separator;
|
||||||
|
}
|
||||||
|
categoryDisplay += name;
|
||||||
|
|
||||||
|
if (index !== filteredCategoryNames.length - 1)
|
||||||
|
categoryDisplay += separator;
|
||||||
});
|
});
|
||||||
return categoryDisplay;
|
return categoryDisplay;
|
||||||
}
|
}
|
||||||
return "no videodata";
|
return "no videodata";
|
||||||
}, [fileData]);
|
}, [issueData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -325,18 +346,12 @@ export const IssueContent = () => {
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{icon ? (
|
<div style={{ display: "flex", alignItems: "center" }}>
|
||||||
<img
|
<IssueIcons
|
||||||
src={icon}
|
iconSources={issueIcons}
|
||||||
width="50px"
|
style={{ marginRight: "20px" }}
|
||||||
style={{
|
|
||||||
borderRadius: "5px",
|
|
||||||
marginRight: "10px",
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
</div>
|
||||||
<AttachFileIcon />
|
|
||||||
)}
|
|
||||||
<FileTitle
|
<FileTitle
|
||||||
variant="h1"
|
variant="h1"
|
||||||
color="textPrimary"
|
color="textPrimary"
|
||||||
@ -344,10 +359,13 @@ export const IssueContent = () => {
|
|||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{fileData?.title}
|
{issueData?.title}
|
||||||
</FileTitle>
|
</FileTitle>
|
||||||
|
{issueData?.feeData?.isPaid && (
|
||||||
|
<IssueIcon iconSrc={QORTicon} style={{ marginLeft: "10px" }} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{fileData?.created && (
|
{issueData?.created && (
|
||||||
<Typography
|
<Typography
|
||||||
variant="h6"
|
variant="h6"
|
||||||
sx={{
|
sx={{
|
||||||
@ -355,7 +373,7 @@ export const IssueContent = () => {
|
|||||||
}}
|
}}
|
||||||
color={theme.palette.text.primary}
|
color={theme.palette.text.primary}
|
||||||
>
|
>
|
||||||
{formatDate(fileData.created)}
|
{formatDate(issueData.created)}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -407,12 +425,13 @@ export const IssueContent = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<ImageContainer>
|
<ImageContainer>
|
||||||
{fileData?.images &&
|
{issueData?.images &&
|
||||||
fileData.images.map(image => {
|
issueData.images.map(image => {
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
|
key={image}
|
||||||
src={image}
|
src={image}
|
||||||
width={`${1080 / fileData.images.length}px`}
|
width={`${1080 / issueData.images.length}px`}
|
||||||
style={{
|
style={{
|
||||||
marginRight: "10px",
|
marginRight: "10px",
|
||||||
marginBottom: "10px",
|
marginBottom: "10px",
|
||||||
@ -464,12 +483,12 @@ export const IssueContent = () => {
|
|||||||
? "auto"
|
? "auto"
|
||||||
: isExpandedDescription
|
: isExpandedDescription
|
||||||
? "auto"
|
? "auto"
|
||||||
: "100px",
|
: "30vh",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{fileData?.htmlDescription ? (
|
{issueData?.htmlDescription ? (
|
||||||
<DisplayHtml html={fileData?.htmlDescription} />
|
<DisplayHtml html={issueData?.htmlDescription} />
|
||||||
) : (
|
) : (
|
||||||
<FileDescription
|
<FileDescription
|
||||||
variant="body1"
|
variant="body1"
|
||||||
@ -478,7 +497,7 @@ export const IssueContent = () => {
|
|||||||
cursor: "default",
|
cursor: "default",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{fileData?.fullDescription}
|
{issueData?.fullDescription}
|
||||||
</FileDescription>
|
</FileDescription>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
@ -509,7 +528,7 @@ export const IssueContent = () => {
|
|||||||
marginTop: "25px",
|
marginTop: "25px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{fileData?.files?.map((file, index) => {
|
{issueData?.files?.map((file, index) => {
|
||||||
return (
|
return (
|
||||||
<FileAttachmentContainer
|
<FileAttachmentContainer
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
|
||||||
interface AuthState {
|
interface AuthState {
|
||||||
user: {
|
user: {
|
||||||
address: string;
|
address: string;
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
} | null;
|
} | null;
|
||||||
}
|
}
|
||||||
const initialState: AuthState = {
|
const initialState: AuthState = {
|
||||||
user: null
|
user: null
|
||||||
};
|
};
|
||||||
|
|
||||||
export const authSlice = createSlice({
|
export const authSlice = createSlice({
|
||||||
name: 'auth',
|
name: 'auth',
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
addUser: (state, action) => {
|
addUser: (state, action) => {
|
||||||
state.user = action.payload;
|
state.user = action.payload;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { addUser } = authSlice.actions;
|
export const { addUser } = authSlice.actions;
|
||||||
|
|
||||||
export default authSlice.reducer;
|
export default authSlice.reducer;
|
@ -1,10 +1,10 @@
|
|||||||
import { createSlice } from "@reduxjs/toolkit";
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
import { RootState } from "../store";
|
import { PublishFeeData } from "../../constants/PublishFees/SendFeeFunctions.ts";
|
||||||
|
|
||||||
interface GlobalState {
|
interface GlobalState {
|
||||||
files: Video[];
|
files: Issue[];
|
||||||
filteredFiles: Video[];
|
filteredFiles: Issue[];
|
||||||
hashMapFiles: Record<string, Video>;
|
hashMapFiles: Record<string, Issue>;
|
||||||
countNewFiles: number;
|
countNewFiles: number;
|
||||||
isFiltering: boolean;
|
isFiltering: boolean;
|
||||||
filterValue: string;
|
filterValue: string;
|
||||||
@ -14,6 +14,7 @@ interface GlobalState {
|
|||||||
selectedCategoryFiles: any[];
|
selectedCategoryFiles: any[];
|
||||||
editFileProperties: any;
|
editFileProperties: any;
|
||||||
editPlaylistProperties: any;
|
editPlaylistProperties: any;
|
||||||
|
publishedQappNames: string[];
|
||||||
}
|
}
|
||||||
const initialState: GlobalState = {
|
const initialState: GlobalState = {
|
||||||
files: [],
|
files: [],
|
||||||
@ -28,9 +29,10 @@ const initialState: GlobalState = {
|
|||||||
selectedCategoryFiles: [null, null, null, null],
|
selectedCategoryFiles: [null, null, null, null],
|
||||||
editFileProperties: null,
|
editFileProperties: null,
|
||||||
editPlaylistProperties: null,
|
editPlaylistProperties: null,
|
||||||
|
publishedQappNames: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface Video {
|
export interface Issue {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
created: number | string;
|
created: number | string;
|
||||||
@ -44,6 +46,8 @@ export interface Video {
|
|||||||
updated?: number | string;
|
updated?: number | string;
|
||||||
isValid?: boolean;
|
isValid?: boolean;
|
||||||
code?: string;
|
code?: string;
|
||||||
|
feeData?: PublishFeeData;
|
||||||
|
paymentVerified?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fileSlice = createSlice({
|
export const fileSlice = createSlice({
|
||||||
@ -113,12 +117,12 @@ export const fileSlice = createSlice({
|
|||||||
},
|
},
|
||||||
addArrayToHashMap: (state, action) => {
|
addArrayToHashMap: (state, action) => {
|
||||||
const videos = action.payload;
|
const videos = action.payload;
|
||||||
videos.forEach((video: Video) => {
|
videos.forEach((video: Issue) => {
|
||||||
state.hashMapFiles[video.id] = video;
|
state.hashMapFiles[video.id] = video;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
upsertFiles: (state, action) => {
|
upsertFiles: (state, action) => {
|
||||||
action.payload.forEach((video: Video) => {
|
action.payload.forEach((video: Issue) => {
|
||||||
const index = state.files.findIndex(p => p.id === video.id);
|
const index = state.files.findIndex(p => p.id === video.id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
state.files[index] = video;
|
state.files[index] = video;
|
||||||
@ -128,7 +132,7 @@ export const fileSlice = createSlice({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
upsertFilteredFiles: (state, action) => {
|
upsertFilteredFiles: (state, action) => {
|
||||||
action.payload.forEach((video: Video) => {
|
action.payload.forEach((video: Issue) => {
|
||||||
const index = state.filteredFiles.findIndex(p => p.id === video.id);
|
const index = state.filteredFiles.findIndex(p => p.id === video.id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
state.filteredFiles[index] = video;
|
state.filteredFiles[index] = video;
|
||||||
@ -138,7 +142,7 @@ export const fileSlice = createSlice({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
upsertFilesBeginning: (state, action) => {
|
upsertFilesBeginning: (state, action) => {
|
||||||
action.payload.reverse().forEach((video: Video) => {
|
action.payload.reverse().forEach((video: Issue) => {
|
||||||
const index = state.files.findIndex(p => p.id === video.id);
|
const index = state.files.findIndex(p => p.id === video.id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
state.files[index] = video;
|
state.files[index] = video;
|
||||||
@ -157,6 +161,9 @@ export const fileSlice = createSlice({
|
|||||||
const username = action.payload;
|
const username = action.payload;
|
||||||
state.files = state.files.filter(item => item.user !== username);
|
state.files = state.files.filter(item => item.user !== username);
|
||||||
},
|
},
|
||||||
|
setQappNames: (state, action) => {
|
||||||
|
state.publishedQappNames = action.payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -183,6 +190,7 @@ export const {
|
|||||||
blockUser,
|
blockUser,
|
||||||
setEditFile,
|
setEditFile,
|
||||||
setEditPlaylist,
|
setEditPlaylist,
|
||||||
|
setQappNames,
|
||||||
} = fileSlice.actions;
|
} = fileSlice.actions;
|
||||||
|
|
||||||
export default fileSlice.reducer;
|
export default fileSlice.reducer;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { createSlice } from "@reduxjs/toolkit";
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
|
import { FeePrice } from "../../constants/PublishFees/FeePricePublish/FeePricePublish.ts";
|
||||||
|
|
||||||
interface GlobalState {
|
interface GlobalState {
|
||||||
isLoadingGlobal: boolean;
|
isLoadingGlobal: boolean;
|
||||||
@ -9,6 +10,7 @@ interface GlobalState {
|
|||||||
totalFilesPublished: number;
|
totalFilesPublished: number;
|
||||||
totalNamesPublished: number;
|
totalNamesPublished: number;
|
||||||
filesPerNamePublished: number;
|
filesPerNamePublished: number;
|
||||||
|
feeData: FeePrice[];
|
||||||
}
|
}
|
||||||
const initialState: GlobalState = {
|
const initialState: GlobalState = {
|
||||||
isLoadingGlobal: false,
|
isLoadingGlobal: false,
|
||||||
@ -19,6 +21,7 @@ const initialState: GlobalState = {
|
|||||||
totalFilesPublished: null,
|
totalFilesPublished: null,
|
||||||
totalNamesPublished: null,
|
totalNamesPublished: null,
|
||||||
filesPerNamePublished: null,
|
filesPerNamePublished: null,
|
||||||
|
feeData: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const globalSlice = createSlice({
|
export const globalSlice = createSlice({
|
||||||
@ -61,6 +64,9 @@ export const globalSlice = createSlice({
|
|||||||
setFilesPerNamePublished: (state, action) => {
|
setFilesPerNamePublished: (state, action) => {
|
||||||
state.filesPerNamePublished = action.payload;
|
state.filesPerNamePublished = action.payload;
|
||||||
},
|
},
|
||||||
|
setFeeData: (state, action) => {
|
||||||
|
state.feeData = action.payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -74,6 +80,7 @@ export const {
|
|||||||
setTotalFilesPublished,
|
setTotalFilesPublished,
|
||||||
setTotalNamesPublished,
|
setTotalNamesPublished,
|
||||||
setFilesPerNamePublished,
|
setFilesPerNamePublished,
|
||||||
|
setFeeData,
|
||||||
} = globalSlice.actions;
|
} = globalSlice.actions;
|
||||||
|
|
||||||
export default globalSlice.reducer;
|
export default globalSlice.reducer;
|
||||||
|
@ -1,73 +1,73 @@
|
|||||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
interface AlertTypes {
|
interface AlertTypes {
|
||||||
alertSuccess: string
|
alertSuccess: string
|
||||||
alertError: string
|
alertError: string
|
||||||
alertInfo: string
|
alertInfo: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface InitialState {
|
interface InitialState {
|
||||||
alertTypes: AlertTypes
|
alertTypes: AlertTypes
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: InitialState = {
|
const initialState: InitialState = {
|
||||||
alertTypes: {
|
alertTypes: {
|
||||||
alertSuccess: '',
|
alertSuccess: '',
|
||||||
alertError: '',
|
alertError: '',
|
||||||
alertInfo: ''
|
alertInfo: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const notificationsSlice = createSlice({
|
export const notificationsSlice = createSlice({
|
||||||
name: "notifications",
|
name: "notifications",
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
setNotification: (
|
setNotification: (
|
||||||
state: InitialState,
|
state: InitialState,
|
||||||
action: PayloadAction<{ alertType: string; msg: string }>
|
action: PayloadAction<{ alertType: string; msg: string }>
|
||||||
) => {
|
) => {
|
||||||
if (action.payload.alertType === "success") {
|
if (action.payload.alertType === "success") {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
alertTypes: {
|
alertTypes: {
|
||||||
...state.alertTypes,
|
...state.alertTypes,
|
||||||
alertSuccess: action.payload.msg,
|
alertSuccess: action.payload.msg,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else if (action.payload.alertType === "error") {
|
} else if (action.payload.alertType === "error") {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
alertTypes: {
|
alertTypes: {
|
||||||
...state.alertTypes,
|
...state.alertTypes,
|
||||||
alertError: action.payload.msg,
|
alertError: action.payload.msg,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else if (action.payload.alertType === "info") {
|
} else if (action.payload.alertType === "info") {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
alertTypes: {
|
alertTypes: {
|
||||||
...state.alertTypes,
|
...state.alertTypes,
|
||||||
alertInfo: action.payload.msg,
|
alertInfo: action.payload.msg,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
},
|
},
|
||||||
removeNotification: (state: InitialState) => {
|
removeNotification: (state: InitialState) => {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
alertTypes: {
|
alertTypes: {
|
||||||
...state.alertTypes,
|
...state.alertTypes,
|
||||||
alertSuccess: '',
|
alertSuccess: '',
|
||||||
alertError: '',
|
alertError: '',
|
||||||
alertInfo: ''
|
alertInfo: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { setNotification, removeNotification } =
|
export const { setNotification, removeNotification } =
|
||||||
notificationsSlice.actions;
|
notificationsSlice.actions;
|
||||||
|
|
||||||
export default notificationsSlice.reducer;
|
export default notificationsSlice.reducer;
|
||||||
|
@ -91,7 +91,7 @@ const lightTheme = createTheme({
|
|||||||
mode: "light",
|
mode: "light",
|
||||||
primary: {
|
primary: {
|
||||||
main: "#FCFCFC",
|
main: "#FCFCFC",
|
||||||
dark: "#F5F5F5",
|
dark: "#E0E0E0",
|
||||||
light: "#FFFFFF",
|
light: "#FFFFFF",
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
@ -138,14 +138,14 @@ const darkTheme = createTheme({
|
|||||||
palette: {
|
palette: {
|
||||||
mode: "dark",
|
mode: "dark",
|
||||||
primary: {
|
primary: {
|
||||||
main: "#01a9e9", //
|
main: "#01a9e9", // Qortal Blue
|
||||||
dark: "#008fcd", //
|
dark: "#008fcd",
|
||||||
light: "#44c4ff", //
|
light: "#44c4ff",
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
main: "#007FFF", // Electric blue
|
main: "#007FFF", // Electric blue
|
||||||
dark: "#0059B2", // Darker shade of electric blue
|
dark: "#0059B2",
|
||||||
light: "#3399FF", // Lighter shade of electric blue
|
light: "#3399FF",
|
||||||
},
|
},
|
||||||
background: {
|
background: {
|
||||||
default: "#1C1C1C", // Deep space black
|
default: "#1C1C1C", // Deep space black
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
export const checkStructure = (content: any) => {
|
export const checkStructure = (content: any) => {
|
||||||
let isValid = true
|
let isValid = true
|
||||||
|
|
||||||
return isValid
|
return isValid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
export function extractTextFromSlate(nodes: any) {
|
export function extractTextFromSlate(nodes: any) {
|
||||||
if(!Array.isArray(nodes)) return ""
|
if(!Array.isArray(nodes)) return ""
|
||||||
let text = "";
|
let text = "";
|
||||||
|
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
if (node.text) {
|
if (node.text) {
|
||||||
text += node.text;
|
text += node.text;
|
||||||
} else if (node.children) {
|
} else if (node.children) {
|
||||||
text += extractTextFromSlate(node.children);
|
text += extractTextFromSlate(node.children);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
@ -17,6 +17,7 @@ export const fetchAndEvaluateIssues = async (data: any) => {
|
|||||||
service: content?.service || "DOCUMENT",
|
service: content?.service || "DOCUMENT",
|
||||||
identifier: videoId,
|
identifier: videoId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (checkStructure(responseData)) {
|
if (checkStructure(responseData)) {
|
||||||
obj = {
|
obj = {
|
||||||
...content,
|
...content,
|
||||||
|
33
src/utils/qortalRequests.ts
Normal file
33
src/utils/qortalRequests.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { NameData } from "../constants/PublishFees/SendFeeFunctions.ts";
|
||||||
|
import { getUserAccountNames } from "../constants/PublishFees/VerifyPayment-Functions.ts";
|
||||||
|
|
||||||
|
export const getNameData = async (name: string) => {
|
||||||
|
return (await qortalRequest({
|
||||||
|
action: "GET_NAME_DATA",
|
||||||
|
name: name,
|
||||||
|
})) as NameData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendQchatDM = async (
|
||||||
|
recipientName: string,
|
||||||
|
message: string,
|
||||||
|
allowSelfAsRecipient = false
|
||||||
|
) => {
|
||||||
|
if (!allowSelfAsRecipient) {
|
||||||
|
const userAccountNames = await getUserAccountNames();
|
||||||
|
const userNames = userAccountNames.map(name => name.name);
|
||||||
|
if (userNames.includes(recipientName)) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const address = await getNameData(recipientName);
|
||||||
|
try {
|
||||||
|
return await qortalRequest({
|
||||||
|
action: "SEND_CHAT_MESSAGE",
|
||||||
|
destinationAddress: address.owner,
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
@ -1,43 +1,43 @@
|
|||||||
type QueueItem = {
|
type QueueItem = {
|
||||||
request: () => Promise<any>;
|
request: () => Promise<any>;
|
||||||
resolve: (value: any | PromiseLike<any>) => void;
|
resolve: (value: any | PromiseLike<any>) => void;
|
||||||
reject: (reason?: any) => void;
|
reject: (reason?: any) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class RequestQueue {
|
export class RequestQueue {
|
||||||
private queue: QueueItem[];
|
private queue: QueueItem[];
|
||||||
private maxConcurrent: number;
|
private maxConcurrent: number;
|
||||||
private currentConcurrent: number;
|
private currentConcurrent: number;
|
||||||
|
|
||||||
constructor(maxConcurrent = 5) {
|
constructor(maxConcurrent = 5) {
|
||||||
this.queue = [];
|
this.queue = [];
|
||||||
this.maxConcurrent = maxConcurrent;
|
this.maxConcurrent = maxConcurrent;
|
||||||
this.currentConcurrent = 0;
|
this.currentConcurrent = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
async push(request: () => Promise<any>): Promise<any> {
|
async push(request: () => Promise<any>): Promise<any> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.queue.push({
|
this.queue.push({
|
||||||
request,
|
request,
|
||||||
resolve,
|
resolve,
|
||||||
reject,
|
reject,
|
||||||
});
|
});
|
||||||
this.checkQueue();
|
this.checkQueue();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private checkQueue(): void {
|
private checkQueue(): void {
|
||||||
if (this.queue.length === 0 || this.currentConcurrent >= this.maxConcurrent) return;
|
if (this.queue.length === 0 || this.currentConcurrent >= this.maxConcurrent) return;
|
||||||
|
|
||||||
const { request, resolve, reject } = this.queue.shift() as QueueItem;
|
const { request, resolve, reject } = this.queue.shift() as QueueItem;
|
||||||
this.currentConcurrent++;
|
this.currentConcurrent++;
|
||||||
|
|
||||||
request()
|
request()
|
||||||
.then(resolve)
|
.then(resolve)
|
||||||
.catch(reject)
|
.catch(reject)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.currentConcurrent--;
|
this.currentConcurrent--;
|
||||||
this.checkQueue();
|
this.checkQueue();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,46 +1,46 @@
|
|||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
|
||||||
export function formatTimestamp(timestamp: number): string {
|
export function formatTimestamp(timestamp: number): string {
|
||||||
const now = moment()
|
const now = moment()
|
||||||
const timestampMoment = moment(timestamp)
|
const timestampMoment = moment(timestamp)
|
||||||
const elapsedTime = now.diff(timestampMoment, 'minutes')
|
const elapsedTime = now.diff(timestampMoment, 'minutes')
|
||||||
|
|
||||||
if (elapsedTime < 1) {
|
if (elapsedTime < 1) {
|
||||||
return 'Just now'
|
return 'Just now'
|
||||||
} else if (elapsedTime < 60) {
|
} else if (elapsedTime < 60) {
|
||||||
return `${elapsedTime}m`
|
return `${elapsedTime}m`
|
||||||
} else if (elapsedTime < 1440) {
|
} else if (elapsedTime < 1440) {
|
||||||
return `${Math.floor(elapsedTime / 60)}h`
|
return `${Math.floor(elapsedTime / 60)}h`
|
||||||
} else {
|
} else {
|
||||||
return timestampMoment.format('MMM D')
|
return timestampMoment.format('MMM D')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatTimestampSeconds(timestamp: number): string {
|
export function formatTimestampSeconds(timestamp: number): string {
|
||||||
const now = moment()
|
const now = moment()
|
||||||
const timestampMoment = moment.unix(timestamp)
|
const timestampMoment = moment.unix(timestamp)
|
||||||
const elapsedTime = now.diff(timestampMoment, 'minutes')
|
const elapsedTime = now.diff(timestampMoment, 'minutes')
|
||||||
|
|
||||||
if (elapsedTime < 1) {
|
if (elapsedTime < 1) {
|
||||||
return 'Just now'
|
return 'Just now'
|
||||||
} else if (elapsedTime < 60) {
|
} else if (elapsedTime < 60) {
|
||||||
return `${elapsedTime}m`
|
return `${elapsedTime}m`
|
||||||
} else if (elapsedTime < 1440) {
|
} else if (elapsedTime < 1440) {
|
||||||
return `${Math.floor(elapsedTime / 60)}h`
|
return `${Math.floor(elapsedTime / 60)}h`
|
||||||
} else {
|
} else {
|
||||||
return timestampMoment.format('MMM D')
|
return timestampMoment.format('MMM D')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const formatDate = (unixTimestamp: number): string => {
|
export const formatDate = (unixTimestamp: number): string => {
|
||||||
const date = moment(unixTimestamp, 'x').fromNow()
|
const date = moment(unixTimestamp, 'x').fromNow()
|
||||||
|
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
export const formatDateSeconds = (unixTimestamp: number): string => {
|
export const formatDateSeconds = (unixTimestamp: number): string => {
|
||||||
const date = moment.unix(unixTimestamp).fromNow();
|
const date = moment.unix(unixTimestamp).fromNow();
|
||||||
|
|
||||||
return date
|
return date
|
||||||
}
|
}
|
@ -1,174 +1,174 @@
|
|||||||
export const toBase64 = (file: File): Promise<string | ArrayBuffer | null> =>
|
export const toBase64 = (file: File): Promise<string | ArrayBuffer | null> =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
reader.readAsDataURL(file)
|
reader.readAsDataURL(file)
|
||||||
|
|
||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
const result = reader.result
|
const result = reader.result
|
||||||
reader.onload = null // remove onload handler
|
reader.onload = null // remove onload handler
|
||||||
reader.onerror = null // remove onerror handler
|
reader.onerror = null // remove onerror handler
|
||||||
resolve(result)
|
resolve(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.onerror = (error) => {
|
reader.onerror = (error) => {
|
||||||
reader.onload = null // remove onload handler
|
reader.onload = null // remove onload handler
|
||||||
reader.onerror = null // remove onerror handler
|
reader.onerror = null // remove onerror handler
|
||||||
reject(error)
|
reject(error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export function objectToBase64(obj: any) {
|
export function objectToBase64(obj: any) {
|
||||||
// Step 1: Convert the object to a JSON string
|
// Step 1: Convert the object to a JSON string
|
||||||
const jsonString = JSON.stringify(obj)
|
const jsonString = JSON.stringify(obj)
|
||||||
|
|
||||||
// Step 2: Create a Blob from the JSON string
|
// Step 2: Create a Blob from the JSON string
|
||||||
const blob = new Blob([jsonString], { type: 'application/json' })
|
const blob = new Blob([jsonString], { type: 'application/json' })
|
||||||
|
|
||||||
// Step 3: Create a FileReader to read the Blob as a base64-encoded string
|
// Step 3: Create a FileReader to read the Blob as a base64-encoded string
|
||||||
return new Promise<string>((resolve, reject) => {
|
return new Promise<string>((resolve, reject) => {
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
reader.onloadend = () => {
|
reader.onloadend = () => {
|
||||||
if (typeof reader.result === 'string') {
|
if (typeof reader.result === 'string') {
|
||||||
// Remove 'data:application/json;base64,' prefix
|
// Remove 'data:application/json;base64,' prefix
|
||||||
const base64 = reader.result.replace(
|
const base64 = reader.result.replace(
|
||||||
'data:application/json;base64,',
|
'data:application/json;base64,',
|
||||||
''
|
''
|
||||||
)
|
)
|
||||||
resolve(base64)
|
resolve(base64)
|
||||||
} else {
|
} else {
|
||||||
reject(new Error('Failed to read the Blob as a base64-encoded string'))
|
reject(new Error('Failed to read the Blob as a base64-encoded string'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reader.onerror = () => {
|
reader.onerror = () => {
|
||||||
reject(reader.error)
|
reject(reader.error)
|
||||||
}
|
}
|
||||||
reader.readAsDataURL(blob)
|
reader.readAsDataURL(blob)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function objectToUint8Array(obj: any) {
|
export function objectToUint8Array(obj: any) {
|
||||||
// Convert the object to a JSON string
|
// Convert the object to a JSON string
|
||||||
const jsonString = JSON.stringify(obj)
|
const jsonString = JSON.stringify(obj)
|
||||||
|
|
||||||
// Encode the JSON string as a byte array using TextEncoder
|
// Encode the JSON string as a byte array using TextEncoder
|
||||||
const encoder = new TextEncoder()
|
const encoder = new TextEncoder()
|
||||||
const byteArray = encoder.encode(jsonString)
|
const byteArray = encoder.encode(jsonString)
|
||||||
|
|
||||||
// Create a new Uint8Array and set its content to the encoded byte array
|
// Create a new Uint8Array and set its content to the encoded byte array
|
||||||
const uint8Array = new Uint8Array(byteArray)
|
const uint8Array = new Uint8Array(byteArray)
|
||||||
|
|
||||||
return uint8Array
|
return uint8Array
|
||||||
}
|
}
|
||||||
|
|
||||||
export function uint8ArrayToBase64(uint8Array: Uint8Array): string {
|
export function uint8ArrayToBase64(uint8Array: Uint8Array): string {
|
||||||
const length = uint8Array.length
|
const length = uint8Array.length
|
||||||
let binaryString = ''
|
let binaryString = ''
|
||||||
const chunkSize = 1024 * 1024 // Process 1MB at a time
|
const chunkSize = 1024 * 1024 // Process 1MB at a time
|
||||||
|
|
||||||
for (let i = 0; i < length; i += chunkSize) {
|
for (let i = 0; i < length; i += chunkSize) {
|
||||||
const chunkEnd = Math.min(i + chunkSize, length)
|
const chunkEnd = Math.min(i + chunkSize, length)
|
||||||
const chunk = uint8Array.subarray(i, chunkEnd)
|
const chunk = uint8Array.subarray(i, chunkEnd)
|
||||||
binaryString += Array.from(chunk, (byte) => String.fromCharCode(byte)).join(
|
binaryString += Array.from(chunk, (byte) => String.fromCharCode(byte)).join(
|
||||||
''
|
''
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return btoa(binaryString)
|
return btoa(binaryString)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function objectToUint8ArrayFromResponse(obj: any) {
|
export function objectToUint8ArrayFromResponse(obj: any) {
|
||||||
const len = Object.keys(obj).length
|
const len = Object.keys(obj).length
|
||||||
const result = new Uint8Array(len)
|
const result = new Uint8Array(len)
|
||||||
|
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
result[i] = obj[i]
|
result[i] = obj[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
// export function uint8ArrayToBase64(arrayBuffer: Uint8Array): string {
|
// export function uint8ArrayToBase64(arrayBuffer: Uint8Array): string {
|
||||||
// let binary = ''
|
// let binary = ''
|
||||||
// const bytes = new Uint8Array(arrayBuffer)
|
// const bytes = new Uint8Array(arrayBuffer)
|
||||||
// const len = bytes.length
|
// const len = bytes.length
|
||||||
|
|
||||||
// for (let i = 0; i < len; i++) {
|
// for (let i = 0; i < len; i++) {
|
||||||
// binary += String.fromCharCode(bytes[i])
|
// binary += String.fromCharCode(bytes[i])
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// return btoa(binary)
|
// return btoa(binary)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
export function base64ToUint8Array(base64: string) {
|
export function base64ToUint8Array(base64: string) {
|
||||||
const binaryString = atob(base64)
|
const binaryString = atob(base64)
|
||||||
const len = binaryString.length
|
const len = binaryString.length
|
||||||
const bytes = new Uint8Array(len)
|
const bytes = new Uint8Array(len)
|
||||||
|
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
bytes[i] = binaryString.charCodeAt(i)
|
bytes[i] = binaryString.charCodeAt(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
return bytes
|
return bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
export function uint8ArrayToObject(uint8Array: Uint8Array) {
|
export function uint8ArrayToObject(uint8Array: Uint8Array) {
|
||||||
// Decode the byte array using TextDecoder
|
// Decode the byte array using TextDecoder
|
||||||
const decoder = new TextDecoder()
|
const decoder = new TextDecoder()
|
||||||
const jsonString = decoder.decode(uint8Array)
|
const jsonString = decoder.decode(uint8Array)
|
||||||
|
|
||||||
// Convert the JSON string back into an object
|
// Convert the JSON string back into an object
|
||||||
const obj = JSON.parse(jsonString)
|
const obj = JSON.parse(jsonString)
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
export function processFileInChunks(file: File): Promise<Uint8Array> {
|
export function processFileInChunks(file: File): Promise<Uint8Array> {
|
||||||
return new Promise(
|
return new Promise(
|
||||||
(resolve: (value: Uint8Array) => void, reject: (reason?: any) => void) => {
|
(resolve: (value: Uint8Array) => void, reject: (reason?: any) => void) => {
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
|
|
||||||
reader.onload = function (event: ProgressEvent<FileReader>) {
|
reader.onload = function (event: ProgressEvent<FileReader>) {
|
||||||
const arrayBuffer = event.target?.result as ArrayBuffer
|
const arrayBuffer = event.target?.result as ArrayBuffer
|
||||||
const uint8Array = new Uint8Array(arrayBuffer)
|
const uint8Array = new Uint8Array(arrayBuffer)
|
||||||
resolve(uint8Array)
|
resolve(uint8Array)
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.onerror = function (error: ProgressEvent<FileReader>) {
|
reader.onerror = function (error: ProgressEvent<FileReader>) {
|
||||||
reject(error)
|
reject(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.readAsArrayBuffer(file)
|
reader.readAsArrayBuffer(file)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// export async function processFileInChunks(file: File, chunkSize = 1024 * 1024): Promise<Uint8Array> {
|
// export async function processFileInChunks(file: File, chunkSize = 1024 * 1024): Promise<Uint8Array> {
|
||||||
// const fileStream = file.stream();
|
// const fileStream = file.stream();
|
||||||
// const reader = fileStream.getReader();
|
// const reader = fileStream.getReader();
|
||||||
// const totalLength = file.size;
|
// const totalLength = file.size;
|
||||||
|
|
||||||
// if (totalLength <= 0 || isNaN(totalLength)) {
|
// if (totalLength <= 0 || isNaN(totalLength)) {
|
||||||
// throw new Error('Invalid file size');
|
// throw new Error('Invalid file size');
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// const combinedArray = new Uint8Array(totalLength);
|
// const combinedArray = new Uint8Array(totalLength);
|
||||||
// let offset = 0;
|
// let offset = 0;
|
||||||
|
|
||||||
// while (offset < totalLength) {
|
// while (offset < totalLength) {
|
||||||
// const { value, done } = await reader.read();
|
// const { value, done } = await reader.read();
|
||||||
|
|
||||||
// if (done) {
|
// if (done) {
|
||||||
// break;
|
// break;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// const chunk = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
// const chunk = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
||||||
|
|
||||||
// // Set elements one by one instead of using combinedArray.set(chunk, offset)
|
// // Set elements one by one instead of using combinedArray.set(chunk, offset)
|
||||||
// for (let i = 0; i < chunk.length; i++) {
|
// for (let i = 0; i < chunk.length; i++) {
|
||||||
// combinedArray[offset + i] = chunk[i];
|
// combinedArray[offset + i] = chunk[i];
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// offset += chunk.length;
|
// offset += chunk.length;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// return combinedArray;
|
// return combinedArray;
|
||||||
// }
|
// }
|
||||||
|
2
src/vite-env.d.ts
vendored
2
src/vite-env.d.ts
vendored
@ -1 +1 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
@ -1,213 +1,213 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
setAddToDownloads,
|
setAddToDownloads,
|
||||||
updateDownloads
|
updateDownloads
|
||||||
} from '../state/features/globalSlice'
|
} from '../state/features/globalSlice'
|
||||||
|
|
||||||
import { DownloadTaskManager } from '../components/common/DownloadTaskManager'
|
import { DownloadTaskManager } from '../components/common/DownloadTaskManager'
|
||||||
import { RootState } from '../state/store'
|
import { RootState } from '../state/store'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const defaultValues: MyContextInterface = {
|
const defaultValues: MyContextInterface = {
|
||||||
downloadVideo: () => {}
|
downloadVideo: () => {}
|
||||||
}
|
}
|
||||||
interface IDownloadVideoParams {
|
interface IDownloadVideoParams {
|
||||||
name: string
|
name: string
|
||||||
service: string
|
service: string
|
||||||
identifier: string
|
identifier: string
|
||||||
properties: any
|
properties: any
|
||||||
}
|
}
|
||||||
interface MyContextInterface {
|
interface MyContextInterface {
|
||||||
downloadVideo: ({
|
downloadVideo: ({
|
||||||
name,
|
name,
|
||||||
service,
|
service,
|
||||||
identifier,
|
identifier,
|
||||||
properties
|
properties
|
||||||
}: IDownloadVideoParams) => void
|
}: IDownloadVideoParams) => void
|
||||||
}
|
}
|
||||||
export const MyContext = React.createContext<MyContextInterface>(defaultValues)
|
export const MyContext = React.createContext<MyContextInterface>(defaultValues)
|
||||||
|
|
||||||
const DownloadWrapper: React.FC<Props> = ({ children }) => {
|
const DownloadWrapper: React.FC<Props> = ({ children }) => {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const downloads = useSelector((state: RootState) => state.global?.downloads);
|
const downloads = useSelector((state: RootState) => state.global?.downloads);
|
||||||
|
|
||||||
|
|
||||||
const fetchResource = async ({ name, service, identifier }: any) => {
|
const fetchResource = async ({ name, service, identifier }: any) => {
|
||||||
try {
|
try {
|
||||||
await qortalRequest({
|
await qortalRequest({
|
||||||
action: 'GET_QDN_RESOURCE_PROPERTIES',
|
action: 'GET_QDN_RESOURCE_PROPERTIES',
|
||||||
name,
|
name,
|
||||||
service,
|
service,
|
||||||
identifier
|
identifier
|
||||||
})
|
})
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchVideoUrl = async ({ name, service, identifier }: any) => {
|
const fetchVideoUrl = async ({ name, service, identifier }: any) => {
|
||||||
try {
|
try {
|
||||||
fetchResource({ name, service, identifier })
|
fetchResource({ name, service, identifier })
|
||||||
let url = await qortalRequest({
|
let url = await qortalRequest({
|
||||||
action: 'GET_QDN_RESOURCE_URL',
|
action: 'GET_QDN_RESOURCE_URL',
|
||||||
service: service,
|
service: service,
|
||||||
name: name,
|
name: name,
|
||||||
identifier: identifier
|
identifier: identifier
|
||||||
})
|
})
|
||||||
if (url) {
|
if (url) {
|
||||||
dispatch(
|
dispatch(
|
||||||
updateDownloads({
|
updateDownloads({
|
||||||
name,
|
name,
|
||||||
service,
|
service,
|
||||||
identifier,
|
identifier,
|
||||||
url
|
url
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const performDownload = ({
|
const performDownload = ({
|
||||||
name,
|
name,
|
||||||
service,
|
service,
|
||||||
identifier,
|
identifier,
|
||||||
properties
|
properties
|
||||||
}: IDownloadVideoParams) => {
|
}: IDownloadVideoParams) => {
|
||||||
if(downloads[identifier]) return
|
if(downloads[identifier]) return
|
||||||
dispatch(
|
dispatch(
|
||||||
setAddToDownloads({
|
setAddToDownloads({
|
||||||
name,
|
name,
|
||||||
service,
|
service,
|
||||||
identifier,
|
identifier,
|
||||||
properties
|
properties
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
let isCalling = false
|
let isCalling = false
|
||||||
let percentLoaded = 0
|
let percentLoaded = 0
|
||||||
let timer = 24
|
let timer = 24
|
||||||
const intervalId = setInterval(async () => {
|
const intervalId = setInterval(async () => {
|
||||||
if (isCalling) return
|
if (isCalling) return
|
||||||
isCalling = true
|
isCalling = true
|
||||||
const res = await qortalRequest({
|
const res = await qortalRequest({
|
||||||
action: 'GET_QDN_RESOURCE_STATUS',
|
action: 'GET_QDN_RESOURCE_STATUS',
|
||||||
name: name,
|
name: name,
|
||||||
service: service,
|
service: service,
|
||||||
identifier: identifier
|
identifier: identifier
|
||||||
})
|
})
|
||||||
if(res?.status === 'NOT_PUBLISHED'){
|
if(res?.status === 'NOT_PUBLISHED'){
|
||||||
dispatch(
|
dispatch(
|
||||||
updateDownloads({
|
updateDownloads({
|
||||||
name,
|
name,
|
||||||
service,
|
service,
|
||||||
identifier,
|
identifier,
|
||||||
status: res
|
status: res
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
clearInterval(intervalId)
|
clearInterval(intervalId)
|
||||||
}
|
}
|
||||||
isCalling = false
|
isCalling = false
|
||||||
if (res.localChunkCount) {
|
if (res.localChunkCount) {
|
||||||
if (res.percentLoaded) {
|
if (res.percentLoaded) {
|
||||||
if (
|
if (
|
||||||
res.percentLoaded === percentLoaded &&
|
res.percentLoaded === percentLoaded &&
|
||||||
res.percentLoaded !== 100
|
res.percentLoaded !== 100
|
||||||
) {
|
) {
|
||||||
timer = timer - 5
|
timer = timer - 5
|
||||||
} else {
|
} else {
|
||||||
timer = 24
|
timer = 24
|
||||||
}
|
}
|
||||||
if (timer < 0) {
|
if (timer < 0) {
|
||||||
timer = 24
|
timer = 24
|
||||||
isCalling = true
|
isCalling = true
|
||||||
dispatch(
|
dispatch(
|
||||||
updateDownloads({
|
updateDownloads({
|
||||||
name,
|
name,
|
||||||
service,
|
service,
|
||||||
identifier,
|
identifier,
|
||||||
status: {
|
status: {
|
||||||
...res,
|
...res,
|
||||||
status: 'REFETCHING'
|
status: 'REFETCHING'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
isCalling = false
|
isCalling = false
|
||||||
fetchResource({
|
fetchResource({
|
||||||
name,
|
name,
|
||||||
service,
|
service,
|
||||||
identifier
|
identifier
|
||||||
})
|
})
|
||||||
}, 25000)
|
}, 25000)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
percentLoaded = res.percentLoaded
|
percentLoaded = res.percentLoaded
|
||||||
}
|
}
|
||||||
dispatch(
|
dispatch(
|
||||||
updateDownloads({
|
updateDownloads({
|
||||||
name,
|
name,
|
||||||
service,
|
service,
|
||||||
identifier,
|
identifier,
|
||||||
status: res
|
status: res
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if progress is 100% and clear interval if true
|
// check if progress is 100% and clear interval if true
|
||||||
if (res?.status === 'READY') {
|
if (res?.status === 'READY') {
|
||||||
clearInterval(intervalId)
|
clearInterval(intervalId)
|
||||||
dispatch(
|
dispatch(
|
||||||
updateDownloads({
|
updateDownloads({
|
||||||
name,
|
name,
|
||||||
service,
|
service,
|
||||||
identifier,
|
identifier,
|
||||||
status: res
|
status: res
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, 5000) // 1 second interval
|
}, 5000) // 1 second interval
|
||||||
|
|
||||||
fetchVideoUrl({
|
fetchVideoUrl({
|
||||||
name,
|
name,
|
||||||
service,
|
service,
|
||||||
identifier
|
identifier
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadVideo = async ({
|
const downloadVideo = async ({
|
||||||
name,
|
name,
|
||||||
service,
|
service,
|
||||||
identifier,
|
identifier,
|
||||||
properties
|
properties
|
||||||
}: IDownloadVideoParams) => {
|
}: IDownloadVideoParams) => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
|
||||||
performDownload({
|
performDownload({
|
||||||
name,
|
name,
|
||||||
service,
|
service,
|
||||||
identifier,
|
identifier,
|
||||||
properties
|
properties
|
||||||
})
|
})
|
||||||
return 'addedToList'
|
return 'addedToList'
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MyContext.Provider value={{ downloadVideo }}>
|
<MyContext.Provider value={{ downloadVideo }}>
|
||||||
{/* <DownloadTaskManager /> */}
|
{/* <DownloadTaskManager /> */}
|
||||||
{children}
|
{children}
|
||||||
</MyContext.Provider>
|
</MyContext.Provider>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DownloadWrapper
|
export default DownloadWrapper
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
|
|
||||||
/* Bundler mode */
|
/* Bundler mode */
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
"strict": false,
|
"strict": false,
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": false,
|
||||||
"noUnusedParameters": false,
|
"noUnusedParameters": false,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"strictNullChecks": false,
|
"strictNullChecks": false,
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowSyntheticDefaultImports": true
|
"allowSyntheticDefaultImports": true
|
||||||
},
|
},
|
||||||
"include": ["vite.config.ts"]
|
"include": ["vite.config.ts"]
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
base: ""
|
base: ""
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user