mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-03-11 21:27:05 +00:00
Merge branch 'development' into refactor/install-addons-button-stream-list
This commit is contained in:
commit
a7d2e9d13e
69 changed files with 947 additions and 367 deletions
73
package-lock.json
generated
73
package-lock.json
generated
|
|
@ -1,20 +1,20 @@
|
|||
{
|
||||
"name": "stremio",
|
||||
"version": "5.0.0-beta.13",
|
||||
"version": "5.0.0-beta.15",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "stremio",
|
||||
"version": "5.0.0-beta.13",
|
||||
"version": "5.0.0-beta.15",
|
||||
"license": "gpl-2.0",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.26.0",
|
||||
"@sentry/browser": "8.42.0",
|
||||
"@stremio/stremio-colors": "5.2.0",
|
||||
"@stremio/stremio-core-web": "0.48.1",
|
||||
"@stremio/stremio-icons": "5.4.0",
|
||||
"@stremio/stremio-video": "0.0.46",
|
||||
"@stremio/stremio-core-web": "0.48.3",
|
||||
"@stremio/stremio-icons": "5.4.1",
|
||||
"@stremio/stremio-video": "0.0.48",
|
||||
"a-color-picker": "1.2.1",
|
||||
"bowser": "2.11.0",
|
||||
"buffer": "6.0.3",
|
||||
|
|
@ -67,6 +67,7 @@
|
|||
"postcss-loader": "8.1.1",
|
||||
"readdirp": "4.0.2",
|
||||
"terser-webpack-plugin": "5.3.10",
|
||||
"thread-loader": "^4.0.4",
|
||||
"ts-loader": "^9.5.1",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript-eslint": "^8.17.0",
|
||||
|
|
@ -1869,7 +1870,6 @@
|
|||
"version": "7.26.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
|
||||
"integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
|
|
@ -1880,8 +1880,7 @@
|
|||
"node_modules/@babel/runtime/node_modules/regenerator-runtime": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||
"license": "MIT"
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.25.9",
|
||||
|
|
@ -3261,7 +3260,6 @@
|
|||
"version": "8.42.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.42.0.tgz",
|
||||
"integrity": "sha512-xzgRI0wglKYsPrna574w1t38aftuvo44gjOKFvPNGPnYfiW9y4m+64kUz3JFbtanvOrKPcaITpdYiB4DeJXEbA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/core": "8.42.0"
|
||||
},
|
||||
|
|
@ -3273,7 +3271,6 @@
|
|||
"version": "8.42.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.42.0.tgz",
|
||||
"integrity": "sha512-dkIw5Wdukwzngg5gNJ0QcK48LyJaMAnBspqTqZ3ItR01STi6Z+6+/Bt5XgmrvDgRD+FNBinflc5zMmfdFXXhvw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/core": "8.42.0"
|
||||
},
|
||||
|
|
@ -3285,7 +3282,6 @@
|
|||
"version": "8.42.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.42.0.tgz",
|
||||
"integrity": "sha512-oNcJEBlDfXnRFYC5Mxj5fairyZHNqlnU4g8kPuztB9G5zlsyLgWfPxzcn1ixVQunth2/WZRklDi4o1ZfyHww7w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry-internal/browser-utils": "8.42.0",
|
||||
"@sentry/core": "8.42.0"
|
||||
|
|
@ -3298,7 +3294,6 @@
|
|||
"version": "8.42.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.42.0.tgz",
|
||||
"integrity": "sha512-XrPErqVhPsPh/oFLVKvz7Wb+Fi2J1zCPLeZCxWqFuPWI2agRyLVu0KvqJyzSpSrRAEJC/XFzuSVILlYlXXSfgA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry-internal/replay": "8.42.0",
|
||||
"@sentry/core": "8.42.0"
|
||||
|
|
@ -3311,7 +3306,6 @@
|
|||
"version": "8.42.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.42.0.tgz",
|
||||
"integrity": "sha512-lStrEk609KJHwXfDrOgoYVVoFFExixHywxSExk7ZDtwj2YPv6r6Y1gogvgr7dAZj7jWzadHkxZ33l9EOSJBfug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry-internal/browser-utils": "8.42.0",
|
||||
"@sentry-internal/feedback": "8.42.0",
|
||||
|
|
@ -3327,7 +3321,6 @@
|
|||
"version": "8.42.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.42.0.tgz",
|
||||
"integrity": "sha512-ac6O3pgoIbU6rpwz6LlwW0wp3/GAHuSI0C5IsTgIY6baN8rOBnlAtG6KrHDDkGmUQ2srxkDJu9n1O6Td3cBCqw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.18"
|
||||
}
|
||||
|
|
@ -3375,13 +3368,12 @@
|
|||
"node_modules/@stremio/stremio-colors": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-colors/-/stremio-colors-5.2.0.tgz",
|
||||
"integrity": "sha512-dYlPgu9W/H7c9s1zmW5tiDnRenaUa4Hg1QCyOg1lhOcgSfM/bVTi5nnqX+IfvGTTUNA0zgzh8hI3o3miwnZxTg==",
|
||||
"license": "MIT"
|
||||
"integrity": "sha512-dYlPgu9W/H7c9s1zmW5tiDnRenaUa4Hg1QCyOg1lhOcgSfM/bVTi5nnqX+IfvGTTUNA0zgzh8hI3o3miwnZxTg=="
|
||||
},
|
||||
"node_modules/@stremio/stremio-core-web": {
|
||||
"version": "0.48.1",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.48.1.tgz",
|
||||
"integrity": "sha512-bdWxBuuOOC0NdG1Mg60lEhpK7Bw/Ea6D89bRcvIvM3WnJrUpGA4jbx4xWj3KQRM08PM3WWCY9/FzctlWCxFMRg==",
|
||||
"version": "0.48.3",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.48.3.tgz",
|
||||
"integrity": "sha512-JL8pOLOEVACYG+33Dtp/mrB2/vuc7RoYZdxX1BQa5MPR8EzsODjpvL5uETmdxo/swgtMZyx2A6/e1B53eKA4oQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.24.1"
|
||||
}
|
||||
|
|
@ -3405,9 +3397,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@stremio/stremio-icons": {
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-icons/-/stremio-icons-5.4.0.tgz",
|
||||
"integrity": "sha512-rRWNER+wLgMjxd6sKT0MMq4lzXDOobY3GNdT3NDeeymBtB/CD0YmYqQuUOyYDjEZ1btIbNaniUOBoPW9d3ZQ8A==",
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-icons/-/stremio-icons-5.4.1.tgz",
|
||||
"integrity": "sha512-7g4JP7tPRT1UDZxbuH/Urq7fc6te3joy8qyx/NGWIW7wO169TTISO7ZWdejzESvUVgZ/7i6rzkRmXZ3wefWcBg==",
|
||||
"workspaces": [
|
||||
"react",
|
||||
"react-native",
|
||||
|
|
@ -3416,9 +3408,10 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@stremio/stremio-video": {
|
||||
"version": "0.0.46",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-video/-/stremio-video-0.0.46.tgz",
|
||||
"integrity": "sha512-U15CGB6CrUZKq3IKcEouAEH2RQoLy2+BI/hDStEYEACxlRlFaavKPI2opl37muh9TY089RnZVBYAM3yDidBZdg==",
|
||||
"version": "0.0.48",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-video/-/stremio-video-0.0.48.tgz",
|
||||
"integrity": "sha512-6ALGXCZC4NPsfhPcrwFWQzvH6UMMRsgSkHetnOhv9WmZ5ubiyUdbBzj9atGiGuuQz8pRcze66ztrub+dsaQbpw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer": "6.0.3",
|
||||
"color": "4.2.3",
|
||||
|
|
@ -10161,6 +10154,13 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-parse-better-errors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
|
||||
"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-parse-even-better-errors": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||
|
|
@ -13938,6 +13938,29 @@
|
|||
"node": ">=0.2.6"
|
||||
}
|
||||
},
|
||||
"node_modules/thread-loader": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/thread-loader/-/thread-loader-4.0.4.tgz",
|
||||
"integrity": "sha512-tXagu6Hivd03wB2tiS1bqvw345sc7mKei32EgpYpq31ZLes9FN0mEK2nKzXLRFgwt3PsBB0E/MZDp159rDoqwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"json-parse-better-errors": "^1.0.2",
|
||||
"loader-runner": "^4.1.0",
|
||||
"neo-async": "^2.6.2",
|
||||
"schema-utils": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"webpack": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/thunky": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
|
||||
|
|
|
|||
9
package.json
Executable file → Normal file
9
package.json
Executable file → Normal file
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "stremio",
|
||||
"displayName": "Stremio",
|
||||
"version": "5.0.0-beta.13",
|
||||
"version": "5.0.0-beta.15",
|
||||
"author": "Smart Code OOD",
|
||||
"private": true,
|
||||
"license": "gpl-2.0",
|
||||
|
|
@ -16,9 +16,9 @@
|
|||
"@babel/runtime": "7.26.0",
|
||||
"@sentry/browser": "8.42.0",
|
||||
"@stremio/stremio-colors": "5.2.0",
|
||||
"@stremio/stremio-core-web": "0.48.1",
|
||||
"@stremio/stremio-icons": "5.4.0",
|
||||
"@stremio/stremio-video": "0.0.46",
|
||||
"@stremio/stremio-core-web": "0.48.3",
|
||||
"@stremio/stremio-icons": "5.4.1",
|
||||
"@stremio/stremio-video": "0.0.48",
|
||||
"a-color-picker": "1.2.1",
|
||||
"bowser": "2.11.0",
|
||||
"buffer": "6.0.3",
|
||||
|
|
@ -71,6 +71,7 @@
|
|||
"postcss-loader": "8.1.1",
|
||||
"readdirp": "4.0.2",
|
||||
"terser-webpack-plugin": "5.3.10",
|
||||
"thread-loader": "^4.0.4",
|
||||
"ts-loader": "^9.5.1",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript-eslint": "^8.17.0",
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
--quaternary-accent-color: rgba(18, 69, 166, 1);
|
||||
--overlay-color: rgba(255, 255, 255, 0.05);
|
||||
--modal-background-color: rgba(15, 13, 32, 1);
|
||||
--outer-glow: 0px 0px 30px rgba(123, 91, 245, 0.37);
|
||||
--outer-glow: 0px 0px 15px rgba(123, 91, 245, 0.37);
|
||||
--border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
|
|
@ -112,7 +112,7 @@ html {
|
|||
left: auto;
|
||||
z-index: 1;
|
||||
padding: 0 calc(0.5 * var(--horizontal-nav-bar-size));
|
||||
overflow-y: auto;
|
||||
overflow: visible;
|
||||
scrollbar-width: none;
|
||||
pointer-events: none;
|
||||
|
||||
|
|
|
|||
|
|
@ -87,7 +87,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: @xsmall) {
|
||||
@media only screen and (min-width: @small) and (orientation: portait) {
|
||||
.bottom-sheet {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: @xsmall) and (orientation: landscape) {
|
||||
.bottom-sheet {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
outline-width: var(--focus-outline-size);
|
||||
outline-color: @color-surface-light5;
|
||||
outline-offset: calc(-1 * var(--focus-outline-size));
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
cursor: pointer;
|
||||
|
||||
&:focus {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 0.5rem;
|
||||
border: thin solid @color-surface-light5-20;
|
||||
pointer-events: none;
|
||||
|
||||
.transparent-label {
|
||||
|
|
|
|||
|
|
@ -19,26 +19,30 @@
|
|||
flex-direction: column;
|
||||
align-items: center;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
|
||||
.modal-dialog-content {
|
||||
.body-container {
|
||||
overflow-y: visible;
|
||||
}
|
||||
|
||||
|
||||
.image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin-top: -10rem;
|
||||
position: absolute;
|
||||
top: -10rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
object-fit: cover;
|
||||
width: 30rem;
|
||||
height: 30rem;
|
||||
}
|
||||
|
||||
|
||||
.info-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 2.5rem;
|
||||
padding: 1rem 4rem;
|
||||
margin-top: -7rem;
|
||||
|
||||
padding: 10rem 4rem 0;
|
||||
|
||||
.title-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -50,7 +54,7 @@
|
|||
text-align: center;
|
||||
padding: 0 6rem;
|
||||
}
|
||||
|
||||
|
||||
.label {
|
||||
color: var(--primary-foreground-color);
|
||||
font-size: 1rem;
|
||||
|
|
@ -58,7 +62,7 @@
|
|||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.addon-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -76,19 +80,19 @@
|
|||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.action-button {
|
||||
background-color: var(--primary-foreground-color);
|
||||
border: 2px solid var(--primary-foreground-color);
|
||||
padding: 0.8rem 2rem;
|
||||
border-radius: 2rem;
|
||||
|
||||
|
||||
.button-label {
|
||||
color: var(--primary-accent-color);
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
|
@ -96,15 +100,34 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
@media (orientation: landscape) and (max-height: @minimum) {
|
||||
.event-modal {
|
||||
.modal-dialog-container {
|
||||
.modal-dialog-content {
|
||||
.image {
|
||||
height: 125%;
|
||||
width: 125%;
|
||||
overflow-y: auto;
|
||||
|
||||
.body-container {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.image {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.info-container {
|
||||
padding: 1rem 4rem 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
.event-modal {
|
||||
.modal-dialog-container {
|
||||
.modal-dialog-content {
|
||||
.info-container {
|
||||
.title-container {
|
||||
.title {
|
||||
|
|
@ -120,4 +143,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ const MainNavBars = React.memo(({ className, route, query, children }) => {
|
|||
query={query}
|
||||
backButton={false}
|
||||
searchBar={true}
|
||||
addonsButton={true}
|
||||
fullscreenButton={true}
|
||||
navMenu={true}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -166,6 +166,7 @@
|
|||
object-position: center;
|
||||
object-fit: cover;
|
||||
opacity: 0.9;
|
||||
overflow-clip-margin: unset;
|
||||
}
|
||||
|
||||
.placeholder-icon {
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@
|
|||
.body-container {
|
||||
flex: 1;
|
||||
align-self: stretch;
|
||||
overflow-y: auto;
|
||||
overflow: visible;
|
||||
padding: 2rem 0;
|
||||
|
||||
&:last-child {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
popup-menu-container: menu-container;
|
||||
}
|
||||
|
||||
@parent-height: 10rem;
|
||||
|
||||
.label-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
@ -48,6 +50,8 @@
|
|||
|
||||
.modal-container, .popup-menu-container {
|
||||
.menu-container {
|
||||
max-height: calc(3.2rem * 7);
|
||||
|
||||
.option-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
@ -101,4 +105,12 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (orientation: landscape) and (max-width: @xsmall) {
|
||||
.modal-container, .popup-menu-container {
|
||||
.menu-container {
|
||||
max-height: calc(100dvh - var(--horizontal-nav-bar-size) - @parent-height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
@import (reference) '~stremio/common/screen-sizes.less';
|
||||
|
||||
@parent-height: 10rem;
|
||||
|
||||
.dropdown {
|
||||
background: var(--modal-background-color);
|
||||
display: none;
|
||||
|
|
@ -16,7 +18,7 @@
|
|||
|
||||
&.open {
|
||||
display: block;
|
||||
max-height: calc(3.2rem * 10);
|
||||
max-height: calc(3.3rem * 7);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
|
@ -33,10 +35,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
@media (orientation: landscape) and (max-width: @xsmall) {
|
||||
.dropdown {
|
||||
&.open {
|
||||
max-height: calc(3.2rem * 7);
|
||||
max-height: calc(100dvh - var(--horizontal-nav-bar-size) - @parent-height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ const NavMenu = require('./NavMenu');
|
|||
const styles = require('./styles');
|
||||
const { t } = require('i18next');
|
||||
|
||||
const HorizontalNavBar = React.memo(({ className, route, query, title, backButton, searchBar, addonsButton, fullscreenButton, navMenu, ...props }) => {
|
||||
const HorizontalNavBar = React.memo(({ className, route, query, title, backButton, searchBar, fullscreenButton, navMenu, ...props }) => {
|
||||
const backButtonOnClick = React.useCallback(() => {
|
||||
window.history.back();
|
||||
}, []);
|
||||
|
|
@ -54,14 +54,6 @@ const HorizontalNavBar = React.memo(({ className, route, query, title, backButto
|
|||
null
|
||||
}
|
||||
<div className={styles['buttons-container']}>
|
||||
{
|
||||
addonsButton ?
|
||||
<Button className={styles['button-container']} href={'#/addons'} title={t('ADDONS')} tabIndex={-1}>
|
||||
<Icon className={styles['icon']} name={'addons-outline'} />
|
||||
</Button>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
!isIOSPWA && fullscreenButton ?
|
||||
<Button className={styles['button-container']} title={fullscreen ? t('EXIT_FULLSCREEN') : t('ENTER_FULLSCREEN')} tabIndex={-1} onClick={fullscreen ? exitFullscreen : requestFullscreen}>
|
||||
|
|
@ -90,7 +82,6 @@ HorizontalNavBar.propTypes = {
|
|||
title: PropTypes.string,
|
||||
backButton: PropTypes.bool,
|
||||
searchBar: PropTypes.bool,
|
||||
addonsButton: PropTypes.bool,
|
||||
fullscreenButton: PropTypes.bool,
|
||||
navMenu: PropTypes.bool
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
}
|
||||
.nav-menu-container {
|
||||
width: 22rem;
|
||||
max-height: calc(100vh - var(--horizontal-nav-bar-size));
|
||||
max-height: calc(100vh - var(--horizontal-nav-bar-size) - 1rem);
|
||||
overflow-y: auto;
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--modal-background-color);
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ const SearchBar = React.memo(({ className, query, active }) => {
|
|||
|
||||
const queryInputOnSubmit = React.useCallback((event) => {
|
||||
event.preventDefault();
|
||||
const searchValue = `/search?search=${event.target.value}`;
|
||||
const searchValue = `/search?search=${encodeURIComponent(event.target.value)}`;
|
||||
setCurrentQuery(searchValue);
|
||||
if (searchInputRef.current && searchValue) {
|
||||
window.location.hash = searchValue;
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@
|
|||
cursor: pointer;
|
||||
outline: none;
|
||||
user-select: none;
|
||||
margin-right: 0.75rem;
|
||||
outline-width: var(--focus-outline-size);
|
||||
outline-color: @color-surface-light5;
|
||||
outline-offset: calc(-1 * var(--focus-outline-size));
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
|
||||
input[type='radio'] {
|
||||
opacity: 0;
|
||||
|
|
|
|||
|
|
@ -1,32 +1,25 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React from 'react';
|
||||
import React, { forwardRef, useCallback } from 'react';
|
||||
import { type KeyboardEvent, type InputHTMLAttributes } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import styles from './styles.less';
|
||||
|
||||
type Props = React.InputHTMLAttributes<HTMLInputElement> & {
|
||||
type Props = InputHTMLAttributes<HTMLInputElement> & {
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||
onSubmit?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||
onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
|
||||
onSubmit?: (event: KeyboardEvent<HTMLInputElement>) => void;
|
||||
};
|
||||
|
||||
const TextInput = React.forwardRef<HTMLInputElement, Props>((props, ref) => {
|
||||
const { onSubmit, className, disabled, ...rest } = props;
|
||||
const TextInput = forwardRef<HTMLInputElement, Props>((props, ref) => {
|
||||
const onKeyDown = useCallback((event: KeyboardEvent<HTMLInputElement>) => {
|
||||
props.onKeyDown && props.onKeyDown(event);
|
||||
|
||||
const onKeyDown = React.useCallback((event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (typeof props.onKeyDown === 'function') {
|
||||
props.onKeyDown(event);
|
||||
if (event.key === 'Enter' ) {
|
||||
props.onSubmit && props.onSubmit(event);
|
||||
}
|
||||
|
||||
if (
|
||||
event.key === 'Enter' &&
|
||||
!(event.nativeEvent as any).submitPrevented &&
|
||||
typeof onSubmit === 'function'
|
||||
) {
|
||||
onSubmit(event);
|
||||
}
|
||||
}, [props.onKeyDown, onSubmit]);
|
||||
}, [props.onKeyDown, props.onSubmit]);
|
||||
|
||||
return (
|
||||
<input
|
||||
|
|
@ -36,10 +29,10 @@ const TextInput = React.forwardRef<HTMLInputElement, Props>((props, ref) => {
|
|||
autoComplete={'off'}
|
||||
spellCheck={false}
|
||||
tabIndex={0}
|
||||
{...props}
|
||||
ref={ref}
|
||||
className={classnames(className, styles['text-input'], { disabled })}
|
||||
className={classnames(props.className, styles['text-input'], { 'disabled': props.disabled })}
|
||||
onKeyDown={onKeyDown}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,14 +10,8 @@ const TooltipItem = React.memo(({ className, active, label, position, margin, pa
|
|||
|
||||
const [style, setStyle] = React.useState(null);
|
||||
|
||||
const onTransitionEnd = React.useCallback(() => {
|
||||
if (!active) {
|
||||
setStyle(null);
|
||||
}
|
||||
}, [active]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!ref.current) return setStyle(null);
|
||||
if (!ref.current || !active) return setStyle(null);
|
||||
|
||||
const tooltipBounds = ref.current.getBoundingClientRect();
|
||||
const parentBounds = parent.getBoundingClientRect();
|
||||
|
|
@ -47,7 +41,7 @@ const TooltipItem = React.memo(({ className, active, label, position, margin, pa
|
|||
}, [active, position, margin, parent, label]);
|
||||
|
||||
return (
|
||||
<div ref={ref} className={classNames(className, styles['tooltip-item'], { 'active': active })} style={style} onTransitionEnd={onTransitionEnd}>
|
||||
<div ref={ref} className={classNames(className, styles['tooltip-item'], { 'active': active })} style={style}>
|
||||
{ label }
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
60
src/common/Transition/Transition.tsx
Normal file
60
src/common/Transition/Transition.tsx
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import { cloneElement, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
type Props = {
|
||||
children: JSX.Element,
|
||||
when: boolean,
|
||||
name: string,
|
||||
};
|
||||
|
||||
const Transition = ({ children, when, name }: Props) => {
|
||||
const [element, setElement] = useState<HTMLElement | null>(null);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
const [state, setState] = useState('enter');
|
||||
const [active, setActive] = useState(false);
|
||||
|
||||
const callbackRef = useCallback((element: HTMLElement | null) => {
|
||||
setElement(element);
|
||||
}, []);
|
||||
|
||||
const className = useMemo(() => {
|
||||
const animationClass = `${name}-${state}`;
|
||||
const activeClass = active ? `${name}-active` : null;
|
||||
|
||||
return children && classNames(
|
||||
children.props.className,
|
||||
animationClass,
|
||||
activeClass,
|
||||
);
|
||||
}, [name, state, active, children]);
|
||||
|
||||
const onTransitionEnd = useCallback(() => {
|
||||
state === 'exit' && setMounted(false);
|
||||
}, [state]);
|
||||
|
||||
useEffect(() => {
|
||||
setState(when ? 'enter' : 'exit');
|
||||
when && setMounted(true);
|
||||
}, [when]);
|
||||
|
||||
useEffect(() => {
|
||||
requestAnimationFrame(() => {
|
||||
setActive(!!element);
|
||||
});
|
||||
}, [element]);
|
||||
|
||||
useEffect(() => {
|
||||
element?.addEventListener('transitionend', onTransitionEnd);
|
||||
return () => element?.removeEventListener('transitionend', onTransitionEnd);
|
||||
}, [element, onTransitionEnd]);
|
||||
|
||||
return (
|
||||
mounted && cloneElement(children, {
|
||||
ref: callbackRef,
|
||||
className,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export default Transition;
|
||||
2
src/common/Transition/index.ts
Normal file
2
src/common/Transition/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import Transition from './Transition';
|
||||
export default Transition;
|
||||
|
|
@ -4,15 +4,16 @@ const React = require('react');
|
|||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { t } = require('i18next');
|
||||
const { useServices } = require('stremio/services');
|
||||
const { useRouteFocused } = require('stremio-router');
|
||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const { Button, Image, Popup, useBinaryState } = require('stremio/common');
|
||||
const Button = require('stremio/common/Button');
|
||||
const Image = require('stremio/common/Image');
|
||||
const Popup = require('stremio/common/Popup');
|
||||
const useBinaryState = require('stremio/common/useBinaryState');
|
||||
const VideoPlaceholder = require('./VideoPlaceholder');
|
||||
const styles = require('./styles');
|
||||
|
||||
const Video = ({ className, id, title, thumbnail, episode, released, upcoming, watched, progress, scheduled, deepLinks, ...props }) => {
|
||||
const { core } = useServices();
|
||||
const Video = ({ className, id, title, thumbnail, episode, released, upcoming, watched, progress, scheduled, deepLinks, onMarkVideoAsWatched, ...props }) => {
|
||||
const routeFocused = useRouteFocused();
|
||||
const [menuOpen, , closeMenu, toggleMenu] = useBinaryState(false);
|
||||
const popupLabelOnMouseUp = React.useCallback((event) => {
|
||||
|
|
@ -49,13 +50,7 @@ const Video = ({ className, id, title, thumbnail, episode, released, upcoming, w
|
|||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
closeMenu();
|
||||
core.transport.dispatch({
|
||||
action: 'MetaDetails',
|
||||
args: {
|
||||
action: 'MarkVideoAsWatched',
|
||||
args: [{ id, released }, !watched]
|
||||
}
|
||||
});
|
||||
onMarkVideoAsWatched({ id, released }, watched);
|
||||
}, [id, released, watched]);
|
||||
const videoButtonOnClick = React.useCallback(() => {
|
||||
if (deepLinks) {
|
||||
|
|
@ -198,7 +193,8 @@ Video.propTypes = {
|
|||
deepLinks: PropTypes.shape({
|
||||
metaDetailsStreams: PropTypes.string,
|
||||
player: PropTypes.string
|
||||
})
|
||||
}),
|
||||
onMarkVideoAsWatched: PropTypes.func,
|
||||
};
|
||||
|
||||
module.exports = Video;
|
||||
|
|
@ -38,4 +38,17 @@
|
|||
100% {
|
||||
transform: translateY(0%);
|
||||
}
|
||||
}
|
||||
|
||||
.slide-left-enter {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
.slide-left-active {
|
||||
transform: translateX(0%);
|
||||
transition: transform 0.3s cubic-bezier(0.32, 0, 0.67, 0);
|
||||
}
|
||||
|
||||
.slide-left-exit {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
|
@ -29,6 +29,8 @@ const Slider = require('./Slider');
|
|||
const { default: TextInput } = require('./TextInput');
|
||||
const { ToastProvider, useToast } = require('./Toast');
|
||||
const { TooltipProvider, Tooltip } = require('./Tooltips');
|
||||
const { default: Transition } = require('./Transition');
|
||||
const Video = require('./Video');
|
||||
const comparatorWithPriorities = require('./comparatorWithPriorities');
|
||||
const CONSTANTS = require('./CONSTANTS');
|
||||
const { withCoreSuspender, useCoreSuspender } = require('./CoreSuspender');
|
||||
|
|
@ -83,6 +85,8 @@ module.exports = {
|
|||
useToast,
|
||||
TooltipProvider,
|
||||
Tooltip,
|
||||
Transition,
|
||||
Video,
|
||||
comparatorWithPriorities,
|
||||
CONSTANTS,
|
||||
withCoreSuspender,
|
||||
|
|
|
|||
143
src/routes/Addons/AddonPlaceholder/AddonPlaceholder.less
Normal file
143
src/routes/Addons/AddonPlaceholder/AddonPlaceholder.less
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
@import (reference) '~stremio/common/screen-sizes.less';
|
||||
|
||||
@placeholder-opacity: 0.1;
|
||||
@padding: 1.5rem;
|
||||
@small-padding: 1rem;
|
||||
@logo-size: 8rem;
|
||||
|
||||
.placeholder-pill(@width: 100%, @height: 1.3rem) {
|
||||
background-color: var(--primary-foreground-color);
|
||||
border-radius: var(--border-radius);
|
||||
opacity: @placeholder-opacity;
|
||||
width: @width;
|
||||
height: @height;
|
||||
}
|
||||
|
||||
.placeholder-logo(@size: @logo-size) {
|
||||
width: @size;
|
||||
height: @size;
|
||||
border-radius: 50%;
|
||||
background-color: var(--primary-foreground-color);
|
||||
opacity: @placeholder-opacity;
|
||||
}
|
||||
|
||||
.addon-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
padding: @padding;
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--overlay-color);
|
||||
cursor: inherit;
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
|
||||
.logo-container {
|
||||
flex: none;
|
||||
.placeholder-logo(@logo-size);
|
||||
}
|
||||
|
||||
.info-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: @small-padding;
|
||||
flex: 1;
|
||||
|
||||
.placeholder-pill:nth-child(1) {
|
||||
.placeholder-pill(40%);
|
||||
}
|
||||
|
||||
.placeholder-pill:nth-child(2) {
|
||||
.placeholder-pill(60%);
|
||||
}
|
||||
|
||||
.placeholder-pill:nth-child(3) {
|
||||
.placeholder-pill(80%);
|
||||
}
|
||||
|
||||
.placeholder-pill:nth-child(4) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.buttons-container {
|
||||
flex: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: @small-padding;
|
||||
width: 30%;
|
||||
max-width: 18rem;
|
||||
|
||||
.action-buttons-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: @small-padding;
|
||||
|
||||
.placeholder-pill:nth-child(1), .placeholder-pill:nth-child(2) {
|
||||
.placeholder-pill(50%, 3.5rem);
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder-pill:last-child {
|
||||
.placeholder-pill(100%, 3.5rem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: @minimum) {
|
||||
.addon-container {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
width: 100%;
|
||||
gap: 1rem;
|
||||
|
||||
.content {
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
|
||||
.logo-container {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.info-container {
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
|
||||
.placeholder-pill:nth-child(1) {
|
||||
.placeholder-pill(60%);
|
||||
}
|
||||
|
||||
.placeholder-pill:nth-child(2) {
|
||||
.placeholder-pill(40%);
|
||||
}
|
||||
|
||||
.placeholder-pill:nth-child(3) {
|
||||
.placeholder-pill(80%, 0.8rem);
|
||||
}
|
||||
|
||||
.placeholder-pill:nth-child(4) {
|
||||
display: block;
|
||||
.placeholder-pill(20%, 0.8rem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.buttons-container {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
width: 100%;
|
||||
gap: 0.5rem;
|
||||
max-width: none;
|
||||
|
||||
.action-buttons-container {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/routes/Addons/AddonPlaceholder/AddonPlaceholder.tsx
Normal file
34
src/routes/Addons/AddonPlaceholder/AddonPlaceholder.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import styles from './AddonPlaceholder.less';
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const AddonPlaceholder = ({ className }: Props) => {
|
||||
return (
|
||||
<div className={classnames(className, styles['addon-container'])}>
|
||||
<div className={styles['content']}>
|
||||
<div className={styles['logo-container']}>
|
||||
<div className={styles['placeholder-logo']} />
|
||||
</div>
|
||||
<div className={styles['info-container']}>
|
||||
<div className={styles['placeholder-pill']} />
|
||||
<div className={styles['placeholder-pill']} />
|
||||
<div className={styles['placeholder-pill']} />
|
||||
<div className={styles['placeholder-pill']} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles['buttons-container']}>
|
||||
<div className={styles['action-buttons-container']}>
|
||||
<div className={styles['placeholder-pill']} />
|
||||
<div className={styles['placeholder-pill']} />
|
||||
</div>
|
||||
<div className={styles['placeholder-pill']} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
7
src/routes/Addons/AddonPlaceholder/index.ts
Normal file
7
src/routes/Addons/AddonPlaceholder/index.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import { AddonPlaceholder } from './AddonPlaceholder';
|
||||
|
||||
export {
|
||||
AddonPlaceholder
|
||||
};
|
||||
|
|
@ -12,6 +12,7 @@ const useRemoteAddons = require('./useRemoteAddons');
|
|||
const useAddonDetailsTransportUrl = require('./useAddonDetailsTransportUrl');
|
||||
const useSelectableInputs = require('./useSelectableInputs');
|
||||
const styles = require('./styles');
|
||||
const { AddonPlaceholder } = require('./AddonPlaceholder');
|
||||
|
||||
const Addons = ({ urlParams, queryParams }) => {
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -94,7 +95,7 @@ const Addons = ({ urlParams, queryParams }) => {
|
|||
<div className={styles['spacing']} />
|
||||
<Button className={styles['add-button-container']} title={t('ADD_ADDON')} onClick={openAddAddonModal}>
|
||||
<Icon className={styles['icon']} name={'add'} />
|
||||
<div className={styles['add-button-label']}>{ t('ADD_ADDON') }</div>
|
||||
<div className={styles['add-button-label']}>{t('ADD_ADDON')}</div>
|
||||
</Button>
|
||||
<SearchBar
|
||||
className={styles['search-bar']}
|
||||
|
|
@ -150,8 +151,10 @@ const Addons = ({ urlParams, queryParams }) => {
|
|||
</div>
|
||||
:
|
||||
remoteAddons.catalog.content.type === 'Loading' ?
|
||||
<div className={styles['message-container']}>
|
||||
Loading!
|
||||
<div className={styles['addons-list-container']}>
|
||||
{Array.from({ length: 6 }).map((_, index) => (
|
||||
<AddonPlaceholder key={index} className={styles['addon']} />
|
||||
))}
|
||||
</div>
|
||||
:
|
||||
<div className={styles['addons-list-container']}>
|
||||
|
|
@ -179,8 +182,10 @@ const Addons = ({ urlParams, queryParams }) => {
|
|||
}
|
||||
</div>
|
||||
:
|
||||
<div className={styles['message-container']}>
|
||||
No select
|
||||
<div className={styles['addons-list-container']}>
|
||||
{Array.from({ length: 6 }).map((_, index) => (
|
||||
<AddonPlaceholder key={index} className={styles['addon']} />
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
|
@ -205,7 +210,7 @@ const Addons = ({ urlParams, queryParams }) => {
|
|||
title={t('ADD_ADDON')}
|
||||
buttons={addAddonModalButtons}
|
||||
onCloseRequest={closeAddAddonModal}>
|
||||
<div className={styles['notice']}>{ t('ADD_ADDON_DESCRIPTION') }</div>
|
||||
<div className={styles['notice']}>{t('ADD_ADDON_DESCRIPTION')}</div>
|
||||
<TextInput
|
||||
ref={addAddonUrlInputRef}
|
||||
className={styles['addon-url-input']}
|
||||
|
|
|
|||
|
|
@ -89,7 +89,6 @@
|
|||
margin-right: 1.5rem;
|
||||
|
||||
.multiselect-menu-container {
|
||||
max-height: calc(3.2rem * 7);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
|
@ -156,6 +155,10 @@
|
|||
.select-input-container {
|
||||
height: 3rem;
|
||||
|
||||
.multiselect-menu-container {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,8 +128,10 @@
|
|||
border-color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
&:not(.active):hover {
|
||||
border-color: var(--overlay-color);
|
||||
@media (pointer: fine) {
|
||||
&:not(.active):hover {
|
||||
border-color: var(--overlay-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ const Cell = ({ selected, monthInfo, date, items, onClick }: Props) => {
|
|||
<HorizontalScroll className={styles['items']}>
|
||||
{
|
||||
items.map(({ id, name, poster, deepLinks }) => (
|
||||
<Button key={id} className={styles['item']} href={deepLinks.metaDetailsStreams}>
|
||||
<Button key={id} className={styles['item']} href={deepLinks.metaDetailsStreams} tabIndex={-1}>
|
||||
<Icon className={styles['icon']} name={'play'} />
|
||||
<Image
|
||||
className={styles['poster']}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@
|
|||
}
|
||||
|
||||
.multiselect-menu-container {
|
||||
max-height: calc(3.2rem * 7);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
|
@ -220,7 +219,7 @@
|
|||
}
|
||||
|
||||
.multiselect-menu-container {
|
||||
max-height: calc(3.2rem * 4);
|
||||
max-height: calc(3.2rem * 3);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@
|
|||
}
|
||||
|
||||
.multiselect-menu-container {
|
||||
max-height: calc(3.2rem * 7);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,6 @@ const MetaDetails = ({ urlParams, queryParams }) => {
|
|||
<HorizontalNavBar
|
||||
className={styles['nav-bar']}
|
||||
backButton={true}
|
||||
addonsButton={true}
|
||||
fullscreenButton={true}
|
||||
navMenu={true}
|
||||
/>
|
||||
|
|
@ -210,7 +209,6 @@ const MetaDetailsFallback = () => (
|
|||
<HorizontalNavBar
|
||||
className={styles['nav-bar']}
|
||||
backButton={true}
|
||||
addonsButton={true}
|
||||
fullscreenButton={true}
|
||||
navMenu={true}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -9,16 +9,20 @@ const { Button, Image, Multiselect } = require('stremio/common');
|
|||
const { useServices } = require('stremio/services');
|
||||
const Stream = require('./Stream');
|
||||
const styles = require('./styles');
|
||||
const { usePlatform } = require('stremio/common');
|
||||
|
||||
const ALL_ADDONS_KEY = 'ALL';
|
||||
|
||||
const StreamsList = ({ className, video, ...props }) => {
|
||||
const { t } = useTranslation();
|
||||
const { core } = useServices();
|
||||
const platform = usePlatform();
|
||||
const streamsContainerRef = React.useRef(null);
|
||||
const [selectedAddon, setSelectedAddon] = React.useState(ALL_ADDONS_KEY);
|
||||
const onAddonSelected = React.useCallback((event) => {
|
||||
streamsContainerRef.current.scrollTo({ top: 0, left: 0, behavior: platform.name === 'ios' ? 'smooth' : 'instant' });
|
||||
setSelectedAddon(event.value);
|
||||
}, []);
|
||||
}, [platform]);
|
||||
const backButtonOnClick = React.useCallback(() => {
|
||||
if (video.deepLinks && typeof video.deepLinks.metaDetailsVideos === 'string') {
|
||||
window.location.replace(video.deepLinks.metaDetailsVideos + (
|
||||
|
|
@ -142,7 +146,7 @@ const StreamsList = ({ className, video, ...props }) => {
|
|||
:
|
||||
null
|
||||
}
|
||||
<div className={styles['streams-container']}>
|
||||
<div className={styles['streams-container']} ref={streamsContainerRef}>
|
||||
{filteredStreams.map((stream, index) => (
|
||||
<Stream
|
||||
key={index}
|
||||
|
|
|
|||
|
|
@ -124,7 +124,6 @@
|
|||
}
|
||||
|
||||
.multiselect-menu-container {
|
||||
max-height: calc(3.2rem * 7);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,10 +36,6 @@
|
|||
background-color: var(--overlay-color);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
&>:first-child {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
|
@ -77,7 +73,6 @@
|
|||
}
|
||||
|
||||
.multiselect-menu-container {
|
||||
max-height: calc(3.2rem * 7);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
|
@ -86,11 +81,5 @@
|
|||
@media only screen and (max-width: @minimum) {
|
||||
.seasons-bar-container {
|
||||
height: 6rem;
|
||||
|
||||
.seasons-popup-label-container {
|
||||
.multiselect-menu-container {
|
||||
max-height: calc(3.2rem * 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,12 +4,13 @@ const React = require('react');
|
|||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { t } = require('i18next');
|
||||
const { Image, SearchBar, Toggle } = require('stremio/common');
|
||||
const { useServices } = require('stremio/services');
|
||||
const { Image, SearchBar, Toggle, Video } = require('stremio/common');
|
||||
const SeasonsBar = require('./SeasonsBar');
|
||||
const Video = require('./Video');
|
||||
const styles = require('./styles');
|
||||
|
||||
const VideosList = ({ className, metaItem, libraryItem, season, seasonOnSelect, toggleNotifications }) => {
|
||||
const { core } = useServices();
|
||||
const showNotificationsToggle = React.useMemo(() => {
|
||||
return metaItem?.content?.content?.inLibrary && metaItem?.content?.content?.videos?.length;
|
||||
}, [metaItem]);
|
||||
|
|
@ -59,6 +60,17 @@ const VideosList = ({ className, metaItem, libraryItem, season, seasonOnSelect,
|
|||
const searchInputOnChange = React.useCallback((event) => {
|
||||
setSearch(event.currentTarget.value);
|
||||
}, []);
|
||||
|
||||
const onMarkVideoAsWatched = (video, watched) => {
|
||||
core.transport.dispatch({
|
||||
action: 'MetaDetails',
|
||||
args: {
|
||||
action: 'MarkVideoAsWatched',
|
||||
args: [video, !watched]
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classnames(className, styles['videos-list-container'])}>
|
||||
{
|
||||
|
|
@ -130,6 +142,7 @@ const VideosList = ({ className, metaItem, libraryItem, season, seasonOnSelect,
|
|||
progress={video.progress}
|
||||
deepLinks={video.deepLinks}
|
||||
scheduled={video.scheduled}
|
||||
onMarkVideoAsWatched={onMarkVideoAsWatched}
|
||||
/>
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,9 +35,8 @@ const ControlBar = ({
|
|||
onVolumeChangeRequested,
|
||||
onSeekRequested,
|
||||
onToggleSubtitlesMenu,
|
||||
onToggleInfoMenu,
|
||||
onToggleSpeedMenu,
|
||||
onToggleVideosMenu,
|
||||
onToggleSideDrawer,
|
||||
onToggleOptionsMenu,
|
||||
onToggleStatisticsMenu,
|
||||
...props
|
||||
|
|
@ -48,9 +47,6 @@ const ControlBar = ({
|
|||
const onSubtitlesButtonMouseDown = React.useCallback((event) => {
|
||||
event.nativeEvent.subtitlesMenuClosePrevented = true;
|
||||
}, []);
|
||||
const onInfoButtonMouseDown = React.useCallback((event) => {
|
||||
event.nativeEvent.infoMenuClosePrevented = true;
|
||||
}, []);
|
||||
const onSpeedButtonMouseDown = React.useCallback((event) => {
|
||||
event.nativeEvent.speedMenuClosePrevented = true;
|
||||
}, []);
|
||||
|
|
@ -151,9 +147,6 @@ const ControlBar = ({
|
|||
<Button className={classnames(styles['control-bar-button'], { 'disabled': playbackSpeed === null })} tabIndex={-1} onMouseDown={onSpeedButtonMouseDown} onClick={onToggleSpeedMenu}>
|
||||
<Icon className={styles['icon']} name={'speed'} />
|
||||
</Button>
|
||||
<Button className={classnames(styles['control-bar-button'], { 'disabled': metaItem === null || metaItem.type !== 'Ready' })} tabIndex={-1} onMouseDown={onInfoButtonMouseDown} onClick={onToggleInfoMenu}>
|
||||
<Icon className={styles['icon']} name={'about'} />
|
||||
</Button>
|
||||
<Button className={classnames(styles['control-bar-button'], { 'disabled': !chromecastServiceActive })} tabIndex={-1} onClick={onChromecastButtonClick}>
|
||||
<Icon className={styles['icon']} name={'cast'} />
|
||||
</Button>
|
||||
|
|
@ -162,7 +155,7 @@ const ControlBar = ({
|
|||
</Button>
|
||||
{
|
||||
metaItem?.content?.videos?.length > 0 ?
|
||||
<Button className={styles['control-bar-button']} tabIndex={-1} onMouseDown={onVideosButtonMouseDown} onClick={onToggleVideosMenu}>
|
||||
<Button className={styles['control-bar-button']} tabIndex={-1} onMouseDown={onVideosButtonMouseDown} onClick={onToggleSideDrawer}>
|
||||
<Icon className={styles['icon']} name={'episodes'} />
|
||||
</Button>
|
||||
:
|
||||
|
|
@ -200,9 +193,8 @@ ControlBar.propTypes = {
|
|||
onVolumeChangeRequested: PropTypes.func,
|
||||
onSeekRequested: PropTypes.func,
|
||||
onToggleSubtitlesMenu: PropTypes.func,
|
||||
onToggleInfoMenu: PropTypes.func,
|
||||
onToggleSpeedMenu: PropTypes.func,
|
||||
onToggleVideosMenu: PropTypes.func,
|
||||
onToggleSideDrawer: PropTypes.func,
|
||||
onToggleOptionsMenu: PropTypes.func,
|
||||
onToggleStatisticsMenu: PropTypes.func,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -100,14 +100,16 @@
|
|||
|
||||
.control-bar-buttons-menu-container {
|
||||
position: absolute;
|
||||
right: 0.15rem;
|
||||
right: 0rem;
|
||||
bottom: 4.5rem;
|
||||
flex-direction: column;
|
||||
padding: 0.5rem;
|
||||
margin: 0.5rem;
|
||||
max-width: calc(100dvw - 1rem);
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--modal-background-color);
|
||||
box-shadow: 0 1.35rem 2.7rem @color-background-dark5-40,
|
||||
0 1.1rem 0.85rem @color-background-dark5-20;
|
||||
overflow-x: auto;
|
||||
|
||||
&:not(:global(.open)) {
|
||||
display: none;
|
||||
|
|
|
|||
|
|
@ -1,77 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
// const Stream = require('stremio/routes/MetaDetails/StreamsList/Stream');
|
||||
// const AddonDetails = require('stremio/common/AddonDetailsModal/AddonDetails');
|
||||
const { MetaPreview, CONSTANTS } = require('stremio/common');
|
||||
const styles = require('./styles');
|
||||
|
||||
const InfoMenu = ({ className, ...props }) => {
|
||||
const metaItem = React.useMemo(() => {
|
||||
return props.metaItem !== null ?
|
||||
{
|
||||
...props.metaItem,
|
||||
links: props.metaItem.links.filter(({ category }) => category === CONSTANTS.SHARE_LINK_CATEGORY)
|
||||
}
|
||||
:
|
||||
null;
|
||||
}, [props.metaItem]);
|
||||
const onMouseDown = React.useCallback((event) => {
|
||||
event.nativeEvent.infoMenuClosePrevented = true;
|
||||
}, []);
|
||||
return (
|
||||
<div className={classnames(className, styles['info-menu-container'])} onMouseDown={onMouseDown}>
|
||||
{
|
||||
metaItem !== null ?
|
||||
<MetaPreview
|
||||
className={styles['meta-preview']}
|
||||
compact={true}
|
||||
name={metaItem.name}
|
||||
logo={metaItem.logo}
|
||||
runtime={metaItem.runtime}
|
||||
releaseInfo={metaItem.releaseInfo}
|
||||
released={metaItem.released}
|
||||
description={metaItem.description}
|
||||
links={metaItem.links}
|
||||
/>
|
||||
:
|
||||
null
|
||||
}
|
||||
{/* {
|
||||
props.stream !== null ?
|
||||
<Stream
|
||||
{...props.stream}
|
||||
className={classnames(styles['stream'], 'active')}
|
||||
addonName={props.addon !== null ? props.addon.manifest.name : ''}
|
||||
/>
|
||||
:
|
||||
null
|
||||
} */}
|
||||
{/* {
|
||||
props.addon !== null ?
|
||||
<AddonDetails
|
||||
id={props.addon.manifest.id}
|
||||
name={props.addon.manifest.name}
|
||||
version={props.addon.manifest.version}
|
||||
logo={props.addon.manifest.logo}
|
||||
description={props.addon.manifest.description}
|
||||
types={props.addon.manifest.types}
|
||||
transportUrl={props.addon.transportUrl}
|
||||
/>
|
||||
:
|
||||
null
|
||||
} */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
InfoMenu.propTypes = {
|
||||
className: PropTypes.string,
|
||||
metaItem: PropTypes.object,
|
||||
addon: PropTypes.object,
|
||||
stream: PropTypes.object
|
||||
};
|
||||
|
||||
module.exports = InfoMenu;
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const InfoMenu = require('./InfoMenu');
|
||||
|
||||
module.exports = InfoMenu;
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
.info-menu-container {
|
||||
width: 30rem;
|
||||
padding: 2rem;
|
||||
|
||||
.stream {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,18 +8,18 @@ const langs = require('langs');
|
|||
const { useTranslation } = require('react-i18next');
|
||||
const { useRouteFocused } = require('stremio-router');
|
||||
const { useServices } = require('stremio/services');
|
||||
const { HorizontalNavBar, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender } = require('stremio/common');
|
||||
const { HorizontalNavBar, Transition, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender } = require('stremio/common');
|
||||
const BufferingLoader = require('./BufferingLoader');
|
||||
const VolumeChangeIndicator = require('./VolumeChangeIndicator');
|
||||
const Error = require('./Error');
|
||||
const ControlBar = require('./ControlBar');
|
||||
const NextVideoPopup = require('./NextVideoPopup');
|
||||
const StatisticsMenu = require('./StatisticsMenu');
|
||||
const InfoMenu = require('./InfoMenu');
|
||||
const OptionsMenu = require('./OptionsMenu');
|
||||
const VideosMenu = require('./VideosMenu');
|
||||
const SubtitlesMenu = require('./SubtitlesMenu');
|
||||
const SpeedMenu = require('./SpeedMenu');
|
||||
const { default: SideDrawerButton } = require('./SideDrawerButton');
|
||||
const { default: SideDrawer } = require('./SideDrawer');
|
||||
const usePlayer = require('./usePlayer');
|
||||
const useSettings = require('./useSettings');
|
||||
const useStatistics = require('./useStatistics');
|
||||
|
|
@ -54,23 +54,21 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
|
||||
const [optionsMenuOpen, , closeOptionsMenu, toggleOptionsMenu] = useBinaryState(false);
|
||||
const [subtitlesMenuOpen, , closeSubtitlesMenu, toggleSubtitlesMenu] = useBinaryState(false);
|
||||
const [infoMenuOpen, , closeInfoMenu, toggleInfoMenu] = useBinaryState(false);
|
||||
const [speedMenuOpen, , closeSpeedMenu, toggleSpeedMenu] = useBinaryState(false);
|
||||
const [videosMenuOpen, , closeVideosMenu, toggleVideosMenu] = useBinaryState(false);
|
||||
const [statisticsMenuOpen, , closeStatisticsMenu, toggleStatisticsMenu] = useBinaryState(false);
|
||||
const [nextVideoPopupOpen, openNextVideoPopup, closeNextVideoPopup] = useBinaryState(false);
|
||||
const [sideDrawerOpen, , closeSideDrawer, toggleSideDrawer] = useBinaryState(false);
|
||||
|
||||
const menusOpen = React.useMemo(() => {
|
||||
return optionsMenuOpen || subtitlesMenuOpen || infoMenuOpen || speedMenuOpen || videosMenuOpen || statisticsMenuOpen;
|
||||
}, [optionsMenuOpen, subtitlesMenuOpen, infoMenuOpen, speedMenuOpen, videosMenuOpen, statisticsMenuOpen]);
|
||||
return optionsMenuOpen || subtitlesMenuOpen || speedMenuOpen || statisticsMenuOpen || sideDrawerOpen;
|
||||
}, [optionsMenuOpen, subtitlesMenuOpen, speedMenuOpen, statisticsMenuOpen, sideDrawerOpen]);
|
||||
|
||||
const closeMenus = React.useCallback(() => {
|
||||
closeOptionsMenu();
|
||||
closeSubtitlesMenu();
|
||||
closeInfoMenu();
|
||||
closeSpeedMenu();
|
||||
closeVideosMenu();
|
||||
closeStatisticsMenu();
|
||||
closeSideDrawer();
|
||||
}, []);
|
||||
|
||||
const overlayHidden = React.useMemo(() => {
|
||||
|
|
@ -237,18 +235,14 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
if (!event.nativeEvent.subtitlesMenuClosePrevented) {
|
||||
closeSubtitlesMenu();
|
||||
}
|
||||
if (!event.nativeEvent.infoMenuClosePrevented) {
|
||||
closeInfoMenu();
|
||||
}
|
||||
if (!event.nativeEvent.speedMenuClosePrevented) {
|
||||
closeSpeedMenu();
|
||||
}
|
||||
if (!event.nativeEvent.videosMenuClosePrevented) {
|
||||
closeVideosMenu();
|
||||
}
|
||||
if (!event.nativeEvent.statisticsMenuClosePrevented) {
|
||||
closeStatisticsMenu();
|
||||
}
|
||||
|
||||
closeSideDrawer();
|
||||
}, []);
|
||||
|
||||
const onContainerMouseMove = React.useCallback((event) => {
|
||||
|
|
@ -271,10 +265,9 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
|
||||
React.useEffect(() => {
|
||||
setError(null);
|
||||
if (player.selected === null) {
|
||||
video.unload();
|
||||
} else if (streamingServer.settings !== null && streamingServer.settings.type !== 'Loading' &&
|
||||
(player.selected.metaRequest === null || (player.metaItem !== null && player.metaItem.type !== 'Loading'))) {
|
||||
video.unload();
|
||||
|
||||
if (player.selected && streamingServer.settings?.type !== 'Loading') {
|
||||
video.load({
|
||||
stream: {
|
||||
...player.selected.stream,
|
||||
|
|
@ -309,7 +302,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
shellTransport: shell.active ? shell.transport : null,
|
||||
});
|
||||
}
|
||||
}, [streamingServer.baseUrl, player.selected, player.metaItem, forceTranscoding, casting]);
|
||||
}, [streamingServer.baseUrl, player.selected, forceTranscoding, casting]);
|
||||
React.useEffect(() => {
|
||||
if (video.state.stream !== null) {
|
||||
const tracks = player.subtitles.map((subtitles) => ({
|
||||
|
|
@ -412,13 +405,6 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
}
|
||||
}, [video.state.audioTracks, video.state.subtitlesTracks, video.state.extraSubtitlesTracks]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (player.metaItem === null || player.metaItem.type !== 'Ready') {
|
||||
closeInfoMenu();
|
||||
closeVideosMenu();
|
||||
}
|
||||
}, [player.metaItem]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (video.state.playbackSpeed === null) {
|
||||
closeSpeedMenu();
|
||||
|
|
@ -521,7 +507,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
case 'KeyI': {
|
||||
closeMenus();
|
||||
if (player.metaItem !== null && player.metaItem.type === 'Ready') {
|
||||
toggleInfoMenu();
|
||||
toggleSideDrawer();
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -534,14 +520,6 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
|
||||
break;
|
||||
}
|
||||
case 'KeyV': {
|
||||
closeMenus();
|
||||
if (player.metaItem !== null && player.metaItem.type === 'Ready' && player.metaItem?.content?.videos?.length > 0) {
|
||||
toggleVideosMenu();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'KeyD': {
|
||||
closeMenus();
|
||||
if (streamingServer.statistics !== null && streamingServer.statistics.type !== 'Err' && player.selected && typeof player.selected.stream.infoHash === 'string' && typeof player.selected.stream.fileIdx === 'number') {
|
||||
|
|
@ -582,7 +560,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
window.removeEventListener('keyup', onKeyUp);
|
||||
window.removeEventListener('wheel', onWheel);
|
||||
};
|
||||
}, [player.metaItem, player.selected, streamingServer.statistics, settings.seekTimeDuration, settings.seekShortTimeDuration, routeFocused, menusOpen, nextVideoPopupOpen, video.state.paused, video.state.time, video.state.volume, video.state.audioTracks, video.state.subtitlesTracks, video.state.extraSubtitlesTracks, video.state.playbackSpeed, toggleSubtitlesMenu, toggleInfoMenu, toggleVideosMenu, toggleStatisticsMenu]);
|
||||
}, [player.metaItem, player.selected, streamingServer.statistics, settings.seekTimeDuration, settings.seekShortTimeDuration, routeFocused, menusOpen, nextVideoPopupOpen, video.state.paused, video.state.time, video.state.volume, video.state.audioTracks, video.state.subtitlesTracks, video.state.extraSubtitlesTracks, video.state.playbackSpeed, toggleSubtitlesMenu, toggleStatisticsMenu, toggleSideDrawer]);
|
||||
|
||||
React.useEffect(() => {
|
||||
video.events.on('error', onError);
|
||||
|
|
@ -667,6 +645,15 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
onMouseMove={onBarMouseMove}
|
||||
onMouseOver={onBarMouseMove}
|
||||
/>
|
||||
{
|
||||
player.metaItem?.type === 'Ready' ?
|
||||
<SideDrawerButton
|
||||
className={classnames(styles['layer'], styles['side-drawer-button-layer'])}
|
||||
onClick={toggleSideDrawer}
|
||||
/>
|
||||
:
|
||||
null
|
||||
}
|
||||
<ControlBar
|
||||
className={classnames(styles['layer'], styles['control-bar-layer'])}
|
||||
paused={video.state.paused}
|
||||
|
|
@ -691,10 +678,9 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
onSeekRequested={onSeekRequested}
|
||||
onToggleOptionsMenu={toggleOptionsMenu}
|
||||
onToggleSubtitlesMenu={toggleSubtitlesMenu}
|
||||
onToggleInfoMenu={toggleInfoMenu}
|
||||
onToggleSpeedMenu={toggleSpeedMenu}
|
||||
onToggleVideosMenu={toggleVideosMenu}
|
||||
onToggleStatisticsMenu={toggleStatisticsMenu}
|
||||
onToggleSideDrawer={toggleSideDrawer}
|
||||
onMouseMove={onBarMouseMove}
|
||||
onMouseOver={onBarMouseMove}
|
||||
/>
|
||||
|
|
@ -719,6 +705,14 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
:
|
||||
null
|
||||
}
|
||||
<Transition when={sideDrawerOpen} name={'slide-left'}>
|
||||
<SideDrawer
|
||||
className={classnames(styles['layer'], styles['side-drawer-layer'])}
|
||||
metaItem={player.metaItem?.content}
|
||||
seriesInfo={player.seriesInfo}
|
||||
closeSideDrawer={closeSideDrawer}
|
||||
/>
|
||||
</Transition>
|
||||
{
|
||||
subtitlesMenuOpen ?
|
||||
<SubtitlesMenu
|
||||
|
|
@ -746,17 +740,6 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
infoMenuOpen ?
|
||||
<InfoMenu
|
||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||
stream={player.selected !== null ? player.selected.stream : null}
|
||||
addon={player.addon}
|
||||
metaItem={player.metaItem !== null && player.metaItem.type === 'Ready' ? player.metaItem.content : null}
|
||||
/>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
speedMenuOpen ?
|
||||
<SpeedMenu
|
||||
|
|
@ -767,16 +750,6 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
videosMenuOpen ?
|
||||
<VideosMenu
|
||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||
metaItem={player.metaItem !== null && player.metaItem.type === 'Ready' ? player.metaItem.content : null}
|
||||
seriesInfo={player.seriesInfo}
|
||||
/>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
optionsMenuOpen ?
|
||||
<OptionsMenu
|
||||
|
|
|
|||
115
src/routes/Player/SideDrawer/SideDrawer.less
Normal file
115
src/routes/Player/SideDrawer/SideDrawer.less
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
@import (reference) '~stremio/common/screen-sizes.less';
|
||||
|
||||
:import('~stremio/common/MetaPreview/styles.less') {
|
||||
action-buttons-container: action-buttons-container;
|
||||
}
|
||||
|
||||
@padding: 1rem;
|
||||
|
||||
.side-drawer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: @padding;
|
||||
height: 100dvh;
|
||||
max-width: 35rem;
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
border-top-left-radius: var(--border-radius);
|
||||
border-bottom-left-radius: var(--border-radius);
|
||||
background-color: var(--modal-background-color);
|
||||
box-shadow: 0 1.35rem 2.7rem var(--color-background-dark5-40), 0 1.1rem 0.85rem var(--color-background-dark5-20);
|
||||
backdrop-filter: blur(15px);
|
||||
transition: transform 0.3s ease-in-out;
|
||||
z-index: 1;
|
||||
|
||||
.close-button {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 1.3rem;
|
||||
right: 1.3rem;
|
||||
padding: 0.5rem;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
z-index: 2;
|
||||
border-radius: var(--border-radius);
|
||||
transition: 0.3s all ease-in-out;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
|
||||
.icon {
|
||||
color: var(--primary-foreground-color);
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
opacity: 0.6;
|
||||
transition: 0.3s opacity ease-in-out;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--overlay-color);
|
||||
|
||||
.icon {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
padding: @padding;
|
||||
overflow-y: auto;
|
||||
flex: none;
|
||||
|
||||
.side-drawer-meta-preview {
|
||||
.action-buttons-container {
|
||||
padding-top: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.series-content {
|
||||
flex: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.videos {
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: @small) {
|
||||
.side-drawer {
|
||||
max-width: 40dvw;
|
||||
}
|
||||
}
|
||||
|
||||
@media (orientation: portrait) and (max-width: @xsmall) {
|
||||
.side-drawer {
|
||||
max-width: 100dvw;
|
||||
|
||||
.close-button {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (orientation: landscape) and (max-width: @xsmall) {
|
||||
.side-drawer {
|
||||
max-width: 50dvw;
|
||||
|
||||
.info {
|
||||
max-height: 30dvh;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: @xxsmall) {
|
||||
.side-drawer {
|
||||
padding: calc(@padding / 2);
|
||||
|
||||
.info {
|
||||
padding: calc(@padding / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
118
src/routes/Player/SideDrawer/SideDrawer.tsx
Normal file
118
src/routes/Player/SideDrawer/SideDrawer.tsx
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React, { useMemo, useCallback, useState, forwardRef, memo } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Icon from '@stremio/stremio-icons/react';
|
||||
import { useServices } from 'stremio/services';
|
||||
import { CONSTANTS } from 'stremio/common';
|
||||
import MetaPreview from 'stremio/common/MetaPreview/MetaPreview';
|
||||
import Video from 'stremio/common/Video/Video';
|
||||
import SeasonsBar from 'stremio/routes/MetaDetails/VideosList/SeasonsBar';
|
||||
import styles from './SideDrawer.less';
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
seriesInfo: SeriesInfo;
|
||||
metaItem: MetaItem;
|
||||
closeSideDrawer: () => void;
|
||||
};
|
||||
|
||||
const SideDrawer = memo(forwardRef<HTMLDivElement, Props>(({ seriesInfo, className, closeSideDrawer, ...props }: Props, ref) => {
|
||||
const { core } = useServices();
|
||||
const [season, setSeason] = useState<number>(seriesInfo?.season);
|
||||
const metaItem = useMemo(() => {
|
||||
return seriesInfo ?
|
||||
{
|
||||
...props.metaItem,
|
||||
links: props.metaItem.links.filter(({ category }) => category === CONSTANTS.SHARE_LINK_CATEGORY)
|
||||
}
|
||||
:
|
||||
props.metaItem;
|
||||
}, [props.metaItem]);
|
||||
const videos = useMemo(() => {
|
||||
return Array.isArray(metaItem.videos) ?
|
||||
metaItem.videos.filter((video) => video.season === season)
|
||||
:
|
||||
metaItem.videos;
|
||||
}, [metaItem, season]);
|
||||
const seasons = useMemo(() => {
|
||||
return props.metaItem.videos
|
||||
.map(({ season }) => season)
|
||||
.filter((season, index, seasons) => {
|
||||
return seasons.indexOf(season) === index;
|
||||
})
|
||||
.sort((a, b) => (a || Number.MAX_SAFE_INTEGER) - (b || Number.MAX_SAFE_INTEGER));
|
||||
}, [props.metaItem.videos]);
|
||||
|
||||
const seasonOnSelect = useCallback((event: { value: string }) => {
|
||||
setSeason(parseInt(event.value));
|
||||
}, []);
|
||||
|
||||
const onMarkVideoAsWatched = useCallback((video: Video, watched: boolean) => {
|
||||
core.transport.dispatch({
|
||||
action: 'Player',
|
||||
args: {
|
||||
action: 'MarkVideoAsWatched',
|
||||
args: [video, !watched]
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onMouseDown = (event: React.MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={ref} className={classNames(styles['side-drawer'], className)} onMouseDown={onMouseDown}>
|
||||
<div className={styles['close-button']} onClick={closeSideDrawer}>
|
||||
<Icon className={styles['icon']} name={'chevron-forward'} />
|
||||
</div>
|
||||
<div className={styles['info']}>
|
||||
<MetaPreview
|
||||
className={styles['side-drawer-meta-preview']}
|
||||
compact={true}
|
||||
name={metaItem.name}
|
||||
logo={metaItem.logo}
|
||||
runtime={metaItem.runtime}
|
||||
releaseInfo={metaItem.releaseInfo}
|
||||
released={metaItem.released}
|
||||
description={metaItem.description}
|
||||
links={metaItem.links}
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
seriesInfo ?
|
||||
<div className={styles['series-content']}>
|
||||
<SeasonsBar
|
||||
season={season}
|
||||
seasons={seasons}
|
||||
onSelect={seasonOnSelect}
|
||||
/>
|
||||
<div className={styles['videos']}>
|
||||
{videos.map((video, index) => (
|
||||
<Video
|
||||
key={index}
|
||||
className={styles['video']}
|
||||
id={video.id}
|
||||
title={video.title}
|
||||
thumbnail={video.thumbnail}
|
||||
episode={video.episode}
|
||||
released={video.released}
|
||||
upcoming={video.upcoming}
|
||||
watched={video.watched}
|
||||
progress={video.progress}
|
||||
deepLinks={video.deepLinks}
|
||||
scheduled={video.scheduled}
|
||||
onMarkVideoAsWatched={onMarkVideoAsWatched}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
|
||||
</div>
|
||||
);
|
||||
}));
|
||||
|
||||
export default SideDrawer;
|
||||
2
src/routes/Player/SideDrawer/index.ts
Normal file
2
src/routes/Player/SideDrawer/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import SideDrawer from './SideDrawer';
|
||||
export default SideDrawer;
|
||||
47
src/routes/Player/SideDrawerButton/SideDrawerButton.less
Normal file
47
src/routes/Player/SideDrawerButton/SideDrawerButton.less
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
@import (reference) '~stremio/common/screen-sizes.less';
|
||||
|
||||
.side-drawer-button {
|
||||
height: 12.5rem;
|
||||
width: 7.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
padding-left: 0.5rem;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
background-color: var(--modal-background-color);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
will-change: opacity;
|
||||
transition: opacity 0.3s ease-in-out, border-radius 0.3s ease-in-out;
|
||||
|
||||
.icon {
|
||||
position: relative;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.6;
|
||||
transition: 0.3s opacity ease-in-out;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.icon {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: @xsmall) {
|
||||
.side-drawer-button {
|
||||
height: 8rem;
|
||||
width: 4.5rem;
|
||||
|
||||
.icon {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/routes/Player/SideDrawerButton/SideDrawerButton.tsx
Normal file
21
src/routes/Player/SideDrawerButton/SideDrawerButton.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Icon from '@stremio/stremio-icons/react';
|
||||
import styles from './SideDrawerButton.less';
|
||||
|
||||
type Props = {
|
||||
className: string,
|
||||
onClick: () => void,
|
||||
};
|
||||
|
||||
const SideDrawerButton = ({ className, onClick }: Props) => {
|
||||
return (
|
||||
<div className={classNames(className, styles['side-drawer-button'])} onClick={onClick}>
|
||||
<Icon name={'chevron-back'} className={styles['icon']} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SideDrawerButton;
|
||||
2
src/routes/Player/SideDrawerButton/index.ts
Normal file
2
src/routes/Player/SideDrawerButton/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import SideDrawerButton from './SideDrawerButton';
|
||||
export default SideDrawerButton;
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
|
||||
|
||||
.speed-menu-container {
|
||||
width: 14rem;
|
||||
overflow: visible !important;
|
||||
|
||||
.title {
|
||||
flex: none;
|
||||
|
|
@ -18,7 +17,6 @@
|
|||
flex: 0 1 auto;
|
||||
max-height: calc(3.2rem * 8);
|
||||
padding: 0 1rem 0.5rem;
|
||||
overflow-y: auto;
|
||||
|
||||
.option {
|
||||
height: 3.2rem;
|
||||
|
|
|
|||
|
|
@ -129,19 +129,19 @@ const SubtitlesMenu = React.memo((props) => {
|
|||
const onSubtitlesOffsetChanged = React.useCallback((event) => {
|
||||
const delta = event.value === 'increment' ? 1 : -1;
|
||||
if (typeof props.selectedSubtitlesTrackId === 'string') {
|
||||
if (props.extraSubtitlesOffset !== null && !isNaN(props.extraSubtitlesOffset)) {
|
||||
const offset = Math.max(0, Math.min(100, Math.floor(props.extraSubtitlesOffset + delta)));
|
||||
if (typeof props.onExtraSubtitlesOffsetChanged === 'function') {
|
||||
props.onExtraSubtitlesOffsetChanged(offset);
|
||||
}
|
||||
}
|
||||
} else if (typeof props.selectedExtraSubtitlesTrackId === 'string') {
|
||||
if (props.subtitlesOffset !== null && !isNaN(props.subtitlesOffset)) {
|
||||
const offset = Math.max(0, Math.min(100, Math.floor(props.subtitlesOffset + delta)));
|
||||
if (typeof props.onSubtitlesOffsetChanged === 'function') {
|
||||
props.onSubtitlesOffsetChanged(offset);
|
||||
}
|
||||
}
|
||||
} else if (typeof props.selectedExtraSubtitlesTrackId === 'string') {
|
||||
if (props.extraSubtitlesOffset !== null && !isNaN(props.extraSubtitlesOffset)) {
|
||||
const offset = Math.max(0, Math.min(100, Math.floor(props.extraSubtitlesOffset + delta)));
|
||||
if (typeof props.onExtraSubtitlesOffsetChanged === 'function') {
|
||||
props.onExtraSubtitlesOffsetChanged(offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [props.selectedSubtitlesTrackId, props.selectedExtraSubtitlesTrackId, props.subtitlesOffset, props.extraSubtitlesOffset, props.onSubtitlesOffsetChanged, props.onExtraSubtitlesOffsetChanged]);
|
||||
const audioTrackOnClick = React.useCallback((event) => {
|
||||
|
|
|
|||
|
|
@ -3,19 +3,34 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const Video = require('../../MetaDetails/VideosList/Video');
|
||||
const { useServices } = require('stremio/services');
|
||||
const { Video } = require('stremio/common');
|
||||
const styles = require('./styles');
|
||||
|
||||
const VideosMenu = ({ className, metaItem, seriesInfo }) => {
|
||||
const { core } = useServices();
|
||||
|
||||
const onMouseDown = React.useCallback((event) => {
|
||||
event.nativeEvent.videosMenuClosePrevented = true;
|
||||
}, []);
|
||||
|
||||
const videos = React.useMemo(() => {
|
||||
return seriesInfo && typeof seriesInfo.season === 'number' && Array.isArray(metaItem.videos) ?
|
||||
metaItem.videos.filter(({ season }) => season === seriesInfo.season)
|
||||
:
|
||||
metaItem.videos;
|
||||
}, [metaItem, seriesInfo]);
|
||||
|
||||
const onMarkVideoAsWatched = (video, watched) => {
|
||||
core.transport.dispatch({
|
||||
action: 'Player',
|
||||
args: {
|
||||
action: 'MarkVideoAsWatched',
|
||||
args: [video, !watched]
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classnames(className, styles['videos-menu-container'])} onMouseDown={onMouseDown}>
|
||||
{
|
||||
|
|
@ -32,6 +47,7 @@ const VideosMenu = ({ className, metaItem, seriesInfo }) => {
|
|||
progress={video.progress}
|
||||
deepLinks={video.deepLinks}
|
||||
scheduled={video.scheduled}
|
||||
onMarkVideoAsWatched={onMarkVideoAsWatched}
|
||||
/>
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const VideosMenu = require('./VideosMenu');
|
||||
|
||||
module.exports = VideosMenu;
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
.videos-menu-container {
|
||||
width: 30rem;
|
||||
padding: 1rem;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
|
||||
@import (reference) '~stremio/common/screen-sizes.less';
|
||||
|
||||
:import('~stremio/common/Slider/styles.less') {
|
||||
active-slider-within: active-slider-within;
|
||||
|
|
@ -18,7 +19,7 @@ html:not(.active-slider-within) {
|
|||
.player-container.overlayHidden {
|
||||
cursor: none;
|
||||
|
||||
.nav-bar-layer, .control-bar-layer, .menu-layer {
|
||||
.nav-bar-layer, .control-bar-layer, .menu-layer, .side-drawer-button-layer {
|
||||
opacity: 0;
|
||||
transition: opacity 200ms;
|
||||
}
|
||||
|
|
@ -83,6 +84,13 @@ html:not(.active-slider-within) {
|
|||
}
|
||||
}
|
||||
|
||||
&.side-drawer-button-layer {
|
||||
top: 50%;
|
||||
right: -4rem;
|
||||
left: initial;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
&.control-bar-layer {
|
||||
top: initial;
|
||||
overflow: visible;
|
||||
|
|
@ -101,7 +109,7 @@ html:not(.active-slider-within) {
|
|||
&.menu-layer {
|
||||
top: initial;
|
||||
left: initial;
|
||||
right: 2rem;
|
||||
right: 4rem;
|
||||
bottom: 8rem;
|
||||
max-height: calc(100% - 13.5rem);
|
||||
max-width: calc(100% - 4rem);
|
||||
|
|
@ -112,5 +120,33 @@ html:not(.active-slider-within) {
|
|||
backdrop-filter: blur(15px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&.side-drawer-layer {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: initial;
|
||||
bottom: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: @xsmall) {
|
||||
.player-container {
|
||||
.layer {
|
||||
&.side-drawer-button-layer {
|
||||
right: -2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (orientation: portrait) and (max-width: @minimum) {
|
||||
.player-container {
|
||||
.layer {
|
||||
&.menu-layer {
|
||||
right: 2.5rem;
|
||||
bottom: 11rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,10 @@
|
|||
|
||||
.add-item {
|
||||
display: flex;
|
||||
padding: 0.5rem 1.5rem;
|
||||
gap: 1rem;
|
||||
border-radius: var(--border-radius);
|
||||
padding: 0.35rem 1.5rem;
|
||||
border-radius: 2.5rem;
|
||||
transition: 0.3s all ease-in-out;
|
||||
background-color: transparent;
|
||||
background-color: var(--overlay-color);
|
||||
border: 2px solid transparent;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
|
|
@ -77,13 +76,12 @@
|
|||
}
|
||||
|
||||
&:hover {
|
||||
border: 2px solid transparent;
|
||||
background-color: var(--overlay-color);
|
||||
border: 2px solid var(--overlay-color);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
.add-item {
|
||||
padding: 0.5rem;
|
||||
padding: 0.35rem 0.5rem;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,10 +4,10 @@
|
|||
|
||||
.item {
|
||||
display: flex;
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: var(--border-radius);
|
||||
padding: 0.7rem 1.5rem;
|
||||
border-radius: 2.5rem;
|
||||
transition: 0.3s all ease-in-out;
|
||||
background-color: transparent;
|
||||
background-color: var(--overlay-color);
|
||||
border: 2px solid transparent;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
.actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-right: 5rem;
|
||||
padding-right: 4rem;
|
||||
|
||||
.status {
|
||||
display: flex;
|
||||
|
|
@ -100,7 +100,7 @@
|
|||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--overlay-color);
|
||||
border: 2px solid var(--overlay-color);
|
||||
|
||||
.actions {
|
||||
.delete {
|
||||
|
|
@ -114,13 +114,11 @@
|
|||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
.item {
|
||||
padding: 1rem 0.5rem;
|
||||
padding: 0.7rem 1rem;
|
||||
|
||||
.actions {
|
||||
margin-right: 4rem;
|
||||
|
||||
.delete {
|
||||
right: 0.5rem;
|
||||
right: 1rem;
|
||||
|
||||
.icon {
|
||||
opacity: 0.6;
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ const Item = ({ url }: Props) => {
|
|||
|
||||
const handleDelete = useCallback(() => {
|
||||
deleteServerUrl(url);
|
||||
selectServerUrl(DEFAULT_STREAMING_SERVER_URL);
|
||||
}, [url]);
|
||||
selected && selectServerUrl(DEFAULT_STREAMING_SERVER_URL);
|
||||
}, [url, selected]);
|
||||
|
||||
const handleSelect = useCallback(() => {
|
||||
selectServerUrl(url);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@
|
|||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
justify-content: space-between;
|
||||
padding: 0 3rem;
|
||||
align-items: center;
|
||||
|
||||
.label {
|
||||
|
|
@ -16,6 +17,10 @@
|
|||
color: var(--primary-foreground-color);
|
||||
font-weight: 400;
|
||||
opacity: 0.6;
|
||||
|
||||
&:last-of-type {
|
||||
padding-right: 3rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -286,7 +286,6 @@
|
|||
}
|
||||
|
||||
.multiselect-menu-container {
|
||||
max-height: calc(3.2rem * 7);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
|
@ -316,7 +315,14 @@
|
|||
}
|
||||
|
||||
&.color-input-container {
|
||||
padding: 1.75rem 1rem;
|
||||
padding: 1.3rem 1rem;
|
||||
border-radius: 3rem;
|
||||
border: 2px solid transparent;
|
||||
transition: 0.3s all ease-in-out;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--overlay-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.info-container {
|
||||
|
|
|
|||
2
src/types/MetaItem.d.ts
vendored
2
src/types/MetaItem.d.ts
vendored
|
|
@ -15,7 +15,7 @@ type MetaItemPreview = {
|
|||
posterShape: PosterShape,
|
||||
releaseInfo: string | null,
|
||||
runtime: string | null,
|
||||
released: string | null,
|
||||
released: Date | null | undefined,
|
||||
trailerStreams: TrailerStream[],
|
||||
links: Link[],
|
||||
behaviorHints: BehaviorHints,
|
||||
|
|
|
|||
5
src/types/Video.d.ts
vendored
5
src/types/Video.d.ts
vendored
|
|
@ -14,4 +14,9 @@ type Video = {
|
|||
episode?: number,
|
||||
streams: Stream[],
|
||||
trailerStreams: TrailerStream[],
|
||||
watched: boolean,
|
||||
progress: number,
|
||||
upcoming: boolean,
|
||||
deepLinks: VideoDeepLinks,
|
||||
scheduled: boolean,
|
||||
};
|
||||
|
|
|
|||
10
src/types/models/Player.d.ts
vendored
10
src/types/models/Player.d.ts
vendored
|
|
@ -25,6 +25,11 @@ type Subtitle = {
|
|||
url: string,
|
||||
};
|
||||
|
||||
type SeriesInfo = {
|
||||
episode: number,
|
||||
season: number,
|
||||
};
|
||||
|
||||
type Player = {
|
||||
addon: Addon | null,
|
||||
libraryItem: LibraryItemPlayer | null,
|
||||
|
|
@ -36,10 +41,7 @@ type Player = {
|
|||
streamRequest: ResourceRequest,
|
||||
subtitlesPath: ResourceRequestPath,
|
||||
} | null,
|
||||
seriesInfo: {
|
||||
season: number,
|
||||
episode: number,
|
||||
} | null,
|
||||
seriesInfo: SeriesInfo | null,
|
||||
subtitles: Subtitle[],
|
||||
title: string | null,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const { execSync } = require('child_process');
|
||||
const webpack = require('webpack');
|
||||
const threadLoader = require('thread-loader');
|
||||
const HtmlWebPackPlugin = require('html-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
|
|
@ -14,6 +16,25 @@ const pachageJson = require('./package.json');
|
|||
|
||||
const COMMIT_HASH = execSync('git rev-parse HEAD').toString().trim();
|
||||
|
||||
const THREAD_LOADER = {
|
||||
loader: 'thread-loader',
|
||||
options: {
|
||||
name: 'shared-pool',
|
||||
workers: os.cpus().length,
|
||||
},
|
||||
};
|
||||
|
||||
threadLoader.warmup(
|
||||
THREAD_LOADER.options,
|
||||
[
|
||||
'babel-loader',
|
||||
'ts-loader',
|
||||
'css-loader',
|
||||
'postcss-loader',
|
||||
'less-loader',
|
||||
],
|
||||
);
|
||||
|
||||
module.exports = (env, argv) => ({
|
||||
mode: argv.mode,
|
||||
devtool: argv.mode === 'production' ? 'source-map' : 'eval-source-map',
|
||||
|
|
@ -30,20 +51,31 @@ module.exports = (env, argv) => ({
|
|||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: [
|
||||
'@babel/preset-env',
|
||||
'@babel/preset-react'
|
||||
],
|
||||
use: [
|
||||
THREAD_LOADER,
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: [
|
||||
'@babel/preset-env',
|
||||
'@babel/preset-react'
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(ts|tsx)$/,
|
||||
exclude: /node_modules/,
|
||||
use: 'ts-loader',
|
||||
use: [
|
||||
THREAD_LOADER,
|
||||
{
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
happyPackMode: true,
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
|
|
@ -55,6 +87,7 @@ module.exports = (env, argv) => ({
|
|||
esModule: false
|
||||
}
|
||||
},
|
||||
THREAD_LOADER,
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
|
|
|
|||
Loading…
Reference in a new issue