mirror of
https://github.com/Qortal/q-tube.git
synced 2025-12-12 15:52:59 +00:00
remove unused code
This commit is contained in:
3
.prettierignore
Normal file
3
.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
build
|
||||
dist
|
||||
23
.prettierrc
23
.prettierrc
@@ -1,10 +1,23 @@
|
||||
{
|
||||
"printWidth": 80,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "always",
|
||||
"bracketSameLine": false,
|
||||
"bracketSpacing": true,
|
||||
"embeddedLanguageFormatting": "auto",
|
||||
"endOfLine": "lf",
|
||||
"experimentalTernaries": false,
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"insertPragma": false,
|
||||
"jsxBracketSameLine": false,
|
||||
"arrowParens": "avoid",
|
||||
"jsxSingleQuote": false,
|
||||
"printWidth": 80,
|
||||
"proseWrap": "preserve",
|
||||
"quoteProps": "as-needed",
|
||||
"requirePragma": false,
|
||||
"semi": true,
|
||||
"singleAttributePerLine": false,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"semi": true
|
||||
"trailingComma": "es5",
|
||||
"useTabs": false,
|
||||
"vueIndentScriptAndStyle": false
|
||||
}
|
||||
|
||||
195
package-lock.json
generated
195
package-lock.json
generated
@@ -17,6 +17,7 @@
|
||||
"@reduxjs/toolkit": "^2.5.0",
|
||||
"compressorjs": "^1.2.1",
|
||||
"dompurify": "^3.2.3",
|
||||
"i18n": "^0.15.1",
|
||||
"jotai": "^2.12.4",
|
||||
"localforage": "^1.10.0",
|
||||
"mediainfo.js": "^0.3.5",
|
||||
@@ -27,6 +28,7 @@
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-dropzone": "^14.3.5",
|
||||
"react-i18next": "^15.5.3",
|
||||
"react-idle-timer": "^5.7.2",
|
||||
"react-intersection-observer": "^9.14.0",
|
||||
"react-quill-new": "^3.3.3",
|
||||
@@ -295,9 +297,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz",
|
||||
"integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==",
|
||||
"version": "7.27.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
|
||||
"integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -1178,6 +1180,50 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@messageformat/core": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@messageformat/core/-/core-3.4.0.tgz",
|
||||
"integrity": "sha512-NgCFubFFIdMWJGN5WuQhHCNmzk7QgiVfrViFxcS99j7F5dDS5EP6raR54I+2ydhe4+5/XTn/YIEppFaqqVWHsw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@messageformat/date-skeleton": "^1.0.0",
|
||||
"@messageformat/number-skeleton": "^1.0.0",
|
||||
"@messageformat/parser": "^5.1.0",
|
||||
"@messageformat/runtime": "^3.0.1",
|
||||
"make-plural": "^7.0.0",
|
||||
"safe-identifier": "^0.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@messageformat/date-skeleton": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@messageformat/date-skeleton/-/date-skeleton-1.1.0.tgz",
|
||||
"integrity": "sha512-rmGAfB1tIPER+gh3p/RgA+PVeRE/gxuQ2w4snFWPF5xtb5mbWR7Cbw7wCOftcUypbD6HVoxrVdyyghPm3WzP5A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@messageformat/number-skeleton": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@messageformat/number-skeleton/-/number-skeleton-1.2.0.tgz",
|
||||
"integrity": "sha512-xsgwcL7J7WhlHJ3RNbaVgssaIwcEyFkBqxHdcdaiJzwTZAWEOD8BuUFxnxV9k5S0qHN3v/KzUpq0IUpjH1seRg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@messageformat/parser": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@messageformat/parser/-/parser-5.1.1.tgz",
|
||||
"integrity": "sha512-3p0YRGCcTUCYvBKLIxtDDyrJ0YijGIwrTRu1DT8gIviIDZru8H23+FkY6MJBzM1n9n20CiM4VeDYuBsrrwnLjg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"moo": "^0.5.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@messageformat/runtime": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@messageformat/runtime/-/runtime-3.0.1.tgz",
|
||||
"integrity": "sha512-6RU5ol2lDtO8bD9Yxe6CZkl0DArdv0qkuoZC+ZwowU+cdRlVE1157wjCmlA5Rsf1Xc/brACnsZa5PZpEDfTFFg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"make-plural": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/core-downloads-tracker": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.1.0.tgz",
|
||||
@@ -3170,6 +3216,15 @@
|
||||
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-printf": {
|
||||
"version": "1.6.10",
|
||||
"resolved": "https://registry.npmjs.org/fast-printf/-/fast-printf-1.6.10.tgz",
|
||||
"integrity": "sha512-GwTgG9O4FVIdShhbVF3JxOgSBY2+ePGsu2V/UONgoCPzF9VY6ZdBMKsHKCYQHZwNk3qNouUolRDsgVxcVA5G1w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.18.0",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz",
|
||||
@@ -3467,6 +3522,67 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"node_modules/html-parse-stringify": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
||||
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"void-elements": "3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/i18n": {
|
||||
"version": "0.15.1",
|
||||
"resolved": "https://registry.npmjs.org/i18n/-/i18n-0.15.1.tgz",
|
||||
"integrity": "sha512-yue187t8MqUPMHdKjiZGrX+L+xcUsDClGO0Cz4loaKUOK9WrGw5pgan4bv130utOwX7fHE9w2iUeHFalVQWkXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@messageformat/core": "^3.0.0",
|
||||
"debug": "^4.3.3",
|
||||
"fast-printf": "^1.6.9",
|
||||
"make-plural": "^7.0.0",
|
||||
"math-interval-parser": "^2.0.1",
|
||||
"mustache": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/mashpie"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "25.2.1",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.2.1.tgz",
|
||||
"integrity": "sha512-+UoXK5wh+VlE1Zy5p6MjcvctHXAhRwQKCxiJD8noKZzIXmnAX8gdHX5fLPA3MEVxEN4vbZkQFy8N0LyD9tUqPw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com/i18next.html"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
@@ -3896,6 +4012,21 @@
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/make-plural": {
|
||||
"version": "7.4.0",
|
||||
"resolved": "https://registry.npmjs.org/make-plural/-/make-plural-7.4.0.tgz",
|
||||
"integrity": "sha512-4/gC9KVNTV6pvYg2gFeQYTW3mWaoJt7WZE5vrp1KnQDgW92JtYZnzmZT81oj/dUTqAIu0ufI2x3dkgu3bB1tYg==",
|
||||
"license": "Unicode-DFS-2016"
|
||||
},
|
||||
"node_modules/math-interval-parser": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/math-interval-parser/-/math-interval-parser-2.0.1.tgz",
|
||||
"integrity": "sha512-VmlAmb0UJwlvMyx8iPhXUDnVW1F9IrGEd9CIOmv+XL8AErCUUuozoDMrgImvnYt2A+53qVX/tPW6YJurMKYsvA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
@@ -3964,11 +4095,26 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/moo": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz",
|
||||
"integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/mustache": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
|
||||
"integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mustache": "bin/mustache"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
@@ -4472,6 +4618,32 @@
|
||||
"react-dom": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/react-i18next": {
|
||||
"version": "15.5.3",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.3.tgz",
|
||||
"integrity": "sha512-ypYmOKOnjqPEJZO4m1BI0kS8kWqkBNsKYyhVUfij0gvjy9xJNoG/VcGkxq5dRlVwzmrmY1BQMAmpbbUBLwC4Kw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.6",
|
||||
"html-parse-stringify": "^3.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"i18next": ">= 23.2.3",
|
||||
"react": ">= 16.8.0",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
},
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-idle-timer": {
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/react-idle-timer/-/react-idle-timer-5.7.2.tgz",
|
||||
@@ -4801,6 +4973,12 @@
|
||||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-identifier": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-identifier/-/safe-identifier-0.4.2.tgz",
|
||||
"integrity": "sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
|
||||
@@ -5067,7 +5245,7 @@
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz",
|
||||
"integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -5263,6 +5441,15 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/void-elements": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
|
||||
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"@reduxjs/toolkit": "^2.5.0",
|
||||
"compressorjs": "^1.2.1",
|
||||
"dompurify": "^3.2.3",
|
||||
"i18n": "^0.15.1",
|
||||
"jotai": "^2.12.4",
|
||||
"localforage": "^1.10.0",
|
||||
"mediainfo.js": "^0.3.5",
|
||||
@@ -29,6 +30,7 @@
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-dropzone": "^14.3.5",
|
||||
"react-i18next": "^15.5.3",
|
||||
"react-idle-timer": "^5.7.2",
|
||||
"react-intersection-observer": "^9.14.0",
|
||||
"react-quill-new": "^3.3.3",
|
||||
@@ -49,10 +51,10 @@
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-plugin-react-hooks": "^5.1.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.16",
|
||||
"globals": "^15.15.0",
|
||||
"prettier": "^3.4.2",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^6.3.5",
|
||||
"globals": "^15.15.0",
|
||||
"vite-plugin-static-copy": "^3.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
48
src/App.tsx
48
src/App.tsx
@@ -1,32 +1,21 @@
|
||||
import { CssBaseline } from "@mui/material";
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Provider, useSelector } from "react-redux";
|
||||
import { PersistGate } from "redux-persist/integration/react";
|
||||
import { subscriptionListFilter } from "./App-Functions.ts";
|
||||
import Notification from "./components/common/Notification/Notification";
|
||||
import { useIframe } from "./hooks/useIframe.tsx";
|
||||
import { IndividualProfile } from "./pages/ContentPages/IndividualProfile/IndividualProfile";
|
||||
import { PlaylistContent } from "./pages/ContentPages/PlaylistContent/PlaylistContent";
|
||||
import { VideoContent } from "./pages/ContentPages/VideoContent/VideoContent";
|
||||
import { Home } from "./pages/Home/Home";
|
||||
import { setFilteredSubscriptions } from "./state/features/videoSlice.ts";
|
||||
import { store, persistor, RootState } from "./state/store";
|
||||
import { darkTheme, lightTheme } from "./styles/theme";
|
||||
import DownloadWrapper from "./wrappers/DownloadWrapper";
|
||||
import GlobalWrapper from "./wrappers/GlobalWrapper";
|
||||
import { ScrollWrapper } from "./wrappers/ScrollWrapper.tsx";
|
||||
import { QappCoreWrapper } from "./QappCoreWrapper.tsx";
|
||||
import { Routes } from "./Routes.tsx";
|
||||
import { CssBaseline } from '@mui/material';
|
||||
import { ThemeProvider } from '@mui/material/styles';
|
||||
import { useEffect } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { PersistGate } from 'redux-persist/integration/react';
|
||||
import { subscriptionListFilter } from './App-Functions.ts';
|
||||
import Notification from './components/common/Notification/Notification';
|
||||
|
||||
import { setFilteredSubscriptions } from './state/features/videoSlice.ts';
|
||||
import { store, persistor } from './state/store';
|
||||
import { darkTheme } from './styles/theme';
|
||||
import DownloadWrapper from './wrappers/DownloadWrapper';
|
||||
|
||||
import { Routes } from './Routes.tsx';
|
||||
|
||||
function App() {
|
||||
// const themeColor = window._qdnTheme
|
||||
|
||||
|
||||
// useIframe();
|
||||
|
||||
useEffect(() => {
|
||||
subscriptionListFilter(false).then(filteredList => {
|
||||
subscriptionListFilter(false).then((filteredList) => {
|
||||
store.dispatch(setFilteredSubscriptions(filteredList));
|
||||
});
|
||||
}, []);
|
||||
@@ -35,13 +24,10 @@ function App() {
|
||||
<Provider store={store}>
|
||||
<PersistGate loading={null} persistor={persistor}>
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<CssBaseline />
|
||||
<CssBaseline />
|
||||
<Notification />
|
||||
<DownloadWrapper>
|
||||
|
||||
|
||||
<Routes />
|
||||
|
||||
<Routes />
|
||||
</DownloadWrapper>
|
||||
</ThemeProvider>
|
||||
</PersistGate>
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { GlobalProvider } from 'qapp-core'
|
||||
import { useSelector } from 'react-redux';
|
||||
import { RootState } from './state/store';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
export const QappCoreWrapper = ({children}) => {
|
||||
const { user } = useSelector((state: RootState) => state.auth);
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
return (
|
||||
<GlobalProvider
|
||||
config={{
|
||||
auth: {
|
||||
authenticateOnMount: false,
|
||||
userAccountInfo: {
|
||||
address: user?.address,
|
||||
publicKey: user?.publicKey
|
||||
}
|
||||
},
|
||||
publicSalt: "usVbeM9YpjGCbLrTcc78YJS0ap1AxDkHAOMZrp3+wDY=",
|
||||
appName: "Q-Tube",
|
||||
}}
|
||||
navigate={navigate}
|
||||
location={location}
|
||||
>
|
||||
{children}
|
||||
</GlobalProvider>
|
||||
)
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
import { Box, CircularProgress, Typography } from "@mui/material";
|
||||
import { minDuration } from "../../../../constants/Misc.ts";
|
||||
import { setVideoPlaying } from "../../../../state/features/globalSlice.ts";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { PlayArrow } from "@mui/icons-material";
|
||||
import { formatTime } from "../../../../utils/numberFunctions.ts";
|
||||
import { useVideoContext } from "./VideoContext.ts";
|
||||
|
||||
export const LoadingVideo = () => {
|
||||
const {
|
||||
isLoading,
|
||||
resourceStatus,
|
||||
src,
|
||||
startPlay,
|
||||
canPlay,
|
||||
from,
|
||||
togglePlay,
|
||||
duration,
|
||||
} = useVideoContext();
|
||||
|
||||
const getDownloadProgress = (current: number, total: number) => {
|
||||
const progress = (current / total) * 100;
|
||||
return Number.isNaN(progress) ? "" : progress.toFixed(0) + "%";
|
||||
};
|
||||
|
||||
const dispatch = useDispatch();
|
||||
return (
|
||||
<>
|
||||
{isLoading.value && (
|
||||
<Box
|
||||
position="absolute"
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={resourceStatus?.status === "READY" ? "55px " : 0}
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
zIndex={25}
|
||||
bgcolor="rgba(0, 0, 0, 0.6)"
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "10px",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<CircularProgress color="secondary" />
|
||||
{resourceStatus && (
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
component="div"
|
||||
sx={{
|
||||
color: "white",
|
||||
fontSize: "15px",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
{resourceStatus?.status === "NOT_PUBLISHED" && (
|
||||
<>Video file was not published. Please inform the publisher!</>
|
||||
)}
|
||||
{resourceStatus?.status === "REFETCHING" ? (
|
||||
<>
|
||||
<>
|
||||
{getDownloadProgress(
|
||||
resourceStatus?.localChunkCount,
|
||||
resourceStatus?.totalChunkCount
|
||||
)}
|
||||
</>
|
||||
|
||||
<> Refetching in 25 seconds</>
|
||||
</>
|
||||
) : resourceStatus?.status === "DOWNLOADED" ? (
|
||||
<>Download Completed: building video...</>
|
||||
) : resourceStatus?.status !== "READY" ? (
|
||||
<>
|
||||
{getDownloadProgress(
|
||||
resourceStatus?.localChunkCount,
|
||||
resourceStatus?.totalChunkCount
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>Fetching video...</>
|
||||
)}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{((!src && !isLoading.value) || (!startPlay.value && !canPlay.value)) && (
|
||||
<>
|
||||
{duration > minDuration && (
|
||||
<Box
|
||||
position="absolute"
|
||||
right={0}
|
||||
bottom={0}
|
||||
bgcolor="#202020"
|
||||
zIndex={999}
|
||||
>
|
||||
<Typography color="white">{formatTime(duration)}</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<Box
|
||||
position="absolute"
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
zIndex={500}
|
||||
bgcolor="rgba(0, 0, 0, 0.6)"
|
||||
onClick={() => {
|
||||
if (from === "create") return;
|
||||
dispatch(setVideoPlaying(null));
|
||||
|
||||
togglePlay();
|
||||
}}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<PlayArrow
|
||||
sx={{
|
||||
width: "50px",
|
||||
height: "50px",
|
||||
color: "white",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,71 +0,0 @@
|
||||
import { MoreVert as MoreIcon } from "@mui/icons-material";
|
||||
import { Box, IconButton, Menu, MenuItem } from "@mui/material";
|
||||
import { useVideoContext } from "./VideoContext.ts";
|
||||
import {
|
||||
FullscreenButton,
|
||||
ObjectFitButton,
|
||||
PictureInPictureButton,
|
||||
PlaybackRate,
|
||||
PlayButton,
|
||||
ProgressSlider,
|
||||
ReloadButton,
|
||||
VideoTime,
|
||||
VolumeControl,
|
||||
} from "./VideoControls.tsx";
|
||||
|
||||
export const MobileControlsBar = () => {
|
||||
const { handleMenuOpen, handleMenuClose, anchorEl, controlsHeight } =
|
||||
useVideoContext();
|
||||
|
||||
const controlGroupSX = {
|
||||
display: "flex",
|
||||
gap: "5px",
|
||||
alignItems: "center",
|
||||
height: controlsHeight,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={controlGroupSX}>
|
||||
<PlayButton />
|
||||
<ReloadButton />
|
||||
<ProgressSlider />
|
||||
<VideoTime />
|
||||
</Box>
|
||||
|
||||
<Box sx={controlGroupSX}>
|
||||
<PlaybackRate />
|
||||
<FullscreenButton />
|
||||
|
||||
<IconButton
|
||||
edge="end"
|
||||
color="inherit"
|
||||
aria-label="menu"
|
||||
onClick={handleMenuOpen}
|
||||
sx={{ paddingLeft: "0px", marginRight: "0px" }}
|
||||
>
|
||||
<MoreIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Menu
|
||||
id="simple-menu"
|
||||
anchorEl={anchorEl.value}
|
||||
keepMounted
|
||||
open={Boolean(anchorEl.value)}
|
||||
onClose={handleMenuClose}
|
||||
PaperProps={{
|
||||
style: {
|
||||
width: "250px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<MenuItem>
|
||||
<ObjectFitButton />
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<PictureInPictureButton />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,28 +0,0 @@
|
||||
import { createContext, useContext } from "react";
|
||||
import { useVideoPlayerState } from "../VideoPlayer-State.ts";
|
||||
import { VideoPlayerProps } from "../VideoPlayer.tsx";
|
||||
import { useVideoControlsState } from "./VideoControls-State.ts";
|
||||
|
||||
export type VideoContextType = VideoPlayerProps &
|
||||
ReturnType<typeof useVideoPlayerState> &
|
||||
ReturnType<typeof useVideoControlsState>;
|
||||
|
||||
export const VideoContext = createContext<VideoContextType | undefined>(
|
||||
undefined
|
||||
);
|
||||
export const useContextData = (props, ref): VideoContextType => {
|
||||
const videoState = useVideoPlayerState(props, ref);
|
||||
const controlState = useVideoControlsState(props, videoState);
|
||||
|
||||
return {
|
||||
...props,
|
||||
...videoState,
|
||||
...controlState,
|
||||
};
|
||||
};
|
||||
|
||||
export const useVideoContext = () => {
|
||||
const context = useContext<VideoContextType>(VideoContext);
|
||||
if (!context) throw new Error("VideoContext is NULL");
|
||||
return context;
|
||||
};
|
||||
@@ -1,378 +0,0 @@
|
||||
import { useSignal } from "@preact/signals-react";
|
||||
import { useSignalEffect, useSignals } from "@preact/signals-react/runtime";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
// import { Key } from "ts-key-enum";
|
||||
import { setVideoPlaying } from "../../../../state/features/globalSlice.ts";
|
||||
import { RootState } from "../../../../state/store.ts";
|
||||
import { useVideoPlayerState } from "../VideoPlayer-State.ts";
|
||||
import { VideoPlayerProps } from "../VideoPlayer.tsx";
|
||||
|
||||
export const useVideoControlsState = (
|
||||
props: VideoPlayerProps,
|
||||
videoPlayerState: ReturnType<typeof useVideoPlayerState>
|
||||
) => {
|
||||
const {
|
||||
src,
|
||||
getSrc,
|
||||
resourceStatus,
|
||||
videoRef,
|
||||
playbackRate,
|
||||
playing,
|
||||
isLoading,
|
||||
startPlay,
|
||||
volume,
|
||||
isMuted,
|
||||
mutedVolume,
|
||||
progress,
|
||||
videoObjectFit,
|
||||
canPlay,
|
||||
containerRef,
|
||||
alwaysShowControls,
|
||||
} = videoPlayerState;
|
||||
const { identifier, autoPlay } = props;
|
||||
|
||||
const showControlsFullScreen = useSignal(true);
|
||||
const dispatch = useDispatch();
|
||||
const persistSelector = useSelector((root: RootState) => root.persist);
|
||||
|
||||
const videoPlaying = useSelector(
|
||||
(state: RootState) => state.global.videoPlaying
|
||||
);
|
||||
|
||||
const controlsHeight = "42px";
|
||||
const minSpeed = 0.25;
|
||||
const maxSpeed = 4.0;
|
||||
const speedChange = 0.25;
|
||||
|
||||
const updatePlaybackRate = (newSpeed: number) => {
|
||||
if (videoRef.current) {
|
||||
if (newSpeed > maxSpeed || newSpeed < minSpeed) newSpeed = minSpeed;
|
||||
|
||||
videoRef.current.playbackRate = newSpeed;
|
||||
playbackRate.value = newSpeed;
|
||||
}
|
||||
};
|
||||
|
||||
const increaseSpeed = (wrapOverflow = true) => {
|
||||
const changedSpeed = playbackRate.value + speedChange;
|
||||
const newSpeed = wrapOverflow
|
||||
? changedSpeed
|
||||
: Math.min(changedSpeed, maxSpeed);
|
||||
|
||||
updatePlaybackRate(newSpeed);
|
||||
};
|
||||
|
||||
const decreaseSpeed = () => {
|
||||
updatePlaybackRate(playbackRate.value - speedChange);
|
||||
};
|
||||
|
||||
const isFullscreen = useSignal(false);
|
||||
|
||||
const enterFullscreen = () => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
if (containerRef.current.requestFullscreen && !isFullscreen.value) {
|
||||
containerRef.current.requestFullscreen();
|
||||
}
|
||||
};
|
||||
|
||||
const exitFullscreen = () => {
|
||||
if (isFullscreen.value) document.exitFullscreen();
|
||||
};
|
||||
|
||||
const toggleFullscreen = () => {
|
||||
isFullscreen.value ? exitFullscreen() : enterFullscreen();
|
||||
};
|
||||
const togglePictureInPicture = async () => {
|
||||
if (!videoRef.current) return;
|
||||
if (document.pictureInPictureElement === videoRef.current) {
|
||||
await document.exitPictureInPicture();
|
||||
} else {
|
||||
await videoRef.current.requestPictureInPicture();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleFullscreenChange = () => {
|
||||
isFullscreen.value = !!document.fullscreenElement;
|
||||
};
|
||||
|
||||
document.addEventListener("fullscreenchange", handleFullscreenChange);
|
||||
return () => {
|
||||
document.removeEventListener("fullscreenchange", handleFullscreenChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const reloadVideo = async () => {
|
||||
if (!videoRef.current || !src) return;
|
||||
const currentTime = videoRef.current.currentTime;
|
||||
videoRef.current.src = src;
|
||||
videoRef.current.load();
|
||||
videoRef.current.currentTime = currentTime;
|
||||
playing.value = true;
|
||||
togglePlay();
|
||||
};
|
||||
|
||||
const firstPlay = useSignal(true);
|
||||
const handleCanPlay = () => {
|
||||
if (firstPlay.value) setPlaying(true); // makes the video play when fully loaded
|
||||
firstPlay.value = false;
|
||||
isLoading.value = false;
|
||||
updatePlaybackRate(persistSelector.playbackRate);
|
||||
};
|
||||
|
||||
const setPlaying = async (setPlay: boolean) => {
|
||||
setPlay ? await videoRef.current.play() : videoRef.current.pause();
|
||||
playing.value = setPlay;
|
||||
};
|
||||
|
||||
const togglePlay = async () => {
|
||||
if (!videoRef.current) return;
|
||||
if (!src || resourceStatus?.status !== "READY") {
|
||||
const el = document.getElementById("videoWrapper");
|
||||
if (el) {
|
||||
el?.parentElement?.removeChild(el);
|
||||
}
|
||||
ReactDOM.flushSync(() => {
|
||||
isLoading.value = true;
|
||||
});
|
||||
getSrc();
|
||||
}
|
||||
|
||||
startPlay.value = true;
|
||||
setPlaying(!playing.value);
|
||||
};
|
||||
|
||||
const onVolumeChange = (_: any, value: number | number[]) => {
|
||||
if (!videoRef.current) return;
|
||||
const newVolume = value as number;
|
||||
isMuted.value = false;
|
||||
mutedVolume.value = newVolume;
|
||||
volume.value = newVolume;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (autoPlay && identifier) togglePlay();
|
||||
}, [autoPlay, identifier]);
|
||||
|
||||
const mute = () => {
|
||||
isMuted.value = true;
|
||||
mutedVolume.value = volume.value;
|
||||
volume.value = 0;
|
||||
};
|
||||
const unMute = () => {
|
||||
isMuted.value = false;
|
||||
volume.value = mutedVolume.value;
|
||||
};
|
||||
|
||||
const toggleMute = () => {
|
||||
isMuted.value ? unMute() : mute();
|
||||
};
|
||||
|
||||
const changeVolume = (volumeChange: number) => {
|
||||
if (videoRef.current) {
|
||||
const minVolume = 0;
|
||||
const maxVolume = 1;
|
||||
|
||||
let newVolume = volumeChange + volume.value;
|
||||
|
||||
newVolume = Math.max(newVolume, minVolume);
|
||||
newVolume = Math.min(newVolume, maxVolume);
|
||||
newVolume = +newVolume.toFixed(2);
|
||||
isMuted.value = false;
|
||||
mutedVolume.value = newVolume;
|
||||
volume.value = newVolume;
|
||||
}
|
||||
};
|
||||
const setProgressRelative = (secondsChange: number) => {
|
||||
if (videoRef.current) {
|
||||
const currentTime = videoRef.current?.currentTime;
|
||||
const minTime = 0;
|
||||
const maxTime = videoRef.current?.duration || 100;
|
||||
|
||||
let newTime = currentTime + secondsChange;
|
||||
newTime = Math.max(newTime, minTime);
|
||||
newTime = Math.min(newTime, maxTime);
|
||||
videoRef.current.currentTime = newTime;
|
||||
progress.value = newTime;
|
||||
}
|
||||
};
|
||||
|
||||
const setProgressAbsolute = (videoPercent: number) => {
|
||||
if (videoRef.current) {
|
||||
videoPercent = Math.min(videoPercent, 100);
|
||||
videoPercent = Math.max(videoPercent, 0);
|
||||
const finalTime = (videoRef.current?.duration * videoPercent) / 100;
|
||||
videoRef.current.currentTime = finalTime;
|
||||
progress.value = finalTime;
|
||||
}
|
||||
};
|
||||
|
||||
const setObjectFit = (value: "contain" | "fill") => {
|
||||
videoObjectFit.value = value;
|
||||
};
|
||||
|
||||
const toggleObjectFit = () => {
|
||||
videoObjectFit.value =
|
||||
videoObjectFit.value === "contain" ? "fill" : "contain";
|
||||
};
|
||||
|
||||
const toggleAlwaysShowControls = () => {
|
||||
alwaysShowControls.value = !alwaysShowControls.value;
|
||||
};
|
||||
const keyboardShortcuts = (
|
||||
e: KeyboardEvent | React.KeyboardEvent<HTMLDivElement>
|
||||
) => {
|
||||
e.preventDefault();
|
||||
// console.log("hotkey is: ", '"' + e.key + '"');
|
||||
|
||||
// switch (e.key) {
|
||||
// case "o":
|
||||
// toggleObjectFit();
|
||||
// break;
|
||||
// case "c":
|
||||
// toggleAlwaysShowControls();
|
||||
// break;
|
||||
// case Key.Add:
|
||||
// increaseSpeed(false);
|
||||
// break;
|
||||
// case "+":
|
||||
// increaseSpeed(false);
|
||||
// break;
|
||||
// case ">":
|
||||
// increaseSpeed(false);
|
||||
// break;
|
||||
|
||||
// case Key.Subtract:
|
||||
// decreaseSpeed();
|
||||
// break;
|
||||
// case "-":
|
||||
// decreaseSpeed();
|
||||
// break;
|
||||
// case "<":
|
||||
// decreaseSpeed();
|
||||
// break;
|
||||
|
||||
// case Key.ArrowLeft:
|
||||
// {
|
||||
// if (e.shiftKey) setProgressRelative(-300);
|
||||
// else if (e.ctrlKey) setProgressRelative(-60);
|
||||
// else if (e.altKey) setProgressRelative(-10);
|
||||
// else setProgressRelative(-5);
|
||||
// }
|
||||
// break;
|
||||
|
||||
// case Key.ArrowRight:
|
||||
// {
|
||||
// if (e.shiftKey) setProgressRelative(300);
|
||||
// else if (e.ctrlKey) setProgressRelative(60);
|
||||
// else if (e.altKey) setProgressRelative(10);
|
||||
// else setProgressRelative(5);
|
||||
// }
|
||||
// break;
|
||||
|
||||
// case Key.ArrowDown:
|
||||
// changeVolume(-0.05);
|
||||
// break;
|
||||
// case Key.ArrowUp:
|
||||
// changeVolume(0.05);
|
||||
// break;
|
||||
// case " ":
|
||||
// togglePlay();
|
||||
// break;
|
||||
// case "m":
|
||||
// toggleMute();
|
||||
// break;
|
||||
|
||||
// case "f":
|
||||
// toggleFullscreen();
|
||||
// break;
|
||||
// case Key.Escape:
|
||||
// exitFullscreen();
|
||||
// break;
|
||||
|
||||
// case "r":
|
||||
// reloadVideo();
|
||||
// break;
|
||||
|
||||
// case "p":
|
||||
// togglePictureInPicture();
|
||||
// break;
|
||||
|
||||
// case "0":
|
||||
// setProgressAbsolute(0);
|
||||
// break;
|
||||
// case "1":
|
||||
// setProgressAbsolute(10);
|
||||
// break;
|
||||
// case "2":
|
||||
// setProgressAbsolute(20);
|
||||
// break;
|
||||
// case "3":
|
||||
// setProgressAbsolute(30);
|
||||
// break;
|
||||
// case "4":
|
||||
// setProgressAbsolute(40);
|
||||
// break;
|
||||
// case "5":
|
||||
// setProgressAbsolute(50);
|
||||
// break;
|
||||
// case "6":
|
||||
// setProgressAbsolute(60);
|
||||
// break;
|
||||
// case "7":
|
||||
// setProgressAbsolute(70);
|
||||
// break;
|
||||
// case "8":
|
||||
// setProgressAbsolute(80);
|
||||
// break;
|
||||
// case "9":
|
||||
// setProgressAbsolute(90);
|
||||
// break;
|
||||
// }
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
videoRef.current.volume = volume.value;
|
||||
if (
|
||||
videoPlaying &&
|
||||
videoPlaying.id === identifier &&
|
||||
src &&
|
||||
videoRef?.current
|
||||
) {
|
||||
handleCanPlay();
|
||||
|
||||
videoRef.current.currentTime = videoPlaying.currentTime;
|
||||
videoRef.current.play().then(() => {
|
||||
playing.value = true;
|
||||
startPlay.value = true;
|
||||
dispatch(setVideoPlaying(null));
|
||||
});
|
||||
}
|
||||
}, [videoPlaying, identifier, src]);
|
||||
|
||||
useSignalEffect(() => {
|
||||
console.log("canPlay is: ", canPlay.value); // makes the function execute when canPlay changes
|
||||
});
|
||||
|
||||
return {
|
||||
reloadVideo,
|
||||
togglePlay,
|
||||
onVolumeChange,
|
||||
increaseSpeed,
|
||||
togglePictureInPicture,
|
||||
toggleFullscreen,
|
||||
keyboardShortcuts,
|
||||
handleCanPlay,
|
||||
toggleMute,
|
||||
showControlsFullScreen,
|
||||
setPlaying,
|
||||
isFullscreen,
|
||||
toggleObjectFit,
|
||||
setObjectFit,
|
||||
controlsHeight,
|
||||
};
|
||||
};
|
||||
@@ -1,254 +0,0 @@
|
||||
import { Box, IconButton, Slider, Typography } from "@mui/material";
|
||||
import { fontSizeExSmall, fontSizeSmall } from "../../../../constants/Misc.ts";
|
||||
import { CustomFontTooltip } from "../../../../utils/CustomFontTooltip.tsx";
|
||||
import { formatTime } from "../../../../utils/numberFunctions.ts";
|
||||
import { useVideoContext } from "./VideoContext.ts";
|
||||
import AspectRatioIcon from "@mui/icons-material/AspectRatio";
|
||||
import {
|
||||
Fullscreen,
|
||||
Pause,
|
||||
PictureInPicture,
|
||||
PlayArrow,
|
||||
Refresh,
|
||||
VolumeOff,
|
||||
VolumeUp,
|
||||
} from "@mui/icons-material";
|
||||
import { useSignalEffect } from "@preact/signals-react";
|
||||
|
||||
const buttonPaddingBig = "6px";
|
||||
const buttonPaddingSmall = "4px";
|
||||
|
||||
export const PlayButton = () => {
|
||||
const { togglePlay, playing, isScreenSmall } = useVideoContext();
|
||||
return (
|
||||
<CustomFontTooltip title="Pause/Play (Spacebar)" placement="bottom" arrow>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "white",
|
||||
padding: isScreenSmall ? buttonPaddingSmall : buttonPaddingBig,
|
||||
}}
|
||||
onClick={() => togglePlay()}
|
||||
>
|
||||
{playing.value ? <Pause /> : <PlayArrow />}
|
||||
</IconButton>
|
||||
</CustomFontTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export const ReloadButton = () => {
|
||||
const { reloadVideo, isScreenSmall } = useVideoContext();
|
||||
return (
|
||||
<CustomFontTooltip title="Reload Video (R)" placement="bottom" arrow>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "white",
|
||||
padding: isScreenSmall ? buttonPaddingSmall : buttonPaddingBig,
|
||||
}}
|
||||
onClick={reloadVideo}
|
||||
>
|
||||
<Refresh />
|
||||
</IconButton>
|
||||
</CustomFontTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export const ProgressSlider = () => {
|
||||
const { progress, onProgressChange, videoRef } = useVideoContext();
|
||||
return (
|
||||
<Slider
|
||||
value={progress.value}
|
||||
onChange={onProgressChange}
|
||||
min={0}
|
||||
max={videoRef.current?.duration || 100}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
bottom: "42px",
|
||||
color: "#00abff",
|
||||
padding: "0px",
|
||||
// prevents the slider from jumping up 20px in certain mobile conditions
|
||||
"@media (pointer: coarse)": { padding: "0px" },
|
||||
|
||||
"& .MuiSlider-thumb": {
|
||||
backgroundColor: "#fff",
|
||||
width: "16px",
|
||||
height: "16px",
|
||||
},
|
||||
"& .MuiSlider-thumb::after": { width: "20px", height: "20px" },
|
||||
"& .MuiSlider-rail": { opacity: 0.5, height: "6px" },
|
||||
"& .MuiSlider-track": { height: "6px", border: "0px" },
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const VideoTime = () => {
|
||||
const { videoRef, progress, isScreenSmall } = useVideoContext();
|
||||
|
||||
return (
|
||||
<CustomFontTooltip
|
||||
title="Seek video in 10% increments (0-9)"
|
||||
placement="bottom"
|
||||
arrow
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: isScreenSmall ? fontSizeExSmall : fontSizeSmall,
|
||||
color: "white",
|
||||
visibility: !videoRef.current?.duration ? "hidden" : "visible",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{videoRef.current?.duration ? formatTime(progress.value) : ""}
|
||||
{" / "}
|
||||
{videoRef.current?.duration
|
||||
? formatTime(videoRef.current?.duration)
|
||||
: ""}
|
||||
</Typography>
|
||||
</CustomFontTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const VolumeButton = () => {
|
||||
const { isMuted, toggleMute } = useVideoContext();
|
||||
return (
|
||||
<CustomFontTooltip
|
||||
title="Toggle Mute (M), Raise (UP), Lower (DOWN)"
|
||||
placement="bottom"
|
||||
arrow
|
||||
>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "white",
|
||||
}}
|
||||
onClick={toggleMute}
|
||||
>
|
||||
{isMuted.value ? <VolumeOff /> : <VolumeUp />}
|
||||
</IconButton>
|
||||
</CustomFontTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const VolumeSlider = ({ width }: { width: string }) => {
|
||||
const { volume, onVolumeChange } = useVideoContext();
|
||||
let color = "";
|
||||
if (volume.value <= 0.5) color = "green";
|
||||
else if (volume.value <= 0.75) color = "yellow";
|
||||
else color = "red";
|
||||
|
||||
return (
|
||||
<Slider
|
||||
value={volume.value}
|
||||
onChange={onVolumeChange}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
sx={{
|
||||
width,
|
||||
marginRight: "10px",
|
||||
color,
|
||||
"& .MuiSlider-thumb": {
|
||||
backgroundColor: "#fff",
|
||||
width: "16px",
|
||||
height: "16px",
|
||||
},
|
||||
"& .MuiSlider-thumb::after": { width: "16px", height: "16px" },
|
||||
"& .MuiSlider-rail": { opacity: 0.5, height: "6px" },
|
||||
"& .MuiSlider-track": { height: "6px", border: "0px" },
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const VolumeControl = ({ sliderWidth }: { sliderWidth: string }) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{ display: "flex", gap: "5px", alignItems: "center", width: "100%" }}
|
||||
>
|
||||
<VolumeButton />
|
||||
<VolumeSlider width={sliderWidth} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const PlaybackRate = () => {
|
||||
const { playbackRate, increaseSpeed, isScreenSmall } = useVideoContext();
|
||||
return (
|
||||
<CustomFontTooltip
|
||||
title="Video Speed. Increase (+ or >), Decrease (- or <)"
|
||||
placement="bottom"
|
||||
arrow
|
||||
>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "white",
|
||||
fontSize: fontSizeSmall,
|
||||
padding: isScreenSmall ? buttonPaddingSmall : buttonPaddingBig,
|
||||
}}
|
||||
onClick={() => increaseSpeed()}
|
||||
>
|
||||
{playbackRate}x
|
||||
</IconButton>
|
||||
</CustomFontTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export const ObjectFitButton = () => {
|
||||
const { toggleObjectFit, isScreenSmall } = useVideoContext();
|
||||
return (
|
||||
<CustomFontTooltip title="Toggle Aspect Ratio (O)" placement="bottom" arrow>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "white",
|
||||
padding: isScreenSmall ? buttonPaddingSmall : buttonPaddingBig,
|
||||
}}
|
||||
onClick={() => toggleObjectFit()}
|
||||
>
|
||||
<AspectRatioIcon />
|
||||
</IconButton>
|
||||
</CustomFontTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export const PictureInPictureButton = () => {
|
||||
const { isFullscreen, toggleRef, togglePictureInPicture, isScreenSmall } =
|
||||
useVideoContext();
|
||||
return (
|
||||
<>
|
||||
{!isFullscreen.value && (
|
||||
<CustomFontTooltip
|
||||
title="Picture in Picture (P)"
|
||||
placement="bottom"
|
||||
arrow
|
||||
>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "white",
|
||||
padding: isScreenSmall ? buttonPaddingSmall : buttonPaddingBig,
|
||||
}}
|
||||
ref={toggleRef}
|
||||
onClick={togglePictureInPicture}
|
||||
>
|
||||
<PictureInPicture />
|
||||
</IconButton>
|
||||
</CustomFontTooltip>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const FullscreenButton = () => {
|
||||
const { toggleFullscreen, isScreenSmall } = useVideoContext();
|
||||
return (
|
||||
<CustomFontTooltip title="Toggle Fullscreen (F)" placement="bottom" arrow>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "white",
|
||||
padding: isScreenSmall ? buttonPaddingSmall : buttonPaddingBig,
|
||||
}}
|
||||
onClick={() => toggleFullscreen()}
|
||||
>
|
||||
<Fullscreen />
|
||||
</IconButton>
|
||||
</CustomFontTooltip>
|
||||
);
|
||||
};
|
||||
@@ -1,60 +0,0 @@
|
||||
import { Box } from "@mui/material";
|
||||
import { ControlsContainer } from "../VideoPlayer-styles.ts";
|
||||
import { MobileControlsBar } from "./MobileControlsBar.tsx";
|
||||
import { useVideoContext } from "./VideoContext.ts";
|
||||
import {
|
||||
FullscreenButton,
|
||||
ObjectFitButton,
|
||||
PictureInPictureButton,
|
||||
PlaybackRate,
|
||||
PlayButton,
|
||||
ProgressSlider,
|
||||
ReloadButton,
|
||||
VideoTime,
|
||||
VolumeControl,
|
||||
} from "./VideoControls.tsx";
|
||||
|
||||
export const VideoControlsBar = () => {
|
||||
const { canPlay, isScreenSmall, controlsHeight } = useVideoContext();
|
||||
|
||||
const showMobileControls = isScreenSmall && canPlay.value;
|
||||
|
||||
const controlGroupSX = {
|
||||
display: "flex",
|
||||
gap: "5px",
|
||||
alignItems: "center",
|
||||
height: controlsHeight,
|
||||
};
|
||||
|
||||
return (
|
||||
<ControlsContainer
|
||||
style={{
|
||||
padding: "0px",
|
||||
height: controlsHeight,
|
||||
}}
|
||||
>
|
||||
{showMobileControls ? (
|
||||
<MobileControlsBar />
|
||||
) : canPlay.value ? (
|
||||
<>
|
||||
<Box sx={controlGroupSX}>
|
||||
<PlayButton />
|
||||
<ReloadButton />
|
||||
|
||||
<ProgressSlider />
|
||||
|
||||
<VolumeControl sliderWidth={"100px"} />
|
||||
<VideoTime />
|
||||
</Box>
|
||||
|
||||
<Box sx={controlGroupSX}>
|
||||
<PlaybackRate />
|
||||
<ObjectFitButton />
|
||||
<PictureInPictureButton />
|
||||
<FullscreenButton />
|
||||
</Box>
|
||||
</>
|
||||
) : null}
|
||||
</ControlsContainer>
|
||||
);
|
||||
};
|
||||
@@ -1,324 +0,0 @@
|
||||
import { useMediaQuery } from "@mui/material";
|
||||
import {
|
||||
useSignal,
|
||||
useSignalEffect,
|
||||
useSignals,
|
||||
} from "@preact/signals-react/runtime";
|
||||
import React, {
|
||||
useContext,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from "react";
|
||||
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { smallVideoSize } from "../../../constants/Misc.ts";
|
||||
import { setVideoPlaying } from "../../../state/features/globalSlice.ts";
|
||||
import {
|
||||
setAlwaysShowControls,
|
||||
setIsMuted,
|
||||
setMutedVolumeSetting,
|
||||
setReduxPlaybackRate,
|
||||
setStretchVideoSetting,
|
||||
setVolumeSetting,
|
||||
StretchVideoType,
|
||||
} from "../../../state/features/persistSlice.ts";
|
||||
import { RootState } from "../../../state/store.ts";
|
||||
import { MyContext } from "../../../wrappers/DownloadWrapper.tsx";
|
||||
import { VideoPlayerProps } from "./VideoPlayer.tsx";
|
||||
|
||||
export const useVideoPlayerState = (props: VideoPlayerProps, ref: any) => {
|
||||
useSignals();
|
||||
const persistSelector = useSelector((state: RootState) => state.persist);
|
||||
|
||||
const playing = useSignal(false);
|
||||
const progress = useSignal(0);
|
||||
const isLoading = useSignal(false);
|
||||
const canPlay = useSignal(false);
|
||||
const startPlay = useSignal(false);
|
||||
|
||||
const isMuted = useSignal(persistSelector.isMuted);
|
||||
const volume = useSignal(persistSelector.volume);
|
||||
const mutedVolume = useSignal(persistSelector.mutedVolume);
|
||||
const playbackRate = useSignal(persistSelector.playbackRate);
|
||||
const videoObjectFit = useSignal<StretchVideoType>(
|
||||
persistSelector.stretchVideoSetting
|
||||
);
|
||||
const alwaysShowControls = useSignal<boolean>(
|
||||
persistSelector.alwaysShowControls
|
||||
);
|
||||
|
||||
useSignalEffect(() => {
|
||||
dispatch(setIsMuted(isMuted.value));
|
||||
});
|
||||
|
||||
useSignalEffect(() => {
|
||||
dispatch(setVolumeSetting(volume.value));
|
||||
if (videoRef.current) videoRef.current.volume = volume.value;
|
||||
});
|
||||
useSignalEffect(() => {
|
||||
dispatch(setMutedVolumeSetting(mutedVolume.value));
|
||||
});
|
||||
useSignalEffect(() => {
|
||||
if (videoRef.current) videoRef.current.playbackRate = playbackRate.value;
|
||||
dispatch(setReduxPlaybackRate(playbackRate.value));
|
||||
});
|
||||
useSignalEffect(() => {
|
||||
dispatch(setStretchVideoSetting(videoObjectFit.value));
|
||||
});
|
||||
useSignalEffect(() => {
|
||||
dispatch(setAlwaysShowControls(alwaysShowControls.value));
|
||||
});
|
||||
|
||||
const anchorEl = useSignal(null);
|
||||
|
||||
const {
|
||||
name,
|
||||
identifier,
|
||||
service,
|
||||
user = "",
|
||||
jsonId = "",
|
||||
nextVideo,
|
||||
onEnd,
|
||||
} = props;
|
||||
const dispatch = useDispatch();
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { downloads } = useSelector((state: RootState) => state.global);
|
||||
const reDownload = useRef<boolean>(false);
|
||||
const reDownloadNextVid = useRef<boolean>(false);
|
||||
const isFetchingProperties = useRef<boolean>(false);
|
||||
const status = useRef<null | string>(null);
|
||||
|
||||
const download = useMemo(() => {
|
||||
if (!downloads || !identifier) return {};
|
||||
const findDownload = downloads[identifier];
|
||||
|
||||
if (!findDownload) return {};
|
||||
return findDownload;
|
||||
}, [downloads, identifier]);
|
||||
|
||||
const src = useMemo(() => {
|
||||
return download?.url || null;
|
||||
}, [download?.url]);
|
||||
|
||||
const resourceStatus = useMemo(() => {
|
||||
return download?.status || {};
|
||||
}, [download]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
getVideoRef: () => {
|
||||
return videoRef;
|
||||
},
|
||||
getContainerRef: () => {
|
||||
return containerRef;
|
||||
},
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
reDownload.current = false;
|
||||
reDownloadNextVid.current = false;
|
||||
isLoading.value = false;
|
||||
canPlay.value = false;
|
||||
progress.value = 0;
|
||||
playing.value = false;
|
||||
startPlay.value = false;
|
||||
isFetchingProperties.current = false;
|
||||
status.current = null;
|
||||
}, [identifier]);
|
||||
|
||||
const refetch = React.useCallback(async () => {
|
||||
if (!name || !identifier || !service || isFetchingProperties.current)
|
||||
return;
|
||||
try {
|
||||
isFetchingProperties.current = true;
|
||||
await qortalRequest({
|
||||
action: "GET_QDN_RESOURCE_PROPERTIES",
|
||||
name,
|
||||
service,
|
||||
identifier,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
isFetchingProperties.current = false;
|
||||
}
|
||||
}, [identifier, name, service]);
|
||||
|
||||
const toggleRef = useRef<any>(null);
|
||||
const { downloadVideo } = useContext(MyContext);
|
||||
|
||||
const onProgressChange = async (_: any, value: number | number[]) => {
|
||||
if (!videoRef.current) return;
|
||||
videoRef.current.currentTime = value as number;
|
||||
progress.value = value as number;
|
||||
};
|
||||
|
||||
const handleEnded = () => {
|
||||
playing.value = false;
|
||||
if (onEnd) {
|
||||
onEnd();
|
||||
}
|
||||
};
|
||||
|
||||
const updateProgress = () => {
|
||||
if (!videoRef.current) return;
|
||||
progress.value = videoRef.current.currentTime;
|
||||
};
|
||||
|
||||
const videoCanPlayIfDownloaded = () => {
|
||||
if (resourceStatus?.status === "READY") {
|
||||
canPlay.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
videoCanPlayIfDownloaded();
|
||||
|
||||
const getSrc = React.useCallback(async () => {
|
||||
if (!name || !identifier || !service || !jsonId || !user) return;
|
||||
try {
|
||||
downloadVideo({
|
||||
name,
|
||||
service,
|
||||
identifier,
|
||||
properties: {
|
||||
jsonId,
|
||||
user,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}, [identifier, name, service, jsonId, user]);
|
||||
|
||||
useEffect(() => {
|
||||
const videoElement = videoRef.current;
|
||||
|
||||
const handleLeavePictureInPicture = async (event: any) => {
|
||||
const target = event?.target;
|
||||
if (target) {
|
||||
target.pause();
|
||||
if (playing.value) playing.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
if (videoElement) {
|
||||
videoElement.addEventListener(
|
||||
"leavepictureinpicture",
|
||||
handleLeavePictureInPicture
|
||||
);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (videoElement) {
|
||||
videoElement.removeEventListener(
|
||||
"leavepictureinpicture",
|
||||
handleLeavePictureInPicture
|
||||
);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const videoElement = videoRef.current;
|
||||
|
||||
const minimizeVideo = async () => {
|
||||
if (!videoElement) return;
|
||||
|
||||
dispatch(setVideoPlaying(videoElement));
|
||||
};
|
||||
|
||||
return () => {
|
||||
if (videoElement) {
|
||||
if (videoElement && !videoElement.paused && !videoElement.ended) {
|
||||
minimizeVideo();
|
||||
}
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const refetchInInterval = () => {
|
||||
try {
|
||||
const interval = setInterval(() => {
|
||||
if (status?.current === "DOWNLOADED") refetch();
|
||||
if (status?.current === "READY") clearInterval(interval);
|
||||
}, 7500);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (resourceStatus?.status) {
|
||||
status.current = resourceStatus?.status;
|
||||
}
|
||||
if (
|
||||
resourceStatus?.status === "DOWNLOADED" &&
|
||||
reDownload?.current === false
|
||||
) {
|
||||
refetchInInterval();
|
||||
reDownload.current = true;
|
||||
}
|
||||
}, [getSrc, resourceStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
if (resourceStatus?.status) {
|
||||
status.current = resourceStatus?.status;
|
||||
}
|
||||
if (
|
||||
resourceStatus?.status === "READY" &&
|
||||
reDownloadNextVid?.current === false
|
||||
) {
|
||||
if (nextVideo) {
|
||||
downloadVideo({
|
||||
name: nextVideo?.name,
|
||||
service: nextVideo?.service,
|
||||
identifier: nextVideo?.identifier,
|
||||
properties: {
|
||||
jsonId: nextVideo?.jsonId,
|
||||
user,
|
||||
},
|
||||
});
|
||||
}
|
||||
reDownloadNextVid.current = true;
|
||||
}
|
||||
}, [getSrc, resourceStatus]);
|
||||
|
||||
const handleMenuOpen = (event: any) => {
|
||||
anchorEl.value = event.currentTarget;
|
||||
};
|
||||
|
||||
const handleMenuClose = () => {
|
||||
anchorEl.value = null;
|
||||
};
|
||||
|
||||
const isScreenSmall = !useMediaQuery(smallVideoSize);
|
||||
return {
|
||||
containerRef,
|
||||
resourceStatus,
|
||||
videoRef,
|
||||
src,
|
||||
getSrc,
|
||||
updateProgress,
|
||||
handleEnded,
|
||||
onProgressChange,
|
||||
handleMenuOpen,
|
||||
handleMenuClose,
|
||||
toggleRef,
|
||||
playing,
|
||||
isMuted,
|
||||
progress,
|
||||
isLoading,
|
||||
canPlay,
|
||||
startPlay,
|
||||
volume,
|
||||
mutedVolume,
|
||||
playbackRate,
|
||||
anchorEl,
|
||||
videoObjectFit,
|
||||
isScreenSmall,
|
||||
alwaysShowControls,
|
||||
};
|
||||
};
|
||||
@@ -1,12 +1,8 @@
|
||||
import CSS from "csstype";
|
||||
import { forwardRef, useCallback, useMemo, useRef } from "react";
|
||||
import useIdleTimeout from "../../../hooks/useIdleTimeout.ts";
|
||||
import { LoadingVideo } from "./Components/LoadingVideo.tsx";
|
||||
import { useContextData, VideoContext } from "./Components/VideoContext.ts";
|
||||
import { VideoControlsBar } from "./Components/VideoControlsBar.tsx";
|
||||
import { VideoContainer, VideoElement } from "./VideoPlayer-styles.ts";
|
||||
import {VideoPlayer as QappVideoPlayer, Service, TimelineAction} from 'qapp-core'
|
||||
import { Box } from "@mui/material";
|
||||
import CSS from 'csstype';
|
||||
import { useRef } from 'react';
|
||||
|
||||
import { VideoPlayer as QappVideoPlayer, Service } from 'qapp-core';
|
||||
import { Box } from '@mui/material';
|
||||
export interface VideoStyles {
|
||||
videoContainer?: CSS.Properties;
|
||||
video?: CSS.Properties;
|
||||
@@ -17,7 +13,7 @@ export interface VideoPlayerProps {
|
||||
poster?: string;
|
||||
name?: string;
|
||||
identifier?: string;
|
||||
service?: string;
|
||||
service?: Service;
|
||||
autoplay?: boolean;
|
||||
from?: string | null;
|
||||
videoStyles?: VideoStyles;
|
||||
@@ -30,123 +26,26 @@ export interface VideoPlayerProps {
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
export type videoRefType = {
|
||||
getContainerRef: () => React.MutableRefObject<HTMLDivElement>;
|
||||
getVideoRef: () => React.MutableRefObject<HTMLVideoElement>;
|
||||
};
|
||||
export const VideoPlayer = forwardRef<videoRefType, VideoPlayerProps>(
|
||||
(props: VideoPlayerProps, ref) => {
|
||||
const videoRef = useRef(null)
|
||||
// const contextData = useContextData(props, ref);
|
||||
export const VideoPlayer = ({ ...props }: VideoPlayerProps) => {
|
||||
const videoRef = useRef(null);
|
||||
|
||||
// const {
|
||||
// keyboardShortcuts,
|
||||
// from,
|
||||
// videoStyles,
|
||||
// containerRef,
|
||||
// resourceStatus,
|
||||
// src,
|
||||
// togglePlay,
|
||||
// identifier,
|
||||
// videoRef,
|
||||
// poster,
|
||||
// updateProgress,
|
||||
// autoplay,
|
||||
// handleEnded,
|
||||
// handleCanPlay,
|
||||
// startPlay,
|
||||
// videoObjectFit,
|
||||
// showControlsFullScreen,
|
||||
// isFullscreen,
|
||||
// alwaysShowControls,
|
||||
// } = contextData;
|
||||
|
||||
// const showControls =
|
||||
// !isFullscreen.value ||
|
||||
// (isFullscreen.value && showControlsFullScreen.value) ||
|
||||
// alwaysShowControls.value;
|
||||
|
||||
// const idleTime = 5000; // Time in milliseconds
|
||||
// useIdleTimeout({
|
||||
// onIdle: () => (showControlsFullScreen.value = false),
|
||||
// onActive: () => (showControlsFullScreen.value = true),
|
||||
// idleTime,
|
||||
// });
|
||||
|
||||
const timelineActions: TimelineAction[] = useMemo(()=> {
|
||||
return [{
|
||||
label: "Skip intro",
|
||||
type: 'SEEK',
|
||||
duration: 10,
|
||||
time: 60,
|
||||
seekToTime: 3600,
|
||||
placement: 'BOTTOM-RIGHT'
|
||||
}, {
|
||||
label: "Skip intro",
|
||||
type: 'SEEK',
|
||||
duration: 10,
|
||||
time: 3610,
|
||||
seekToTime: 7200,
|
||||
placement: 'TOP-RIGHT'
|
||||
}]
|
||||
}, [])
|
||||
|
||||
return (
|
||||
// <VideoContext.Provider value={contextData}>
|
||||
<Box sx={{
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
height: '70vh',
|
||||
background: 'black'
|
||||
}}>
|
||||
<QappVideoPlayer timelineActions={timelineActions} poster={props.poster} videoRef={videoRef} qortalVideoResource={{name: props.name, service: props.service as Service, identifier: props.identifier}} />
|
||||
</Box>
|
||||
// <VideoContainer
|
||||
// tabIndex={0}
|
||||
// onKeyDown={keyboardShortcuts}
|
||||
// style={{
|
||||
// padding: from === "create" ? "8px" : 0,
|
||||
// cursor:
|
||||
// !showControlsFullScreen.value && isFullscreen.value
|
||||
// ? "none"
|
||||
// : "auto",
|
||||
// ...videoStyles?.videoContainer,
|
||||
// }}
|
||||
// onMouseEnter={e => {
|
||||
// showControlsFullScreen.value = true;
|
||||
// }}
|
||||
// onMouseLeave={e => {
|
||||
// showControlsFullScreen.value = false;
|
||||
// }}
|
||||
// ref={containerRef}
|
||||
// >
|
||||
// <LoadingVideo />
|
||||
// <VideoElement
|
||||
// id={identifier}
|
||||
// ref={videoRef}
|
||||
// src={
|
||||
// resourceStatus?.status === "READY" && startPlay.value ? src : null
|
||||
// }
|
||||
// poster={startPlay.value ? "" : poster}
|
||||
// onTimeUpdate={updateProgress}
|
||||
// autoPlay={autoplay}
|
||||
// onClick={() => togglePlay()}
|
||||
// onEnded={handleEnded}
|
||||
// // onLoadedMetadata={handleLoadedMetadata}
|
||||
// onCanPlay={handleCanPlay}
|
||||
// preload="metadata"
|
||||
// style={{
|
||||
// ...videoStyles?.video,
|
||||
// objectFit: videoObjectFit.value,
|
||||
// backgroundColor: "#000000",
|
||||
// height:
|
||||
// isFullscreen.value && showControls
|
||||
// ? "calc(100vh - 40px)"
|
||||
// : "100%",
|
||||
// }}
|
||||
// />
|
||||
// {showControls && <VideoControlsBar />}
|
||||
// </VideoContainer>
|
||||
// </VideoContext.Provider>
|
||||
);
|
||||
}
|
||||
);
|
||||
background: 'black',
|
||||
}}
|
||||
>
|
||||
<QappVideoPlayer
|
||||
poster={props.poster}
|
||||
videoRef={videoRef}
|
||||
qortalVideoResource={{
|
||||
name: props.name,
|
||||
service: props.service as Service,
|
||||
identifier: props.identifier,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,663 +0,0 @@
|
||||
import {
|
||||
Fullscreen,
|
||||
MoreVert as MoreIcon,
|
||||
Pause,
|
||||
PictureInPicture,
|
||||
PlayArrow,
|
||||
Refresh,
|
||||
VolumeOff,
|
||||
VolumeUp,
|
||||
} from "@mui/icons-material";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import {
|
||||
Box,
|
||||
IconButton,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Slider,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { styled } from "@mui/system";
|
||||
import React, { useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
// import { Key } from "ts-key-enum";
|
||||
import { setVideoPlaying } from "../../../state/features/globalSlice.ts";
|
||||
import { RootState } from "../../../state/store.ts";
|
||||
import { formatTime } from "../../../utils/numberFunctions.ts";
|
||||
import { MyContext } from "../../../wrappers/DownloadWrapper.tsx";
|
||||
|
||||
const VideoContainer = styled(Box)`
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
`;
|
||||
|
||||
const VideoElement = styled("video")`
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: calc(100vh - 150px);
|
||||
background: rgb(33, 33, 33);
|
||||
`;
|
||||
|
||||
const ControlsContainer = styled(Box)`
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 8px;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
`;
|
||||
|
||||
interface VideoPlayerProps {
|
||||
src?: string;
|
||||
poster?: string;
|
||||
name?: string;
|
||||
identifier?: string;
|
||||
service?: string;
|
||||
autoplay?: boolean;
|
||||
from?: string | null;
|
||||
customStyle?: any;
|
||||
user?: string;
|
||||
jsonId?: string;
|
||||
element?: null | any;
|
||||
checkIfDrag?: () => boolean;
|
||||
}
|
||||
|
||||
export const VideoPlayerGlobal: React.FC<VideoPlayerProps> = ({
|
||||
poster,
|
||||
name,
|
||||
identifier,
|
||||
service,
|
||||
autoplay = true,
|
||||
from = null,
|
||||
customStyle = {},
|
||||
user = "",
|
||||
jsonId = "",
|
||||
element,
|
||||
checkIfDrag,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const videoRef = useRef<HTMLVideoElement | null>(null);
|
||||
const [playing, setPlaying] = useState(false);
|
||||
const [volume, setVolume] = useState(1);
|
||||
const [mutedVolume, setMutedVolume] = useState(1);
|
||||
const [isMuted, setIsMuted] = useState(false);
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [canPlay, setCanPlay] = useState(false);
|
||||
const [startPlay, setStartPlay] = useState(false);
|
||||
const [isMobileView, setIsMobileView] = useState(false);
|
||||
const [playbackRate, setPlaybackRate] = useState(1);
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const dispatch = useDispatch();
|
||||
const reDownload = useRef<boolean>(false);
|
||||
const { downloads } = useSelector((state: RootState) => state.global);
|
||||
const download = useMemo(() => {
|
||||
if (!downloads || !identifier) return {};
|
||||
const findDownload = downloads[identifier];
|
||||
|
||||
if (!findDownload) return {};
|
||||
return findDownload;
|
||||
}, [downloads, identifier]);
|
||||
|
||||
const resourceStatus = useMemo(() => {
|
||||
return download?.status || {};
|
||||
}, [download]);
|
||||
|
||||
const minSpeed = 0.25;
|
||||
const maxSpeed = 4.0;
|
||||
const speedChange = 0.25;
|
||||
|
||||
const updatePlaybackRate = (newSpeed: number) => {
|
||||
if (videoRef.current) {
|
||||
if (newSpeed > maxSpeed || newSpeed < minSpeed) newSpeed = minSpeed;
|
||||
videoRef.current.playbackRate = newSpeed;
|
||||
setPlaybackRate(newSpeed);
|
||||
}
|
||||
};
|
||||
|
||||
const increaseSpeed = (wrapOverflow = true) => {
|
||||
const changedSpeed = playbackRate + speedChange;
|
||||
const newSpeed = wrapOverflow
|
||||
? changedSpeed
|
||||
: Math.min(changedSpeed, maxSpeed);
|
||||
|
||||
if (videoRef.current) {
|
||||
updatePlaybackRate(newSpeed);
|
||||
}
|
||||
};
|
||||
|
||||
const decreaseSpeed = () => {
|
||||
if (videoRef.current) {
|
||||
updatePlaybackRate(playbackRate - speedChange);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleRef = useRef<any>(null);
|
||||
const { downloadVideo } = useContext(MyContext);
|
||||
const togglePlay = async () => {
|
||||
if (checkIfDrag && checkIfDrag()) return;
|
||||
if (!videoRef.current) return;
|
||||
if (playing) {
|
||||
videoRef.current.pause();
|
||||
} else {
|
||||
await videoRef.current.play();
|
||||
}
|
||||
setPlaying(prev => !prev);
|
||||
};
|
||||
|
||||
const onVolumeChange = (_: any, value: number | number[]) => {
|
||||
if (!videoRef.current) return;
|
||||
videoRef.current.volume = value as number;
|
||||
setVolume(value as number);
|
||||
setIsMuted(false);
|
||||
};
|
||||
|
||||
const onProgressChange = async (_: any, value: number | number[]) => {
|
||||
if (!videoRef.current) return;
|
||||
videoRef.current.currentTime = value as number;
|
||||
setProgress(value as number);
|
||||
if (!playing) {
|
||||
await videoRef.current.play();
|
||||
setPlaying(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEnded = () => {
|
||||
setPlaying(false);
|
||||
};
|
||||
|
||||
const updateProgress = () => {
|
||||
if (!videoRef.current) return;
|
||||
setProgress(videoRef.current.currentTime);
|
||||
};
|
||||
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
|
||||
const enterFullscreen = () => {
|
||||
if (!videoRef.current) return;
|
||||
if (videoRef.current.requestFullscreen) {
|
||||
videoRef.current.requestFullscreen();
|
||||
}
|
||||
};
|
||||
|
||||
const exitFullscreen = () => {
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen();
|
||||
}
|
||||
};
|
||||
|
||||
const toggleFullscreen = () => {
|
||||
isFullscreen ? exitFullscreen() : enterFullscreen();
|
||||
};
|
||||
const togglePictureInPicture = async () => {
|
||||
if (!videoRef.current) return;
|
||||
if (document.pictureInPictureElement === videoRef.current) {
|
||||
await document.exitPictureInPicture();
|
||||
} else {
|
||||
await videoRef.current.requestPictureInPicture();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleFullscreenChange = () => {
|
||||
setIsFullscreen(!!document.fullscreenElement);
|
||||
};
|
||||
|
||||
document.addEventListener("fullscreenchange", handleFullscreenChange);
|
||||
return () => {
|
||||
document.removeEventListener("fullscreenchange", handleFullscreenChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleCanPlay = () => {
|
||||
setIsLoading(false);
|
||||
setCanPlay(true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const videoElement = videoRef.current;
|
||||
|
||||
const handleLeavePictureInPicture = async (event: any) => {
|
||||
const target = event?.target;
|
||||
if (target) {
|
||||
target.pause();
|
||||
if (setPlaying) {
|
||||
setPlaying(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (videoElement) {
|
||||
videoElement.addEventListener(
|
||||
"leavepictureinpicture",
|
||||
handleLeavePictureInPicture
|
||||
);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (videoElement) {
|
||||
videoElement.removeEventListener(
|
||||
"leavepictureinpicture",
|
||||
handleLeavePictureInPicture
|
||||
);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const reloadVideo = async () => {
|
||||
if (!videoRef.current) return;
|
||||
const src = videoRef.current.src;
|
||||
const currentTime = videoRef.current.currentTime;
|
||||
videoRef.current.src = src;
|
||||
videoRef.current.load();
|
||||
videoRef.current.currentTime = currentTime;
|
||||
if (playing) {
|
||||
await videoRef.current.play();
|
||||
}
|
||||
};
|
||||
|
||||
const handleMenuOpen = (event: any) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleMenuClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const videoWidth = videoRef?.current?.offsetWidth;
|
||||
if (videoWidth && videoWidth <= 600) {
|
||||
setIsMobileView(true);
|
||||
}
|
||||
}, [canPlay]);
|
||||
|
||||
const getDownloadProgress = (current: number, total: number) => {
|
||||
const progress = (current / total) * 100;
|
||||
return Number.isNaN(progress) ? "" : progress.toFixed(0) + "%";
|
||||
};
|
||||
const mute = () => {
|
||||
setIsMuted(true);
|
||||
setMutedVolume(volume);
|
||||
setVolume(0);
|
||||
if (videoRef.current) videoRef.current.volume = 0;
|
||||
};
|
||||
const unMute = () => {
|
||||
setIsMuted(false);
|
||||
setVolume(mutedVolume);
|
||||
if (videoRef.current) videoRef.current.volume = mutedVolume;
|
||||
};
|
||||
|
||||
const toggleMute = () => {
|
||||
isMuted ? unMute() : mute();
|
||||
};
|
||||
|
||||
const changeVolume = (volumeChange: number) => {
|
||||
if (videoRef.current) {
|
||||
const minVolume = 0;
|
||||
const maxVolume = 1;
|
||||
|
||||
let newVolume = volumeChange + volume;
|
||||
|
||||
newVolume = Math.max(newVolume, minVolume);
|
||||
newVolume = Math.min(newVolume, maxVolume);
|
||||
|
||||
setIsMuted(false);
|
||||
setMutedVolume(newVolume);
|
||||
videoRef.current.volume = newVolume;
|
||||
setVolume(newVolume);
|
||||
}
|
||||
};
|
||||
const setProgressRelative = (secondsChange: number) => {
|
||||
if (videoRef.current) {
|
||||
const currentTime = videoRef.current?.currentTime;
|
||||
const minTime = 0;
|
||||
const maxTime = videoRef.current?.duration || 100;
|
||||
|
||||
let newTime = currentTime + secondsChange;
|
||||
newTime = Math.max(newTime, minTime);
|
||||
newTime = Math.min(newTime, maxTime);
|
||||
videoRef.current.currentTime = newTime;
|
||||
setProgress(newTime);
|
||||
}
|
||||
};
|
||||
|
||||
const setProgressAbsolute = (videoPercent: number) => {
|
||||
if (videoRef.current) {
|
||||
videoPercent = Math.min(videoPercent, 100);
|
||||
videoPercent = Math.max(videoPercent, 0);
|
||||
const finalTime = (videoRef.current?.duration * videoPercent) / 100;
|
||||
videoRef.current.currentTime = finalTime;
|
||||
setProgress(finalTime);
|
||||
}
|
||||
};
|
||||
|
||||
const keyboardShortcutsDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
// switch (e.key) {
|
||||
// case Key.Add:
|
||||
// increaseSpeed(false);
|
||||
// break;
|
||||
// case "+":
|
||||
// increaseSpeed(false);
|
||||
// break;
|
||||
// case ">":
|
||||
// increaseSpeed(false);
|
||||
// break;
|
||||
|
||||
// case Key.Subtract:
|
||||
// decreaseSpeed();
|
||||
// break;
|
||||
// case "-":
|
||||
// decreaseSpeed();
|
||||
// break;
|
||||
// case "<":
|
||||
// decreaseSpeed();
|
||||
// break;
|
||||
|
||||
// case Key.ArrowLeft:
|
||||
// {
|
||||
// if (e.shiftKey) setProgressRelative(-300);
|
||||
// else if (e.ctrlKey) setProgressRelative(-60);
|
||||
// else if (e.altKey) setProgressRelative(-10);
|
||||
// else setProgressRelative(-5);
|
||||
// }
|
||||
// break;
|
||||
|
||||
// case Key.ArrowRight:
|
||||
// {
|
||||
// if (e.shiftKey) setProgressRelative(300);
|
||||
// else if (e.ctrlKey) setProgressRelative(60);
|
||||
// else if (e.altKey) setProgressRelative(10);
|
||||
// else setProgressRelative(5);
|
||||
// }
|
||||
// break;
|
||||
|
||||
// case Key.ArrowDown:
|
||||
// changeVolume(-0.05);
|
||||
// break;
|
||||
// case Key.ArrowUp:
|
||||
// changeVolume(0.05);
|
||||
// break;
|
||||
// }
|
||||
};
|
||||
|
||||
const keyboardShortcutsUp = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
// switch (e.key) {
|
||||
// case " ":
|
||||
// togglePlay();
|
||||
// break;
|
||||
// case "m":
|
||||
// toggleMute();
|
||||
// break;
|
||||
|
||||
// case "f":
|
||||
// enterFullscreen();
|
||||
// break;
|
||||
// case Key.Escape:
|
||||
// exitFullscreen();
|
||||
// break;
|
||||
|
||||
// case "0":
|
||||
// setProgressAbsolute(0);
|
||||
// break;
|
||||
// case "1":
|
||||
// setProgressAbsolute(10);
|
||||
// break;
|
||||
// case "2":
|
||||
// setProgressAbsolute(20);
|
||||
// break;
|
||||
// case "3":
|
||||
// setProgressAbsolute(30);
|
||||
// break;
|
||||
// case "4":
|
||||
// setProgressAbsolute(40);
|
||||
// break;
|
||||
// case "5":
|
||||
// setProgressAbsolute(50);
|
||||
// break;
|
||||
// case "6":
|
||||
// setProgressAbsolute(60);
|
||||
// break;
|
||||
// case "7":
|
||||
// setProgressAbsolute(70);
|
||||
// break;
|
||||
// case "8":
|
||||
// setProgressAbsolute(80);
|
||||
// break;
|
||||
// case "9":
|
||||
// setProgressAbsolute(90);
|
||||
// break;
|
||||
// }
|
||||
};
|
||||
|
||||
// useEffect(() => {
|
||||
// if (element) {
|
||||
// const oldElement = document.getElementById("videoPlayer");
|
||||
// if (oldElement && oldElement?.parentNode) {
|
||||
// oldElement?.parentNode.replaceChild(element, oldElement);
|
||||
// videoRef.current = element;
|
||||
// setPlaying(true);
|
||||
// setCanPlay(true);
|
||||
// setStartPlay(true);
|
||||
// //videoRef?.current?.addEventListener("click", () => {});
|
||||
// videoRef?.current?.addEventListener("timeupdate", updateProgress);
|
||||
// videoRef?.current?.addEventListener("ended", handleEnded);
|
||||
// }
|
||||
// }
|
||||
// }, [element]);
|
||||
|
||||
return (
|
||||
<VideoContainer
|
||||
tabIndex={0}
|
||||
onKeyUp={keyboardShortcutsUp}
|
||||
onKeyDown={keyboardShortcutsDown}
|
||||
style={{
|
||||
padding: from === "create" ? "8px" : 0,
|
||||
zIndex: 1000,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
}}
|
||||
>
|
||||
<div className="closePlayer">
|
||||
<CloseIcon
|
||||
onClick={() => {
|
||||
dispatch(setVideoPlaying(null));
|
||||
}}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
backgroundColor: "rgba(0,0,0,.5)",
|
||||
}}
|
||||
></CloseIcon>
|
||||
</div>
|
||||
{/*<div onClick={togglePlay}>*/}
|
||||
{/* <VideoElement id="videoPlayer" />*/}
|
||||
{/*</div>*/}
|
||||
<ControlsContainer
|
||||
style={{
|
||||
bottom: from === "create" ? "15px" : 0,
|
||||
}}
|
||||
>
|
||||
{isMobileView && canPlay ? (
|
||||
<>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
}}
|
||||
onClick={togglePlay}
|
||||
>
|
||||
{playing ? <Pause /> : <PlayArrow />}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
marginLeft: "15px",
|
||||
}}
|
||||
onClick={reloadVideo}
|
||||
>
|
||||
<Refresh />
|
||||
</IconButton>
|
||||
<Slider
|
||||
value={progress}
|
||||
onChange={onProgressChange}
|
||||
min={0}
|
||||
max={videoRef.current?.duration || 100}
|
||||
sx={{ flexGrow: 1, mx: 2 }}
|
||||
/>
|
||||
<IconButton
|
||||
edge="end"
|
||||
color="inherit"
|
||||
aria-label="menu"
|
||||
onClick={handleMenuOpen}
|
||||
>
|
||||
<MoreIcon />
|
||||
</IconButton>
|
||||
<Menu
|
||||
id="simple-menu"
|
||||
anchorEl={anchorEl}
|
||||
keepMounted
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={handleMenuClose}
|
||||
PaperProps={{
|
||||
style: {
|
||||
width: "250px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<MenuItem>
|
||||
<VolumeUp />
|
||||
<Slider
|
||||
value={volume}
|
||||
onChange={onVolumeChange}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
/>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => increaseSpeed()}>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
fontSize: "14px",
|
||||
}}
|
||||
>
|
||||
Speed: {playbackRate}x
|
||||
</Typography>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={togglePictureInPicture}>
|
||||
<PictureInPicture />
|
||||
</MenuItem>
|
||||
<MenuItem onClick={toggleFullscreen}>
|
||||
<Fullscreen />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</>
|
||||
) : canPlay ? (
|
||||
<>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
}}
|
||||
onClick={togglePlay}
|
||||
>
|
||||
{playing ? <Pause /> : <PlayArrow />}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
marginLeft: "15px",
|
||||
}}
|
||||
onClick={reloadVideo}
|
||||
>
|
||||
<Refresh />
|
||||
</IconButton>
|
||||
<Slider
|
||||
value={progress}
|
||||
onChange={onProgressChange}
|
||||
min={0}
|
||||
max={videoRef.current?.duration || 100}
|
||||
sx={{ flexGrow: 1, mx: 2 }}
|
||||
/>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
marginRight: "5px",
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
visibility:
|
||||
!videoRef.current?.duration || !progress
|
||||
? "hidden"
|
||||
: "visible",
|
||||
}}
|
||||
>
|
||||
{progress && videoRef.current?.duration && formatTime(progress)}/
|
||||
{progress &&
|
||||
videoRef.current?.duration &&
|
||||
formatTime(videoRef.current?.duration)}
|
||||
</Typography>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
marginRight: "10px",
|
||||
}}
|
||||
onClick={toggleMute}
|
||||
>
|
||||
{isMuted ? <VolumeOff /> : <VolumeUp />}
|
||||
</IconButton>
|
||||
<Slider
|
||||
value={volume}
|
||||
onChange={onVolumeChange}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
sx={{
|
||||
maxWidth: "100px",
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
fontSize: "14px",
|
||||
marginLeft: "5px",
|
||||
}}
|
||||
onClick={e => increaseSpeed()}
|
||||
>
|
||||
Speed: {playbackRate}x
|
||||
</IconButton>
|
||||
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
marginLeft: "15px",
|
||||
}}
|
||||
ref={toggleRef}
|
||||
onClick={togglePictureInPicture}
|
||||
>
|
||||
<PictureInPicture />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: "rgba(255, 255, 255, 0.7)",
|
||||
}}
|
||||
onClick={toggleFullscreen}
|
||||
>
|
||||
<Fullscreen />
|
||||
</IconButton>
|
||||
</>
|
||||
) : null}
|
||||
</ControlsContainer>
|
||||
</VideoContainer>
|
||||
);
|
||||
};
|
||||
@@ -1,27 +1,75 @@
|
||||
import { useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useEffect } from 'react';
|
||||
import { To, useNavigate } from 'react-router-dom';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { supportedLanguages } from '../i18n/i18n';
|
||||
import { EnumTheme, themeAtom } from '../state/global/theme';
|
||||
|
||||
type Language = 'de' | 'en' | 'es' | 'fr' | 'it' | 'ja' | 'ru' | 'zh';
|
||||
type Theme = 'dark' | 'light';
|
||||
|
||||
interface CustomWindow extends Window {
|
||||
_qdnTheme: Theme;
|
||||
_qdnLang: Language;
|
||||
}
|
||||
const customWindow = window as unknown as CustomWindow;
|
||||
|
||||
export const useIframe = () => {
|
||||
const setTheme = useSetAtom(themeAtom);
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
const navigate = useNavigate();
|
||||
useEffect(() => {
|
||||
function handleNavigation(event) {
|
||||
if (event.data?.action === "NAVIGATE_TO_PATH" && event.data.path) {
|
||||
console.log("Navigating to path within React app:", event.data.path);
|
||||
const themeColorDefault = customWindow?._qdnTheme;
|
||||
if (themeColorDefault === 'dark') {
|
||||
setTheme(EnumTheme.DARK);
|
||||
} else if (themeColorDefault === 'light') {
|
||||
setTheme(EnumTheme.LIGHT);
|
||||
}
|
||||
|
||||
const languageDefault = customWindow?._qdnLang;
|
||||
|
||||
if (supportedLanguages?.includes(languageDefault)) {
|
||||
i18n.changeLanguage(languageDefault);
|
||||
}
|
||||
|
||||
function handleNavigation(event: {
|
||||
data: {
|
||||
action: string;
|
||||
path: To;
|
||||
theme: Theme;
|
||||
language: Language;
|
||||
};
|
||||
}) {
|
||||
if (event.data?.action === 'NAVIGATE_TO_PATH' && event.data.path) {
|
||||
navigate(event.data.path); // Navigate directly to the specified path
|
||||
|
||||
// Send a response back to the parent window after navigation is handled
|
||||
window.parent.postMessage(
|
||||
{ action: "NAVIGATION_SUCCESS", path: event.data.path },
|
||||
"*"
|
||||
{ action: 'NAVIGATION_SUCCESS', path: event.data.path },
|
||||
'*'
|
||||
);
|
||||
} else if (event.data?.action === 'THEME_CHANGED' && event.data.theme) {
|
||||
const themeColor = event.data.theme;
|
||||
if (themeColor === 'dark') {
|
||||
setTheme(EnumTheme.DARK);
|
||||
} else if (themeColor === 'light') {
|
||||
setTheme(EnumTheme.LIGHT);
|
||||
}
|
||||
} else if (
|
||||
event.data?.action === 'LANGUAGE_CHANGED' &&
|
||||
event.data.language
|
||||
) {
|
||||
if (!supportedLanguages?.includes(event.data.language)) return;
|
||||
i18n.changeLanguage(event.data.language);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("message", handleNavigation);
|
||||
window.addEventListener('message', handleNavigation);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("message", handleNavigation);
|
||||
window.removeEventListener('message', handleNavigation);
|
||||
};
|
||||
}, [navigate]);
|
||||
}, [navigate, setTheme]);
|
||||
return { navigate };
|
||||
};
|
||||
|
||||
56
src/i18n/i18n.ts
Normal file
56
src/i18n/i18n.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import {
|
||||
capitalizeAll,
|
||||
capitalizeFirstChar,
|
||||
capitalizeFirstWord,
|
||||
} from './processors';
|
||||
|
||||
// Load all locale JSON files
|
||||
const modules = import.meta.glob('./locales/**/*.json', {
|
||||
eager: true,
|
||||
}) as Record<string, any>;
|
||||
|
||||
// Dynamically detect unique language codes
|
||||
export const supportedLanguages: string[] = Array.from(
|
||||
new Set(
|
||||
Object.keys(modules)
|
||||
.map((path) => {
|
||||
const match = path.match(/\.\/locales\/([^/]+)\//);
|
||||
return match ? match[1] : null;
|
||||
})
|
||||
.filter((lang): lang is string => typeof lang === 'string')
|
||||
)
|
||||
);
|
||||
|
||||
// Construct i18n resources object
|
||||
const resources: Record<string, Record<string, any>> = {};
|
||||
|
||||
for (const path in modules) {
|
||||
// Path format: './locales/en/core.json'
|
||||
const match = path.match(/\.\/locales\/([^/]+)\/([^/]+)\.json$/);
|
||||
if (!match) continue;
|
||||
|
||||
const [, lang, ns] = match;
|
||||
resources[lang] = resources[lang] || {};
|
||||
resources[lang][ns] = modules[path].default;
|
||||
}
|
||||
|
||||
i18n
|
||||
.use(initReactI18next)
|
||||
.use(capitalizeAll as any)
|
||||
.use(capitalizeFirstChar as any)
|
||||
.use(capitalizeFirstWord as any)
|
||||
.init({
|
||||
resources,
|
||||
fallbackLng: 'en',
|
||||
lng: navigator.language,
|
||||
supportedLngs: supportedLanguages,
|
||||
ns: ['core'],
|
||||
defaultNS: 'core',
|
||||
interpolation: { escapeValue: false },
|
||||
react: { useSuspense: false },
|
||||
debug: import.meta.env.MODE === 'development',
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
4
src/i18n/locales/en/core.json
Normal file
4
src/i18n/locales/en/core.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"using_theme": "this application is using the theme:",
|
||||
"welcome": "welcome to Qortal"
|
||||
}
|
||||
32
src/i18n/processors.ts
Normal file
32
src/i18n/processors.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export const capitalizeAll = {
|
||||
type: 'postProcessor',
|
||||
name: 'capitalizeAll',
|
||||
process: (value: string) => value.toUpperCase(),
|
||||
};
|
||||
|
||||
export const capitalizeFirstChar = {
|
||||
type: 'postProcessor',
|
||||
name: 'capitalizeFirstChar',
|
||||
process: (value: string) => value.charAt(0).toUpperCase() + value.slice(1),
|
||||
};
|
||||
|
||||
export const capitalizeFirstWord = {
|
||||
type: 'postProcessor',
|
||||
name: 'capitalizeFirstWord',
|
||||
process: (value: string) => {
|
||||
if (!value?.trim()) return value;
|
||||
|
||||
const trimmed = value.trimStart();
|
||||
const firstSpaceIndex = trimmed.indexOf(' ');
|
||||
|
||||
if (firstSpaceIndex === -1) {
|
||||
return trimmed.charAt(0).toUpperCase() + trimmed.slice(1);
|
||||
}
|
||||
|
||||
const firstWord = trimmed.slice(0, firstSpaceIndex);
|
||||
const restOfString = trimmed.slice(firstSpaceIndex);
|
||||
const trailingSpaces = value.slice(trimmed.length);
|
||||
|
||||
return firstWord.toUpperCase() + restOfString + trailingSpaces;
|
||||
},
|
||||
};
|
||||
@@ -1,21 +1,20 @@
|
||||
import { Box, SxProps, Theme, Typography, useMediaQuery } from "@mui/material";
|
||||
import React from "react";
|
||||
import { CommentSection } from "../../../components/common/Comments/CommentSection.tsx";
|
||||
import { SuperLikesSection } from "../../../components/common/SuperLikesList/SuperLikesSection.tsx";
|
||||
import { DisplayHtml } from "../../../components/common/TextEditor/DisplayHtml.tsx";
|
||||
import { VideoPlayer } from "../../../components/common/VideoPlayer/VideoPlayer.tsx";
|
||||
import { Playlists } from "../../../components/Playlists/Playlists.tsx";
|
||||
import { fontSizeSmall, minFileSize } from "../../../constants/Misc.ts";
|
||||
import { formatBytes } from "../../../utils/numberFunctions.ts";
|
||||
import { formatDate } from "../../../utils/time.ts";
|
||||
import { VideoActionsBar } from "../VideoContent/VideoActionsBar.tsx";
|
||||
import { usePlaylistContentState } from "./PlaylistContent-State.ts";
|
||||
import { Box, SxProps, Theme, Typography, useMediaQuery } from '@mui/material';
|
||||
import { CommentSection } from '../../../components/common/Comments/CommentSection.tsx';
|
||||
import { SuperLikesSection } from '../../../components/common/SuperLikesList/SuperLikesSection.tsx';
|
||||
import { DisplayHtml } from '../../../components/common/TextEditor/DisplayHtml.tsx';
|
||||
import { VideoPlayer } from '../../../components/common/VideoPlayer/VideoPlayer.tsx';
|
||||
import { Playlists } from '../../../components/Playlists/Playlists.tsx';
|
||||
import { fontSizeSmall, minFileSize } from '../../../constants/Misc.ts';
|
||||
import { formatBytes } from '../../../utils/numberFunctions.ts';
|
||||
import { formatDate } from '../../../utils/time.ts';
|
||||
import { VideoActionsBar } from '../VideoContent/VideoActionsBar.tsx';
|
||||
import { usePlaylistContentState } from './PlaylistContent-State.ts';
|
||||
import {
|
||||
Spacer,
|
||||
VideoDescription,
|
||||
VideoPlayerContainer,
|
||||
VideoTitle,
|
||||
} from "./PlaylistContent-styles.tsx";
|
||||
} from './PlaylistContent-styles.tsx';
|
||||
|
||||
export const PlaylistContent = () => {
|
||||
const {
|
||||
@@ -25,9 +24,7 @@ export const PlaylistContent = () => {
|
||||
superLikeList,
|
||||
setVideoMetadataResource,
|
||||
videoReference,
|
||||
focusVideo,
|
||||
videoCover,
|
||||
containerRef,
|
||||
theme,
|
||||
descriptionHeight,
|
||||
nextVideo,
|
||||
@@ -45,14 +42,14 @@ export const PlaylistContent = () => {
|
||||
const isScreenSmall = !useMediaQuery(`(min-width:950px)`);
|
||||
|
||||
const playlistsSX: SxProps<Theme> = isScreenSmall
|
||||
? { width: "100%", marginTop: "10px" }
|
||||
: { width: "35%", position: "absolute", right: "20px" };
|
||||
? { width: '100%', marginTop: '10px' }
|
||||
: { width: '35%', position: 'absolute', right: '20px' };
|
||||
|
||||
return videoData && videoData?.videos?.length === 0 ? (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
}}
|
||||
>
|
||||
<Typography>This playlist doesn't exist</Typography>
|
||||
@@ -60,20 +57,19 @@ export const PlaylistContent = () => {
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
padding: "0px",
|
||||
marginLeft: "2%",
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
padding: '0px',
|
||||
marginLeft: '2%',
|
||||
}}
|
||||
onClick={focusVideo}
|
||||
>
|
||||
<VideoPlayerContainer
|
||||
sx={{
|
||||
width: isScreenSmall ? "100%" : "60%",
|
||||
alignSelf: "start",
|
||||
paddingRight: isScreenSmall ? "10px" : "0px",
|
||||
marginBottom: "20px",
|
||||
width: isScreenSmall ? '100%' : '60%',
|
||||
alignSelf: 'start',
|
||||
paddingRight: isScreenSmall ? '10px' : '0px',
|
||||
marginBottom: '20px',
|
||||
}}
|
||||
>
|
||||
{videoReference && (
|
||||
@@ -83,13 +79,12 @@ export const PlaylistContent = () => {
|
||||
identifier={videoReference?.identifier}
|
||||
user={channelName}
|
||||
jsonId={id}
|
||||
poster={videoCover || ""}
|
||||
poster={videoCover || ''}
|
||||
nextVideo={nextVideo}
|
||||
onEnd={onEndVideo}
|
||||
autoPlay={doAutoPlay}
|
||||
ref={containerRef}
|
||||
videoStyles={{
|
||||
video: { aspectRatio: "16 / 9" },
|
||||
video: { aspectRatio: '16 / 9' },
|
||||
}}
|
||||
duration={videoData?.duration}
|
||||
/>
|
||||
@@ -98,12 +93,12 @@ export const PlaylistContent = () => {
|
||||
<Playlists
|
||||
playlistData={playlistData}
|
||||
currentVideoIdentifier={videoData?.id}
|
||||
onClick={(name, identifier)=> {
|
||||
onClick={(name, identifier) => {
|
||||
setVideoMetadataResource({
|
||||
name,
|
||||
identifier,
|
||||
service: 'DOCUMENT'
|
||||
})
|
||||
service: 'DOCUMENT',
|
||||
});
|
||||
}}
|
||||
sx={playlistsSX}
|
||||
/>
|
||||
@@ -115,23 +110,23 @@ export const PlaylistContent = () => {
|
||||
videoReference={videoReference}
|
||||
superLikeList={superLikeList}
|
||||
setSuperLikeList={setSuperLikeList}
|
||||
sx={{ width: "100%" }}
|
||||
sx={{ width: '100%' }}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
marginTop: "10px",
|
||||
gap: "10px",
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
marginTop: '10px',
|
||||
gap: '10px',
|
||||
}}
|
||||
>
|
||||
<VideoTitle
|
||||
variant="h1"
|
||||
color="textPrimary"
|
||||
sx={{
|
||||
textAlign: "center",
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
{videoData?.title}
|
||||
@@ -140,9 +135,9 @@ export const PlaylistContent = () => {
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
gap: "20px",
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
gap: '20px',
|
||||
}}
|
||||
>
|
||||
{videoData?.created && (
|
||||
@@ -160,11 +155,11 @@ export const PlaylistContent = () => {
|
||||
<Typography
|
||||
variant="h1"
|
||||
sx={{
|
||||
fontSize: "90%",
|
||||
fontSize: '90%',
|
||||
}}
|
||||
color={"green"}
|
||||
color={'green'}
|
||||
>
|
||||
{formatBytes(videoData.fileSize, 2, "Decimal")}
|
||||
{formatBytes(videoData.fileSize, 2, 'Decimal')}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
@@ -172,30 +167,30 @@ export const PlaylistContent = () => {
|
||||
{videoData?.fullDescription && (
|
||||
<Box
|
||||
sx={{
|
||||
background: "#333333",
|
||||
borderRadius: "5px",
|
||||
padding: "5px",
|
||||
width: "95%",
|
||||
alignSelf: "flex-start",
|
||||
background: '#333333',
|
||||
borderRadius: '5px',
|
||||
padding: '5px',
|
||||
width: '95%',
|
||||
alignSelf: 'flex-start',
|
||||
cursor: !descriptionHeight
|
||||
? "default"
|
||||
? 'default'
|
||||
: isExpandedDescription
|
||||
? "default"
|
||||
: "pointer",
|
||||
? 'default'
|
||||
: 'pointer',
|
||||
}}
|
||||
className={
|
||||
!descriptionHeight ? "" : isExpandedDescription ? "" : "hover-click"
|
||||
!descriptionHeight ? '' : isExpandedDescription ? '' : 'hover-click'
|
||||
}
|
||||
>
|
||||
{descriptionHeight && !isExpandedDescription && (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "0px",
|
||||
right: "0px",
|
||||
left: "0px",
|
||||
bottom: "0px",
|
||||
cursor: "pointer",
|
||||
position: 'absolute',
|
||||
top: '0px',
|
||||
right: '0px',
|
||||
left: '0px',
|
||||
bottom: '0px',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
if (isExpandedDescription) return;
|
||||
@@ -207,11 +202,11 @@ export const PlaylistContent = () => {
|
||||
ref={contentRef}
|
||||
sx={{
|
||||
height: !descriptionHeight
|
||||
? "auto"
|
||||
? 'auto'
|
||||
: isExpandedDescription
|
||||
? "auto"
|
||||
? 'auto'
|
||||
: `${descriptionHeight}px`,
|
||||
overflow: "hidden",
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{videoData?.htmlDescription ? (
|
||||
@@ -221,7 +216,7 @@ export const PlaylistContent = () => {
|
||||
variant="body1"
|
||||
color="textPrimary"
|
||||
sx={{
|
||||
cursor: "default",
|
||||
cursor: 'default',
|
||||
}}
|
||||
>
|
||||
{videoData?.fullDescription}
|
||||
@@ -231,17 +226,17 @@ export const PlaylistContent = () => {
|
||||
{descriptionHeight >= descriptionThreshold && (
|
||||
<Typography
|
||||
onClick={() => {
|
||||
setIsExpandedDescription(prev => !prev);
|
||||
setIsExpandedDescription((prev) => !prev);
|
||||
}}
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
fontSize: "16px",
|
||||
cursor: "pointer",
|
||||
paddingLeft: "15px",
|
||||
paddingTop: "15px",
|
||||
fontWeight: 'bold',
|
||||
fontSize: '16px',
|
||||
cursor: 'pointer',
|
||||
paddingLeft: '15px',
|
||||
paddingTop: '15px',
|
||||
}}
|
||||
>
|
||||
{isExpandedDescription ? "Show less" : "...more"}
|
||||
{isExpandedDescription ? 'Show less' : '...more'}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
@@ -250,14 +245,14 @@ export const PlaylistContent = () => {
|
||||
<SuperLikesSection
|
||||
loadingSuperLikes={loadingSuperLikes}
|
||||
superlikes={superLikeList}
|
||||
postId={videoData?.id || ""}
|
||||
postName={videoData?.user || ""}
|
||||
postId={videoData?.id || ''}
|
||||
postName={videoData?.user || ''}
|
||||
/>
|
||||
)}
|
||||
{videoData?.id && channelName && (
|
||||
<CommentSection
|
||||
postId={videoData?.id || ""}
|
||||
postName={channelName || ""}
|
||||
postId={videoData?.id || ''}
|
||||
postName={channelName || ''}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -1,44 +1,47 @@
|
||||
import { videoRefType } from "../../../components/common/VideoPlayer/VideoPlayer.tsx";
|
||||
import {
|
||||
QTUBE_VIDEO_BASE,
|
||||
SUPER_LIKE_BASE,
|
||||
} from "../../../constants/Identifiers.ts";
|
||||
import {
|
||||
minPriceSuperLike,
|
||||
} from "../../../constants/Misc.ts";
|
||||
import { useFetchSuperLikes } from "../../../hooks/useFetchSuperLikes.tsx";
|
||||
import { setIsLoadingGlobal } from "../../../state/features/globalSlice.ts";
|
||||
import { RootState } from "../../../state/store.ts";
|
||||
import { SUPER_LIKE_BASE } from '../../../constants/Identifiers.ts';
|
||||
import { minPriceSuperLike } from '../../../constants/Misc.ts';
|
||||
import { useFetchSuperLikes } from '../../../hooks/useFetchSuperLikes.tsx';
|
||||
import { RootState } from '../../../state/store.ts';
|
||||
import React, {
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
useMemo,
|
||||
useCallback,
|
||||
} from "react";
|
||||
import { useTheme } from "@mui/material";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { hashWordWithoutPublicSalt, QortalGetMetadata, usePublish } from "qapp-core";
|
||||
} from 'react';
|
||||
import { useTheme } from '@mui/material';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import {
|
||||
hashWordWithoutPublicSalt,
|
||||
QortalGetMetadata,
|
||||
usePublish,
|
||||
} from 'qapp-core';
|
||||
|
||||
const superLikeVersion2Timestamp = 1744041600000;
|
||||
|
||||
|
||||
export const useVideoContentState = () => {
|
||||
const { name: channelName, id } = useParams();
|
||||
console.log('iddd', id)
|
||||
const [videoMetadataResource, setVideoMetadataResource] = useState<null | QortalGetMetadata>(null)
|
||||
const {resource } = usePublish(2, 'JSON', videoMetadataResource ? videoMetadataResource : !id ? null : {
|
||||
identifier: id,
|
||||
name: channelName,
|
||||
service: 'DOCUMENT'
|
||||
})
|
||||
const [videoMetadataResource, setVideoMetadataResource] =
|
||||
useState<null | QortalGetMetadata>(null);
|
||||
const { resource } = usePublish(
|
||||
2,
|
||||
'JSON',
|
||||
videoMetadataResource
|
||||
? videoMetadataResource
|
||||
: !id
|
||||
? null
|
||||
: {
|
||||
identifier: id,
|
||||
name: channelName,
|
||||
service: 'DOCUMENT',
|
||||
}
|
||||
);
|
||||
const [superLikeversion, setSuperLikeVersion] = useState<null | number>(null);
|
||||
const [isExpandedDescription, setIsExpandedDescription] =
|
||||
useState<boolean>(false);
|
||||
const containerRef = useRef<videoRefType>(null);
|
||||
|
||||
const [nameAddress, setNameAddress] = useState<string>("");
|
||||
const [nameAddress, setNameAddress] = useState<string>('');
|
||||
const [descriptionHeight, setDescriptionHeight] = useState<null | number>(
|
||||
null
|
||||
);
|
||||
@@ -49,8 +52,8 @@ export const useVideoContentState = () => {
|
||||
const [loadingSuperLikes, setLoadingSuperLikes] = useState<boolean>(false);
|
||||
const [superLikeList, setSuperLikeList] = useState<any[]>([]);
|
||||
const { addSuperlikeRawDataGetToList } = useFetchSuperLikes();
|
||||
const videoData = useMemo(()=> {
|
||||
if(!resource?.data) return null
|
||||
const videoData = useMemo(() => {
|
||||
if (!resource?.data) return null;
|
||||
|
||||
const resourceData = {
|
||||
title: resource?.qortalMetadata?.metadata?.title,
|
||||
@@ -61,25 +64,24 @@ export const useVideoContentState = () => {
|
||||
created: resource?.qortalMetadata?.created,
|
||||
updated: resource?.qortalMetadata?.updated,
|
||||
user: resource?.qortalMetadata.name,
|
||||
videoImage: "",
|
||||
videoImage: '',
|
||||
id: resource?.qortalMetadata.identifier,
|
||||
};
|
||||
return {
|
||||
...resourceData,
|
||||
...resource.data
|
||||
}
|
||||
}, [resource])
|
||||
...resource.data,
|
||||
};
|
||||
}, [resource]);
|
||||
|
||||
const isVideoLoaded = useMemo(()=> {
|
||||
if(!resource?.data) return false
|
||||
return true
|
||||
}, [resource?.data])
|
||||
console.log('videoData', videoData)
|
||||
const isVideoLoaded = useMemo(() => {
|
||||
if (!resource?.data) return false;
|
||||
return true;
|
||||
}, [resource?.data]);
|
||||
const contentRef = useRef(null);
|
||||
|
||||
const getAddressName = async name => {
|
||||
const getAddressName = async (name) => {
|
||||
const response = await qortalRequest({
|
||||
action: "GET_NAME_DATA",
|
||||
action: 'GET_NAME_DATA',
|
||||
name: name,
|
||||
});
|
||||
|
||||
@@ -94,7 +96,7 @@ export const useVideoContentState = () => {
|
||||
}
|
||||
}, [channelName]);
|
||||
const avatarUrl = useMemo(() => {
|
||||
let url = "";
|
||||
let url = '';
|
||||
if (channelName && userAvatarHash[channelName]) {
|
||||
url = userAvatarHash[channelName];
|
||||
}
|
||||
@@ -104,7 +106,6 @@ export const useVideoContentState = () => {
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
|
||||
|
||||
const videoReference = useMemo(() => {
|
||||
if (!videoData) return null;
|
||||
const { videoReference } = videoData;
|
||||
@@ -124,16 +125,13 @@ export const useVideoContentState = () => {
|
||||
const { videoImage } = videoData;
|
||||
return videoImage || null;
|
||||
}, [videoData]);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(()=> {
|
||||
if(!resource?.qortalMetadata?.created) return
|
||||
useEffect(() => {
|
||||
if (!resource?.qortalMetadata?.created) return;
|
||||
if (resource?.qortalMetadata?.created > superLikeVersion2Timestamp) {
|
||||
setSuperLikeVersion(2);
|
||||
} else setSuperLikeVersion(1);
|
||||
}, [resource?.qortalMetadata?.created])
|
||||
|
||||
|
||||
}, [resource?.qortalMetadata?.created]);
|
||||
|
||||
const descriptionThreshold = 200;
|
||||
useEffect(() => {
|
||||
@@ -152,9 +150,9 @@ export const useVideoContentState = () => {
|
||||
const hashPostId = await hashWordWithoutPublicSalt(id, 20);
|
||||
const urlV2 = `/arbitrary/resources/search?mode=ALL&service=BLOG_COMMENT&query=${SUPER_LIKE_BASE}${hashPostId}&limit=100&includemetadata=true&reverse=true&excludeblocked=true`;
|
||||
const response = await fetch(urlV2, {
|
||||
method: "GET",
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
let responseData = [];
|
||||
@@ -165,9 +163,9 @@ export const useVideoContentState = () => {
|
||||
39
|
||||
)}&limit=100&includemetadata=true&reverse=true&excludeblocked=true`;
|
||||
const responseV1 = await fetch(urlV1, {
|
||||
method: "GET",
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -201,7 +199,7 @@ export const useVideoContentState = () => {
|
||||
...comments,
|
||||
{
|
||||
...comment,
|
||||
message: "",
|
||||
message: '',
|
||||
amount: res.amount,
|
||||
},
|
||||
];
|
||||
@@ -227,42 +225,11 @@ export const useVideoContentState = () => {
|
||||
getComments(id, nameAddress, superLikeversion);
|
||||
}, [getComments, id, nameAddress, superLikeversion]);
|
||||
|
||||
const focusVideo = () => {
|
||||
const focusRef = containerRef.current?.getContainerRef()?.current;
|
||||
focusRef?.focus({ preventScroll: true });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
focusVideo();
|
||||
}, []);
|
||||
|
||||
const focusVideoOnClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
const target = e.target as Element;
|
||||
|
||||
const textTagNames = ["TEXTAREA", "P", "H[1-6]", "STRONG", "svg", "A"];
|
||||
const noText =
|
||||
textTagNames.findIndex(s => {
|
||||
return target?.tagName.match(s);
|
||||
}) < 0;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const clickOnEmptySpace = !target?.onclick && noText;
|
||||
|
||||
// clicking on link in superlikes bar shows deleted video when loading
|
||||
|
||||
if (target == e.currentTarget || clickOnEmptySpace) {
|
||||
console.log("in correctTarget");
|
||||
focusVideo();
|
||||
}
|
||||
};
|
||||
return {
|
||||
focusVideo: focusVideoOnClick,
|
||||
videoReference,
|
||||
channelName,
|
||||
id,
|
||||
videoCover,
|
||||
containerRef,
|
||||
isVideoLoaded,
|
||||
navigate,
|
||||
theme,
|
||||
@@ -276,7 +243,7 @@ export const useVideoContentState = () => {
|
||||
superLikeList,
|
||||
setSuperLikeList,
|
||||
getComments,
|
||||
setVideoMetadataResource
|
||||
setVideoMetadataResource,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -317,23 +284,23 @@ export function extractSigValue(metadescription) {
|
||||
}
|
||||
|
||||
export const getPaymentInfo = async (signature: string) => {
|
||||
if (signature === "undefined" || !signature) return undefined;
|
||||
if (signature === 'undefined' || !signature) return undefined;
|
||||
|
||||
try {
|
||||
const url = `/transactions/signature/${signature}`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
// Coin payment info must be added to responseData so we can display it to the user
|
||||
const responseData = await response.json();
|
||||
if (responseData && !responseData.error) return responseData;
|
||||
else {
|
||||
throw new Error("unable to get payment");
|
||||
throw new Error('unable to get payment');
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error("unable to get payment");
|
||||
throw new Error('unable to get payment');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,36 +1,33 @@
|
||||
import { Box, Button, Typography, useMediaQuery } from "@mui/material";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Box, Typography, useMediaQuery } from '@mui/material';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import DeletedVideo from "../../../assets/img/DeletedVideo.jpg";
|
||||
import { CommentSection } from "../../../components/common/Comments/CommentSection.tsx";
|
||||
import { SuperLikesSection } from "../../../components/common/SuperLikesList/SuperLikesSection.tsx";
|
||||
import { DisplayHtml } from "../../../components/common/TextEditor/DisplayHtml.tsx";
|
||||
import { VideoPlayer } from "../../../components/common/VideoPlayer/VideoPlayer.tsx";
|
||||
import { CommentSection } from '../../../components/common/Comments/CommentSection.tsx';
|
||||
import { SuperLikesSection } from '../../../components/common/SuperLikesList/SuperLikesSection.tsx';
|
||||
import { DisplayHtml } from '../../../components/common/TextEditor/DisplayHtml.tsx';
|
||||
import { VideoPlayer } from '../../../components/common/VideoPlayer/VideoPlayer.tsx';
|
||||
import {
|
||||
fontSizeSmall,
|
||||
minFileSize,
|
||||
smallVideoSize,
|
||||
} from "../../../constants/Misc.ts";
|
||||
import { formatBytes } from "../../../utils/numberFunctions.ts";
|
||||
import { formatDate } from "../../../utils/time.ts";
|
||||
import { VideoActionsBar } from "./VideoActionsBar.tsx";
|
||||
import { useVideoContentState } from "./VideoContent-State.ts";
|
||||
} from '../../../constants/Misc.ts';
|
||||
import { formatBytes } from '../../../utils/numberFunctions.ts';
|
||||
import { formatDate } from '../../../utils/time.ts';
|
||||
import { VideoActionsBar } from './VideoActionsBar.tsx';
|
||||
import { useVideoContentState } from './VideoContent-State.ts';
|
||||
import {
|
||||
Spacer,
|
||||
VideoContentContainer,
|
||||
VideoDescription,
|
||||
VideoPlayerContainer,
|
||||
VideoTitle,
|
||||
} from "./VideoContent-styles.tsx";
|
||||
} from './VideoContent-styles.tsx';
|
||||
|
||||
export const VideoContent = () => {
|
||||
const {
|
||||
focusVideo,
|
||||
videoReference,
|
||||
channelName,
|
||||
id,
|
||||
videoCover,
|
||||
containerRef,
|
||||
isVideoLoaded,
|
||||
theme,
|
||||
videoData,
|
||||
@@ -61,70 +58,56 @@ export const VideoContent = () => {
|
||||
if (videoWidth < minWidthPercent) videoWidth = minWidthPercent;
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("resize", e => {
|
||||
window.addEventListener('resize', (e) => {
|
||||
setScreenWidth(window.innerWidth + 120);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const [newVideo, setNewVideo] = useState(null)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
// padding: `0px 0px 0px ${isScreenSmall ? "0px" : "2%"}`,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: '10px',
|
||||
width: "100%",
|
||||
width: '100%',
|
||||
}}
|
||||
onClick={focusVideo}
|
||||
>
|
||||
<Button onClick={()=> {
|
||||
setNewVideo({
|
||||
identifier: 'MYTEST2_vid_hymn-for-peace_gEpkBT',
|
||||
name: 'SafetyMongoose',
|
||||
service: 'VIDEO'
|
||||
})
|
||||
}}>play another</Button>
|
||||
<Button onClick={()=> setNewVideo(null)}>go back</Button>
|
||||
{videoReference ? (
|
||||
<VideoPlayerContainer
|
||||
sx={{
|
||||
// width: `${videoWidth}%`,
|
||||
// marginLeft: "0%",
|
||||
height: '70vh',
|
||||
backgroundColor: 'black'
|
||||
backgroundColor: 'black',
|
||||
}}
|
||||
>
|
||||
<VideoPlayer
|
||||
name={newVideo?.name || videoReference?.name}
|
||||
service={newVideo?.service || videoReference?.service}
|
||||
identifier={newVideo?.identifier || videoReference?.identifier}
|
||||
name={videoReference?.name}
|
||||
service={videoReference?.service}
|
||||
identifier={videoReference?.identifier}
|
||||
user={channelName}
|
||||
jsonId={id}
|
||||
poster={videoCover || ""}
|
||||
ref={containerRef}
|
||||
poster={videoCover || ''}
|
||||
videoStyles={{
|
||||
videoContainer: { aspectRatio: "16 / 9" },
|
||||
video: { aspectRatio: "16 / 9" },
|
||||
videoContainer: { aspectRatio: '16 / 9' },
|
||||
video: { aspectRatio: '16 / 9' },
|
||||
}}
|
||||
duration={videoData?.duration}
|
||||
|
||||
/>
|
||||
</VideoPlayerContainer>
|
||||
) : (
|
||||
<Box sx={{ width: "100%", height: '70vh', background: 'black'}}></Box>
|
||||
<Box
|
||||
sx={{ width: '100%', height: '70vh', background: 'black' }}
|
||||
></Box>
|
||||
)}
|
||||
<VideoContentContainer
|
||||
sx={{ paddingLeft: isScreenSmall ? "5px" : "0px" }}
|
||||
sx={{ paddingLeft: isScreenSmall ? '5px' : '0px' }}
|
||||
>
|
||||
<VideoTitle
|
||||
variant={isScreenSmall ? "h2" : "h1"}
|
||||
variant={isScreenSmall ? 'h2' : 'h1'}
|
||||
color="textPrimary"
|
||||
sx={{
|
||||
textAlign: "start",
|
||||
marginTop: isScreenSmall ? "20px" : "10px",
|
||||
textAlign: 'start',
|
||||
marginTop: isScreenSmall ? '20px' : '10px',
|
||||
}}
|
||||
>
|
||||
{videoData?.title}
|
||||
@@ -135,7 +118,7 @@ export const VideoContent = () => {
|
||||
variant="h2"
|
||||
sx={{
|
||||
fontSize: fontSizeSmall,
|
||||
display: "inline",
|
||||
display: 'inline',
|
||||
}}
|
||||
color={theme.palette.text.primary}
|
||||
>
|
||||
@@ -147,13 +130,13 @@ export const VideoContent = () => {
|
||||
<Typography
|
||||
variant="h1"
|
||||
sx={{
|
||||
fontSize: "90%",
|
||||
display: "inline",
|
||||
marginLeft: "20px",
|
||||
fontSize: '90%',
|
||||
display: 'inline',
|
||||
marginLeft: '20px',
|
||||
}}
|
||||
color={"green"}
|
||||
color={'green'}
|
||||
>
|
||||
{formatBytes(videoData.fileSize, 2, "Decimal")}
|
||||
{formatBytes(videoData.fileSize, 2, 'Decimal')}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
@@ -163,42 +146,42 @@ export const VideoContent = () => {
|
||||
setSuperLikeList={setSuperLikeList}
|
||||
superLikeList={superLikeList}
|
||||
videoReference={videoReference}
|
||||
sx={{ width: "calc(100% - 5px)" }}
|
||||
sx={{ width: 'calc(100% - 5px)' }}
|
||||
/>
|
||||
|
||||
<Spacer height="15px" />
|
||||
{videoData?.fullDescription && (
|
||||
<Box
|
||||
sx={{
|
||||
background: "#333333",
|
||||
borderRadius: "5px",
|
||||
padding: "5px",
|
||||
width: "95%",
|
||||
background: '#333333',
|
||||
borderRadius: '5px',
|
||||
padding: '5px',
|
||||
width: '95%',
|
||||
cursor: !descriptionHeight
|
||||
? "default"
|
||||
? 'default'
|
||||
: isExpandedDescription
|
||||
? "default"
|
||||
: "pointer",
|
||||
position: "relative",
|
||||
marginBottom: "30px",
|
||||
? 'default'
|
||||
: 'pointer',
|
||||
position: 'relative',
|
||||
marginBottom: '30px',
|
||||
}}
|
||||
className={
|
||||
!descriptionHeight
|
||||
? ""
|
||||
? ''
|
||||
: isExpandedDescription
|
||||
? ""
|
||||
: "hover-click"
|
||||
? ''
|
||||
: 'hover-click'
|
||||
}
|
||||
>
|
||||
{descriptionHeight && !isExpandedDescription && (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "0px",
|
||||
right: "0px",
|
||||
left: "0px",
|
||||
bottom: "0px",
|
||||
cursor: "pointer",
|
||||
position: 'absolute',
|
||||
top: '0px',
|
||||
right: '0px',
|
||||
left: '0px',
|
||||
bottom: '0px',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
if (isExpandedDescription) return;
|
||||
@@ -210,11 +193,11 @@ export const VideoContent = () => {
|
||||
ref={contentRef}
|
||||
sx={{
|
||||
height: !descriptionHeight
|
||||
? "auto"
|
||||
? 'auto'
|
||||
: isExpandedDescription
|
||||
? "auto"
|
||||
: "200px",
|
||||
overflow: "hidden",
|
||||
? 'auto'
|
||||
: '200px',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{videoData?.htmlDescription ? (
|
||||
@@ -224,7 +207,7 @@ export const VideoContent = () => {
|
||||
variant="body1"
|
||||
color="textPrimary"
|
||||
sx={{
|
||||
cursor: "default",
|
||||
cursor: 'default',
|
||||
}}
|
||||
>
|
||||
{videoData?.fullDescription}
|
||||
@@ -234,17 +217,17 @@ export const VideoContent = () => {
|
||||
{descriptionHeight >= descriptionThreshold && (
|
||||
<Typography
|
||||
onClick={() => {
|
||||
setIsExpandedDescription(prev => !prev);
|
||||
setIsExpandedDescription((prev) => !prev);
|
||||
}}
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
fontSize: "16px",
|
||||
cursor: "pointer",
|
||||
paddingLeft: "15px",
|
||||
paddingTop: "15px",
|
||||
fontWeight: 'bold',
|
||||
fontSize: '16px',
|
||||
cursor: 'pointer',
|
||||
paddingLeft: '15px',
|
||||
paddingTop: '15px',
|
||||
}}
|
||||
>
|
||||
{isExpandedDescription ? "Show less" : "...more"}
|
||||
{isExpandedDescription ? 'Show less' : '...more'}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
@@ -257,10 +240,10 @@ export const VideoContent = () => {
|
||||
getMore={() => {}}
|
||||
loadingSuperLikes={loadingSuperLikes}
|
||||
superlikes={superLikeList}
|
||||
postId={id || ""}
|
||||
postName={channelName || ""}
|
||||
postId={id || ''}
|
||||
postName={channelName || ''}
|
||||
/>
|
||||
<CommentSection postId={id || ""} postName={channelName || ""} />
|
||||
<CommentSection postId={id || ''} postName={channelName || ''} />
|
||||
</>
|
||||
)}
|
||||
</VideoContentContainer>
|
||||
|
||||
@@ -1,30 +1,26 @@
|
||||
import { Box } from "@mui/material";
|
||||
import { useMemo } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Box } from '@mui/material';
|
||||
import { useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { VideoManagerRow } from "./VideoList-styles.tsx";
|
||||
import { VideoManagerRow } from './VideoList-styles.tsx';
|
||||
|
||||
import VideoList from "./VideoList.tsx";
|
||||
import { QortalSearchParams } from "qapp-core";
|
||||
import { QTUBE_PLAYLIST_BASE } from "../../../constants/Identifiers.ts";
|
||||
import VideoList from './VideoList.tsx';
|
||||
import { QortalSearchParams } from 'qapp-core';
|
||||
import { QTUBE_PLAYLIST_BASE } from '../../../constants/Identifiers.ts';
|
||||
|
||||
interface VideoListProps {
|
||||
mode?: string;
|
||||
}
|
||||
|
||||
export const PlayListComponentLevel = ({ mode }: VideoListProps) => {
|
||||
export const PlayListComponentLevel = () => {
|
||||
const { name: paramName } = useParams();
|
||||
|
||||
const searchParameters: QortalSearchParams = useMemo(() => {
|
||||
return {
|
||||
identifier: QTUBE_PLAYLIST_BASE,
|
||||
service: "PLAYLIST",
|
||||
service: 'PLAYLIST',
|
||||
offset: 0,
|
||||
reverse: true,
|
||||
limit: 20,
|
||||
excludeBlocked: true,
|
||||
name: paramName || "",
|
||||
mode: "ALL",
|
||||
name: paramName || '',
|
||||
mode: 'ALL',
|
||||
exactmatchnames: true,
|
||||
};
|
||||
}, [paramName]);
|
||||
@@ -33,10 +29,10 @@ export const PlayListComponentLevel = ({ mode }: VideoListProps) => {
|
||||
<VideoManagerRow>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<VideoList
|
||||
|
||||
@@ -1,77 +1,75 @@
|
||||
import { SelectChangeEvent } from "@mui/material";
|
||||
import { useEffect, useState } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { categories } from "../../../constants/Categories.ts";
|
||||
import { changeFilterType } from "../../../state/features/persistSlice.ts";
|
||||
import { SelectChangeEvent } from '@mui/material';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { categories } from '../../../constants/Categories.ts';
|
||||
import { changeFilterType } from '../../../state/features/persistSlice.ts';
|
||||
import {
|
||||
changefilterName,
|
||||
changefilterSearch,
|
||||
changeSelectedCategoryVideos,
|
||||
changeSelectedSubCategoryVideos,
|
||||
resetVideoState,
|
||||
} from "../../../state/features/videoSlice.ts";
|
||||
import { RootState } from "../../../state/store.ts";
|
||||
} from '../../../state/features/videoSlice.ts';
|
||||
import { RootState } from '../../../state/store.ts';
|
||||
|
||||
export const useSidebarState = (
|
||||
|
||||
) => {
|
||||
const [filterStateSearch, setFilterStateSearch] = useState('')
|
||||
const [filterStateType, setFilterStateType] = useState('videos')
|
||||
const [filterStateName, setFilterStateName] = useState('')
|
||||
const [selectedCategoryVideosState, setSelectedCategoryVideosState] = useState(null)
|
||||
const [selectedSubCategoryVideosState, setSelectedSubCategoryVideosState] = useState(null)
|
||||
export const useSidebarState = () => {
|
||||
const [filterStateSearch, setFilterStateSearch] = useState('');
|
||||
const [filterStateType, setFilterStateType] = useState('videos');
|
||||
const [filterStateName, setFilterStateName] = useState('');
|
||||
const [selectedCategoryVideosState, setSelectedCategoryVideosState] =
|
||||
useState(null);
|
||||
const [selectedSubCategoryVideosState, setSelectedSubCategoryVideosState] =
|
||||
useState(null);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onSearch = ()=> {
|
||||
const onSearch = () => {
|
||||
dispatch(changeFilterType(filterStateType));
|
||||
dispatch(changefilterSearch(filterStateSearch));
|
||||
dispatch(changefilterName(filterStateName));
|
||||
dispatch(changeSelectedCategoryVideos(selectedCategoryVideosState));
|
||||
dispatch(changeSelectedSubCategoryVideos(selectedSubCategoryVideosState));
|
||||
}
|
||||
};
|
||||
|
||||
const {
|
||||
selectedCategoryVideos,
|
||||
selectedSubCategoryVideos,
|
||||
filterName,
|
||||
filterSearch
|
||||
filterSearch,
|
||||
} = useSelector((state: RootState) => state.video);
|
||||
const filterType = useSelector(
|
||||
(state: RootState) => state.persist.filterType
|
||||
);
|
||||
|
||||
console.log('filterName', filterName, )
|
||||
useEffect(() => {
|
||||
setFilterStateSearch(filterSearch);
|
||||
setFilterStateName(filterName);
|
||||
setFilterStateType(filterType);
|
||||
setSelectedCategoryVideosState(selectedCategoryVideos);
|
||||
setSelectedSubCategoryVideosState(selectedSubCategoryVideos);
|
||||
}, [
|
||||
filterName,
|
||||
filterSearch,
|
||||
filterType,
|
||||
selectedCategoryVideos,
|
||||
selectedSubCategoryVideos,
|
||||
]);
|
||||
|
||||
useEffect(()=> {
|
||||
setFilterStateSearch(filterSearch)
|
||||
setFilterStateName(filterName)
|
||||
setFilterStateType(filterType)
|
||||
setSelectedCategoryVideosState(selectedCategoryVideos)
|
||||
setSelectedSubCategoryVideosState(selectedSubCategoryVideos)
|
||||
}, [filterName,
|
||||
filterSearch, filterType, selectedCategoryVideos, selectedSubCategoryVideos])
|
||||
|
||||
const onReset = ()=> {
|
||||
dispatch(resetVideoState())
|
||||
}
|
||||
const onReset = () => {
|
||||
dispatch(resetVideoState());
|
||||
};
|
||||
|
||||
const handleInputKeyDown = (event: any) => {
|
||||
if (event.key === "Enter") {
|
||||
if (event.key === 'Enter') {
|
||||
onSearch();
|
||||
}
|
||||
};
|
||||
|
||||
console.log('filterType', filterType)
|
||||
|
||||
|
||||
|
||||
const handleOptionCategoryChangeVideos = (
|
||||
event: SelectChangeEvent<string>
|
||||
) => {
|
||||
const optionId = event.target.value;
|
||||
const selectedOption = categories.find(option => option.id === +optionId);
|
||||
const selectedOption = categories.find((option) => option.id === +optionId);
|
||||
setSelectedCategoryVideosState(selectedOption || null);
|
||||
};
|
||||
const handleOptionSubCategoryChangeVideos = (
|
||||
@@ -80,13 +78,11 @@ useEffect(()=> {
|
||||
) => {
|
||||
const optionId = event.target.value;
|
||||
const selectedOption = subcategories.find(
|
||||
option => option.id === +optionId
|
||||
(option) => option.id === +optionId
|
||||
);
|
||||
setSelectedSubCategoryVideosState(selectedOption || null);
|
||||
};
|
||||
|
||||
|
||||
|
||||
return {
|
||||
filterSearch: filterStateSearch,
|
||||
setFilterSearch: setFilterStateSearch,
|
||||
@@ -100,6 +96,6 @@ useEffect(()=> {
|
||||
filterType: filterStateType,
|
||||
setFilterType: setFilterStateType,
|
||||
onSearch,
|
||||
onReset
|
||||
onReset,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { blockUser, setEditVideo } from "../../../state/features/videoSlice.ts";
|
||||
import { RootState } from "../../../state/store.ts";
|
||||
import { blockUser, setEditVideo } from '../../../state/features/videoSlice.ts';
|
||||
import { RootState } from '../../../state/store.ts';
|
||||
|
||||
import { VideoCardContainer } from "./VideoList-styles.tsx";
|
||||
import { QortalSearchParams, ResourceListDisplay } from "qapp-core";
|
||||
import { VideoListItem } from "./VideoListItem.tsx";
|
||||
import { VideoLoaderItem } from "./VideoLoaderItem.tsx";
|
||||
import { VideoCardContainer } from './VideoList-styles.tsx';
|
||||
import { QortalSearchParams, ResourceListDisplay } from 'qapp-core';
|
||||
import { VideoListItem } from './VideoListItem.tsx';
|
||||
import { VideoLoaderItem } from './VideoLoaderItem.tsx';
|
||||
|
||||
interface VideoListProps {
|
||||
searchParameters: QortalSearchParams;
|
||||
listName: string;
|
||||
}
|
||||
export const VideoList = ({ searchParameters, listName }: VideoListProps) => {
|
||||
const [showIcons, setShowIcons] = useState(null);
|
||||
const username = useSelector((state: RootState) => state.auth?.user?.name);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const blockUserFunc = async (user: string) => {
|
||||
if (user === "Q-Tube") return;
|
||||
if (user === 'Q-Tube') return;
|
||||
|
||||
try {
|
||||
const response = await qortalRequest({
|
||||
action: "ADD_LIST_ITEMS",
|
||||
list_name: "blockedNames",
|
||||
action: 'ADD_LIST_ITEMS',
|
||||
list_name: 'blockedNames',
|
||||
items: [user],
|
||||
});
|
||||
|
||||
@@ -37,7 +36,7 @@ export const VideoList = ({ searchParameters, listName }: VideoListProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const renderLoaderItem = useCallback(status => {
|
||||
const renderLoaderItem = useCallback((status) => {
|
||||
return <VideoLoaderItem status={status} />;
|
||||
}, []);
|
||||
|
||||
@@ -51,14 +50,12 @@ export const VideoList = ({ searchParameters, listName }: VideoListProps) => {
|
||||
qortalMetadata={qortalMetadata}
|
||||
video={video}
|
||||
blockUserFunc={blockUserFunc}
|
||||
setShowIcons={setShowIcons}
|
||||
showIcons={showIcons}
|
||||
username={username}
|
||||
setEditVideo={setEditVideo}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[username, showIcons]
|
||||
[username]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,144 +1,162 @@
|
||||
import { Avatar, Box, Tooltip, Typography, useTheme } from "@mui/material";
|
||||
import { BlockIconContainer, BottomParent, IconsBox, NameContainer, VideoCard, VideoCardCol, VideoCardName, VideoCardTitle, VideoUploadDate } from "./VideoList-styles";
|
||||
import { setEditPlaylist } from "../../../state/features/videoSlice";
|
||||
import ResponsiveImage from "../../../components/ResponsiveImage";
|
||||
import { PlaylistSVG } from "../../../assets/svgs/PlaylistSVG";
|
||||
import { formatTime } from "../../../utils/numberFunctions";
|
||||
import { VideoCardImageContainer } from "./VideoCardImageContainer";
|
||||
import { fontSizeSmall, minDuration } from "../../../constants/Misc";
|
||||
import { formatDate } from "../../../utils/time";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import BlockIcon from "@mui/icons-material/Block";
|
||||
import EditIcon from "@mui/icons-material/Edit";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useGlobal } from "qapp-core";
|
||||
import { Avatar, Box, Tooltip, Typography, useTheme } from '@mui/material';
|
||||
import {
|
||||
BlockIconContainer,
|
||||
BottomParent,
|
||||
IconsBox,
|
||||
NameContainer,
|
||||
VideoCard,
|
||||
VideoCardCol,
|
||||
VideoCardName,
|
||||
VideoCardTitle,
|
||||
VideoUploadDate,
|
||||
} from './VideoList-styles';
|
||||
import { setEditPlaylist } from '../../../state/features/videoSlice';
|
||||
import ResponsiveImage from '../../../components/ResponsiveImage';
|
||||
import { PlaylistSVG } from '../../../assets/svgs/PlaylistSVG';
|
||||
import { formatTime } from '../../../utils/numberFunctions';
|
||||
import { VideoCardImageContainer } from './VideoCardImageContainer';
|
||||
import { fontSizeSmall, minDuration } from '../../../constants/Misc';
|
||||
import { formatDate } from '../../../utils/time';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import BlockIcon from '@mui/icons-material/Block';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useGlobal } from 'qapp-core';
|
||||
import { useState } from 'react';
|
||||
|
||||
export const VideoListItem = ({qortalMetadata, video, setShowIcons, showIcons, username, blockUserFunc, setEditVideo}) => {
|
||||
const navigate = useNavigate()
|
||||
const dispatch = useDispatch();
|
||||
const theme = useTheme()
|
||||
const { lists } = useGlobal();
|
||||
export const VideoListItem = ({
|
||||
qortalMetadata,
|
||||
video,
|
||||
username,
|
||||
blockUserFunc,
|
||||
setEditVideo,
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const [showIcons, setShowIcons] = useState(null);
|
||||
const theme = useTheme();
|
||||
const { lists } = useGlobal();
|
||||
|
||||
const { deleteResource } = lists;
|
||||
const { deleteResource } = lists;
|
||||
|
||||
const isPlaylist = qortalMetadata?.service === "PLAYLIST";
|
||||
const isPlaylist = qortalMetadata?.service === 'PLAYLIST';
|
||||
|
||||
if (isPlaylist) {
|
||||
return (
|
||||
<VideoCardCol
|
||||
key={qortalMetadata?.identifier}
|
||||
onMouseEnter={() => setShowIcons(qortalMetadata?.identifier)}
|
||||
onMouseLeave={() => setShowIcons(null)}
|
||||
if (isPlaylist) {
|
||||
return (
|
||||
<VideoCardCol
|
||||
key={qortalMetadata?.identifier}
|
||||
onMouseEnter={() => setShowIcons(qortalMetadata?.identifier)}
|
||||
onMouseLeave={() => setShowIcons(null)}
|
||||
>
|
||||
<IconsBox
|
||||
sx={{
|
||||
opacity: showIcons === qortalMetadata?.identifier ? 1 : 0,
|
||||
zIndex: 2,
|
||||
}}
|
||||
>
|
||||
<IconsBox
|
||||
sx={{
|
||||
opacity: showIcons === qortalMetadata?.identifier ? 1 : 0,
|
||||
zIndex: 2,
|
||||
}}
|
||||
>
|
||||
{qortalMetadata?.name === username && (
|
||||
<Tooltip title="Edit playlist" placement="top">
|
||||
<BlockIconContainer>
|
||||
<EditIcon
|
||||
onClick={() => {
|
||||
const resourceData = {
|
||||
title: qortalMetadata?.metadata?.title,
|
||||
category: qortalMetadata?.metadata?.category,
|
||||
categoryName: qortalMetadata?.metadata?.categoryName,
|
||||
tags: qortalMetadata?.metadata?.tags || [],
|
||||
description: qortalMetadata?.metadata?.description,
|
||||
created: qortalMetadata?.created,
|
||||
updated: qortalMetadata?.updated,
|
||||
name: qortalMetadata.name,
|
||||
videoImage: "",
|
||||
identifier: qortalMetadata.identifier,
|
||||
service: qortalMetadata.service,
|
||||
};
|
||||
dispatch(setEditPlaylist({...resourceData, ...video}));
|
||||
}}
|
||||
/>
|
||||
</BlockIconContainer>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{qortalMetadata?.name !== username && (
|
||||
<Tooltip title="Block user content" placement="top">
|
||||
<BlockIconContainer>
|
||||
<BlockIcon
|
||||
onClick={() => {
|
||||
blockUserFunc(qortalMetadata?.name);
|
||||
}}
|
||||
/>
|
||||
</BlockIconContainer>
|
||||
</Tooltip>
|
||||
)}
|
||||
</IconsBox>
|
||||
|
||||
<VideoCard
|
||||
|
||||
onClick={() => {
|
||||
navigate(`/playlist/${qortalMetadata?.name}/${qortalMetadata?.identifier}`);
|
||||
}}
|
||||
>
|
||||
<ResponsiveImage
|
||||
src={video?.image}
|
||||
width={266}
|
||||
height={150}
|
||||
style={{
|
||||
maxHeight: "50%",
|
||||
}}
|
||||
/>
|
||||
<VideoCardTitle>{video?.title}</VideoCardTitle>
|
||||
<BottomParent>
|
||||
<NameContainer
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
navigate(`/channel/${qortalMetadata?.name}`);
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
sx={{ height: 24, width: 24 }}
|
||||
src={`/arbitrary/THUMBNAIL/${qortalMetadata?.name}/qortal_avatar`}
|
||||
alt={`${qortalMetadata?.name}'s avatar`}
|
||||
/>
|
||||
<VideoCardName
|
||||
sx={{
|
||||
":hover": {
|
||||
textDecoration: "underline",
|
||||
},
|
||||
{qortalMetadata?.name === username && (
|
||||
<Tooltip title="Edit playlist" placement="top">
|
||||
<BlockIconContainer>
|
||||
<EditIcon
|
||||
onClick={() => {
|
||||
const resourceData = {
|
||||
title: qortalMetadata?.metadata?.title,
|
||||
category: qortalMetadata?.metadata?.category,
|
||||
categoryName: qortalMetadata?.metadata?.categoryName,
|
||||
tags: qortalMetadata?.metadata?.tags || [],
|
||||
description: qortalMetadata?.metadata?.description,
|
||||
created: qortalMetadata?.created,
|
||||
updated: qortalMetadata?.updated,
|
||||
name: qortalMetadata.name,
|
||||
videoImage: '',
|
||||
identifier: qortalMetadata.identifier,
|
||||
service: qortalMetadata.service,
|
||||
};
|
||||
dispatch(setEditPlaylist({ ...resourceData, ...video }));
|
||||
}}
|
||||
>
|
||||
{qortalMetadata?.name}
|
||||
</VideoCardName>
|
||||
/>
|
||||
</BlockIconContainer>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{qortalMetadata?.created && (
|
||||
<VideoUploadDate>
|
||||
{formatDate(qortalMetadata.created)}
|
||||
</VideoUploadDate>
|
||||
)}
|
||||
</NameContainer>
|
||||
<Box
|
||||
{qortalMetadata?.name !== username && (
|
||||
<Tooltip title="Block user content" placement="top">
|
||||
<BlockIconContainer>
|
||||
<BlockIcon
|
||||
onClick={() => {
|
||||
blockUserFunc(qortalMetadata?.name);
|
||||
}}
|
||||
/>
|
||||
</BlockIconContainer>
|
||||
</Tooltip>
|
||||
)}
|
||||
</IconsBox>
|
||||
|
||||
<VideoCard
|
||||
onClick={() => {
|
||||
navigate(
|
||||
`/playlist/${qortalMetadata?.name}/${qortalMetadata?.identifier}`
|
||||
);
|
||||
}}
|
||||
>
|
||||
<ResponsiveImage
|
||||
src={video?.image}
|
||||
width={266}
|
||||
height={150}
|
||||
style={{
|
||||
maxHeight: '50%',
|
||||
}}
|
||||
/>
|
||||
<VideoCardTitle>{video?.title}</VideoCardTitle>
|
||||
<BottomParent>
|
||||
<NameContainer
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate(`/channel/${qortalMetadata?.name}`);
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
sx={{ height: 24, width: 24 }}
|
||||
src={`/arbitrary/THUMBNAIL/${qortalMetadata?.name}/qortal_avatar`}
|
||||
alt={`${qortalMetadata?.name}'s avatar`}
|
||||
/>
|
||||
<VideoCardName
|
||||
sx={{
|
||||
display: "flex",
|
||||
position: "absolute",
|
||||
bottom: "5px",
|
||||
right: "5px",
|
||||
':hover': {
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<PlaylistSVG
|
||||
color={theme.palette.text.primary}
|
||||
height="36px"
|
||||
width="36px"
|
||||
/>
|
||||
</Box>
|
||||
</BottomParent>
|
||||
</VideoCard>
|
||||
</VideoCardCol>
|
||||
);
|
||||
}
|
||||
{qortalMetadata?.name}
|
||||
</VideoCardName>
|
||||
|
||||
{qortalMetadata?.created && (
|
||||
<VideoUploadDate>
|
||||
{formatDate(qortalMetadata.created)}
|
||||
</VideoUploadDate>
|
||||
)}
|
||||
</NameContainer>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
position: 'absolute',
|
||||
bottom: '5px',
|
||||
right: '5px',
|
||||
}}
|
||||
>
|
||||
<PlaylistSVG
|
||||
color={theme.palette.text.primary}
|
||||
height="36px"
|
||||
width="36px"
|
||||
/>
|
||||
</Box>
|
||||
</BottomParent>
|
||||
</VideoCard>
|
||||
</VideoCardCol>
|
||||
);
|
||||
}
|
||||
|
||||
console.log('showIcons', qortalMetadata, showIcons, qortalMetadata?.name === username, qortalMetadata?.name, username)
|
||||
return (
|
||||
<VideoCardCol
|
||||
key={qortalMetadata?.identifier}
|
||||
@@ -165,11 +183,11 @@ export const VideoListItem = ({qortalMetadata, video, setShowIcons, showIcons, u
|
||||
created: qortalMetadata?.created,
|
||||
updated: qortalMetadata?.updated,
|
||||
user: qortalMetadata.name,
|
||||
videoImage: "",
|
||||
videoImage: '',
|
||||
id: qortalMetadata.identifier,
|
||||
};
|
||||
|
||||
dispatch(setEditVideo({...resourceData, ...video}));
|
||||
dispatch(setEditVideo({ ...resourceData, ...video }));
|
||||
}}
|
||||
/>
|
||||
</BlockIconContainer>
|
||||
@@ -192,10 +210,7 @@ export const VideoListItem = ({qortalMetadata, video, setShowIcons, showIcons, u
|
||||
<BlockIconContainer>
|
||||
<DeleteIcon
|
||||
onClick={() => {
|
||||
deleteResource([
|
||||
qortalMetadata,
|
||||
video.videoReference,
|
||||
]);
|
||||
deleteResource([qortalMetadata, video.videoReference]);
|
||||
}}
|
||||
/>
|
||||
</BlockIconContainer>
|
||||
@@ -217,9 +232,7 @@ export const VideoListItem = ({qortalMetadata, video, setShowIcons, showIcons, u
|
||||
bgcolor="#202020"
|
||||
zIndex={999}
|
||||
>
|
||||
<Typography color="white">
|
||||
{formatTime(video.duration)}
|
||||
</Typography>
|
||||
<Typography color="white">{formatTime(video.duration)}</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<VideoCardImageContainer
|
||||
@@ -237,7 +250,7 @@ export const VideoListItem = ({qortalMetadata, video, setShowIcons, showIcons, u
|
||||
</Tooltip>
|
||||
<BottomParent>
|
||||
<NameContainer
|
||||
onClick={e => {
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate(`/channel/${qortalMetadata?.name}`);
|
||||
}}
|
||||
@@ -249,8 +262,8 @@ export const VideoListItem = ({qortalMetadata, video, setShowIcons, showIcons, u
|
||||
/>
|
||||
<VideoCardName
|
||||
sx={{
|
||||
":hover": {
|
||||
textDecoration: "underline",
|
||||
':hover': {
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
}}
|
||||
>
|
||||
@@ -258,8 +271,8 @@ export const VideoListItem = ({qortalMetadata, video, setShowIcons, showIcons, u
|
||||
</VideoCardName>
|
||||
</NameContainer>
|
||||
{qortalMetadata?.created && (
|
||||
<Box sx={{ flexDirection: "row", width: "100%" }}>
|
||||
<VideoUploadDate sx={{ display: "inline" }}>
|
||||
<Box sx={{ flexDirection: 'row', width: '100%' }}>
|
||||
<VideoUploadDate sx={{ display: 'inline' }}>
|
||||
{formatDate(qortalMetadata.created)}
|
||||
</VideoUploadDate>
|
||||
</Box>
|
||||
@@ -268,4 +281,4 @@ export const VideoListItem = ({qortalMetadata, video, setShowIcons, showIcons, u
|
||||
</VideoCard>
|
||||
</VideoCardCol>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import React, { useRef, useState } from 'react';
|
||||
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useFetchVideos } from "../../hooks/useFetchVideos.tsx";
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import {
|
||||
setHomePageSelectedTab,
|
||||
VideoListType,
|
||||
} from "../../state/features/persistSlice.ts";
|
||||
import { RootState } from "../../state/store";
|
||||
import { resetVideoState } from "../../state/features/videoSlice.ts";
|
||||
} from '../../state/features/persistSlice.ts';
|
||||
import { RootState } from '../../state/store';
|
||||
import { resetVideoState } from '../../state/features/videoSlice.ts';
|
||||
|
||||
export const useHomeState = (mode: string) => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -15,7 +14,6 @@ export const useHomeState = (mode: string) => {
|
||||
(state: RootState) => state.persist
|
||||
);
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [tabValue, setTabValue] = useState<VideoListType>(selectedTab);
|
||||
|
||||
const {
|
||||
@@ -31,11 +29,6 @@ export const useHomeState = (mode: string) => {
|
||||
} = useSelector((state: RootState) => state.video);
|
||||
|
||||
const isFilterMode = useRef(false);
|
||||
const firstFetch = useRef(false);
|
||||
const afterFetch = useRef(false);
|
||||
const isFetching = useRef(false);
|
||||
|
||||
const { getVideos, getVideosFiltered } = useFetchVideos();
|
||||
|
||||
const videos = isFiltering ? filteredVideos : globalVideos;
|
||||
|
||||
@@ -46,111 +39,21 @@ export const useHomeState = (mode: string) => {
|
||||
dispatch(setHomePageSelectedTab(newValue));
|
||||
};
|
||||
|
||||
// const getVideosHandlerMount = React.useCallback(async () => {
|
||||
// if (firstFetch.current) return;
|
||||
// firstFetch.current = true;
|
||||
// setIsLoading(true);
|
||||
// await getVideos(
|
||||
// {
|
||||
// name: "",
|
||||
// category: "",
|
||||
// subcategory: "",
|
||||
// keywords: "",
|
||||
// contentType: filterType,
|
||||
// },
|
||||
// null,
|
||||
// null,
|
||||
// 20,
|
||||
// tabValue
|
||||
// );
|
||||
// afterFetch.current = true;
|
||||
// isFetching.current = false;
|
||||
|
||||
// setIsLoading(false);
|
||||
// }, [getVideos]);
|
||||
|
||||
// const getVideosHandler = React.useCallback(
|
||||
// async (reset?: boolean, resetFilters?: boolean) => {
|
||||
// if (!firstFetch.current || !afterFetch.current) return;
|
||||
// if (isFetching.current) return;
|
||||
// isFetching.current = true;
|
||||
|
||||
// await getVideos(
|
||||
// {
|
||||
// name: filterName,
|
||||
// category: selectedCategoryVideos?.id,
|
||||
// subcategory: selectedSubCategoryVideos?.id,
|
||||
// keywords: filterSearch,
|
||||
// contentType: filterType,
|
||||
// },
|
||||
// reset,
|
||||
// resetFilters,
|
||||
// 20,
|
||||
// tabValue
|
||||
// );
|
||||
// isFetching.current = false;
|
||||
// },
|
||||
// [
|
||||
// getVideos,
|
||||
// filterValue,
|
||||
// getVideosFiltered,
|
||||
// isFiltering,
|
||||
// filterName,
|
||||
// selectedCategoryVideos,
|
||||
// selectedSubCategoryVideos,
|
||||
// filterSearch,
|
||||
// filterType,
|
||||
// tabValue,
|
||||
// ]
|
||||
// );
|
||||
|
||||
// useEffect(() => {
|
||||
// getVideosHandler(true);
|
||||
// }, [tabValue]);
|
||||
// const prevVal = useRef("");
|
||||
|
||||
// useEffect(() => {
|
||||
// if (isFiltering && filterValue !== prevVal?.current) {
|
||||
// prevVal.current = filterValue;
|
||||
|
||||
// getVideosHandler();
|
||||
// }
|
||||
// }, [filterValue, isFiltering, filteredVideos]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (
|
||||
// !firstFetch.current &&
|
||||
// !isFilterMode.current &&
|
||||
// globalVideos.length === 0
|
||||
// ) {
|
||||
// isFetching.current = true;
|
||||
// getVideosHandlerMount();
|
||||
// } else {
|
||||
// firstFetch.current = true;
|
||||
// afterFetch.current = true;
|
||||
// }
|
||||
// }, [getVideosHandlerMount, globalVideos]);
|
||||
|
||||
const resetState = ()=> {
|
||||
const resetState = () => {
|
||||
dispatch(resetVideoState());
|
||||
|
||||
}
|
||||
|
||||
console.log('filterName', filterName)
|
||||
};
|
||||
|
||||
return {
|
||||
tabValue,
|
||||
changeTab,
|
||||
videos,
|
||||
isLoading,
|
||||
filteredSubscriptionList,
|
||||
// getVideosHandler,
|
||||
filterName,
|
||||
filterSearch,
|
||||
filterValue,
|
||||
filterType,
|
||||
selectedCategoryVideos,
|
||||
selectedSubCategoryVideos,
|
||||
resetState
|
||||
resetState,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { TabContext, TabList, TabPanel } from "@mui/lab";
|
||||
import { TabContext, TabList, TabPanel } from '@mui/lab';
|
||||
|
||||
import { Box, Tab, useMediaQuery } from "@mui/material";
|
||||
import React, { useMemo } from "react";
|
||||
import LazyLoad from "../../components/common/LazyLoad";
|
||||
import { ListSuperLikeContainer } from "../../components/common/ListSuperLikes/ListSuperLikeContainer.tsx";
|
||||
import { fontSizeLarge, fontSizeSmall } from "../../constants/Misc.ts";
|
||||
import { SearchSidebar } from "./Components/SearchSidebar.tsx";
|
||||
import VideoList from "./Components/VideoList.tsx";
|
||||
import { useHomeState } from "./Home-State.ts";
|
||||
import { QTUBE_PLAYLIST_BASE, QTUBE_VIDEO_BASE } from "../../constants/Identifiers.ts";
|
||||
import { QortalSearchParams } from "qapp-core";
|
||||
import { Box, Tab, useMediaQuery } from '@mui/material';
|
||||
import { useMemo } from 'react';
|
||||
import { ListSuperLikeContainer } from '../../components/common/ListSuperLikes/ListSuperLikeContainer.tsx';
|
||||
import { fontSizeLarge, fontSizeSmall } from '../../constants/Misc.ts';
|
||||
import { SearchSidebar } from './Components/SearchSidebar.tsx';
|
||||
import VideoList from './Components/VideoList.tsx';
|
||||
import { useHomeState } from './Home-State.ts';
|
||||
import {
|
||||
QTUBE_PLAYLIST_BASE,
|
||||
QTUBE_VIDEO_BASE,
|
||||
} from '../../constants/Identifiers.ts';
|
||||
import { QortalSearchParams } from 'qapp-core';
|
||||
|
||||
interface HomeProps {
|
||||
mode?: string;
|
||||
@@ -18,36 +20,34 @@ export const Home = ({ mode }: HomeProps) => {
|
||||
const {
|
||||
tabValue,
|
||||
changeTab,
|
||||
videos,
|
||||
isLoading,
|
||||
filteredSubscriptionList,
|
||||
// getVideosHandler,
|
||||
selectedCategoryVideos,
|
||||
selectedSubCategoryVideos,
|
||||
filterType,
|
||||
filterName,
|
||||
filterSearch,
|
||||
resetState
|
||||
resetState,
|
||||
} = useHomeState(mode);
|
||||
|
||||
const tabPaneSX = {
|
||||
width: "100%",
|
||||
paddingLeft: "0px",
|
||||
paddingRight: "0px",
|
||||
width: '100%',
|
||||
paddingLeft: '0px',
|
||||
paddingRight: '0px',
|
||||
};
|
||||
const isScreenSmall = !useMediaQuery("(min-width:600px)");
|
||||
const isScreenLarge = useMediaQuery("(min-width:1200px)");
|
||||
const isScreenSmall = !useMediaQuery('(min-width:600px)');
|
||||
const isScreenLarge = useMediaQuery('(min-width:1200px)');
|
||||
|
||||
const tabSX = {
|
||||
fontSize: isScreenSmall ? fontSizeSmall : fontSizeLarge,
|
||||
paddingLeft: "0px",
|
||||
paddingRight: "0px",
|
||||
paddingLeft: '0px',
|
||||
paddingRight: '0px',
|
||||
};
|
||||
|
||||
const homeBaseSX = { display: "grid", width: "100%" };
|
||||
const bigGridSX = { gridTemplateColumns: "200px auto 250px" };
|
||||
const mediumGridSX = { gridTemplateColumns: "200px auto" };
|
||||
const smallGridSX = { gridTemplateColumns: "100%", gap: "20px" };
|
||||
const homeBaseSX = { display: 'grid', width: '100%' };
|
||||
const bigGridSX = { gridTemplateColumns: '200px auto 250px' };
|
||||
const mediumGridSX = { gridTemplateColumns: '200px auto' };
|
||||
const smallGridSX = { gridTemplateColumns: '100%', gap: '20px' };
|
||||
|
||||
let homeColumns: object;
|
||||
if (isScreenLarge) homeColumns = bigGridSX;
|
||||
@@ -62,77 +62,79 @@ export const Home = ({ mode }: HomeProps) => {
|
||||
description += `;subcategory:${selectedSubCategoryVideos}`;
|
||||
}
|
||||
|
||||
console.log('filterName', filterName)
|
||||
const searchParameters: QortalSearchParams = useMemo(()=> {
|
||||
const searchOptions: {
|
||||
description?: string
|
||||
query?: string
|
||||
} = {}
|
||||
if (selectedCategoryVideos) {
|
||||
searchOptions.description = `category:${selectedCategoryVideos.id};`;
|
||||
const searchParameters: QortalSearchParams = useMemo(() => {
|
||||
const searchOptions: {
|
||||
description?: string;
|
||||
query?: string;
|
||||
} = {};
|
||||
if (selectedCategoryVideos) {
|
||||
searchOptions.description = `category:${selectedCategoryVideos.id};`;
|
||||
|
||||
if (selectedSubCategoryVideos)
|
||||
searchOptions.description += `subcategory:${selectedSubCategoryVideos.id}`;
|
||||
}
|
||||
if(filterSearch){
|
||||
searchOptions.query = filterSearch
|
||||
}
|
||||
return {
|
||||
identifier:
|
||||
filterType === "playlists" ? QTUBE_PLAYLIST_BASE : QTUBE_VIDEO_BASE,
|
||||
service: filterType === "playlists" ? "PLAYLIST" : "DOCUMENT",
|
||||
offset: 0,
|
||||
reverse: true,
|
||||
limit: 20,
|
||||
excludeBlocked: true,
|
||||
name: filterName || "",
|
||||
...searchOptions,
|
||||
mode: 'ALL'
|
||||
};
|
||||
}, [filterType, filterName, selectedSubCategoryVideos, selectedCategoryVideos, filterSearch])
|
||||
console.log('searchParameters', searchParameters)
|
||||
if (selectedSubCategoryVideos)
|
||||
searchOptions.description += `subcategory:${selectedSubCategoryVideos.id}`;
|
||||
}
|
||||
if (filterSearch) {
|
||||
searchOptions.query = filterSearch;
|
||||
}
|
||||
return {
|
||||
identifier:
|
||||
filterType === 'playlists' ? QTUBE_PLAYLIST_BASE : QTUBE_VIDEO_BASE,
|
||||
service: filterType === 'playlists' ? 'PLAYLIST' : 'DOCUMENT',
|
||||
offset: 0,
|
||||
reverse: true,
|
||||
limit: 20,
|
||||
excludeBlocked: true,
|
||||
name: filterName || '',
|
||||
...searchOptions,
|
||||
mode: 'ALL',
|
||||
};
|
||||
}, [
|
||||
filterType,
|
||||
filterName,
|
||||
selectedSubCategoryVideos,
|
||||
selectedCategoryVideos,
|
||||
filterSearch,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ ...homeBaseSX, ...homeColumns }}>
|
||||
<SearchSidebar onReset={resetState} />
|
||||
<SearchSidebar onReset={resetState} />
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<TabContext value={tabValue}>
|
||||
<TabList
|
||||
onChange={changeTab}
|
||||
textColor={"secondary"}
|
||||
indicatorColor={"secondary"}
|
||||
textColor={'secondary'}
|
||||
indicatorColor={'secondary'}
|
||||
centered={false}
|
||||
>
|
||||
<Tab label="All" value={"all"} sx={tabSX} />
|
||||
<Tab label="Subscriptions" value={"subscriptions"} sx={tabSX} />
|
||||
<Tab label="All" value={'all'} sx={tabSX} />
|
||||
<Tab label="Subscriptions" value={'subscriptions'} sx={tabSX} />
|
||||
</TabList>
|
||||
<TabPanel value={"all"} sx={tabPaneSX}>
|
||||
<VideoList listName="AllVideos" searchParameters={searchParameters} />
|
||||
{/* <LazyLoad
|
||||
onLoadMore={getVideosHandler}
|
||||
isLoading={isLoading}
|
||||
></LazyLoad> */}
|
||||
<TabPanel value={'all'} sx={tabPaneSX}>
|
||||
<VideoList
|
||||
listName="AllVideos"
|
||||
searchParameters={searchParameters}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel value={"subscriptions"} sx={tabPaneSX}>
|
||||
<TabPanel value={'subscriptions'} sx={tabPaneSX}>
|
||||
{filteredSubscriptionList.length > 0 ? (
|
||||
<>
|
||||
<VideoList listName="SubscribedVideos" searchParameters={searchParameters} />
|
||||
{/* <LazyLoad
|
||||
onLoadMore={getVideosHandler}
|
||||
isLoading={isLoading}
|
||||
></LazyLoad> */}
|
||||
<VideoList
|
||||
listName="SubscribedVideos"
|
||||
searchParameters={searchParameters}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
!isLoading && (
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
You have no subscriptions
|
||||
</div>
|
||||
)
|
||||
|
||||
9
src/state/global/theme.ts
Normal file
9
src/state/global/theme.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { atom } from 'jotai';
|
||||
|
||||
export enum EnumTheme {
|
||||
LIGHT = 1,
|
||||
DARK = 2,
|
||||
}
|
||||
|
||||
// Atom to hold the current theme
|
||||
export const themeAtom = atom<EnumTheme>(EnumTheme.DARK);
|
||||
@@ -4,35 +4,34 @@ import React, {
|
||||
useCallback,
|
||||
useRef,
|
||||
useMemo,
|
||||
} from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
} from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import {
|
||||
extractSigValue,
|
||||
getPaymentInfo,
|
||||
isTimestampWithinRange,
|
||||
} from "../pages/ContentPages/VideoContent/VideoContent-State.ts";
|
||||
} from '../pages/ContentPages/VideoContent/VideoContent-State.ts';
|
||||
|
||||
import { addUser } from "../state/features/authSlice";
|
||||
import NavBar from "../components/layout/Navbar/Navbar";
|
||||
import PageLoader from "../components/common/PageLoader";
|
||||
import { RootState } from "../state/store";
|
||||
import { addUser } from '../state/features/authSlice';
|
||||
import NavBar from '../components/layout/Navbar/Navbar';
|
||||
import PageLoader from '../components/common/PageLoader';
|
||||
import { RootState } from '../state/store';
|
||||
import {
|
||||
setSuperlikesAll,
|
||||
setUserAvatarHash,
|
||||
} from "../state/features/globalSlice";
|
||||
// import { VideoPlayerGlobal } from "../components/common/VideoPlayer/VideoPlayerGlobal.tsx";
|
||||
import { Rnd } from "react-rnd";
|
||||
import { RequestQueue } from "../utils/queue";
|
||||
import { EditVideo } from "../components/Publish/EditVideo/EditVideo";
|
||||
import { EditPlaylist } from "../components/Publish/EditPlaylist/EditPlaylist";
|
||||
import ConsentModal from "../components/common/ConsentModal";
|
||||
import { useFetchSuperLikes } from "../hooks/useFetchSuperLikes";
|
||||
import { SUPER_LIKE_BASE } from "../constants/Identifiers.ts";
|
||||
import { minPriceSuperLike } from "../constants/Misc.ts";
|
||||
} from '../state/features/globalSlice';
|
||||
import { Rnd } from 'react-rnd';
|
||||
import { RequestQueue } from '../utils/queue';
|
||||
import { EditVideo } from '../components/Publish/EditVideo/EditVideo';
|
||||
import { EditPlaylist } from '../components/Publish/EditPlaylist/EditPlaylist';
|
||||
import ConsentModal from '../components/common/ConsentModal';
|
||||
import { useFetchSuperLikes } from '../hooks/useFetchSuperLikes';
|
||||
import { SUPER_LIKE_BASE } from '../constants/Identifiers.ts';
|
||||
import { minPriceSuperLike } from '../constants/Misc.ts';
|
||||
import { useHandleNameData } from './../hooks/useHandleNameData.tsx';
|
||||
import { namesAtom } from './../state/global/names';
|
||||
import { useAtom } from 'jotai';
|
||||
import { getPrimaryAccountName } from "../utils/qortalRequestFunctions.ts";
|
||||
import { getPrimaryAccountName } from '../utils/qortalRequestFunctions.ts';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
@@ -44,21 +43,21 @@ export const queue = new RequestQueue();
|
||||
export const queueSuperlikes = new RequestQueue();
|
||||
|
||||
const GlobalWrapper: React.FC<Props> = ({ children }) => {
|
||||
const [theme, setTheme] = useState('dark')
|
||||
const [theme, setTheme] = useState('dark');
|
||||
const dispatch = useDispatch();
|
||||
const isDragging = useRef(false);
|
||||
const [userAvatar, setUserAvatar] = useState<string>("");
|
||||
const [userAvatar, setUserAvatar] = useState<string>('');
|
||||
const user = useSelector((state: RootState) => state.auth.user);
|
||||
const { addSuperlikeRawDataGetToList } = useFetchSuperLikes();
|
||||
const interval = useRef<any>(null);
|
||||
useHandleNameData();
|
||||
|
||||
|
||||
const videoPlaying = useSelector(
|
||||
(state: RootState) => state.global.videoPlaying
|
||||
);
|
||||
|
||||
const username = useMemo(() => {
|
||||
if (!user?.name) return "";
|
||||
if (!user?.name) return '';
|
||||
|
||||
return user.name;
|
||||
}, [user]);
|
||||
@@ -68,7 +67,6 @@ const GlobalWrapper: React.FC<Props> = ({ children }) => {
|
||||
const getAvatar = React.useCallback(
|
||||
async (author: string) => {
|
||||
try {
|
||||
|
||||
const url = `/arbitrary/THUMBNAIL/${author}/qortal_avatar`;
|
||||
|
||||
if (url) {
|
||||
@@ -98,7 +96,7 @@ const GlobalWrapper: React.FC<Props> = ({ children }) => {
|
||||
const askForAccountInformation = React.useCallback(async () => {
|
||||
try {
|
||||
const account = await qortalRequest({
|
||||
action: "GET_USER_ACCOUNT",
|
||||
action: 'GET_USER_ACCOUNT',
|
||||
});
|
||||
|
||||
const name = await getPrimaryAccountName(account.address);
|
||||
@@ -141,9 +139,9 @@ const GlobalWrapper: React.FC<Props> = ({ children }) => {
|
||||
while (validCount < 20 && totalCount < 100) {
|
||||
const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_COMMENT&query=${SUPER_LIKE_BASE}&limit=1&offset=${totalCount}&includemetadata=true&reverse=true&excludeblocked=true`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const responseData = await response.json();
|
||||
@@ -171,7 +169,7 @@ const GlobalWrapper: React.FC<Props> = ({ children }) => {
|
||||
...comments,
|
||||
{
|
||||
...comment,
|
||||
message: "",
|
||||
message: '',
|
||||
amount: res.amount,
|
||||
},
|
||||
];
|
||||
@@ -210,41 +208,16 @@ const GlobalWrapper: React.FC<Props> = ({ children }) => {
|
||||
<>
|
||||
{isLoadingGlobal && <PageLoader />}
|
||||
<ConsentModal />
|
||||
<EditVideo />
|
||||
<EditPlaylist />
|
||||
<EditVideo />
|
||||
<EditPlaylist />
|
||||
<NavBar
|
||||
setTheme={(val: string) => setTheme(val)}
|
||||
isAuthenticated={!!user?.name}
|
||||
userName={user?.name || ""}
|
||||
userName={user?.name || ''}
|
||||
allNames={names}
|
||||
userAvatar={userAvatar}
|
||||
authenticate={askForAccountInformation}
|
||||
/>
|
||||
|
||||
{/*<Rnd*/}
|
||||
{/* onDragStart={onDragStart}*/}
|
||||
{/* onDragStop={onDragStop}*/}
|
||||
{/* style={{*/}
|
||||
{/* display: videoPlaying ? "block" : "none",*/}
|
||||
{/* position: "fixed",*/}
|
||||
{/* height: "auto",*/}
|
||||
{/* width: 350,*/}
|
||||
{/* zIndex: 1000,*/}
|
||||
{/* maxWidth: 800,*/}
|
||||
{/* }}*/}
|
||||
{/* default={{*/}
|
||||
{/* x: 0,*/}
|
||||
{/* y: 60,*/}
|
||||
{/* width: 350,*/}
|
||||
{/* height: "auto",*/}
|
||||
{/* }}*/}
|
||||
{/* // eslint-disable-next-line @typescript-eslint/no-empty-function*/}
|
||||
{/* onDrag={() => {}}*/}
|
||||
{/*>*/}
|
||||
{/* {videoPlaying && (*/}
|
||||
{/* <VideoPlayerGlobal checkIfDrag={checkIfDrag} element={videoPlaying} />*/}
|
||||
{/* )}*/}
|
||||
{/*</Rnd>*/}
|
||||
|
||||
{children}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user