mirror of
https://github.com/Qortal/q-tube.git
synced 2025-02-11 17:55:51 +00:00
Subscriptions to channels added
Filter added that removes characters that Operating Systems don't allow in filenames when saving file VideoList-styles.tsx uses Radio button instead of Checkbox for main page video/playlist filter Video player has aspect ratio of 16 / 9, doesn't put controls over video, and removes controls if mouse exits video when in fullscreen (but only when playing for some reason) Created new redux slice called settingsSlice.ts. It is used to store settings that are saved to disk automatically
This commit is contained in:
parent
5e5f19053f
commit
6fd206d6fb
488
package-lock.json
generated
488
package-lock.json
generated
@ -11,6 +11,7 @@
|
|||||||
"@emotion/react": "^11.10.6",
|
"@emotion/react": "^11.10.6",
|
||||||
"@emotion/styled": "^11.10.6",
|
"@emotion/styled": "^11.10.6",
|
||||||
"@mui/icons-material": "^5.11.11",
|
"@mui/icons-material": "^5.11.11",
|
||||||
|
"@mui/lab": "^5.0.0-alpha.163",
|
||||||
"@mui/material": "^5.11.13",
|
"@mui/material": "^5.11.13",
|
||||||
"@reduxjs/toolkit": "^1.9.3",
|
"@reduxjs/toolkit": "^1.9.3",
|
||||||
"compressorjs": "^1.2.1",
|
"compressorjs": "^1.2.1",
|
||||||
@ -27,6 +28,7 @@
|
|||||||
"react-rnd": "^10.4.1",
|
"react-rnd": "^10.4.1",
|
||||||
"react-router-dom": "^6.9.0",
|
"react-router-dom": "^6.9.0",
|
||||||
"react-toastify": "^9.1.2",
|
"react-toastify": "^9.1.2",
|
||||||
|
"redux-persist": "^6.0.0",
|
||||||
"short-unique-id": "^4.4.4",
|
"short-unique-id": "^4.4.4",
|
||||||
"ts-key-enum": "^2.0.12"
|
"ts-key-enum": "^2.0.12"
|
||||||
},
|
},
|
||||||
@ -355,11 +357,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/runtime": {
|
"node_modules/@babel/runtime": {
|
||||||
"version": "7.22.5",
|
"version": "7.23.9",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz",
|
||||||
"integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==",
|
"integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"regenerator-runtime": "^0.13.11"
|
"regenerator-runtime": "^0.14.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@ -985,6 +987,40 @@
|
|||||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@floating-ui/core": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/utils": "^0.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/dom": {
|
||||||
|
"version": "1.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.1.tgz",
|
||||||
|
"integrity": "sha512-iA8qE43/H5iGozC3W0YSnVSW42Vh522yyM1gj+BqRwVsTNOyr231PsXDaV04yT39PsO0QL2QpbI/M0ZaLUQgRQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/core": "^1.6.0",
|
||||||
|
"@floating-ui/utils": "^0.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/react-dom": {
|
||||||
|
"version": "2.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz",
|
||||||
|
"integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/dom": "^1.6.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0",
|
||||||
|
"react-dom": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/utils": {
|
||||||
|
"version": "0.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz",
|
||||||
|
"integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q=="
|
||||||
|
},
|
||||||
"node_modules/@humanwhocodes/config-array": {
|
"node_modules/@humanwhocodes/config-array": {
|
||||||
"version": "0.11.10",
|
"version": "0.11.10",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
|
||||||
@ -1067,25 +1103,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/base": {
|
"node_modules/@mui/base": {
|
||||||
"version": "5.0.0-beta.4",
|
"version": "5.0.0-beta.34",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.4.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.34.tgz",
|
||||||
"integrity": "sha512-ejhtqYJpjDgHGEljjMBQWZ22yEK0OzIXNa7toJmmXsP4TT3W7xVy8bTJ0TniPDf+JNjrsgfgiFTDGdlEhV1E+g==",
|
"integrity": "sha512-e2mbTGTtReD/y5RFwnhkl1Tgl3XwgJhY040IlfkTVaU9f5LWrVhEnpRsYXu3B1CtLrwiWs4cu7aMHV9yRd4jpw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.21.0",
|
"@babel/runtime": "^7.23.9",
|
||||||
"@emotion/is-prop-valid": "^1.2.1",
|
"@floating-ui/react-dom": "^2.0.8",
|
||||||
"@mui/types": "^7.2.4",
|
"@mui/types": "^7.2.13",
|
||||||
"@mui/utils": "^5.13.1",
|
"@mui/utils": "^5.15.7",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^2.1.0",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1"
|
||||||
"react-is": "^18.2.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0"
|
"node": ">=12.0.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/mui"
|
"url": "https://opencollective.com/mui-org"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^17.0.0 || ^18.0.0",
|
"@types/react": "^17.0.0 || ^18.0.0",
|
||||||
@ -1098,13 +1133,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@mui/base/node_modules/clsx": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@mui/core-downloads-tracker": {
|
"node_modules/@mui/core-downloads-tracker": {
|
||||||
"version": "5.13.4",
|
"version": "5.15.7",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.13.4.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.7.tgz",
|
||||||
"integrity": "sha512-yFrMWcrlI0TqRN5jpb6Ma9iI7sGTHpytdzzL33oskFHNQ8UgrtPas33Y1K7sWAMwCrr1qbWDrOHLAQG4tAzuSw==",
|
"integrity": "sha512-AuF+Wo2Mp/edaO6vJnWjg+gj4tzEz5ChMZnAQpc22DXpSvM8ddgGcZvM7D7F99pIBoSv8ub+Iz0viL+yuGVmhg==",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/mui"
|
"url": "https://opencollective.com/mui-org"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/icons-material": {
|
"node_modules/@mui/icons-material": {
|
||||||
@ -1132,19 +1175,67 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/material": {
|
"node_modules/@mui/lab": {
|
||||||
"version": "5.13.5",
|
"version": "5.0.0-alpha.163",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.13.5.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.163.tgz",
|
||||||
"integrity": "sha512-eMay+Ue1OYXOFMQA5Aau7qbAa/kWHLAyi0McsbPTWssCbGehqkF6CIdPsfVGw6tlO+xPee1hUitphHJNL3xpOQ==",
|
"integrity": "sha512-ieOX3LFBln78jgNsBca0JUX+zAC2p6/u2P9b7rU9eZIr0AK44b5Qr8gDOWI1JfJtib4kxLGd1Msasrbxy5cMSQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.21.0",
|
"@babel/runtime": "^7.23.9",
|
||||||
"@mui/base": "5.0.0-beta.4",
|
"@mui/base": "5.0.0-beta.34",
|
||||||
"@mui/core-downloads-tracker": "^5.13.4",
|
"@mui/system": "^5.15.7",
|
||||||
"@mui/system": "^5.13.5",
|
"@mui/types": "^7.2.13",
|
||||||
"@mui/types": "^7.2.4",
|
"@mui/utils": "^5.15.7",
|
||||||
"@mui/utils": "^5.13.1",
|
"clsx": "^2.1.0",
|
||||||
"@types/react-transition-group": "^4.4.6",
|
"prop-types": "^15.8.1"
|
||||||
"clsx": "^1.2.1",
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui-org"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/react": "^11.5.0",
|
||||||
|
"@emotion/styled": "^11.3.0",
|
||||||
|
"@mui/material": ">=5.15.0",
|
||||||
|
"@types/react": "^17.0.0 || ^18.0.0",
|
||||||
|
"react": "^17.0.0 || ^18.0.0",
|
||||||
|
"react-dom": "^17.0.0 || ^18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@emotion/styled": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mui/lab/node_modules/clsx": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mui/material": {
|
||||||
|
"version": "5.15.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.7.tgz",
|
||||||
|
"integrity": "sha512-l6+AiKZH3iOJmZCnlpel8ghYQe9Lq0BEuKP8fGj3g5xz4arO9GydqYAtLPMvuHKtArj8lJGNuT2yHYxmejincA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.23.9",
|
||||||
|
"@mui/base": "5.0.0-beta.34",
|
||||||
|
"@mui/core-downloads-tracker": "^5.15.7",
|
||||||
|
"@mui/system": "^5.15.7",
|
||||||
|
"@mui/types": "^7.2.13",
|
||||||
|
"@mui/utils": "^5.15.7",
|
||||||
|
"@types/react-transition-group": "^4.4.10",
|
||||||
|
"clsx": "^2.1.0",
|
||||||
"csstype": "^3.1.2",
|
"csstype": "^3.1.2",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react-is": "^18.2.0",
|
"react-is": "^18.2.0",
|
||||||
@ -1155,7 +1246,7 @@
|
|||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/mui"
|
"url": "https://opencollective.com/mui-org"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@emotion/react": "^11.5.0",
|
"@emotion/react": "^11.5.0",
|
||||||
@ -1176,13 +1267,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@mui/material/node_modules/clsx": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@mui/private-theming": {
|
"node_modules/@mui/private-theming": {
|
||||||
"version": "5.13.1",
|
"version": "5.15.7",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.7.tgz",
|
||||||
"integrity": "sha512-HW4npLUD9BAkVppOUZHeO1FOKUJWAwbpy0VQoGe3McUYTlck1HezGHQCfBQ5S/Nszi7EViqiimECVl9xi+/WjQ==",
|
"integrity": "sha512-bcEeeXm7GyQCQvN9dwo8htGv8/6tP05p0i02Z7GXm5EoDPlBcqTNGugsjNLoGq6B0SsdyanjJGw0Jw00o1yAOA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.21.0",
|
"@babel/runtime": "^7.23.9",
|
||||||
"@mui/utils": "^5.13.1",
|
"@mui/utils": "^5.15.7",
|
||||||
"prop-types": "^15.8.1"
|
"prop-types": "^15.8.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -1190,7 +1289,7 @@
|
|||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/mui"
|
"url": "https://opencollective.com/mui-org"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^17.0.0 || ^18.0.0",
|
"@types/react": "^17.0.0 || ^18.0.0",
|
||||||
@ -1203,11 +1302,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/styled-engine": {
|
"node_modules/@mui/styled-engine": {
|
||||||
"version": "5.13.2",
|
"version": "5.15.7",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.13.2.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.7.tgz",
|
||||||
"integrity": "sha512-VCYCU6xVtXOrIN8lcbuPmoG+u7FYuOERG++fpY74hPpEWkyFQG97F+/XfTQVYzlR2m7nPjnwVUgATcTCMEaMvw==",
|
"integrity": "sha512-ixSdslOjK1kzdGcxqj7O3d14By/LPQ7EWknsViQ8RaeT863EAQemS+zvUJDTcOpkfJh6q6gPnYMIb2TJCs9eWA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.21.0",
|
"@babel/runtime": "^7.23.9",
|
||||||
"@emotion/cache": "^11.11.0",
|
"@emotion/cache": "^11.11.0",
|
||||||
"csstype": "^3.1.2",
|
"csstype": "^3.1.2",
|
||||||
"prop-types": "^15.8.1"
|
"prop-types": "^15.8.1"
|
||||||
@ -1217,7 +1316,7 @@
|
|||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/mui"
|
"url": "https://opencollective.com/mui-org"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@emotion/react": "^11.4.1",
|
"@emotion/react": "^11.4.1",
|
||||||
@ -1234,16 +1333,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/system": {
|
"node_modules/@mui/system": {
|
||||||
"version": "5.13.5",
|
"version": "5.15.7",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.13.5.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.7.tgz",
|
||||||
"integrity": "sha512-n0gzUxoZ2ZHZgnExkh2Htvo9uW2oakofgPRQrDoa/GQOWyRD0NH9MDszBwOb6AAoXZb+OV5TE7I4LeZ/dzgHYA==",
|
"integrity": "sha512-9alZ4/dLxsTwUOdqakgzxiL5YW6ntqj0CfzWImgWnBMTZhgGcPsbYpBLniNkkk7/jptma4/bykWXHwju/ls/pg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.21.0",
|
"@babel/runtime": "^7.23.9",
|
||||||
"@mui/private-theming": "^5.13.1",
|
"@mui/private-theming": "^5.15.7",
|
||||||
"@mui/styled-engine": "^5.13.2",
|
"@mui/styled-engine": "^5.15.7",
|
||||||
"@mui/types": "^7.2.4",
|
"@mui/types": "^7.2.13",
|
||||||
"@mui/utils": "^5.13.1",
|
"@mui/utils": "^5.15.7",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^2.1.0",
|
||||||
"csstype": "^3.1.2",
|
"csstype": "^3.1.2",
|
||||||
"prop-types": "^15.8.1"
|
"prop-types": "^15.8.1"
|
||||||
},
|
},
|
||||||
@ -1252,7 +1351,7 @@
|
|||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/mui"
|
"url": "https://opencollective.com/mui-org"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@emotion/react": "^11.5.0",
|
"@emotion/react": "^11.5.0",
|
||||||
@ -1272,12 +1371,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@mui/system/node_modules/clsx": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@mui/types": {
|
"node_modules/@mui/types": {
|
||||||
"version": "7.2.4",
|
"version": "7.2.13",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.13.tgz",
|
||||||
"integrity": "sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==",
|
"integrity": "sha512-qP9OgacN62s+l8rdDhSFRe05HWtLLJ5TGclC9I1+tQngbssu0m2dmFZs+Px53AcOs9fD7TbYd4gc9AXzVqO/+g==",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "*"
|
"@types/react": "^17.0.0 || ^18.0.0"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"@types/react": {
|
"@types/react": {
|
||||||
@ -1286,13 +1393,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/utils": {
|
"node_modules/@mui/utils": {
|
||||||
"version": "5.13.1",
|
"version": "5.15.7",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.7.tgz",
|
||||||
"integrity": "sha512-6lXdWwmlUbEU2jUI8blw38Kt+3ly7xkmV9ljzY4Q20WhsJMWiNry9CX8M+TaP/HbtuyR8XKsdMgQW7h7MM3n3A==",
|
"integrity": "sha512-8qhsxQRNV6aEOjjSk6YQIYJxkF5klhj8oG1FEEU4z6HV78TjNqRxMP08QGcdsibEbez+nihAaz6vu83b4XqbAg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.21.0",
|
"@babel/runtime": "^7.23.9",
|
||||||
"@types/prop-types": "^15.7.5",
|
"@types/prop-types": "^15.7.11",
|
||||||
"@types/react-is": "^18.2.0",
|
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react-is": "^18.2.0"
|
"react-is": "^18.2.0"
|
||||||
},
|
},
|
||||||
@ -1301,10 +1407,16 @@
|
|||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/mui"
|
"url": "https://opencollective.com/mui-org"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
"@types/react": "^17.0.0 || ^18.0.0",
|
||||||
"react": "^17.0.0 || ^18.0.0"
|
"react": "^17.0.0 || ^18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
@ -1619,9 +1731,9 @@
|
|||||||
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
|
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
|
||||||
},
|
},
|
||||||
"node_modules/@types/prop-types": {
|
"node_modules/@types/prop-types": {
|
||||||
"version": "15.7.5",
|
"version": "15.7.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
|
||||||
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
|
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng=="
|
||||||
},
|
},
|
||||||
"node_modules/@types/quill": {
|
"node_modules/@types/quill": {
|
||||||
"version": "1.3.10",
|
"version": "1.3.10",
|
||||||
@ -1650,18 +1762,10 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/react-is": {
|
|
||||||
"version": "18.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-18.2.1.tgz",
|
|
||||||
"integrity": "sha512-wyUkmaaSZEzFZivD8F2ftSyAfk6L+DfFliVj/mYdOXbVjRcS87fQJLTnhk6dRZPuJjI+9g6RZJO4PNCngUrmyw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/react": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/react-transition-group": {
|
"node_modules/@types/react-transition-group": {
|
||||||
"version": "4.4.6",
|
"version": "4.4.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz",
|
||||||
"integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==",
|
"integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
@ -3988,6 +4092,14 @@
|
|||||||
"@babel/runtime": "^7.9.2"
|
"@babel/runtime": "^7.9.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/redux-persist": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"redux": ">4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/redux-thunk": {
|
"node_modules/redux-thunk": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
|
||||||
@ -3997,9 +4109,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/regenerator-runtime": {
|
"node_modules/regenerator-runtime": {
|
||||||
"version": "0.13.11",
|
"version": "0.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
||||||
},
|
},
|
||||||
"node_modules/regexp.prototype.flags": {
|
"node_modules/regexp.prototype.flags": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
@ -4782,11 +4894,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/runtime": {
|
"@babel/runtime": {
|
||||||
"version": "7.22.5",
|
"version": "7.23.9",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz",
|
||||||
"integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==",
|
"integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"regenerator-runtime": "^0.13.11"
|
"regenerator-runtime": "^0.14.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/template": {
|
"@babel/template": {
|
||||||
@ -5152,6 +5264,36 @@
|
|||||||
"integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==",
|
"integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@floating-ui/core": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==",
|
||||||
|
"requires": {
|
||||||
|
"@floating-ui/utils": "^0.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@floating-ui/dom": {
|
||||||
|
"version": "1.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.1.tgz",
|
||||||
|
"integrity": "sha512-iA8qE43/H5iGozC3W0YSnVSW42Vh522yyM1gj+BqRwVsTNOyr231PsXDaV04yT39PsO0QL2QpbI/M0ZaLUQgRQ==",
|
||||||
|
"requires": {
|
||||||
|
"@floating-ui/core": "^1.6.0",
|
||||||
|
"@floating-ui/utils": "^0.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@floating-ui/react-dom": {
|
||||||
|
"version": "2.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz",
|
||||||
|
"integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==",
|
||||||
|
"requires": {
|
||||||
|
"@floating-ui/dom": "^1.6.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@floating-ui/utils": {
|
||||||
|
"version": "0.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz",
|
||||||
|
"integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q=="
|
||||||
|
},
|
||||||
"@humanwhocodes/config-array": {
|
"@humanwhocodes/config-array": {
|
||||||
"version": "0.11.10",
|
"version": "0.11.10",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
|
||||||
@ -5215,24 +5357,30 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@mui/base": {
|
"@mui/base": {
|
||||||
"version": "5.0.0-beta.4",
|
"version": "5.0.0-beta.34",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.4.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.34.tgz",
|
||||||
"integrity": "sha512-ejhtqYJpjDgHGEljjMBQWZ22yEK0OzIXNa7toJmmXsP4TT3W7xVy8bTJ0TniPDf+JNjrsgfgiFTDGdlEhV1E+g==",
|
"integrity": "sha512-e2mbTGTtReD/y5RFwnhkl1Tgl3XwgJhY040IlfkTVaU9f5LWrVhEnpRsYXu3B1CtLrwiWs4cu7aMHV9yRd4jpw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.21.0",
|
"@babel/runtime": "^7.23.9",
|
||||||
"@emotion/is-prop-valid": "^1.2.1",
|
"@floating-ui/react-dom": "^2.0.8",
|
||||||
"@mui/types": "^7.2.4",
|
"@mui/types": "^7.2.13",
|
||||||
"@mui/utils": "^5.13.1",
|
"@mui/utils": "^5.15.7",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^2.1.0",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1"
|
||||||
"react-is": "^18.2.0"
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"clsx": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg=="
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@mui/core-downloads-tracker": {
|
"@mui/core-downloads-tracker": {
|
||||||
"version": "5.13.4",
|
"version": "5.15.7",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.13.4.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.7.tgz",
|
||||||
"integrity": "sha512-yFrMWcrlI0TqRN5jpb6Ma9iI7sGTHpytdzzL33oskFHNQ8UgrtPas33Y1K7sWAMwCrr1qbWDrOHLAQG4tAzuSw=="
|
"integrity": "sha512-AuF+Wo2Mp/edaO6vJnWjg+gj4tzEz5ChMZnAQpc22DXpSvM8ddgGcZvM7D7F99pIBoSv8ub+Iz0viL+yuGVmhg=="
|
||||||
},
|
},
|
||||||
"@mui/icons-material": {
|
"@mui/icons-material": {
|
||||||
"version": "5.11.16",
|
"version": "5.11.16",
|
||||||
@ -5242,75 +5390,109 @@
|
|||||||
"@babel/runtime": "^7.21.0"
|
"@babel/runtime": "^7.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@mui/material": {
|
"@mui/lab": {
|
||||||
"version": "5.13.5",
|
"version": "5.0.0-alpha.163",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.13.5.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.163.tgz",
|
||||||
"integrity": "sha512-eMay+Ue1OYXOFMQA5Aau7qbAa/kWHLAyi0McsbPTWssCbGehqkF6CIdPsfVGw6tlO+xPee1hUitphHJNL3xpOQ==",
|
"integrity": "sha512-ieOX3LFBln78jgNsBca0JUX+zAC2p6/u2P9b7rU9eZIr0AK44b5Qr8gDOWI1JfJtib4kxLGd1Msasrbxy5cMSQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.21.0",
|
"@babel/runtime": "^7.23.9",
|
||||||
"@mui/base": "5.0.0-beta.4",
|
"@mui/base": "5.0.0-beta.34",
|
||||||
"@mui/core-downloads-tracker": "^5.13.4",
|
"@mui/system": "^5.15.7",
|
||||||
"@mui/system": "^5.13.5",
|
"@mui/types": "^7.2.13",
|
||||||
"@mui/types": "^7.2.4",
|
"@mui/utils": "^5.15.7",
|
||||||
"@mui/utils": "^5.13.1",
|
"clsx": "^2.1.0",
|
||||||
"@types/react-transition-group": "^4.4.6",
|
"prop-types": "^15.8.1"
|
||||||
"clsx": "^1.2.1",
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"clsx": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@mui/material": {
|
||||||
|
"version": "5.15.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.7.tgz",
|
||||||
|
"integrity": "sha512-l6+AiKZH3iOJmZCnlpel8ghYQe9Lq0BEuKP8fGj3g5xz4arO9GydqYAtLPMvuHKtArj8lJGNuT2yHYxmejincA==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.23.9",
|
||||||
|
"@mui/base": "5.0.0-beta.34",
|
||||||
|
"@mui/core-downloads-tracker": "^5.15.7",
|
||||||
|
"@mui/system": "^5.15.7",
|
||||||
|
"@mui/types": "^7.2.13",
|
||||||
|
"@mui/utils": "^5.15.7",
|
||||||
|
"@types/react-transition-group": "^4.4.10",
|
||||||
|
"clsx": "^2.1.0",
|
||||||
"csstype": "^3.1.2",
|
"csstype": "^3.1.2",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react-is": "^18.2.0",
|
"react-is": "^18.2.0",
|
||||||
"react-transition-group": "^4.4.5"
|
"react-transition-group": "^4.4.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"clsx": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg=="
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@mui/private-theming": {
|
"@mui/private-theming": {
|
||||||
"version": "5.13.1",
|
"version": "5.15.7",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.7.tgz",
|
||||||
"integrity": "sha512-HW4npLUD9BAkVppOUZHeO1FOKUJWAwbpy0VQoGe3McUYTlck1HezGHQCfBQ5S/Nszi7EViqiimECVl9xi+/WjQ==",
|
"integrity": "sha512-bcEeeXm7GyQCQvN9dwo8htGv8/6tP05p0i02Z7GXm5EoDPlBcqTNGugsjNLoGq6B0SsdyanjJGw0Jw00o1yAOA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.21.0",
|
"@babel/runtime": "^7.23.9",
|
||||||
"@mui/utils": "^5.13.1",
|
"@mui/utils": "^5.15.7",
|
||||||
"prop-types": "^15.8.1"
|
"prop-types": "^15.8.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@mui/styled-engine": {
|
"@mui/styled-engine": {
|
||||||
"version": "5.13.2",
|
"version": "5.15.7",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.13.2.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.7.tgz",
|
||||||
"integrity": "sha512-VCYCU6xVtXOrIN8lcbuPmoG+u7FYuOERG++fpY74hPpEWkyFQG97F+/XfTQVYzlR2m7nPjnwVUgATcTCMEaMvw==",
|
"integrity": "sha512-ixSdslOjK1kzdGcxqj7O3d14By/LPQ7EWknsViQ8RaeT863EAQemS+zvUJDTcOpkfJh6q6gPnYMIb2TJCs9eWA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.21.0",
|
"@babel/runtime": "^7.23.9",
|
||||||
"@emotion/cache": "^11.11.0",
|
"@emotion/cache": "^11.11.0",
|
||||||
"csstype": "^3.1.2",
|
"csstype": "^3.1.2",
|
||||||
"prop-types": "^15.8.1"
|
"prop-types": "^15.8.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@mui/system": {
|
"@mui/system": {
|
||||||
"version": "5.13.5",
|
"version": "5.15.7",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.13.5.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.7.tgz",
|
||||||
"integrity": "sha512-n0gzUxoZ2ZHZgnExkh2Htvo9uW2oakofgPRQrDoa/GQOWyRD0NH9MDszBwOb6AAoXZb+OV5TE7I4LeZ/dzgHYA==",
|
"integrity": "sha512-9alZ4/dLxsTwUOdqakgzxiL5YW6ntqj0CfzWImgWnBMTZhgGcPsbYpBLniNkkk7/jptma4/bykWXHwju/ls/pg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.21.0",
|
"@babel/runtime": "^7.23.9",
|
||||||
"@mui/private-theming": "^5.13.1",
|
"@mui/private-theming": "^5.15.7",
|
||||||
"@mui/styled-engine": "^5.13.2",
|
"@mui/styled-engine": "^5.15.7",
|
||||||
"@mui/types": "^7.2.4",
|
"@mui/types": "^7.2.13",
|
||||||
"@mui/utils": "^5.13.1",
|
"@mui/utils": "^5.15.7",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^2.1.0",
|
||||||
"csstype": "^3.1.2",
|
"csstype": "^3.1.2",
|
||||||
"prop-types": "^15.8.1"
|
"prop-types": "^15.8.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"clsx": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg=="
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@mui/types": {
|
"@mui/types": {
|
||||||
"version": "7.2.4",
|
"version": "7.2.13",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.13.tgz",
|
||||||
"integrity": "sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==",
|
"integrity": "sha512-qP9OgacN62s+l8rdDhSFRe05HWtLLJ5TGclC9I1+tQngbssu0m2dmFZs+Px53AcOs9fD7TbYd4gc9AXzVqO/+g==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"@mui/utils": {
|
"@mui/utils": {
|
||||||
"version": "5.13.1",
|
"version": "5.15.7",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.7.tgz",
|
||||||
"integrity": "sha512-6lXdWwmlUbEU2jUI8blw38Kt+3ly7xkmV9ljzY4Q20WhsJMWiNry9CX8M+TaP/HbtuyR8XKsdMgQW7h7MM3n3A==",
|
"integrity": "sha512-8qhsxQRNV6aEOjjSk6YQIYJxkF5klhj8oG1FEEU4z6HV78TjNqRxMP08QGcdsibEbez+nihAaz6vu83b4XqbAg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.21.0",
|
"@babel/runtime": "^7.23.9",
|
||||||
"@types/prop-types": "^15.7.5",
|
"@types/prop-types": "^15.7.11",
|
||||||
"@types/react-is": "^18.2.0",
|
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react-is": "^18.2.0"
|
"react-is": "^18.2.0"
|
||||||
}
|
}
|
||||||
@ -5521,9 +5703,9 @@
|
|||||||
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
|
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
|
||||||
},
|
},
|
||||||
"@types/prop-types": {
|
"@types/prop-types": {
|
||||||
"version": "15.7.5",
|
"version": "15.7.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
|
||||||
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
|
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng=="
|
||||||
},
|
},
|
||||||
"@types/quill": {
|
"@types/quill": {
|
||||||
"version": "1.3.10",
|
"version": "1.3.10",
|
||||||
@ -5552,18 +5734,10 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/react-is": {
|
|
||||||
"version": "18.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-18.2.1.tgz",
|
|
||||||
"integrity": "sha512-wyUkmaaSZEzFZivD8F2ftSyAfk6L+DfFliVj/mYdOXbVjRcS87fQJLTnhk6dRZPuJjI+9g6RZJO4PNCngUrmyw==",
|
|
||||||
"requires": {
|
|
||||||
"@types/react": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@types/react-transition-group": {
|
"@types/react-transition-group": {
|
||||||
"version": "4.4.6",
|
"version": "4.4.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz",
|
||||||
"integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==",
|
"integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
@ -7206,6 +7380,12 @@
|
|||||||
"@babel/runtime": "^7.9.2"
|
"@babel/runtime": "^7.9.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"redux-persist": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"redux-thunk": {
|
"redux-thunk": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
|
||||||
@ -7213,9 +7393,9 @@
|
|||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"regenerator-runtime": {
|
"regenerator-runtime": {
|
||||||
"version": "0.13.11",
|
"version": "0.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
||||||
},
|
},
|
||||||
"regexp.prototype.flags": {
|
"regexp.prototype.flags": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"@emotion/react": "^11.10.6",
|
"@emotion/react": "^11.10.6",
|
||||||
"@emotion/styled": "^11.10.6",
|
"@emotion/styled": "^11.10.6",
|
||||||
"@mui/icons-material": "^5.11.11",
|
"@mui/icons-material": "^5.11.11",
|
||||||
|
"@mui/lab": "^5.0.0-alpha.163",
|
||||||
"@mui/material": "^5.11.13",
|
"@mui/material": "^5.11.13",
|
||||||
"@reduxjs/toolkit": "^1.9.3",
|
"@reduxjs/toolkit": "^1.9.3",
|
||||||
"compressorjs": "^1.2.1",
|
"compressorjs": "^1.2.1",
|
||||||
@ -29,6 +30,7 @@
|
|||||||
"react-rnd": "^10.4.1",
|
"react-rnd": "^10.4.1",
|
||||||
"react-router-dom": "^6.9.0",
|
"react-router-dom": "^6.9.0",
|
||||||
"react-toastify": "^9.1.2",
|
"react-toastify": "^9.1.2",
|
||||||
|
"redux-persist": "^6.0.0",
|
||||||
"short-unique-id": "^4.4.4",
|
"short-unique-id": "^4.4.4",
|
||||||
"ts-key-enum": "^2.0.12"
|
"ts-key-enum": "^2.0.12"
|
||||||
},
|
},
|
||||||
|
36
src/App.tsx
36
src/App.tsx
@ -12,28 +12,36 @@ import { VideoContent } from "./pages/VideoContent/VideoContent";
|
|||||||
import DownloadWrapper from "./wrappers/DownloadWrapper";
|
import DownloadWrapper from "./wrappers/DownloadWrapper";
|
||||||
import { IndividualProfile } from "./pages/IndividualProfile/IndividualProfile";
|
import { IndividualProfile } from "./pages/IndividualProfile/IndividualProfile";
|
||||||
import { PlaylistContent } from "./pages/PlaylistContent/PlaylistContent";
|
import { PlaylistContent } from "./pages/PlaylistContent/PlaylistContent";
|
||||||
|
import { PersistGate } from "redux-persist/integration/react";
|
||||||
|
import { persistStore } from "redux-persist";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
// const themeColor = window._qdnTheme
|
// const themeColor = window._qdnTheme
|
||||||
|
|
||||||
const [theme, setTheme] = useState("dark");
|
const [theme, setTheme] = useState("dark");
|
||||||
|
let persistor = persistStore(store);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ThemeProvider theme={theme === "light" ? lightTheme : darkTheme}>
|
<PersistGate loading={null} persistor={persistor}>
|
||||||
<Notification />
|
<ThemeProvider theme={theme === "light" ? lightTheme : darkTheme}>
|
||||||
<DownloadWrapper>
|
<Notification />
|
||||||
<GlobalWrapper setTheme={(val: string) => setTheme(val)}>
|
<DownloadWrapper>
|
||||||
<CssBaseline />
|
<GlobalWrapper setTheme={(val: string) => setTheme(val)}>
|
||||||
<Routes>
|
<CssBaseline />
|
||||||
<Route path="/" element={<Home />} />
|
<Routes>
|
||||||
<Route path="/video/:name/:id" element={<VideoContent />} />
|
<Route path="/" element={<Home />} />
|
||||||
<Route path="/playlist/:name/:id" element={<PlaylistContent />} />
|
<Route path="/video/:name/:id" element={<VideoContent />} />
|
||||||
<Route path="/channel/:name" element={<IndividualProfile />} />
|
<Route
|
||||||
</Routes>
|
path="/playlist/:name/:id"
|
||||||
</GlobalWrapper>
|
element={<PlaylistContent />}
|
||||||
</DownloadWrapper>
|
/>
|
||||||
</ThemeProvider>
|
<Route path="/channel/:name" element={<IndividualProfile />} />
|
||||||
|
</Routes>
|
||||||
|
</GlobalWrapper>
|
||||||
|
</DownloadWrapper>
|
||||||
|
</ThemeProvider>
|
||||||
|
</PersistGate>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,39 @@
|
|||||||
import { Button, ButtonProps } from "@mui/material";
|
import { Button, ButtonProps } from "@mui/material";
|
||||||
import { MouseEvent } from "react";
|
import { MouseEvent, useEffect, useState } from "react";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { RootState } from "../../state/store.ts";
|
||||||
|
import { subscribe, unSubscribe } from "../../state/features/videoSlice.ts";
|
||||||
|
|
||||||
interface SubscribeButtonProps extends ButtonProps {
|
interface SubscribeButtonProps extends ButtonProps {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSubscribed = false;
|
|
||||||
export const SubscribeButton = ({ name, ...props }: SubscribeButtonProps) => {
|
export const SubscribeButton = ({ name, ...props }: SubscribeButtonProps) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const select = useSelector((state: RootState) => {
|
||||||
|
return state.video;
|
||||||
|
});
|
||||||
|
const [isSubscribed, setIsSubscribed] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsSubscribed(select.subscriptionList.includes(name));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const subscribeToRedux = () => {
|
||||||
|
dispatch(subscribe(name));
|
||||||
|
setIsSubscribed(true);
|
||||||
|
};
|
||||||
|
const unSubscribeFromRedux = () => {
|
||||||
|
dispatch(unSubscribe(name));
|
||||||
|
setIsSubscribed(false);
|
||||||
|
};
|
||||||
|
|
||||||
const manageSubscription = (e: MouseEvent<HTMLButtonElement>) => {
|
const manageSubscription = (e: MouseEvent<HTMLButtonElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
console.log("subscribed to: ", name);
|
isSubscribed ? unSubscribeFromRedux() : subscribeToRedux();
|
||||||
};
|
};
|
||||||
|
|
||||||
const verticalPadding = "3px";
|
const verticalPadding = "3px";
|
||||||
const horizontalPadding = "8px";
|
const horizontalPadding = "8px";
|
||||||
const buttonStyle = {
|
const buttonStyle = {
|
||||||
@ -23,7 +45,7 @@ export const SubscribeButton = ({ name, ...props }: SubscribeButtonProps) => {
|
|||||||
paddingLeft: horizontalPadding,
|
paddingLeft: horizontalPadding,
|
||||||
paddingRight: horizontalPadding,
|
paddingRight: horizontalPadding,
|
||||||
borderRadius: 28,
|
borderRadius: 28,
|
||||||
display: "none",
|
height: "40px",
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
32
src/components/common/VideoPlayer/VideoPlayer-styles.ts
Normal file
32
src/components/common/VideoPlayer/VideoPlayer-styles.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { styled } from "@mui/system";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
|
||||||
|
export const VideoContainer = styled(Box)`
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
max-height: 70vh;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const VideoElement = styled("video")`
|
||||||
|
width: 100%;
|
||||||
|
background: rgb(33, 33, 33);
|
||||||
|
max-height: 70vh;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ControlsContainer = styled(Box)`
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
`;
|
@ -12,44 +12,20 @@ import {
|
|||||||
VolumeOff,
|
VolumeOff,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import { styled } from "@mui/system";
|
import { styled } from "@mui/system";
|
||||||
import { MyContext } from "../../wrappers/DownloadWrapper";
|
import { MyContext } from "../../../wrappers/DownloadWrapper.tsx";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { RootState } from "../../state/store";
|
import { RootState } from "../../../state/store.ts";
|
||||||
import { Refresh } from "@mui/icons-material";
|
import { Refresh } from "@mui/icons-material";
|
||||||
|
|
||||||
import { Menu, MenuItem } from "@mui/material";
|
import { Menu, MenuItem } from "@mui/material";
|
||||||
import { MoreVert as MoreIcon } from "@mui/icons-material";
|
import { MoreVert as MoreIcon } from "@mui/icons-material";
|
||||||
import { setVideoPlaying } from "../../state/features/globalSlice";
|
import { setVideoPlaying } from "../../../state/features/globalSlice.ts";
|
||||||
const VideoContainer = styled(Box)`
|
import {
|
||||||
position: relative;
|
ControlsContainer,
|
||||||
display: flex;
|
VideoContainer,
|
||||||
flex-direction: column;
|
VideoElement,
|
||||||
align-items: center;
|
} from "./VideoPlayer-styles.ts";
|
||||||
justify-content: center;
|
import CSS from "csstype";
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
margin: 0px;
|
|
||||||
padding: 0px;
|
|
||||||
max-height: 70vh;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const VideoElement = styled("video")`
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
background: rgb(33, 33, 33);
|
|
||||||
max-height: 70vh;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ControlsContainer = styled(Box)`
|
|
||||||
position: absolute;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.6);
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface VideoPlayerProps {
|
interface VideoPlayerProps {
|
||||||
src?: string;
|
src?: string;
|
||||||
@ -65,6 +41,7 @@ interface VideoPlayerProps {
|
|||||||
nextVideo?: any;
|
nextVideo?: any;
|
||||||
onEnd?: () => void;
|
onEnd?: () => void;
|
||||||
autoPlay?: boolean;
|
autoPlay?: boolean;
|
||||||
|
style?: CSS.Properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
||||||
@ -80,6 +57,7 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
nextVideo,
|
nextVideo,
|
||||||
onEnd,
|
onEnd,
|
||||||
autoPlay,
|
autoPlay,
|
||||||
|
style = {},
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const videoRef = useRef<HTMLVideoElement | null>(null);
|
const videoRef = useRef<HTMLVideoElement | null>(null);
|
||||||
@ -94,16 +72,21 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
const [isMobileView, setIsMobileView] = useState(false);
|
const [isMobileView, setIsMobileView] = useState(false);
|
||||||
const [playbackRate, setPlaybackRate] = useState(1);
|
const [playbackRate, setPlaybackRate] = useState(1);
|
||||||
const [anchorEl, setAnchorEl] = useState(null);
|
const [anchorEl, setAnchorEl] = useState(null);
|
||||||
|
const [showControlsFullScreen, setShowControlsFullScreen] =
|
||||||
|
useState<boolean>(true);
|
||||||
const videoPlaying = useSelector(
|
const videoPlaying = useSelector(
|
||||||
(state: RootState) => state.global.videoPlaying
|
(state: RootState) => state.global.videoPlaying
|
||||||
);
|
);
|
||||||
|
const settingsState = useSelector((state: RootState) => state.settings);
|
||||||
|
const { downloads } = useSelector((state: RootState) => state.global);
|
||||||
|
|
||||||
const reDownload = useRef<boolean>(false);
|
const reDownload = useRef<boolean>(false);
|
||||||
const reDownloadNextVid = useRef<boolean>(false);
|
const reDownloadNextVid = useRef<boolean>(false);
|
||||||
|
|
||||||
const isFetchingProperties = useRef<boolean>(false);
|
const isFetchingProperties = useRef<boolean>(false);
|
||||||
|
|
||||||
const status = useRef<null | string>(null);
|
const status = useRef<null | string>(null);
|
||||||
const { downloads } = useSelector((state: RootState) => state.global);
|
|
||||||
const download = useMemo(() => {
|
const download = useMemo(() => {
|
||||||
if (!downloads || !identifier) return {};
|
if (!downloads || !identifier) return {};
|
||||||
const findDownload = downloads[identifier];
|
const findDownload = downloads[identifier];
|
||||||
@ -678,6 +661,7 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
onKeyDown={keyboardShortcutsDown}
|
onKeyDown={keyboardShortcutsDown}
|
||||||
style={{
|
style={{
|
||||||
padding: from === "create" ? "8px" : 0,
|
padding: from === "create" ? "8px" : 0,
|
||||||
|
...customStyle,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
@ -781,28 +765,31 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
onEnded={handleEnded}
|
onEnded={handleEnded}
|
||||||
// onLoadedMetadata={handleLoadedMetadata}
|
// onLoadedMetadata={handleLoadedMetadata}
|
||||||
onCanPlay={handleCanPlay}
|
onCanPlay={handleCanPlay}
|
||||||
|
onMouseEnter={e => {
|
||||||
|
setShowControlsFullScreen(true);
|
||||||
|
console.log("entering video, fullscreen is: ", isFullscreen);
|
||||||
|
}}
|
||||||
|
onMouseLeave={e => {
|
||||||
|
setShowControlsFullScreen(false);
|
||||||
|
console.log("leaving video, fullscreen is: ", isFullscreen);
|
||||||
|
}}
|
||||||
preload="metadata"
|
preload="metadata"
|
||||||
style={
|
style={
|
||||||
startPlay
|
startPlay
|
||||||
? {
|
? {
|
||||||
...customStyle,
|
...customStyle,
|
||||||
objectFit: "fill",
|
objectFit: settingsState.stretchVideoSetting,
|
||||||
|
height: "calc(100% - 80px)",
|
||||||
}
|
}
|
||||||
: { ...customStyle }
|
: { ...customStyle, height: "100%" }
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ControlsContainer
|
<ControlsContainer
|
||||||
style={
|
style={{ bottom: from === "create" ? "15px" : 0, padding: "0px" }}
|
||||||
startPlay
|
display={showControlsFullScreen ? "flex" : "none"}
|
||||||
? {
|
|
||||||
bottom: from === "create" ? "15px" : 0,
|
|
||||||
padding: "8px",
|
|
||||||
}
|
|
||||||
: { bottom: from === "create" ? "15px" : 0, padding: "0px" }
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{isMobileView && canPlay ? (
|
{isMobileView && canPlay && showControlsFullScreen ? (
|
||||||
<>
|
<>
|
||||||
<IconButton
|
<IconButton
|
||||||
sx={{
|
sx={{
|
682
src/components/common/VideoPlayer/VideoPlayerGlobal.tsx
Normal file
682
src/components/common/VideoPlayer/VideoPlayerGlobal.tsx
Normal file
@ -0,0 +1,682 @@
|
|||||||
|
import React, { useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
import ReactDOM from "react-dom";
|
||||||
|
import { Box, IconButton, Slider, useTheme } from "@mui/material";
|
||||||
|
import { CircularProgress, Typography } from "@mui/material";
|
||||||
|
import { Key } from "ts-key-enum";
|
||||||
|
import {
|
||||||
|
PlayArrow,
|
||||||
|
Pause,
|
||||||
|
VolumeUp,
|
||||||
|
Fullscreen,
|
||||||
|
PictureInPicture,
|
||||||
|
VolumeOff,
|
||||||
|
} from "@mui/icons-material";
|
||||||
|
import { styled } from "@mui/system";
|
||||||
|
import { MyContext } from "../../../wrappers/DownloadWrapper.tsx";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { RootState } from "../../../state/store.ts";
|
||||||
|
import { Refresh } from "@mui/icons-material";
|
||||||
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
|
|
||||||
|
import { Menu, MenuItem } from "@mui/material";
|
||||||
|
import { MoreVert as MoreIcon } from "@mui/icons-material";
|
||||||
|
import { setVideoPlaying } from "../../../state/features/globalSlice.ts";
|
||||||
|
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;
|
||||||
|
let 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 {
|
||||||
|
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 = (_: any, value: number | number[]) => {
|
||||||
|
if (!videoRef.current) return;
|
||||||
|
videoRef.current.currentTime = value as number;
|
||||||
|
setProgress(value as number);
|
||||||
|
if (!playing) {
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function formatTime(seconds: number): string {
|
||||||
|
seconds = Math.floor(seconds);
|
||||||
|
let minutes: number | string = Math.floor(seconds / 60);
|
||||||
|
let hours: number | string = Math.floor(minutes / 60);
|
||||||
|
|
||||||
|
let remainingSeconds: number | string = seconds % 60;
|
||||||
|
let remainingMinutes: number | string = minutes % 60;
|
||||||
|
|
||||||
|
if (remainingSeconds < 10) {
|
||||||
|
remainingSeconds = "0" + remainingSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remainingMinutes < 10) {
|
||||||
|
remainingMinutes = "0" + remainingMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hours === 0) {
|
||||||
|
hours = "";
|
||||||
|
} else {
|
||||||
|
hours = hours + ":";
|
||||||
|
}
|
||||||
|
|
||||||
|
return hours + remainingMinutes + ":" + remainingSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reloadVideo = () => {
|
||||||
|
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) {
|
||||||
|
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) {
|
||||||
|
let 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,648 +0,0 @@
|
|||||||
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'
|
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
import { Box, IconButton, Slider, useTheme } from '@mui/material'
|
|
||||||
import { CircularProgress, Typography } from '@mui/material'
|
|
||||||
import { Key } from 'ts-key-enum'
|
|
||||||
import {
|
|
||||||
PlayArrow,
|
|
||||||
Pause,
|
|
||||||
VolumeUp,
|
|
||||||
Fullscreen,
|
|
||||||
PictureInPicture, VolumeOff
|
|
||||||
} from '@mui/icons-material'
|
|
||||||
import { styled } from '@mui/system'
|
|
||||||
import { MyContext } from '../../wrappers/DownloadWrapper'
|
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
|
||||||
import { RootState } from '../../state/store'
|
|
||||||
import { Refresh } from '@mui/icons-material'
|
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
|
||||||
|
|
||||||
import { Menu, MenuItem } from '@mui/material'
|
|
||||||
import { MoreVert as MoreIcon } from '@mui/icons-material'
|
|
||||||
import { setVideoPlaying } from '../../state/features/globalSlice'
|
|
||||||
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
|
|
||||||
let 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 {
|
|
||||||
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 = (_: any, value: number | number[]) => {
|
|
||||||
if (!videoRef.current) return
|
|
||||||
videoRef.current.currentTime = value as number
|
|
||||||
setProgress(value as number)
|
|
||||||
if (!playing) {
|
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function formatTime(seconds: number): string {
|
|
||||||
seconds = Math.floor(seconds)
|
|
||||||
let minutes: number | string = Math.floor(seconds / 60)
|
|
||||||
let hours: number | string = Math.floor(minutes / 60)
|
|
||||||
|
|
||||||
let remainingSeconds: number | string = seconds % 60
|
|
||||||
let remainingMinutes: number | string = minutes % 60
|
|
||||||
|
|
||||||
if (remainingSeconds < 10) {
|
|
||||||
remainingSeconds = '0' + remainingSeconds
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remainingMinutes < 10) {
|
|
||||||
remainingMinutes = '0' + remainingMinutes
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hours === 0) {
|
|
||||||
hours = ''
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
hours = hours + ':'
|
|
||||||
}
|
|
||||||
|
|
||||||
return hours + remainingMinutes + ':' + remainingSeconds
|
|
||||||
}
|
|
||||||
|
|
||||||
const reloadVideo = () => {
|
|
||||||
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) {
|
|
||||||
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){
|
|
||||||
let 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,4 +1,6 @@
|
|||||||
export const minPriceSuperlike = 10;
|
export const minPriceSuperlike = 10;
|
||||||
export const titleFormatter = /[^a-zA-Z0-9\s-_!?()&'",.;:|—~@#$%^*+=<>]/g;
|
export const titleFormatter = /[^a-zA-Z0-9\s-_!?()&'",.;:|—~@#$%^*+=<>]/g;
|
||||||
|
export const titleFormatterOnSave = /[^a-zA-Z0-9\s-_!()&',.;—~@#$%^+=]/g;
|
||||||
|
|
||||||
export const titleSaveFormatter = /[^a-zA-Z0-9\s-_!()&',.;—~@#$%^+=]/g;
|
export const allTabValue = "all";
|
||||||
|
export const subscriptionTabValue = "subscriptions";
|
||||||
|
86
src/global.d.ts
vendored
86
src/global.d.ts
vendored
@ -1,55 +1,55 @@
|
|||||||
// src/global.d.ts
|
// src/global.d.ts
|
||||||
interface QortalRequestOptions {
|
interface QortalRequestOptions {
|
||||||
action: string
|
action: string;
|
||||||
name?: string
|
name?: string;
|
||||||
service?: string
|
service?: string;
|
||||||
data64?: string
|
data64?: string;
|
||||||
title?: string
|
title?: string;
|
||||||
description?: string
|
description?: string;
|
||||||
category?: string
|
category?: string;
|
||||||
tags?: string[]
|
tags?: string[];
|
||||||
identifier?: string
|
identifier?: string;
|
||||||
address?: string
|
address?: string;
|
||||||
metaData?: string
|
metaData?: string;
|
||||||
encoding?: string
|
encoding?: string;
|
||||||
includeMetadata?: boolean
|
includeMetadata?: boolean;
|
||||||
limit?: numebr
|
limit?: number;
|
||||||
offset?: number
|
offset?: number;
|
||||||
reverse?: boolean
|
reverse?: boolean;
|
||||||
resources?: any[]
|
resources?: any[];
|
||||||
filename?: string
|
filename?: string;
|
||||||
list_name?: string
|
list_name?: string;
|
||||||
item?: string
|
item?: string;
|
||||||
items?: strings[]
|
items?: strings[];
|
||||||
tag1?: string
|
tag1?: string;
|
||||||
tag2?: string
|
tag2?: string;
|
||||||
tag3?: string
|
tag3?: string;
|
||||||
tag4?: string
|
tag4?: string;
|
||||||
tag5?: string
|
tag5?: string;
|
||||||
coin?: string
|
coin?: string;
|
||||||
destinationAddress?: string
|
destinationAddress?: string;
|
||||||
amount?: number
|
amount?: number;
|
||||||
blob?: Blob
|
blob?: Blob;
|
||||||
mimeType?: string
|
mimeType?: string;
|
||||||
file?: File
|
file?: File;
|
||||||
encryptedData?: string
|
encryptedData?: string;
|
||||||
name?: string
|
mode?: string;
|
||||||
mode?: string
|
query?: string;
|
||||||
query?: string
|
excludeBlocked?: boolean;
|
||||||
excludeBlocked?: boolean
|
exactMatchNames?: boolean;
|
||||||
exactMatchNames?: boolean
|
nameListFilter?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
declare function qortalRequest(options: QortalRequestOptions): Promise<any>
|
declare function qortalRequest(options: QortalRequestOptions): Promise<any>;
|
||||||
declare function qortalRequestWithTimeout(
|
declare function qortalRequestWithTimeout(
|
||||||
options: QortalRequestOptions,
|
options: QortalRequestOptions,
|
||||||
time: number
|
time: number
|
||||||
): Promise<any>
|
): Promise<any>;
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
_qdnBase: any // Replace 'any' with the appropriate type if you know it
|
_qdnBase: any; // Replace 'any' with the appropriate type if you know it
|
||||||
_qdnTheme: string
|
_qdnTheme: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,6 +57,6 @@ declare global {
|
|||||||
interface Window {
|
interface Window {
|
||||||
showSaveFilePicker: (
|
showSaveFilePicker: (
|
||||||
options?: SaveFilePickerOptions
|
options?: SaveFilePickerOptions
|
||||||
) => Promise<FileSystemFileHandle>
|
) => Promise<FileSystemFileHandle>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import {
|
|||||||
QTUBE_PLAYLIST_BASE,
|
QTUBE_PLAYLIST_BASE,
|
||||||
QTUBE_VIDEO_BASE,
|
QTUBE_VIDEO_BASE,
|
||||||
} from "../constants/Identifiers.ts";
|
} from "../constants/Identifiers.ts";
|
||||||
|
import { allTabValue, subscriptionTabValue } from "../constants/Misc.ts";
|
||||||
|
|
||||||
export const useFetchVideos = () => {
|
export const useFetchVideos = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -35,6 +36,10 @@ export const useFetchVideos = () => {
|
|||||||
(state: RootState) => state.video.filteredVideos
|
(state: RootState) => state.video.filteredVideos
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const subscriptions = useSelector(
|
||||||
|
(state: RootState) => state.video.subscriptionList
|
||||||
|
);
|
||||||
|
|
||||||
const checkAndUpdateVideo = React.useCallback(
|
const checkAndUpdateVideo = React.useCallback(
|
||||||
(video: Video) => {
|
(video: Video) => {
|
||||||
const existingVideo = hashMapVideos[video.id + "-" + video.user];
|
const existingVideo = hashMapVideos[video.id + "-" + video.user];
|
||||||
@ -100,27 +105,29 @@ export const useFetchVideos = () => {
|
|||||||
try {
|
try {
|
||||||
dispatch(setIsLoadingGlobal(true));
|
dispatch(setIsLoadingGlobal(true));
|
||||||
|
|
||||||
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QTUBE_VIDEO_BASE}&limit=20&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true`;
|
// const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QTUBE_VIDEO_BASE}&limit=20&includemetadata=false&reverse=true&exc
|
||||||
const response = await fetch(url, {
|
// ludeblocked=true&exactmatchnames=true`;
|
||||||
method: "GET",
|
//
|
||||||
headers: {
|
// const response = await fetch(url, {
|
||||||
"Content-Type": "application/json",
|
// method: "GET",
|
||||||
},
|
// headers: {
|
||||||
});
|
// "Content-Type": "application/json",
|
||||||
const responseData = await response.json();
|
// },
|
||||||
|
// });
|
||||||
|
// const responseData = await response.json();
|
||||||
|
|
||||||
|
const responseData = await qortalRequest({
|
||||||
|
action: "SEARCH_QDN_RESOURCES",
|
||||||
|
mode: "ALL",
|
||||||
|
service: "DOCUMENT",
|
||||||
|
query: "${QTUBE_VIDEO_BASE}",
|
||||||
|
limit: 20,
|
||||||
|
includeMetadata: true,
|
||||||
|
reverse: true,
|
||||||
|
excludeBlocked: true,
|
||||||
|
exactMatchNames: true,
|
||||||
|
});
|
||||||
|
|
||||||
// const responseData = await qortalRequest({
|
|
||||||
// action: "SEARCH_QDN_RESOURCES",
|
|
||||||
// mode: "ALL",
|
|
||||||
// service: "DOCUMENT",
|
|
||||||
// query: "${QTUBE_VIDEO_BASE}",
|
|
||||||
// limit: 20,
|
|
||||||
// includeMetadata: true,
|
|
||||||
// reverse: true,
|
|
||||||
// excludeBlocked: true,
|
|
||||||
// exactMatchNames: true,
|
|
||||||
// name: names
|
|
||||||
// })
|
|
||||||
const latestVideo = videos[0];
|
const latestVideo = videos[0];
|
||||||
if (!latestVideo) return;
|
if (!latestVideo) return;
|
||||||
const findVideo = responseData?.findIndex(
|
const findVideo = responseData?.findIndex(
|
||||||
@ -174,8 +181,9 @@ export const useFetchVideos = () => {
|
|||||||
async (
|
async (
|
||||||
filters = {},
|
filters = {},
|
||||||
reset?: boolean,
|
reset?: boolean,
|
||||||
resetFilers?: boolean,
|
resetFilters?: boolean,
|
||||||
limit?: number
|
limit?: number,
|
||||||
|
listType = allTabValue
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
@ -184,7 +192,7 @@ export const useFetchVideos = () => {
|
|||||||
subcategory = "",
|
subcategory = "",
|
||||||
keywords = "",
|
keywords = "",
|
||||||
type = "",
|
type = "",
|
||||||
}: any = resetFilers ? {} : filters;
|
}: any = resetFilters ? {} : filters;
|
||||||
let offset = videos.length;
|
let offset = videos.length;
|
||||||
if (reset) {
|
if (reset) {
|
||||||
offset = 0;
|
offset = 0;
|
||||||
@ -196,6 +204,12 @@ export const useFetchVideos = () => {
|
|||||||
if (name) {
|
if (name) {
|
||||||
defaultUrl = defaultUrl + `&name=${name}`;
|
defaultUrl = defaultUrl + `&name=${name}`;
|
||||||
}
|
}
|
||||||
|
if (listType === subscriptionTabValue) {
|
||||||
|
subscriptions.map(sub => {
|
||||||
|
defaultUrl += `&name=${sub}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (category) {
|
if (category) {
|
||||||
if (!subcategory) {
|
if (!subcategory) {
|
||||||
defaultUrl = defaultUrl + `&description=category:${category}`;
|
defaultUrl = defaultUrl + `&description=category:${category}`;
|
||||||
|
@ -1,77 +1,77 @@
|
|||||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from "react-redux";
|
||||||
import { RootState } from '../../state/store'
|
import { RootState } from "../../state/store";
|
||||||
|
import { Box, useTheme } from "@mui/material";
|
||||||
import {
|
import {
|
||||||
Avatar,
|
BottomParent,
|
||||||
Box,
|
NameContainer,
|
||||||
Button,
|
VideoCard,
|
||||||
Typography,
|
VideoCardName,
|
||||||
useTheme
|
VideoCardTitle,
|
||||||
} from '@mui/material'
|
VideoContainer,
|
||||||
import { useFetchVideos } from '../../hooks/useFetchVideos'
|
VideoUploadDate,
|
||||||
import LazyLoad from '../../components/common/LazyLoad'
|
} from "./VideoList-styles";
|
||||||
import { BottomParent, NameContainer, VideoCard, VideoCardName, VideoCardTitle, VideoContainer, VideoUploadDate } from './VideoList-styles'
|
import ResponsiveImage from "../../components/ResponsiveImage";
|
||||||
import ResponsiveImage from '../../components/ResponsiveImage'
|
import { formatDate, formatTimestampSeconds } from "../../utils/time";
|
||||||
import { formatDate, formatTimestampSeconds } from '../../utils/time'
|
import { ChannelCard, ChannelTitle } from "./Home-styles";
|
||||||
import { ChannelCard, ChannelTitle } from './Home-styles'
|
|
||||||
|
|
||||||
interface VideoListProps {
|
interface VideoListProps {
|
||||||
mode?: string
|
mode?: string;
|
||||||
}
|
}
|
||||||
export const Channels = ({ mode }: VideoListProps) => {
|
export const Channels = ({ mode }: VideoListProps) => {
|
||||||
const theme = useTheme()
|
const theme = useTheme();
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate();
|
||||||
const publishNames = useSelector((state: RootState)=> state.global.publishNames)
|
const publishNames = useSelector(
|
||||||
|
(state: RootState) => state.global.publishNames
|
||||||
|
);
|
||||||
const userAvatarHash = useSelector(
|
const userAvatarHash = useSelector(
|
||||||
(state: RootState) => state.global.userAvatarHash
|
(state: RootState) => state.global.userAvatarHash
|
||||||
)
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{
|
<Box
|
||||||
width: '100%',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center',
|
|
||||||
minHeight: '50vh'
|
|
||||||
}}>
|
|
||||||
<VideoContainer>
|
|
||||||
{publishNames && publishNames?.slice(0, 10).map((name)=> {
|
|
||||||
let avatarUrl = ''
|
|
||||||
if(userAvatarHash[name]){
|
|
||||||
avatarUrl = userAvatarHash[name]
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
width: "100%",
|
||||||
flex: 0,
|
display: "flex",
|
||||||
alignItems: 'center',
|
flexDirection: "column",
|
||||||
width: 'auto',
|
alignItems: "center",
|
||||||
position: 'relative',
|
minHeight: "50vh",
|
||||||
' @media (max-width: 450px)': {
|
|
||||||
width: '100%'
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
key={name}
|
|
||||||
>
|
>
|
||||||
<ChannelCard
|
<VideoContainer>
|
||||||
|
{publishNames &&
|
||||||
|
publishNames?.slice(0, 10).map(name => {
|
||||||
|
let avatarUrl = "";
|
||||||
|
if (userAvatarHash[name]) {
|
||||||
|
avatarUrl = userAvatarHash[name];
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flex: 0,
|
||||||
|
alignItems: "center",
|
||||||
|
width: "auto",
|
||||||
|
position: "relative",
|
||||||
|
" @media (max-width: 450px)": {
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
key={name}
|
||||||
|
>
|
||||||
|
<ChannelCard
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/channel/${name}`)
|
navigate(`/channel/${name}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ChannelTitle>{name}</ChannelTitle>
|
<ChannelTitle>{name}</ChannelTitle>
|
||||||
<ResponsiveImage src={avatarUrl} width={50} height={50}/>
|
<ResponsiveImage src={avatarUrl} width={50} height={50} />
|
||||||
</ChannelCard>
|
</ChannelCard>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</VideoContainer>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
})}
|
};
|
||||||
</VideoContainer>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,15 +1,582 @@
|
|||||||
import React from 'react'
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { VideoList } from './VideoList'
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import ReactDOM from "react-dom";
|
||||||
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
|
import { RootState } from "../../state/store";
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
Grid,
|
||||||
|
Input,
|
||||||
|
InputLabel,
|
||||||
|
MenuItem,
|
||||||
|
OutlinedInput,
|
||||||
|
Select,
|
||||||
|
SelectChangeEvent,
|
||||||
|
Tab,
|
||||||
|
Tabs,
|
||||||
|
Tooltip,
|
||||||
|
Typography,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { useFetchVideos } from "../../hooks/useFetchVideos";
|
||||||
|
import LazyLoad from "../../components/common/LazyLoad";
|
||||||
|
import {
|
||||||
|
BlockIconContainer,
|
||||||
|
BottomParent,
|
||||||
|
FilterSelect,
|
||||||
|
FiltersCheckbox,
|
||||||
|
FiltersCol,
|
||||||
|
FiltersContainer,
|
||||||
|
FiltersRow,
|
||||||
|
FiltersSubContainer,
|
||||||
|
FiltersTitle,
|
||||||
|
IconsBox,
|
||||||
|
NameContainer,
|
||||||
|
VideoCardCol,
|
||||||
|
ProductManagerRow,
|
||||||
|
VideoCardContainer,
|
||||||
|
VideoCard,
|
||||||
|
VideoCardName,
|
||||||
|
VideoCardTitle,
|
||||||
|
VideoContainer,
|
||||||
|
VideoUploadDate,
|
||||||
|
FiltersRadioButton,
|
||||||
|
} from "./VideoList-styles";
|
||||||
|
import ResponsiveImage from "../../components/ResponsiveImage";
|
||||||
|
import { formatDate, formatTimestampSeconds } from "../../utils/time";
|
||||||
|
import { Subtitle, SubtitleContainer } from "./Home-styles";
|
||||||
|
import { ExpandMoreSVG } from "../../assets/svgs/ExpandMoreSVG";
|
||||||
|
import {
|
||||||
|
addVideos,
|
||||||
|
blockUser,
|
||||||
|
changeFilterType,
|
||||||
|
changeSelectedCategoryVideos,
|
||||||
|
changeSelectedSubCategoryVideos,
|
||||||
|
changefilterName,
|
||||||
|
changefilterSearch,
|
||||||
|
clearVideoList,
|
||||||
|
setEditPlaylist,
|
||||||
|
setEditVideo,
|
||||||
|
} from "../../state/features/videoSlice";
|
||||||
|
import { categories, subCategories } from "../../constants/Categories.ts";
|
||||||
|
import { Playlists } from "../../components/Playlists/Playlists";
|
||||||
|
import { PlaylistSVG } from "../../assets/svgs/PlaylistSVG";
|
||||||
|
import BlockIcon from "@mui/icons-material/Block";
|
||||||
|
import EditIcon from "@mui/icons-material/Edit";
|
||||||
|
import { ListSuperLikeContainer } from "../../components/common/ListSuperLikes/ListSuperLikeContainer.tsx";
|
||||||
|
import { VideoCardImageContainer } from "./VideoCardImageContainer";
|
||||||
|
import { SubscribeButton } from "../../components/common/SubscribeButton.tsx";
|
||||||
|
import { TabContext, TabList, TabPanel } from "@mui/lab";
|
||||||
|
import VideoList from "./VideoList.tsx";
|
||||||
|
import { allTabValue, subscriptionTabValue } from "../../constants/Misc.ts";
|
||||||
|
import { setHomePageSelectedTab } from "../../state/features/settingsSlice.ts";
|
||||||
|
|
||||||
import { useSelector } from 'react-redux'
|
interface HomeProps {
|
||||||
import { RootState } from '../../state/store'
|
mode?: string;
|
||||||
|
}
|
||||||
|
export const Home = ({ mode }: HomeProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const prevVal = useRef("");
|
||||||
|
const isFiltering = useSelector(
|
||||||
|
(state: RootState) => state.video.isFiltering
|
||||||
|
);
|
||||||
|
const filterValue = useSelector(
|
||||||
|
(state: RootState) => state.video.filterValue
|
||||||
|
);
|
||||||
|
|
||||||
export const Home = () => {
|
const settingsState = useSelector((state: RootState) => state.settings);
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
const [tabValue, setTabValue] = useState<string>(settingsState.selectedTab);
|
||||||
|
|
||||||
|
const tabFontSize = "20px";
|
||||||
|
const filterType = useSelector((state: RootState) => state.video.filterType);
|
||||||
|
|
||||||
|
const setFilterType = payload => {
|
||||||
|
dispatch(changeFilterType(payload));
|
||||||
|
};
|
||||||
|
const filterSearch = useSelector(
|
||||||
|
(state: RootState) => state.video.filterSearch
|
||||||
|
);
|
||||||
|
|
||||||
|
const setFilterSearch = payload => {
|
||||||
|
dispatch(changefilterSearch(payload));
|
||||||
|
};
|
||||||
|
const filterName = useSelector((state: RootState) => state.video.filterName);
|
||||||
|
|
||||||
|
const setFilterName = payload => {
|
||||||
|
dispatch(changefilterName(payload));
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectedCategoryVideos = useSelector(
|
||||||
|
(state: RootState) => state.video.selectedCategoryVideos
|
||||||
|
);
|
||||||
|
|
||||||
|
const setSelectedCategoryVideos = payload => {
|
||||||
|
dispatch(changeSelectedCategoryVideos(payload));
|
||||||
|
};
|
||||||
|
const selectedSubCategoryVideos = useSelector(
|
||||||
|
(state: RootState) => state.video.selectedSubCategoryVideos
|
||||||
|
);
|
||||||
|
|
||||||
|
const setSelectedSubCategoryVideos = payload => {
|
||||||
|
dispatch(changeSelectedSubCategoryVideos(payload));
|
||||||
|
};
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const filteredVideos = useSelector(
|
||||||
|
(state: RootState) => state.video.filteredVideos
|
||||||
|
);
|
||||||
|
|
||||||
|
const isFilterMode = useRef(false);
|
||||||
|
const firstFetch = useRef(false);
|
||||||
|
const afterFetch = useRef(false);
|
||||||
|
const isFetchingFiltered = useRef(false);
|
||||||
|
const isFetching = useRef(false);
|
||||||
|
|
||||||
|
const countNewVideos = useSelector(
|
||||||
|
(state: RootState) => state.video.countNewVideos
|
||||||
|
);
|
||||||
|
|
||||||
|
const videoSelector = useSelector((state: RootState) => state.video);
|
||||||
|
|
||||||
|
const { videos: globalVideos } = useSelector(
|
||||||
|
(state: RootState) => state.video
|
||||||
|
);
|
||||||
|
|
||||||
|
const { getVideos, getNewVideos, checkNewVideos, getVideosFiltered } =
|
||||||
|
useFetchVideos();
|
||||||
|
|
||||||
|
const getVideosHandler = React.useCallback(
|
||||||
|
async (reset?: boolean, resetFilers?: 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,
|
||||||
|
type: filterType,
|
||||||
|
},
|
||||||
|
reset,
|
||||||
|
resetFilers,
|
||||||
|
20,
|
||||||
|
tabValue
|
||||||
|
);
|
||||||
|
isFetching.current = false;
|
||||||
|
},
|
||||||
|
[
|
||||||
|
getVideos,
|
||||||
|
filterValue,
|
||||||
|
getVideosFiltered,
|
||||||
|
isFiltering,
|
||||||
|
filterName,
|
||||||
|
selectedCategoryVideos,
|
||||||
|
selectedSubCategoryVideos,
|
||||||
|
filterSearch,
|
||||||
|
filterType,
|
||||||
|
tabValue,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isFiltering && filterValue !== prevVal?.current) {
|
||||||
|
prevVal.current = filterValue;
|
||||||
|
getVideosHandler();
|
||||||
|
}
|
||||||
|
}, [filterValue, isFiltering, filteredVideos]);
|
||||||
|
|
||||||
|
const getVideosHandlerMount = React.useCallback(async () => {
|
||||||
|
if (firstFetch.current) return;
|
||||||
|
firstFetch.current = true;
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
await getVideos({}, null, null, 20, tabValue);
|
||||||
|
afterFetch.current = true;
|
||||||
|
isFetching.current = false;
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
}, [getVideos]);
|
||||||
|
|
||||||
|
let videos = globalVideos;
|
||||||
|
|
||||||
|
if (isFiltering) {
|
||||||
|
videos = filteredVideos;
|
||||||
|
isFilterMode.current = true;
|
||||||
|
} else {
|
||||||
|
isFilterMode.current = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// const interval = useRef<any>(null);
|
||||||
|
|
||||||
|
// const checkNewVideosFunc = useCallback(() => {
|
||||||
|
// let isCalling = false;
|
||||||
|
// interval.current = setInterval(async () => {
|
||||||
|
// if (isCalling || !firstFetch.current) return;
|
||||||
|
// isCalling = true;
|
||||||
|
// await checkNewVideos();
|
||||||
|
// isCalling = false;
|
||||||
|
// }, 30000); // 1 second interval
|
||||||
|
// }, [checkNewVideos]);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (isFiltering && interval.current) {
|
||||||
|
// clearInterval(interval.current);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// checkNewVideosFunc();
|
||||||
|
|
||||||
|
// return () => {
|
||||||
|
// if (interval?.current) {
|
||||||
|
// clearInterval(interval.current);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }, [mode, checkNewVideosFunc, isFiltering]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
!firstFetch.current &&
|
||||||
|
!isFilterMode.current &&
|
||||||
|
globalVideos.length === 0
|
||||||
|
) {
|
||||||
|
isFetching.current = true;
|
||||||
|
getVideosHandlerMount();
|
||||||
|
} else {
|
||||||
|
firstFetch.current = true;
|
||||||
|
afterFetch.current = true;
|
||||||
|
}
|
||||||
|
}, [getVideosHandlerMount, globalVideos]);
|
||||||
|
|
||||||
|
const filtersToDefault = async () => {
|
||||||
|
setFilterType("videos");
|
||||||
|
setFilterSearch("");
|
||||||
|
setFilterName("");
|
||||||
|
setSelectedCategoryVideos(null);
|
||||||
|
setSelectedSubCategoryVideos(null);
|
||||||
|
|
||||||
|
ReactDOM.flushSync(() => {
|
||||||
|
getVideosHandler(true, true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOptionCategoryChangeVideos = (
|
||||||
|
event: SelectChangeEvent<string>
|
||||||
|
) => {
|
||||||
|
const optionId = event.target.value;
|
||||||
|
const selectedOption = categories.find(option => option.id === +optionId);
|
||||||
|
setSelectedCategoryVideos(selectedOption || null);
|
||||||
|
};
|
||||||
|
const handleOptionSubCategoryChangeVideos = (
|
||||||
|
event: SelectChangeEvent<string>,
|
||||||
|
subcategories: any[]
|
||||||
|
) => {
|
||||||
|
const optionId = event.target.value;
|
||||||
|
const selectedOption = subcategories.find(
|
||||||
|
option => option.id === +optionId
|
||||||
|
);
|
||||||
|
setSelectedSubCategoryVideos(selectedOption || null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputKeyDown = (event: any) => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
getVideosHandler(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getVideosHandler(true);
|
||||||
|
}, [tabValue]);
|
||||||
|
|
||||||
|
const changeTab = (e: React.SyntheticEvent, newValue: string) => {
|
||||||
|
setTabValue(newValue);
|
||||||
|
dispatch(setHomePageSelectedTab(newValue));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Grid container sx={{ width: "100%" }}>
|
||||||
<VideoList />
|
<FiltersCol item xs={12} md={2} lg={2} xl={2} sm={3}>
|
||||||
</>
|
<FiltersContainer>
|
||||||
|
<Input
|
||||||
)
|
id="standard-adornment-name"
|
||||||
}
|
onChange={e => {
|
||||||
|
setFilterSearch(e.target.value);
|
||||||
|
}}
|
||||||
|
value={filterSearch}
|
||||||
|
placeholder="Search"
|
||||||
|
onKeyDown={handleInputKeyDown}
|
||||||
|
sx={{
|
||||||
|
borderBottom: "1px solid white",
|
||||||
|
"&&:before": {
|
||||||
|
borderBottom: "none",
|
||||||
|
},
|
||||||
|
"&&:after": {
|
||||||
|
borderBottom: "none",
|
||||||
|
},
|
||||||
|
"&&:hover:before": {
|
||||||
|
borderBottom: "none",
|
||||||
|
},
|
||||||
|
"&&.Mui-focused:before": {
|
||||||
|
borderBottom: "none",
|
||||||
|
},
|
||||||
|
"&&.Mui-focused": {
|
||||||
|
outline: "none",
|
||||||
|
},
|
||||||
|
fontSize: "18px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
id="standard-adornment-name"
|
||||||
|
onChange={e => {
|
||||||
|
setFilterName(e.target.value);
|
||||||
|
}}
|
||||||
|
value={filterName}
|
||||||
|
placeholder="User's Name (Exact)"
|
||||||
|
onKeyDown={handleInputKeyDown}
|
||||||
|
sx={{
|
||||||
|
marginTop: "20px",
|
||||||
|
borderBottom: "1px solid white",
|
||||||
|
"&&:before": {
|
||||||
|
borderBottom: "none",
|
||||||
|
},
|
||||||
|
"&&:after": {
|
||||||
|
borderBottom: "none",
|
||||||
|
},
|
||||||
|
"&&:hover:before": {
|
||||||
|
borderBottom: "none",
|
||||||
|
},
|
||||||
|
"&&.Mui-focused:before": {
|
||||||
|
borderBottom: "none",
|
||||||
|
},
|
||||||
|
"&&.Mui-focused": {
|
||||||
|
outline: "none",
|
||||||
|
},
|
||||||
|
fontSize: "18px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FiltersTitle>
|
||||||
|
Categories
|
||||||
|
<ExpandMoreSVG
|
||||||
|
color={theme.palette.text.primary}
|
||||||
|
height={"22"}
|
||||||
|
width={"22"}
|
||||||
|
/>
|
||||||
|
</FiltersTitle>
|
||||||
|
<FiltersSubContainer>
|
||||||
|
<FormControl sx={{ width: "100%" }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "20px",
|
||||||
|
alignItems: "center",
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormControl fullWidth sx={{ marginBottom: 1 }}>
|
||||||
|
<InputLabel
|
||||||
|
sx={{
|
||||||
|
fontSize: "16px",
|
||||||
|
}}
|
||||||
|
id="Category"
|
||||||
|
>
|
||||||
|
Category
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="Category"
|
||||||
|
input={<OutlinedInput label="Category" />}
|
||||||
|
value={selectedCategoryVideos?.id || ""}
|
||||||
|
onChange={handleOptionCategoryChangeVideos}
|
||||||
|
sx={{
|
||||||
|
// Target the input field
|
||||||
|
".MuiSelect-select": {
|
||||||
|
fontSize: "16px", // Change font size for the selected value
|
||||||
|
padding: "10px 5px 15px 15px;",
|
||||||
|
},
|
||||||
|
// Target the dropdown icon
|
||||||
|
".MuiSelect-icon": {
|
||||||
|
fontSize: "20px", // Adjust if needed
|
||||||
|
},
|
||||||
|
// Target the dropdown menu
|
||||||
|
"& .MuiMenu-paper": {
|
||||||
|
".MuiMenuItem-root": {
|
||||||
|
fontSize: "14px", // Change font size for the menu items
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{categories.map(option => (
|
||||||
|
<MenuItem key={option.id} value={option.id}>
|
||||||
|
{option.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
{selectedCategoryVideos &&
|
||||||
|
subCategories[selectedCategoryVideos?.id] && (
|
||||||
|
<FormControl fullWidth sx={{ marginBottom: 2 }}>
|
||||||
|
<InputLabel
|
||||||
|
sx={{
|
||||||
|
fontSize: "16px",
|
||||||
|
}}
|
||||||
|
id="Sub-Category"
|
||||||
|
>
|
||||||
|
Sub-Category
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="Sub-Category"
|
||||||
|
input={<OutlinedInput label="Sub-Category" />}
|
||||||
|
value={selectedSubCategoryVideos?.id || ""}
|
||||||
|
onChange={e =>
|
||||||
|
handleOptionSubCategoryChangeVideos(
|
||||||
|
e,
|
||||||
|
subCategories[selectedCategoryVideos?.id]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
sx={{
|
||||||
|
// Target the input field
|
||||||
|
".MuiSelect-select": {
|
||||||
|
fontSize: "16px", // Change font size for the selected value
|
||||||
|
padding: "10px 5px 15px 15px;",
|
||||||
|
},
|
||||||
|
// Target the dropdown icon
|
||||||
|
".MuiSelect-icon": {
|
||||||
|
fontSize: "20px", // Adjust if needed
|
||||||
|
},
|
||||||
|
// Target the dropdown menu
|
||||||
|
"& .MuiMenu-paper": {
|
||||||
|
".MuiMenuItem-root": {
|
||||||
|
fontSize: "14px", // Change font size for the menu items
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{subCategories[selectedCategoryVideos.id].map(
|
||||||
|
option => (
|
||||||
|
<MenuItem key={option.id} value={option.id}>
|
||||||
|
{option.name}
|
||||||
|
</MenuItem>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</FormControl>
|
||||||
|
</FiltersSubContainer>
|
||||||
|
<FiltersTitle>
|
||||||
|
Type
|
||||||
|
<ExpandMoreSVG
|
||||||
|
color={theme.palette.text.primary}
|
||||||
|
height={"22"}
|
||||||
|
width={"22"}
|
||||||
|
/>
|
||||||
|
</FiltersTitle>
|
||||||
|
<FiltersSubContainer>
|
||||||
|
<FiltersRow>
|
||||||
|
Videos
|
||||||
|
<FiltersRadioButton
|
||||||
|
checked={filterType === "videos"}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setFilterType("videos");
|
||||||
|
}}
|
||||||
|
inputProps={{ "aria-label": "controlled" }}
|
||||||
|
/>
|
||||||
|
</FiltersRow>
|
||||||
|
<FiltersRow>
|
||||||
|
Playlists
|
||||||
|
<FiltersRadioButton
|
||||||
|
checked={filterType === "playlists"}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setFilterType("playlists");
|
||||||
|
}}
|
||||||
|
inputProps={{ "aria-label": "controlled" }}
|
||||||
|
/>
|
||||||
|
</FiltersRow>
|
||||||
|
</FiltersSubContainer>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
filtersToDefault();
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
marginTop: "20px",
|
||||||
|
}}
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
|
reset
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
getVideosHandler(true);
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
marginTop: "20px",
|
||||||
|
}}
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
</FiltersContainer>
|
||||||
|
</FiltersCol>
|
||||||
|
<Grid item xs={12} md={10} lg={7} xl={8} sm={9}>
|
||||||
|
<ProductManagerRow>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
marginTop: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SubtitleContainer
|
||||||
|
sx={{
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
paddingLeft: "15px",
|
||||||
|
width: "100%",
|
||||||
|
maxWidth: "1400px",
|
||||||
|
}}
|
||||||
|
></SubtitleContainer>
|
||||||
|
<TabContext value={tabValue}>
|
||||||
|
<TabList
|
||||||
|
onChange={changeTab}
|
||||||
|
textColor={"secondary"}
|
||||||
|
indicatorColor={"secondary"}
|
||||||
|
>
|
||||||
|
<Tab
|
||||||
|
label="All Videos"
|
||||||
|
value={allTabValue}
|
||||||
|
sx={{ fontSize: tabFontSize }}
|
||||||
|
/>
|
||||||
|
<Tab
|
||||||
|
label="Subsciptions"
|
||||||
|
value={subscriptionTabValue}
|
||||||
|
sx={{ fontSize: tabFontSize }}
|
||||||
|
/>
|
||||||
|
</TabList>
|
||||||
|
<TabPanel value="all" sx={{ width: "100%" }}>
|
||||||
|
<VideoList videos={videos} />
|
||||||
|
<LazyLoad
|
||||||
|
onLoadMore={getVideosHandler}
|
||||||
|
isLoading={isLoading}
|
||||||
|
></LazyLoad>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value="subscriptions" sx={{ width: "100%" }}>
|
||||||
|
<VideoList videos={videos} />
|
||||||
|
<LazyLoad
|
||||||
|
onLoadMore={getVideosHandler}
|
||||||
|
isLoading={isLoading}
|
||||||
|
></LazyLoad>
|
||||||
|
</TabPanel>
|
||||||
|
</TabContext>
|
||||||
|
</Box>
|
||||||
|
</ProductManagerRow>
|
||||||
|
</Grid>
|
||||||
|
<FiltersCol item xs={0} lg={3} xl={2}>
|
||||||
|
<ListSuperLikeContainer />
|
||||||
|
</FiltersCol>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
TextField,
|
TextField,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
Autocomplete,
|
Autocomplete,
|
||||||
|
Radio,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
||||||
export const VideoContainer = styled(Grid)(({ theme }) => ({
|
export const VideoContainer = styled(Grid)(({ theme }) => ({
|
||||||
@ -131,7 +132,7 @@ export const VideoCardTitle = styled(DoubleLine)(({ theme }) => ({
|
|||||||
}));
|
}));
|
||||||
export const VideoCardName = styled(Typography)(({ theme }) => ({
|
export const VideoCardName = styled(Typography)(({ theme }) => ({
|
||||||
fontFamily: "Cairo",
|
fontFamily: "Cairo",
|
||||||
fontSize: "18px",
|
fontSize: "16px",
|
||||||
letterSpacing: "0.4px",
|
letterSpacing: "0.4px",
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
userSelect: "none",
|
userSelect: "none",
|
||||||
@ -270,6 +271,13 @@ export const FiltersCheckbox = styled(Checkbox)(({ theme }) => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
export const FiltersRadioButton = styled(Radio)(({ theme }) => ({
|
||||||
|
color: "#c0d4ff",
|
||||||
|
"&.Mui-checked": {
|
||||||
|
color: "#6596ff",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
export const FilterSelect = styled(Autocomplete)(({ theme }) => ({
|
export const FilterSelect = styled(Autocomplete)(({ theme }) => ({
|
||||||
"& #categories-select": {
|
"& #categories-select": {
|
||||||
padding: "7px",
|
padding: "7px",
|
||||||
|
@ -1,276 +1,50 @@
|
|||||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import ReactDOM from "react-dom";
|
|
||||||
import { useSelector, useDispatch } from "react-redux";
|
|
||||||
import { RootState } from "../../state/store";
|
|
||||||
import {
|
|
||||||
Avatar,
|
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
FormControl,
|
|
||||||
Grid,
|
|
||||||
Input,
|
|
||||||
InputLabel,
|
|
||||||
MenuItem,
|
|
||||||
OutlinedInput,
|
|
||||||
Select,
|
|
||||||
SelectChangeEvent,
|
|
||||||
Tooltip,
|
|
||||||
Typography,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useFetchVideos } from "../../hooks/useFetchVideos";
|
|
||||||
import LazyLoad from "../../components/common/LazyLoad";
|
|
||||||
import {
|
import {
|
||||||
BlockIconContainer,
|
BlockIconContainer,
|
||||||
BottomParent,
|
BottomParent,
|
||||||
FilterSelect,
|
|
||||||
FiltersCheckbox,
|
|
||||||
FiltersCol,
|
|
||||||
FiltersContainer,
|
|
||||||
FiltersRow,
|
|
||||||
FiltersSubContainer,
|
|
||||||
FiltersTitle,
|
|
||||||
IconsBox,
|
IconsBox,
|
||||||
NameContainer,
|
NameContainer,
|
||||||
VideoCardCol,
|
|
||||||
ProductManagerRow,
|
|
||||||
VideoCardContainer,
|
|
||||||
VideoCard,
|
VideoCard,
|
||||||
|
VideoCardCol,
|
||||||
|
VideoCardContainer,
|
||||||
VideoCardName,
|
VideoCardName,
|
||||||
VideoCardTitle,
|
VideoCardTitle,
|
||||||
VideoContainer,
|
|
||||||
VideoUploadDate,
|
VideoUploadDate,
|
||||||
} from "./VideoList-styles";
|
} from "./VideoList-styles.tsx";
|
||||||
import ResponsiveImage from "../../components/ResponsiveImage";
|
import { Avatar, Box, Tooltip, useTheme } from "@mui/material";
|
||||||
import { formatDate, formatTimestampSeconds } from "../../utils/time";
|
import EditIcon from "@mui/icons-material/Edit";
|
||||||
import { Subtitle, SubtitleContainer } from "./Home-styles";
|
|
||||||
import { ExpandMoreSVG } from "../../assets/svgs/ExpandMoreSVG";
|
|
||||||
import {
|
import {
|
||||||
addVideos,
|
|
||||||
blockUser,
|
blockUser,
|
||||||
changeFilterType,
|
|
||||||
changeSelectedCategoryVideos,
|
|
||||||
changeSelectedSubCategoryVideos,
|
|
||||||
changefilterName,
|
|
||||||
changefilterSearch,
|
|
||||||
clearVideoList,
|
|
||||||
setEditPlaylist,
|
setEditPlaylist,
|
||||||
setEditVideo,
|
setEditVideo,
|
||||||
} from "../../state/features/videoSlice";
|
} from "../../state/features/videoSlice.ts";
|
||||||
import { categories, subCategories } from "../../constants/Categories.ts";
|
|
||||||
import { Playlists } from "../../components/Playlists/Playlists";
|
|
||||||
import { PlaylistSVG } from "../../assets/svgs/PlaylistSVG";
|
|
||||||
import BlockIcon from "@mui/icons-material/Block";
|
import BlockIcon from "@mui/icons-material/Block";
|
||||||
import EditIcon from "@mui/icons-material/Edit";
|
import ResponsiveImage from "../../components/ResponsiveImage.tsx";
|
||||||
import { ListSuperLikeContainer } from "../../components/common/ListSuperLikes/ListSuperLikeContainer.tsx";
|
import { formatDate } from "../../utils/time.ts";
|
||||||
import { VideoCardImageContainer } from "./VideoCardImageContainer";
|
import { PlaylistSVG } from "../../assets/svgs/PlaylistSVG.tsx";
|
||||||
import { SubscribeButton } from "../../components/common/SubscribeButton.tsx";
|
import { VideoCardImageContainer } from "./VideoCardImageContainer.tsx";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { Video } from "../../state/features/videoSlice.ts";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { RootState } from "../../state/store.ts";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
interface VideoListProps {
|
interface VideoListProps {
|
||||||
mode?: string;
|
videos: Video[];
|
||||||
}
|
}
|
||||||
export const VideoList = ({ mode }: VideoListProps) => {
|
export const VideoList = ({ videos }: VideoListProps) => {
|
||||||
const theme = useTheme();
|
|
||||||
const prevVal = useRef("");
|
|
||||||
const isFiltering = useSelector(
|
|
||||||
(state: RootState) => state.video.isFiltering
|
|
||||||
);
|
|
||||||
const filterValue = useSelector(
|
|
||||||
(state: RootState) => state.video.filterValue
|
|
||||||
);
|
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
||||||
const [showIcons, setShowIcons] = useState(null);
|
const [showIcons, setShowIcons] = useState(null);
|
||||||
|
|
||||||
const filterType = useSelector((state: RootState) => state.video.filterType);
|
|
||||||
|
|
||||||
const setFilterType = payload => {
|
|
||||||
dispatch(changeFilterType(payload));
|
|
||||||
};
|
|
||||||
const filterSearch = useSelector(
|
|
||||||
(state: RootState) => state.video.filterSearch
|
|
||||||
);
|
|
||||||
|
|
||||||
const setFilterSearch = payload => {
|
|
||||||
dispatch(changefilterSearch(payload));
|
|
||||||
};
|
|
||||||
const filterName = useSelector((state: RootState) => state.video.filterName);
|
|
||||||
|
|
||||||
const setFilterName = payload => {
|
|
||||||
dispatch(changefilterName(payload));
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectedCategoryVideos = useSelector(
|
|
||||||
(state: RootState) => state.video.selectedCategoryVideos
|
|
||||||
);
|
|
||||||
|
|
||||||
const setSelectedCategoryVideos = payload => {
|
|
||||||
dispatch(changeSelectedCategoryVideos(payload));
|
|
||||||
};
|
|
||||||
const selectedSubCategoryVideos = useSelector(
|
|
||||||
(state: RootState) => state.video.selectedSubCategoryVideos
|
|
||||||
);
|
|
||||||
|
|
||||||
const setSelectedSubCategoryVideos = payload => {
|
|
||||||
dispatch(changeSelectedSubCategoryVideos(payload));
|
|
||||||
};
|
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const filteredVideos = useSelector(
|
|
||||||
(state: RootState) => state.video.filteredVideos
|
|
||||||
);
|
|
||||||
const username = useSelector((state: RootState) => state.auth?.user?.name);
|
|
||||||
const isFilterMode = useRef(false);
|
|
||||||
const firstFetch = useRef(false);
|
|
||||||
const afterFetch = useRef(false);
|
|
||||||
const isFetchingFiltered = useRef(false);
|
|
||||||
const isFetching = useRef(false);
|
|
||||||
const hashMapVideos = useSelector(
|
const hashMapVideos = useSelector(
|
||||||
(state: RootState) => state.video.hashMapVideos
|
(state: RootState) => state.video.hashMapVideos
|
||||||
);
|
);
|
||||||
|
|
||||||
const countNewVideos = useSelector(
|
|
||||||
(state: RootState) => state.video.countNewVideos
|
|
||||||
);
|
|
||||||
const userAvatarHash = useSelector(
|
const userAvatarHash = useSelector(
|
||||||
(state: RootState) => state.global.userAvatarHash
|
(state: RootState) => state.global.userAvatarHash
|
||||||
);
|
);
|
||||||
|
const username = useSelector((state: RootState) => state.auth?.user?.name);
|
||||||
|
|
||||||
const { videos: globalVideos } = useSelector(
|
|
||||||
(state: RootState) => state.video
|
|
||||||
);
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { getVideos, getNewVideos, checkNewVideos, getVideosFiltered } =
|
const dispatch = useDispatch();
|
||||||
useFetchVideos();
|
const theme = useTheme();
|
||||||
|
|
||||||
const getVideosHandler = React.useCallback(
|
|
||||||
async (reset?: boolean, resetFilers?: 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,
|
|
||||||
type: filterType,
|
|
||||||
},
|
|
||||||
reset ? true : false,
|
|
||||||
resetFilers
|
|
||||||
);
|
|
||||||
isFetching.current = false;
|
|
||||||
},
|
|
||||||
[
|
|
||||||
getVideos,
|
|
||||||
filterValue,
|
|
||||||
getVideosFiltered,
|
|
||||||
isFiltering,
|
|
||||||
filterName,
|
|
||||||
selectedCategoryVideos,
|
|
||||||
selectedSubCategoryVideos,
|
|
||||||
filterSearch,
|
|
||||||
filterType,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isFiltering && filterValue !== prevVal?.current) {
|
|
||||||
prevVal.current = filterValue;
|
|
||||||
getVideosHandler();
|
|
||||||
}
|
|
||||||
}, [filterValue, isFiltering, filteredVideos]);
|
|
||||||
|
|
||||||
const getVideosHandlerMount = React.useCallback(async () => {
|
|
||||||
if (firstFetch.current) return;
|
|
||||||
firstFetch.current = true;
|
|
||||||
setIsLoading(true);
|
|
||||||
|
|
||||||
await getVideos();
|
|
||||||
afterFetch.current = true;
|
|
||||||
isFetching.current = false;
|
|
||||||
|
|
||||||
setIsLoading(false);
|
|
||||||
}, [getVideos]);
|
|
||||||
|
|
||||||
let videos = globalVideos;
|
|
||||||
|
|
||||||
if (isFiltering) {
|
|
||||||
videos = filteredVideos;
|
|
||||||
isFilterMode.current = true;
|
|
||||||
} else {
|
|
||||||
isFilterMode.current = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// const interval = useRef<any>(null);
|
|
||||||
|
|
||||||
// const checkNewVideosFunc = useCallback(() => {
|
|
||||||
// let isCalling = false;
|
|
||||||
// interval.current = setInterval(async () => {
|
|
||||||
// if (isCalling || !firstFetch.current) return;
|
|
||||||
// isCalling = true;
|
|
||||||
// await checkNewVideos();
|
|
||||||
// isCalling = false;
|
|
||||||
// }, 30000); // 1 second interval
|
|
||||||
// }, [checkNewVideos]);
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// if (isFiltering && interval.current) {
|
|
||||||
// clearInterval(interval.current);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// checkNewVideosFunc();
|
|
||||||
|
|
||||||
// return () => {
|
|
||||||
// if (interval?.current) {
|
|
||||||
// clearInterval(interval.current);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// }, [mode, checkNewVideosFunc, isFiltering]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
!firstFetch.current &&
|
|
||||||
!isFilterMode.current &&
|
|
||||||
globalVideos.length === 0
|
|
||||||
) {
|
|
||||||
isFetching.current = true;
|
|
||||||
getVideosHandlerMount();
|
|
||||||
} else {
|
|
||||||
firstFetch.current = true;
|
|
||||||
afterFetch.current = true;
|
|
||||||
}
|
|
||||||
}, [getVideosHandlerMount, globalVideos]);
|
|
||||||
|
|
||||||
const filtersToDefault = async () => {
|
|
||||||
setFilterType("videos");
|
|
||||||
setFilterSearch("");
|
|
||||||
setFilterName("");
|
|
||||||
setSelectedCategoryVideos(null);
|
|
||||||
setSelectedSubCategoryVideos(null);
|
|
||||||
|
|
||||||
ReactDOM.flushSync(() => {
|
|
||||||
getVideosHandler(true, true);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOptionCategoryChangeVideos = (
|
|
||||||
event: SelectChangeEvent<string>
|
|
||||||
) => {
|
|
||||||
const optionId = event.target.value;
|
|
||||||
const selectedOption = categories.find(option => option.id === +optionId);
|
|
||||||
setSelectedCategoryVideos(selectedOption || null);
|
|
||||||
};
|
|
||||||
const handleOptionSubCategoryChangeVideos = (
|
|
||||||
event: SelectChangeEvent<string>,
|
|
||||||
subcategories: any[]
|
|
||||||
) => {
|
|
||||||
const optionId = event.target.value;
|
|
||||||
const selectedOption = subcategories.find(
|
|
||||||
option => option.id === +optionId
|
|
||||||
);
|
|
||||||
setSelectedSubCategoryVideos(selectedOption || null);
|
|
||||||
};
|
|
||||||
|
|
||||||
const blockUserFunc = async (user: string) => {
|
const blockUserFunc = async (user: string) => {
|
||||||
if (user === "Q-Tube") return;
|
if (user === "Q-Tube") return;
|
||||||
@ -288,483 +62,224 @@ export const VideoList = ({ mode }: VideoListProps) => {
|
|||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInputKeyDown = (event: any) => {
|
|
||||||
if (event.key === "Enter") {
|
|
||||||
getVideosHandler(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container sx={{ width: "100%" }}>
|
<VideoCardContainer>
|
||||||
<FiltersCol item xs={12} md={2} lg={2} xl={2} sm={3}>
|
{videos.map((video: any, index: number) => {
|
||||||
<FiltersContainer>
|
const fullId = video ? `${video.id}-${video.user}` : undefined;
|
||||||
<Input
|
const existingVideo = hashMapVideos[fullId];
|
||||||
id="standard-adornment-name"
|
let hasHash = false;
|
||||||
onChange={e => {
|
let videoObj = video;
|
||||||
setFilterSearch(e.target.value);
|
if (existingVideo) {
|
||||||
}}
|
videoObj = existingVideo;
|
||||||
value={filterSearch}
|
hasHash = true;
|
||||||
placeholder="Search"
|
}
|
||||||
onKeyDown={handleInputKeyDown}
|
|
||||||
sx={{
|
let avatarUrl = "";
|
||||||
borderBottom: "1px solid white",
|
if (userAvatarHash[videoObj?.user]) {
|
||||||
"&&:before": {
|
avatarUrl = userAvatarHash[videoObj?.user];
|
||||||
borderBottom: "none",
|
}
|
||||||
},
|
|
||||||
"&&:after": {
|
// nb. this prevents showing metadata for a video which
|
||||||
borderBottom: "none",
|
// belongs to a different user
|
||||||
},
|
if (
|
||||||
"&&:hover:before": {
|
videoObj?.user &&
|
||||||
borderBottom: "none",
|
videoObj?.videoReference?.name &&
|
||||||
},
|
videoObj.user != videoObj.videoReference.name
|
||||||
"&&.Mui-focused:before": {
|
) {
|
||||||
borderBottom: "none",
|
return null;
|
||||||
},
|
}
|
||||||
"&&.Mui-focused": {
|
|
||||||
outline: "none",
|
if (hasHash && !videoObj?.videoImage && !videoObj?.image) {
|
||||||
},
|
return null;
|
||||||
fontSize: "18px",
|
}
|
||||||
}}
|
const isPlaylist = videoObj?.service === "PLAYLIST";
|
||||||
/>
|
|
||||||
<Input
|
if (isPlaylist) {
|
||||||
id="standard-adornment-name"
|
return (
|
||||||
onChange={e => {
|
<VideoCardCol
|
||||||
setFilterName(e.target.value);
|
onMouseEnter={() => setShowIcons(videoObj.id)}
|
||||||
}}
|
onMouseLeave={() => setShowIcons(null)}
|
||||||
value={filterName}
|
key={videoObj.id}
|
||||||
placeholder="User's Name (Exact)"
|
>
|
||||||
onKeyDown={handleInputKeyDown}
|
<IconsBox
|
||||||
sx={{
|
|
||||||
marginTop: "20px",
|
|
||||||
borderBottom: "1px solid white",
|
|
||||||
"&&:before": {
|
|
||||||
borderBottom: "none",
|
|
||||||
},
|
|
||||||
"&&:after": {
|
|
||||||
borderBottom: "none",
|
|
||||||
},
|
|
||||||
"&&:hover:before": {
|
|
||||||
borderBottom: "none",
|
|
||||||
},
|
|
||||||
"&&.Mui-focused:before": {
|
|
||||||
borderBottom: "none",
|
|
||||||
},
|
|
||||||
"&&.Mui-focused": {
|
|
||||||
outline: "none",
|
|
||||||
},
|
|
||||||
fontSize: "18px",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<FiltersTitle>
|
|
||||||
Categories
|
|
||||||
<ExpandMoreSVG
|
|
||||||
color={theme.palette.text.primary}
|
|
||||||
height={"22"}
|
|
||||||
width={"22"}
|
|
||||||
/>
|
|
||||||
</FiltersTitle>
|
|
||||||
<FiltersSubContainer>
|
|
||||||
<FormControl sx={{ width: "100%" }}>
|
|
||||||
<Box
|
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
opacity: showIcons === videoObj.id ? 1 : 0,
|
||||||
gap: "20px",
|
zIndex: 2,
|
||||||
alignItems: "center",
|
|
||||||
flexDirection: "column",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormControl fullWidth sx={{ marginBottom: 1 }}>
|
{videoObj?.user === username && (
|
||||||
<InputLabel
|
<Tooltip title="Edit playlist" placement="top">
|
||||||
sx={{
|
<BlockIconContainer>
|
||||||
fontSize: "16px",
|
<EditIcon
|
||||||
}}
|
|
||||||
id="Category"
|
|
||||||
>
|
|
||||||
Category
|
|
||||||
</InputLabel>
|
|
||||||
<Select
|
|
||||||
labelId="Category"
|
|
||||||
input={<OutlinedInput label="Category" />}
|
|
||||||
value={selectedCategoryVideos?.id || ""}
|
|
||||||
onChange={handleOptionCategoryChangeVideos}
|
|
||||||
sx={{
|
|
||||||
// Target the input field
|
|
||||||
".MuiSelect-select": {
|
|
||||||
fontSize: "16px", // Change font size for the selected value
|
|
||||||
padding: "10px 5px 15px 15px;",
|
|
||||||
},
|
|
||||||
// Target the dropdown icon
|
|
||||||
".MuiSelect-icon": {
|
|
||||||
fontSize: "20px", // Adjust if needed
|
|
||||||
},
|
|
||||||
// Target the dropdown menu
|
|
||||||
"& .MuiMenu-paper": {
|
|
||||||
".MuiMenuItem-root": {
|
|
||||||
fontSize: "14px", // Change font size for the menu items
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{categories.map(option => (
|
|
||||||
<MenuItem key={option.id} value={option.id}>
|
|
||||||
{option.name}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
{selectedCategoryVideos &&
|
|
||||||
subCategories[selectedCategoryVideos?.id] && (
|
|
||||||
<FormControl fullWidth sx={{ marginBottom: 2 }}>
|
|
||||||
<InputLabel
|
|
||||||
sx={{
|
|
||||||
fontSize: "16px",
|
|
||||||
}}
|
|
||||||
id="Sub-Category"
|
|
||||||
>
|
|
||||||
Sub-Category
|
|
||||||
</InputLabel>
|
|
||||||
<Select
|
|
||||||
labelId="Sub-Category"
|
|
||||||
input={<OutlinedInput label="Sub-Category" />}
|
|
||||||
value={selectedSubCategoryVideos?.id || ""}
|
|
||||||
onChange={e =>
|
|
||||||
handleOptionSubCategoryChangeVideos(
|
|
||||||
e,
|
|
||||||
subCategories[selectedCategoryVideos?.id]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
sx={{
|
|
||||||
// Target the input field
|
|
||||||
".MuiSelect-select": {
|
|
||||||
fontSize: "16px", // Change font size for the selected value
|
|
||||||
padding: "10px 5px 15px 15px;",
|
|
||||||
},
|
|
||||||
// Target the dropdown icon
|
|
||||||
".MuiSelect-icon": {
|
|
||||||
fontSize: "20px", // Adjust if needed
|
|
||||||
},
|
|
||||||
// Target the dropdown menu
|
|
||||||
"& .MuiMenu-paper": {
|
|
||||||
".MuiMenuItem-root": {
|
|
||||||
fontSize: "14px", // Change font size for the menu items
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{subCategories[selectedCategoryVideos.id].map(
|
|
||||||
option => (
|
|
||||||
<MenuItem key={option.id} value={option.id}>
|
|
||||||
{option.name}
|
|
||||||
</MenuItem>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</FormControl>
|
|
||||||
</FiltersSubContainer>
|
|
||||||
<FiltersTitle>
|
|
||||||
Type
|
|
||||||
<ExpandMoreSVG
|
|
||||||
color={theme.palette.text.primary}
|
|
||||||
height={"22"}
|
|
||||||
width={"22"}
|
|
||||||
/>
|
|
||||||
</FiltersTitle>
|
|
||||||
<FiltersSubContainer>
|
|
||||||
<FiltersRow>
|
|
||||||
Videos
|
|
||||||
<FiltersCheckbox
|
|
||||||
checked={filterType === "videos"}
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
setFilterType("videos");
|
|
||||||
}}
|
|
||||||
inputProps={{ "aria-label": "controlled" }}
|
|
||||||
/>
|
|
||||||
</FiltersRow>
|
|
||||||
<FiltersRow>
|
|
||||||
Playlists
|
|
||||||
<FiltersCheckbox
|
|
||||||
checked={filterType === "playlists"}
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
setFilterType("playlists");
|
|
||||||
}}
|
|
||||||
inputProps={{ "aria-label": "controlled" }}
|
|
||||||
/>
|
|
||||||
</FiltersRow>
|
|
||||||
</FiltersSubContainer>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
filtersToDefault();
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
marginTop: "20px",
|
|
||||||
}}
|
|
||||||
variant="contained"
|
|
||||||
>
|
|
||||||
reset
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
getVideosHandler(true);
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
marginTop: "20px",
|
|
||||||
}}
|
|
||||||
variant="contained"
|
|
||||||
>
|
|
||||||
Search
|
|
||||||
</Button>
|
|
||||||
</FiltersContainer>
|
|
||||||
</FiltersCol>
|
|
||||||
<Grid item xs={12} md={10} lg={7} xl={8} sm={9}>
|
|
||||||
<ProductManagerRow>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
width: "100%",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
marginTop: "20px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SubtitleContainer
|
|
||||||
sx={{
|
|
||||||
justifyContent: "flex-start",
|
|
||||||
paddingLeft: "15px",
|
|
||||||
width: "100%",
|
|
||||||
maxWidth: "1400px",
|
|
||||||
}}
|
|
||||||
></SubtitleContainer>
|
|
||||||
|
|
||||||
<VideoCardContainer>
|
|
||||||
{videos.map((video: any, index: number) => {
|
|
||||||
const fullId = video ? `${video.id}-${video.user}` : undefined;
|
|
||||||
const existingVideo = hashMapVideos[fullId];
|
|
||||||
let hasHash = false;
|
|
||||||
let videoObj = video;
|
|
||||||
if (existingVideo) {
|
|
||||||
videoObj = existingVideo;
|
|
||||||
hasHash = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let avatarUrl = "";
|
|
||||||
if (userAvatarHash[videoObj?.user]) {
|
|
||||||
avatarUrl = userAvatarHash[videoObj?.user];
|
|
||||||
}
|
|
||||||
|
|
||||||
// nb. this prevents showing metadata for a video which
|
|
||||||
// belongs to a different user
|
|
||||||
if (videoObj?.user && videoObj?.videoReference?.name
|
|
||||||
&& videoObj.user != videoObj.videoReference.name) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasHash && !videoObj?.videoImage && !videoObj?.image) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const isPlaylist = videoObj?.service === "PLAYLIST";
|
|
||||||
|
|
||||||
if (isPlaylist) {
|
|
||||||
return (
|
|
||||||
<VideoCardCol
|
|
||||||
onMouseEnter={() => setShowIcons(videoObj.id)}
|
|
||||||
onMouseLeave={() => setShowIcons(null)}
|
|
||||||
key={videoObj.id}
|
|
||||||
>
|
|
||||||
<IconsBox
|
|
||||||
sx={{
|
|
||||||
opacity: showIcons === videoObj.id ? 1 : 0,
|
|
||||||
zIndex: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{videoObj?.user === username && (
|
|
||||||
<Tooltip title="Edit playlist" placement="top">
|
|
||||||
<BlockIconContainer>
|
|
||||||
<EditIcon
|
|
||||||
onClick={() => {
|
|
||||||
dispatch(setEditPlaylist(videoObj));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</BlockIconContainer>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Tooltip title="Block user content" placement="top">
|
|
||||||
<BlockIconContainer>
|
|
||||||
<BlockIcon
|
|
||||||
onClick={() => {
|
|
||||||
blockUserFunc(videoObj?.user);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</BlockIconContainer>
|
|
||||||
</Tooltip>
|
|
||||||
</IconsBox>
|
|
||||||
<VideoCard
|
|
||||||
sx={{
|
|
||||||
cursor: !hasHash && "default",
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!hasHash) return;
|
dispatch(setEditPlaylist(videoObj));
|
||||||
navigate(
|
|
||||||
`/playlist/${videoObj?.user}/${videoObj?.id}`
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
>
|
|
||||||
<ResponsiveImage
|
|
||||||
src={videoObj?.image}
|
|
||||||
width={266}
|
|
||||||
height={150}
|
|
||||||
style={{
|
|
||||||
maxHeight: "50%",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<VideoCardTitle>{videoObj?.title}</VideoCardTitle>
|
|
||||||
<BottomParent>
|
|
||||||
<NameContainer
|
|
||||||
onClick={e => {
|
|
||||||
e.stopPropagation();
|
|
||||||
navigate(`/channel/${videoObj?.user}`);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Avatar
|
|
||||||
sx={{ height: 24, width: 24 }}
|
|
||||||
src={`/arbitrary/THUMBNAIL/${videoObj?.user}/qortal_avatar`}
|
|
||||||
alt={`${videoObj?.user}'s avatar`}
|
|
||||||
/>
|
|
||||||
<VideoCardName
|
|
||||||
sx={{
|
|
||||||
":hover": {
|
|
||||||
textDecoration: "underline",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{videoObj?.user}
|
|
||||||
</VideoCardName>
|
|
||||||
|
|
||||||
{videoObj?.created && (
|
|
||||||
<VideoUploadDate>
|
|
||||||
{formatDate(videoObj.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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<VideoCardCol
|
|
||||||
key={videoObj.id}
|
|
||||||
onMouseEnter={() => setShowIcons(videoObj.id)}
|
|
||||||
onMouseLeave={() => setShowIcons(null)}
|
|
||||||
>
|
|
||||||
<IconsBox
|
|
||||||
sx={{
|
|
||||||
opacity: showIcons === videoObj.id ? 1 : 0,
|
|
||||||
zIndex: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{videoObj?.user === username && (
|
|
||||||
<Tooltip title="Edit video properties" placement="top">
|
|
||||||
<BlockIconContainer>
|
|
||||||
<EditIcon
|
|
||||||
onClick={() => {
|
|
||||||
dispatch(setEditVideo(videoObj));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</BlockIconContainer>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Tooltip title="Block user content" placement="top">
|
|
||||||
<BlockIconContainer>
|
|
||||||
<BlockIcon
|
|
||||||
onClick={() => {
|
|
||||||
blockUserFunc(videoObj?.user);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</BlockIconContainer>
|
|
||||||
</Tooltip>
|
|
||||||
</IconsBox>
|
|
||||||
<VideoCard
|
|
||||||
onClick={() => {
|
|
||||||
navigate(`/video/${videoObj?.user}/${videoObj?.id}`);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<VideoCardImageContainer
|
|
||||||
width={266}
|
|
||||||
height={150}
|
|
||||||
videoImage={videoObj.videoImage}
|
|
||||||
frameImages={videoObj?.extracts || []}
|
|
||||||
/>
|
/>
|
||||||
{/* <ResponsiveImage
|
</BlockIconContainer>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Tooltip title="Block user content" placement="top">
|
||||||
|
<BlockIconContainer>
|
||||||
|
<BlockIcon
|
||||||
|
onClick={() => {
|
||||||
|
blockUserFunc(videoObj?.user);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</BlockIconContainer>
|
||||||
|
</Tooltip>
|
||||||
|
</IconsBox>
|
||||||
|
<VideoCard
|
||||||
|
sx={{
|
||||||
|
cursor: !hasHash && "default",
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
if (!hasHash) return;
|
||||||
|
navigate(`/playlist/${videoObj?.user}/${videoObj?.id}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ResponsiveImage
|
||||||
|
src={videoObj?.image}
|
||||||
|
width={266}
|
||||||
|
height={150}
|
||||||
|
style={{
|
||||||
|
maxHeight: "50%",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<VideoCardTitle>{videoObj?.title}</VideoCardTitle>
|
||||||
|
<BottomParent>
|
||||||
|
<NameContainer
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
navigate(`/channel/${videoObj?.user}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Avatar
|
||||||
|
sx={{ height: 24, width: 24 }}
|
||||||
|
src={`/arbitrary/THUMBNAIL/${videoObj?.user}/qortal_avatar`}
|
||||||
|
alt={`${videoObj?.user}'s avatar`}
|
||||||
|
/>
|
||||||
|
<VideoCardName
|
||||||
|
sx={{
|
||||||
|
":hover": {
|
||||||
|
textDecoration: "underline",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{videoObj?.user}
|
||||||
|
</VideoCardName>
|
||||||
|
|
||||||
|
{videoObj?.created && (
|
||||||
|
<VideoUploadDate>
|
||||||
|
{formatDate(videoObj.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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VideoCardCol
|
||||||
|
key={videoObj.id}
|
||||||
|
onMouseEnter={() => setShowIcons(videoObj.id)}
|
||||||
|
onMouseLeave={() => setShowIcons(null)}
|
||||||
|
>
|
||||||
|
<IconsBox
|
||||||
|
sx={{
|
||||||
|
opacity: showIcons === videoObj.id ? 1 : 0,
|
||||||
|
zIndex: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{videoObj?.user === username && (
|
||||||
|
<Tooltip title="Edit video properties" placement="top">
|
||||||
|
<BlockIconContainer>
|
||||||
|
<EditIcon
|
||||||
|
onClick={() => {
|
||||||
|
dispatch(setEditVideo(videoObj));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</BlockIconContainer>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Tooltip title="Block user content" placement="top">
|
||||||
|
<BlockIconContainer>
|
||||||
|
<BlockIcon
|
||||||
|
onClick={() => {
|
||||||
|
blockUserFunc(videoObj?.user);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</BlockIconContainer>
|
||||||
|
</Tooltip>
|
||||||
|
</IconsBox>
|
||||||
|
<VideoCard
|
||||||
|
onClick={() => {
|
||||||
|
navigate(`/video/${videoObj?.user}/${videoObj?.id}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<VideoCardImageContainer
|
||||||
|
width={266}
|
||||||
|
height={150}
|
||||||
|
videoImage={videoObj.videoImage}
|
||||||
|
frameImages={videoObj?.extracts || []}
|
||||||
|
/>
|
||||||
|
{/* <ResponsiveImage
|
||||||
src={videoObj.videoImage}
|
src={videoObj.videoImage}
|
||||||
width={266}
|
width={266}
|
||||||
height={150}
|
height={150}
|
||||||
/> */}
|
/> */}
|
||||||
<VideoCardTitle>{videoObj.title}</VideoCardTitle>
|
<VideoCardTitle>{videoObj.title}</VideoCardTitle>
|
||||||
<BottomParent>
|
<BottomParent>
|
||||||
<NameContainer
|
<NameContainer
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
navigate(`/channel/${videoObj?.user}`);
|
navigate(`/channel/${videoObj?.user}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
sx={{ height: 24, width: 24 }}
|
sx={{ height: 24, width: 24 }}
|
||||||
src={`/arbitrary/THUMBNAIL/${videoObj?.user}/qortal_avatar`}
|
src={`/arbitrary/THUMBNAIL/${videoObj?.user}/qortal_avatar`}
|
||||||
alt={`${videoObj?.user}'s avatar`}
|
alt={`${videoObj?.user}'s avatar`}
|
||||||
/>
|
/>
|
||||||
<VideoCardName
|
<VideoCardName
|
||||||
sx={{
|
sx={{
|
||||||
":hover": {
|
":hover": {
|
||||||
textDecoration: "underline",
|
textDecoration: "underline",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{videoObj?.user}
|
{videoObj?.user}
|
||||||
</VideoCardName>
|
</VideoCardName>
|
||||||
<SubscribeButton name={videoObj?.user} />
|
</NameContainer>
|
||||||
</NameContainer>
|
{videoObj?.created && (
|
||||||
{videoObj?.created && (
|
<VideoUploadDate>
|
||||||
<VideoUploadDate>
|
{formatDate(videoObj.created)}
|
||||||
{formatDate(videoObj.created)}
|
</VideoUploadDate>
|
||||||
</VideoUploadDate>
|
)}
|
||||||
)}
|
</BottomParent>
|
||||||
</BottomParent>
|
</VideoCard>
|
||||||
</VideoCard>
|
</VideoCardCol>
|
||||||
</VideoCardCol>
|
);
|
||||||
);
|
})}
|
||||||
})}
|
</VideoCardContainer>
|
||||||
</VideoCardContainer>
|
|
||||||
|
|
||||||
<LazyLoad
|
|
||||||
onLoadMore={getVideosHandler}
|
|
||||||
isLoading={isLoading}
|
|
||||||
></LazyLoad>
|
|
||||||
</Box>
|
|
||||||
</ProductManagerRow>
|
|
||||||
</Grid>
|
|
||||||
<FiltersCol item xs={0} lg={3} xl={2}>
|
|
||||||
<ListSuperLikeContainer />
|
|
||||||
</FiltersCol>
|
|
||||||
</Grid>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default VideoList;
|
||||||
|
@ -9,7 +9,7 @@ import { useDispatch, useSelector } from "react-redux";
|
|||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { setIsLoadingGlobal } from "../../state/features/globalSlice";
|
import { setIsLoadingGlobal } from "../../state/features/globalSlice";
|
||||||
import { Avatar, Box, Typography, useTheme } from "@mui/material";
|
import { Avatar, Box, Typography, useTheme } from "@mui/material";
|
||||||
import { VideoPlayer } from "../../components/common/VideoPlayer";
|
import { VideoPlayer } from "../../components/common/VideoPlayer/VideoPlayer.tsx";
|
||||||
import { RootState } from "../../state/store";
|
import { RootState } from "../../state/store";
|
||||||
import { addToHashMap } from "../../state/features/videoSlice";
|
import { addToHashMap } from "../../state/features/videoSlice";
|
||||||
import AttachFileIcon from "@mui/icons-material/AttachFile";
|
import AttachFileIcon from "@mui/icons-material/AttachFile";
|
||||||
@ -440,6 +440,7 @@ export const PlaylistContent = () => {
|
|||||||
nextVideo={nextVideo}
|
nextVideo={nextVideo}
|
||||||
onEnd={onEndVideo}
|
onEnd={onEndVideo}
|
||||||
autoPlay={doAutoPlay}
|
autoPlay={doAutoPlay}
|
||||||
|
customStyle={{ aspectRatio: "16/9" }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{playlistData && (
|
{playlistData && (
|
||||||
|
@ -9,7 +9,7 @@ import { useDispatch, useSelector } from "react-redux";
|
|||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { setIsLoadingGlobal } from "../../state/features/globalSlice";
|
import { setIsLoadingGlobal } from "../../state/features/globalSlice";
|
||||||
import { Avatar, Box, Typography, useTheme } from "@mui/material";
|
import { Avatar, Box, Typography, useTheme } from "@mui/material";
|
||||||
import { VideoPlayer } from "../../components/common/VideoPlayer";
|
import { VideoPlayer } from "../../components/common/VideoPlayer/VideoPlayer.tsx";
|
||||||
import { RootState } from "../../state/store";
|
import { RootState } from "../../state/store";
|
||||||
import { addToHashMap } from "../../state/features/videoSlice";
|
import { addToHashMap } from "../../state/features/videoSlice";
|
||||||
import AttachFileIcon from "@mui/icons-material/AttachFile";
|
import AttachFileIcon from "@mui/icons-material/AttachFile";
|
||||||
@ -52,7 +52,10 @@ import {
|
|||||||
QTUBE_VIDEO_BASE,
|
QTUBE_VIDEO_BASE,
|
||||||
SUPER_LIKE_BASE,
|
SUPER_LIKE_BASE,
|
||||||
} from "../../constants/Identifiers.ts";
|
} from "../../constants/Identifiers.ts";
|
||||||
import { minPriceSuperlike } from "../../constants/Misc.ts";
|
import {
|
||||||
|
minPriceSuperlike,
|
||||||
|
titleFormatterOnSave,
|
||||||
|
} from "../../constants/Misc.ts";
|
||||||
import { SubscribeButton } from "../../components/common/SubscribeButton.tsx";
|
import { SubscribeButton } from "../../components/common/SubscribeButton.tsx";
|
||||||
|
|
||||||
export function isTimestampWithinRange(resTimestamp, resCreated) {
|
export function isTimestampWithinRange(resTimestamp, resCreated) {
|
||||||
@ -186,12 +189,12 @@ export const VideoContent = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return videoData.title + ext;
|
return (videoData.title + ext).replace(titleFormatterOnSave, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
// otherwise use QDN filename if applicable
|
// otherwise use QDN filename if applicable
|
||||||
if (videoData?.filename) {
|
if (videoData?.filename) {
|
||||||
return videoData.filename;
|
return videoData.filename.replace(titleFormatterOnSave, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this was the previous value, leaving here as the
|
// TODO: this was the previous value, leaving here as the
|
||||||
@ -360,6 +363,9 @@ export const VideoContent = () => {
|
|||||||
if (!nameAddress || !id) return;
|
if (!nameAddress || !id) return;
|
||||||
getComments(id, nameAddress);
|
getComments(id, nameAddress);
|
||||||
}, [getComments, id, nameAddress]);
|
}, [getComments, id, nameAddress]);
|
||||||
|
const subList = useSelector(
|
||||||
|
(state: RootState) => state.video.subscriptionList
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -374,7 +380,6 @@ export const VideoContent = () => {
|
|||||||
sx={{
|
sx={{
|
||||||
marginBottom: "30px",
|
marginBottom: "30px",
|
||||||
width: "70vw",
|
width: "70vw",
|
||||||
height: "70vw",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{videoReference && (
|
{videoReference && (
|
||||||
@ -385,15 +390,15 @@ export const VideoContent = () => {
|
|||||||
user={name}
|
user={name}
|
||||||
jsonId={id}
|
jsonId={id}
|
||||||
poster={videoCover || ""}
|
poster={videoCover || ""}
|
||||||
|
customStyle={{ aspectRatio: "16/9" }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Spacer height="15px" />
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
display: "grid",
|
display: "grid",
|
||||||
gridTemplateColumns: "1fr 1fr",
|
gridTemplateColumns: "1fr 1fr",
|
||||||
|
marginTop: "15px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box>
|
<Box>
|
||||||
|
30
src/state/features/settingsSlice.ts
Normal file
30
src/state/features/settingsSlice.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||||
|
import { subscriptionTabValue } from "../../constants/Misc.ts";
|
||||||
|
|
||||||
|
type StretchVideoType = "contain" | "fill" | "cover" | "none" | "scale-down";
|
||||||
|
interface settingsState {
|
||||||
|
selectedTab: string;
|
||||||
|
stretchVideoSetting: StretchVideoType;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: settingsState = {
|
||||||
|
selectedTab: subscriptionTabValue,
|
||||||
|
stretchVideoSetting: "contain",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const settingsSlice = createSlice({
|
||||||
|
name: "settings",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setHomePageSelectedTab: (state, action) => {
|
||||||
|
state.selectedTab = action.payload;
|
||||||
|
},
|
||||||
|
setStretchVideoSetting: (state, action) => {
|
||||||
|
state.stretchVideoSetting = action.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { setHomePageSelectedTab } = settingsSlice.actions;
|
||||||
|
|
||||||
|
export default settingsSlice.reducer;
|
@ -1,23 +1,23 @@
|
|||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||||
import { RootState } from '../store'
|
|
||||||
|
|
||||||
|
|
||||||
interface GlobalState {
|
interface GlobalState {
|
||||||
videos: Video[]
|
videos: Video[];
|
||||||
filteredVideos: Video[]
|
filteredVideos: Video[];
|
||||||
hashMapVideos: Record<string, Video>
|
hashMapVideos: Record<string, Video>;
|
||||||
hashMapSuperlikes: Record<string, any>
|
hashMapSuperlikes: Record<string, any>;
|
||||||
countNewVideos: number
|
countNewVideos: number;
|
||||||
isFiltering: boolean
|
isFiltering: boolean;
|
||||||
filterValue: string
|
filterValue: string;
|
||||||
filterType: string
|
filterType: string;
|
||||||
filterSearch: string
|
filterSearch: string;
|
||||||
filterName: string
|
filterName: string;
|
||||||
selectedCategoryVideos: any
|
selectedCategoryVideos: any;
|
||||||
selectedSubCategoryVideos: any
|
selectedSubCategoryVideos: any;
|
||||||
editVideoProperties: any
|
editVideoProperties: any;
|
||||||
editPlaylistProperties: any
|
editPlaylistProperties: any;
|
||||||
|
subscriptionList: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: GlobalState = {
|
const initialState: GlobalState = {
|
||||||
videos: [],
|
videos: [],
|
||||||
filteredVideos: [],
|
filteredVideos: [],
|
||||||
@ -25,159 +25,168 @@ const initialState: GlobalState = {
|
|||||||
hashMapSuperlikes: {},
|
hashMapSuperlikes: {},
|
||||||
countNewVideos: 0,
|
countNewVideos: 0,
|
||||||
isFiltering: false,
|
isFiltering: false,
|
||||||
filterValue: '',
|
filterValue: "",
|
||||||
filterType: 'videos',
|
filterType: "videos",
|
||||||
filterSearch: '',
|
filterSearch: "",
|
||||||
filterName: '',
|
filterName: "",
|
||||||
selectedCategoryVideos: null,
|
selectedCategoryVideos: null,
|
||||||
selectedSubCategoryVideos: null,
|
selectedSubCategoryVideos: null,
|
||||||
editVideoProperties: null,
|
editVideoProperties: null,
|
||||||
editPlaylistProperties: null
|
editPlaylistProperties: null,
|
||||||
}
|
subscriptionList: [],
|
||||||
|
};
|
||||||
|
|
||||||
export interface Video {
|
export interface Video {
|
||||||
title: string
|
title: string;
|
||||||
description: string
|
description: string;
|
||||||
created: number | string
|
created: number | string;
|
||||||
user: string
|
user: string;
|
||||||
service?: string
|
service?: string;
|
||||||
videoImage?: string
|
videoImage?: string;
|
||||||
id: string
|
id: string;
|
||||||
category?: string
|
category?: string;
|
||||||
categoryName?: string
|
categoryName?: string;
|
||||||
tags?: string[]
|
tags?: string[];
|
||||||
updated?: number | string
|
updated?: number | string;
|
||||||
isValid?: boolean
|
isValid?: boolean;
|
||||||
code?: string
|
code?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const videoSlice = createSlice({
|
export const videoSlice = createSlice({
|
||||||
name: 'video',
|
name: "video",
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
setEditVideo: (state, action) => {
|
setEditVideo: (state, action) => {
|
||||||
state.editVideoProperties = action.payload
|
state.editVideoProperties = action.payload;
|
||||||
},
|
},
|
||||||
setEditPlaylist: (state, action) => {
|
setEditPlaylist: (state, action) => {
|
||||||
state.editPlaylistProperties = action.payload
|
state.editPlaylistProperties = action.payload;
|
||||||
},
|
},
|
||||||
changeFilterType: (state, action) => {
|
changeFilterType: (state, action) => {
|
||||||
state.filterType = action.payload
|
state.filterType = action.payload;
|
||||||
},
|
},
|
||||||
changefilterSearch: (state, action) => {
|
changefilterSearch: (state, action) => {
|
||||||
state.filterSearch = action.payload
|
state.filterSearch = action.payload;
|
||||||
},
|
},
|
||||||
changefilterName: (state, action) => {
|
changefilterName: (state, action) => {
|
||||||
state.filterName = action.payload
|
state.filterName = action.payload;
|
||||||
},
|
},
|
||||||
changeSelectedCategoryVideos: (state, action) => {
|
changeSelectedCategoryVideos: (state, action) => {
|
||||||
state.selectedCategoryVideos = action.payload
|
state.selectedCategoryVideos = action.payload;
|
||||||
},
|
},
|
||||||
changeSelectedSubCategoryVideos: (state, action) => {
|
changeSelectedSubCategoryVideos: (state, action) => {
|
||||||
state.selectedSubCategoryVideos = action.payload
|
state.selectedSubCategoryVideos = action.payload;
|
||||||
},
|
},
|
||||||
setCountNewVideos: (state, action) => {
|
setCountNewVideos: (state, action) => {
|
||||||
state.countNewVideos = action.payload
|
state.countNewVideos = action.payload;
|
||||||
},
|
},
|
||||||
addVideos: (state, action) => {
|
addVideos: (state, action) => {
|
||||||
state.videos = action.payload
|
state.videos = action.payload;
|
||||||
},
|
},
|
||||||
addFilteredVideos: (state, action) => {
|
addFilteredVideos: (state, action) => {
|
||||||
state.filteredVideos = action.payload
|
state.filteredVideos = action.payload;
|
||||||
},
|
},
|
||||||
removeVideo: (state, action) => {
|
removeVideo: (state, action) => {
|
||||||
const idToDelete = action.payload
|
const idToDelete = action.payload;
|
||||||
state.videos = state.videos.filter((item) => item.id !== idToDelete)
|
state.videos = state.videos.filter(item => item.id !== idToDelete);
|
||||||
state.filteredVideos = state.filteredVideos.filter(
|
state.filteredVideos = state.filteredVideos.filter(
|
||||||
(item) => item.id !== idToDelete
|
item => item.id !== idToDelete
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
addVideoToBeginning: (state, action) => {
|
addVideoToBeginning: (state, action) => {
|
||||||
state.videos.unshift(action.payload)
|
state.videos.unshift(action.payload);
|
||||||
},
|
},
|
||||||
clearVideoList: (state) => {
|
clearVideoList: state => {
|
||||||
state.videos = []
|
state.videos = [];
|
||||||
},
|
},
|
||||||
updateVideo: (state, action) => {
|
updateVideo: (state, action) => {
|
||||||
const { id } = action.payload
|
const { id } = action.payload;
|
||||||
const index = state.videos.findIndex((video) => video.id === id)
|
const index = state.videos.findIndex(video => video.id === id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
state.videos[index] = { ...action.payload }
|
state.videos[index] = { ...action.payload };
|
||||||
}
|
}
|
||||||
const index2 = state.filteredVideos.findIndex((video) => video.id === id)
|
const index2 = state.filteredVideos.findIndex(video => video.id === id);
|
||||||
if (index2 !== -1) {
|
if (index2 !== -1) {
|
||||||
state.filteredVideos[index2] = { ...action.payload }
|
state.filteredVideos[index2] = { ...action.payload };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addToHashMap: (state, action) => {
|
addToHashMap: (state, action) => {
|
||||||
const video = action.payload
|
const video = action.payload;
|
||||||
state.hashMapVideos[video.id + '-' + video.user] = video
|
state.hashMapVideos[video.id + "-" + video.user] = video;
|
||||||
},
|
},
|
||||||
addtoHashMapSuperlikes: (state, action) => {
|
addtoHashMapSuperlikes: (state, action) => {
|
||||||
const superlike = action.payload
|
const superlike = action.payload;
|
||||||
state.hashMapSuperlikes[superlike.identifier] = superlike
|
state.hashMapSuperlikes[superlike.identifier] = superlike;
|
||||||
},
|
},
|
||||||
updateInHashMap: (state, action) => {
|
updateInHashMap: (state, action) => {
|
||||||
const { id, user } = action.payload
|
const { id, user } = action.payload;
|
||||||
const video = action.payload
|
const video = action.payload;
|
||||||
state.hashMapVideos[id + '-' + user] = { ...video }
|
state.hashMapVideos[id + "-" + user] = { ...video };
|
||||||
},
|
},
|
||||||
removeFromHashMap: (state, action) => {
|
removeFromHashMap: (state, action) => {
|
||||||
const idToDelete = action.payload
|
const idToDelete = action.payload;
|
||||||
delete state.hashMapVideos[idToDelete]
|
delete state.hashMapVideos[idToDelete];
|
||||||
},
|
},
|
||||||
addArrayToHashMap: (state, action) => {
|
addArrayToHashMap: (state, action) => {
|
||||||
const videos = action.payload
|
const videos = action.payload;
|
||||||
videos.forEach((video: Video) => {
|
videos.forEach((video: Video) => {
|
||||||
state.hashMapVideos[video.id + '-' + video.user] = video
|
state.hashMapVideos[video.id + "-" + video.user] = video;
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
upsertVideos: (state, action) => {
|
upsertVideos: (state, action) => {
|
||||||
action.payload.forEach((video: Video) => {
|
action.payload.forEach((video: Video) => {
|
||||||
const index = state.videos.findIndex((p) => p.id === video.id)
|
const index = state.videos.findIndex(p => p.id === video.id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
state.videos[index] = video
|
state.videos[index] = video;
|
||||||
} else {
|
} else {
|
||||||
state.videos.push(video)
|
state.videos.push(video);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
upsertFilteredVideos: (state, action) => {
|
upsertFilteredVideos: (state, action) => {
|
||||||
action.payload.forEach((video: Video) => {
|
action.payload.forEach((video: Video) => {
|
||||||
const index = state.filteredVideos.findIndex((p) => p.id === video.id)
|
const index = state.filteredVideos.findIndex(p => p.id === video.id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
state.filteredVideos[index] = video
|
state.filteredVideos[index] = video;
|
||||||
} else {
|
} else {
|
||||||
state.filteredVideos.push(video)
|
state.filteredVideos.push(video);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
upsertVideosBeginning: (state, action) => {
|
upsertVideosBeginning: (state, action) => {
|
||||||
action.payload.reverse().forEach((video: Video) => {
|
action.payload.reverse().forEach((video: Video) => {
|
||||||
const index = state.videos.findIndex((p) => p.id === video.id)
|
const index = state.videos.findIndex(p => p.id === video.id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
state.videos[index] = video
|
state.videos[index] = video;
|
||||||
} else {
|
} else {
|
||||||
state.videos.unshift(video)
|
state.videos.unshift(video);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
setIsFiltering: (state, action) => {
|
setIsFiltering: (state, action) => {
|
||||||
state.isFiltering = action.payload
|
state.isFiltering = action.payload;
|
||||||
},
|
},
|
||||||
setFilterValue: (state, action) => {
|
setFilterValue: (state, action) => {
|
||||||
state.filterValue = action.payload
|
state.filterValue = action.payload;
|
||||||
},
|
},
|
||||||
blockUser: (state, action) => {
|
blockUser: (state, action) => {
|
||||||
const username = action.payload
|
const username = action.payload;
|
||||||
|
|
||||||
state.videos = state.videos.filter((item) => item.user !== username)
|
state.videos = state.videos.filter(item => item.user !== username);
|
||||||
|
},
|
||||||
}
|
subscribe: (state, action: PayloadAction<string>) => {
|
||||||
}
|
const currentSubscriptions = state.subscriptionList;
|
||||||
})
|
if (!currentSubscriptions.includes(action.payload)) {
|
||||||
|
state.subscriptionList = [...currentSubscriptions, action.payload];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unSubscribe: (state, action) => {
|
||||||
|
state.subscriptionList = state.subscriptionList.filter(
|
||||||
|
item => item !== action.payload
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
setCountNewVideos,
|
setCountNewVideos,
|
||||||
@ -204,8 +213,9 @@ export const {
|
|||||||
blockUser,
|
blockUser,
|
||||||
setEditVideo,
|
setEditVideo,
|
||||||
setEditPlaylist,
|
setEditPlaylist,
|
||||||
addtoHashMapSuperlikes
|
addtoHashMapSuperlikes,
|
||||||
} = videoSlice.actions
|
subscribe,
|
||||||
|
unSubscribe,
|
||||||
export default videoSlice.reducer
|
} = videoSlice.actions;
|
||||||
|
|
||||||
|
export default videoSlice.reducer;
|
||||||
|
@ -1,27 +1,54 @@
|
|||||||
import { configureStore } from '@reduxjs/toolkit'
|
import { combineReducers, configureStore } from "@reduxjs/toolkit";
|
||||||
import notificationsReducer from './features/notificationsSlice'
|
import notificationsReducer from "./features/notificationsSlice";
|
||||||
import authReducer from './features/authSlice'
|
import authReducer from "./features/authSlice";
|
||||||
import globalReducer from './features/globalSlice'
|
import globalReducer from "./features/globalSlice";
|
||||||
import videoReducer from './features/videoSlice'
|
import videoReducer from "./features/videoSlice";
|
||||||
|
import settingsReducer from "./features/settingsSlice";
|
||||||
|
import {
|
||||||
|
persistReducer,
|
||||||
|
FLUSH,
|
||||||
|
REHYDRATE,
|
||||||
|
PAUSE,
|
||||||
|
PERSIST,
|
||||||
|
PURGE,
|
||||||
|
REGISTER,
|
||||||
|
} from "redux-persist";
|
||||||
|
import storage from "redux-persist/lib/storage";
|
||||||
|
|
||||||
|
const persistVideoConfig = {
|
||||||
|
key: "video",
|
||||||
|
version: 1,
|
||||||
|
storage,
|
||||||
|
whitelist: ["subscriptionList"],
|
||||||
|
};
|
||||||
|
|
||||||
|
const persistSettingsConfig = {
|
||||||
|
key: "settings",
|
||||||
|
version: 1,
|
||||||
|
storage,
|
||||||
|
};
|
||||||
|
|
||||||
|
const reducer = combineReducers({
|
||||||
|
notifications: notificationsReducer,
|
||||||
|
auth: authReducer,
|
||||||
|
global: globalReducer,
|
||||||
|
video: persistReducer(persistVideoConfig, videoReducer),
|
||||||
|
settings: persistReducer(persistSettingsConfig, settingsReducer),
|
||||||
|
});
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer,
|
||||||
notifications: notificationsReducer,
|
middleware: getDefaultMiddleware =>
|
||||||
auth: authReducer,
|
|
||||||
global: globalReducer,
|
|
||||||
video: videoReducer,
|
|
||||||
},
|
|
||||||
middleware: (getDefaultMiddleware) =>
|
|
||||||
getDefaultMiddleware({
|
getDefaultMiddleware({
|
||||||
serializableCheck: false
|
serializableCheck: false,
|
||||||
}),
|
}),
|
||||||
preloadedState: undefined // optional, can be any valid state object
|
preloadedState: undefined, // optional, can be any valid state object
|
||||||
})
|
});
|
||||||
|
|
||||||
// Define the RootState type, which is the type of the entire Redux state tree.
|
// Define the RootState type, which is the type of the entire Redux state tree.
|
||||||
// This is useful when you need to access the state in a component or elsewhere.
|
// This is useful when you need to access the state in a component or elsewhere.
|
||||||
export type RootState = ReturnType<typeof store.getState>
|
export type RootState = ReturnType<typeof store.getState>;
|
||||||
|
|
||||||
// Define the AppDispatch type, which is the type of the Redux store's dispatch function.
|
// Define the AppDispatch type, which is the type of the Redux store's dispatch function.
|
||||||
// This is useful when you need to dispatch an action in a component or elsewhere.
|
// This is useful when you need to dispatch an action in a component or elsewhere.
|
||||||
export type AppDispatch = typeof store.dispatch
|
export type AppDispatch = typeof store.dispatch;
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
setSuperlikesAll,
|
setSuperlikesAll,
|
||||||
setUserAvatarHash,
|
setUserAvatarHash,
|
||||||
} from "../state/features/globalSlice";
|
} from "../state/features/globalSlice";
|
||||||
import { VideoPlayerGlobal } from "../components/common/VideoPlayerGlobal";
|
import { VideoPlayerGlobal } from "../components/common/VideoPlayer/VideoPlayerGlobal.tsx";
|
||||||
import { Rnd } from "react-rnd";
|
import { Rnd } from "react-rnd";
|
||||||
import { RequestQueue } from "../utils/queue";
|
import { RequestQueue } from "../utils/queue";
|
||||||
import { EditVideo } from "../components/EditVideo/EditVideo";
|
import { EditVideo } from "../components/EditVideo/EditVideo";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user