Merge branch 'development' into pr/420
2
.github/workflows/build.yml
vendored
|
|
@ -3,7 +3,7 @@ name: Build
|
|||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
- '**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
|
|
|||
1
.gitignore
vendored
|
|
@ -3,3 +3,4 @@
|
|||
/yarn.lock
|
||||
/npm-debug.log
|
||||
.DS_Store
|
||||
.prettierignore
|
||||
55
Dockerfile
|
|
@ -1,24 +1,31 @@
|
|||
# Stremio Node 14.x
|
||||
FROM stremio/node-base:fermium
|
||||
|
||||
# Meta
|
||||
LABEL Description="Stremio Web" Vendor="Smart Code OOD" Version="1.0.0"
|
||||
|
||||
# Update GitHub remote host key
|
||||
RUN echo "github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=" >> ~/.ssh/known_hosts
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p /var/www/stremio-web
|
||||
|
||||
# Install app dependencies
|
||||
WORKDIR /var/www/stremio-web
|
||||
COPY . /var/www/stremio-web
|
||||
RUN npm install
|
||||
|
||||
# Bundle app source
|
||||
WORKDIR /var/www/stremio-web
|
||||
|
||||
RUN npm run build
|
||||
|
||||
EXPOSE 8080
|
||||
CMD ["node", "http_server.js"]
|
||||
# Stremio Node 14.x
|
||||
# the node version for running Stremio Web
|
||||
ARG NODE_VERSION=20-alpine
|
||||
FROM node:$NODE_VERSION AS base
|
||||
|
||||
# Meta
|
||||
LABEL Description="Stremio Web" Vendor="Smart Code OOD" Version="1.0.0"
|
||||
|
||||
RUN mkdir -p /var/www/stremio-web
|
||||
WORKDIR /var/www/stremio-web
|
||||
|
||||
# Install app dependencies
|
||||
FROM base AS prebuild
|
||||
|
||||
RUN apk update && apk upgrade && \
|
||||
apk add --no-cache git
|
||||
WORKDIR /var/www/stremio-web
|
||||
COPY . .
|
||||
RUN npm install
|
||||
RUN npm run build
|
||||
|
||||
# Bundle app source
|
||||
FROM base AS final
|
||||
|
||||
WORKDIR /var/www/stremio-web
|
||||
COPY . .
|
||||
COPY --from=prebuild /var/www/stremio-web/node_modules ./node_modules
|
||||
COPY --from=prebuild /var/www/stremio-web/build ./build
|
||||
|
||||
EXPOSE 8080
|
||||
CMD ["node", "http_server.js"]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Stremio - Freedom to Stream
|
||||
|
||||

|
||||
[](https://stremio.github.io/stremio-web/)
|
||||
[](https://stremio.github.io/stremio-web/development)
|
||||
|
||||
Stremio is a modern media center that's a one-stop solution for your video entertainment. You discover, watch and organize video content from easy to install addons.
|
||||
|
||||
|
|
@ -46,4 +46,4 @@ npm run build
|
|||
|
||||
## License
|
||||
|
||||
Stremio is copyright 2017-2022 Smart code and available under GPLv2 license. See the [LICENSE](/LICENSE.md) file in the project for more information.
|
||||
Stremio is copyright 2017-2023 Smart code and available under GPLv2 license. See the [LICENSE](/LICENSE.md) file in the project for more information.
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
BIN
fonts/PlusJakartaSans.ttf
Normal file
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 22 KiB |
15
images/background_1.svg
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="968" height="565" viewBox="0 0 968 565">
|
||||
<defs>
|
||||
<clipPath id="clip-path">
|
||||
<rect id="Rectangle_1144" data-name="Rectangle 1144" width="968" height="565" transform="translate(0 262)" fill="#fff" stroke="#707070" stroke-width="1"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g id="Mask_Group_31" data-name="Mask Group 31" transform="translate(0 -262)" clip-path="url(#clip-path)">
|
||||
<g id="Group_2309" data-name="Group 2309">
|
||||
<path id="Path_983" data-name="Path 983" d="M410.951-49.5c337,24.76,699.788,308.381,792,500.579S897.064,762.814,577.9,762.814,0,593.971,0,385.694,73.955-74.26,410.951-49.5Z" transform="translate(-301.147 411.907)" fill="#362565" opacity="0.8"/>
|
||||
<path id="Path_979" data-name="Path 979" d="M360.91-73.97c324,27.3,638,301.633,720.932,474.48S806.748,680.86,519.716,680.86,0,529.016,0,341.708,36.91-101.27,360.91-73.97Z" transform="translate(-231.91 594.67)" fill="rgba(123,91,245,0.83)" opacity="0.8"/>
|
||||
<path id="Path_984" data-name="Path 984" d="M262.171-10C444.7-10,659.821,73.865,660.993,203.729S513.025,402.667,330.5,402.667,0,313.6,0,203.729,79.643-10,262.171-10Z" transform="translate(-69 681.267)" fill="#5126ed"/>
|
||||
<path id="Path_980" data-name="Path 980" d="M262.171-10C444.7-10,659.821,66.535,660.993,185.049S513.025,366.6,330.5,366.6,0,285.317,0,185.049,79.643-10,262.171-10Z" transform="translate(-69 762.333)" fill="#4516fc"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
13
images/background_2.svg
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="718" height="356" viewBox="0 0 718 356">
|
||||
<defs>
|
||||
<clipPath id="clip-path">
|
||||
<rect id="Rectangle_1144" data-name="Rectangle 1144" width="718" height="356" transform="translate(602 -8)" fill="#fff" stroke="#707070" stroke-width="1"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g id="Mask_Group_31" data-name="Mask Group 31" transform="translate(-602 8)" clip-path="url(#clip-path)">
|
||||
<g id="Group_2308" data-name="Group 2308" transform="translate(-49.883 86.23)">
|
||||
<path id="Path_982" data-name="Path 982" d="M264.138,0C470.016,0,780.486,131.36,775.97,319.553S578.654,535.889,372.776,535.889,0,418.717,0,274.178,58.26,0,264.138,0Z" transform="translate(1521.635 173.714) rotate(180)" fill="rgba(137,91,245,0.64)" opacity="0.52"/>
|
||||
<path id="Path_981" data-name="Path 981" d="M177.9,0C301.753,0,447.725,59.059,448.52,150.512s-100.4,140.1-224.26,140.1S0,227.885,0,150.512,54.042,0,177.9,0Z" transform="translate(1366.094 26.124) rotate(180)" fill="#4722d2"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 2.1 MiB |
BIN
images/empty.png
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 96 KiB |
BIN
images/icon.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 207 KiB |
BIN
images/logo.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
images/maskable_icon.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 5 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 16 KiB |
|
|
@ -1,40 +0,0 @@
|
|||
{
|
||||
"short_name": "Stremio",
|
||||
"name": "Stremio Web",
|
||||
"description": "Freedom To Stream",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicons/favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "images/icon_x192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "images/icon_x512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
},
|
||||
{
|
||||
"src": "images/maskable_icon_x192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "images/maskable_icon_x512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"start_url": "https://web.stremio.com",
|
||||
"scope": "https://web.stremio.com",
|
||||
"display": "standalone",
|
||||
"orientation": "natural",
|
||||
"theme_color": "#2a2843",
|
||||
"background_color": "#161523"
|
||||
}
|
||||
16108
package-lock.json
generated
16
package.json
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "stremio",
|
||||
"displayName": "Stremio",
|
||||
"version": "5.0.0",
|
||||
"version": "5.0.0-beta.4",
|
||||
"author": "Smart Code OOD",
|
||||
"private": true,
|
||||
"license": "gpl-2.0",
|
||||
|
|
@ -15,9 +15,9 @@
|
|||
"@babel/runtime": "7.16.0",
|
||||
"@sentry/browser": "6.13.3",
|
||||
"@stremio/stremio-colors": "5.0.1",
|
||||
"@stremio/stremio-core-web": "0.44.20",
|
||||
"@stremio/stremio-icons": "4.0.0",
|
||||
"@stremio/stremio-video": "0.0.24",
|
||||
"@stremio/stremio-core-web": "0.46.0",
|
||||
"@stremio/stremio-icons": "5.2.0",
|
||||
"@stremio/stremio-video": "0.0.30",
|
||||
"a-color-picker": "1.2.1",
|
||||
"bowser": "2.11.0",
|
||||
"buffer": "6.0.3",
|
||||
|
|
@ -38,8 +38,8 @@
|
|||
"react-focus-lock": "2.9.1",
|
||||
"react-i18next": "^12.1.1",
|
||||
"react-is": "18.2.0",
|
||||
"spatial-navigation-polyfill": "https://github.com/Stremio/spatial-navigation.git#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
|
||||
"stremio-translations": "https://github.com/Stremio/stremio-translations.git#92675658de92113c5888cf5e57003e468e8b8c9c",
|
||||
"spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
|
||||
"stremio-translations": "github:Stremio/stremio-translations#12b1307f95249496960d2a257b371db5700721e6",
|
||||
"url": "0.11.0",
|
||||
"use-long-press": "^3.1.5"
|
||||
},
|
||||
|
|
@ -49,6 +49,7 @@
|
|||
"@babel/plugin-proposal-object-rest-spread": "7.16.0",
|
||||
"@babel/preset-env": "7.16.0",
|
||||
"@babel/preset-react": "7.16.0",
|
||||
"@types/react": "^18.2.9",
|
||||
"babel-loader": "8.2.3",
|
||||
"clean-webpack-plugin": "4.0.0",
|
||||
"copy-webpack-plugin": "9.0.1",
|
||||
|
|
@ -67,7 +68,8 @@
|
|||
"terser-webpack-plugin": "5.2.4",
|
||||
"webpack": "5.61.0",
|
||||
"webpack-cli": "4.9.1",
|
||||
"webpack-dev-server": "4.7.4",
|
||||
"webpack-dev-server": "^4.7.4",
|
||||
"webpack-pwa-manifest": "^4.3.0",
|
||||
"workbox-webpack-plugin": "^6.5.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 976 KiB After Width: | Height: | Size: 1 MiB |
BIN
screenshots/board_narrow.webp
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
screenshots/board_wide.webp
Normal file
|
After Width: | Height: | Size: 159 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 947 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.2 MiB |
|
|
@ -6,7 +6,7 @@ const { useTranslation } = require('react-i18next');
|
|||
const { Router } = require('stremio-router');
|
||||
const { Core, Shell, Chromecast, DragAndDrop, KeyboardShortcuts, ServicesProvider } = require('stremio/services');
|
||||
const { NotFound } = require('stremio/routes');
|
||||
const { ToastProvider, CONSTANTS, withCoreSuspender } = require('stremio/common');
|
||||
const { ToastProvider, TooltipProvider, CONSTANTS, withCoreSuspender } = require('stremio/common');
|
||||
const ServicesToaster = require('./ServicesToaster');
|
||||
const DeepLinkHandler = require('./DeepLinkHandler');
|
||||
const DefaultSettingsHandler = require('./DefaultSettingsHandler');
|
||||
|
|
@ -130,6 +130,12 @@ const App = () => {
|
|||
action: 'SyncLibraryWithAPI'
|
||||
}
|
||||
});
|
||||
services.core.transport.dispatch({
|
||||
action: 'Ctx',
|
||||
args: {
|
||||
action: 'PullNotifications'
|
||||
}
|
||||
});
|
||||
};
|
||||
if (services.core.active) {
|
||||
onWindowFocus();
|
||||
|
|
@ -141,8 +147,10 @@ const App = () => {
|
|||
.catch((e) => console.error(e));
|
||||
}
|
||||
return () => {
|
||||
window.removeEventListener('focus', onWindowFocus);
|
||||
services.core.transport.off('CoreEvent', onCoreEvent);
|
||||
if (services.core.active) {
|
||||
window.removeEventListener('focus', onWindowFocus);
|
||||
services.core.transport.off('CoreEvent', onCoreEvent);
|
||||
}
|
||||
};
|
||||
}, [initialized]);
|
||||
return (
|
||||
|
|
@ -154,14 +162,15 @@ const App = () => {
|
|||
<ErrorDialog className={styles['error-container']} />
|
||||
:
|
||||
<ToastProvider className={styles['toasts-container']}>
|
||||
<ServicesToaster />
|
||||
<DeepLinkHandler />
|
||||
<DefaultSettingsHandler />
|
||||
<RouterWithProtectedRoutes
|
||||
className={styles['router']}
|
||||
viewsConfig={routerViewsConfig}
|
||||
onPathNotMatch={onPathNotMatch}
|
||||
/>
|
||||
<TooltipProvider className={styles['tooltip-container']}>
|
||||
<ServicesToaster />
|
||||
<DeepLinkHandler />
|
||||
<RouterWithProtectedRoutes
|
||||
className={styles['router']}
|
||||
viewsConfig={routerViewsConfig}
|
||||
onPathNotMatch={onPathNotMatch}
|
||||
/>
|
||||
</TooltipProvider>
|
||||
</ToastProvider>
|
||||
:
|
||||
<div className={styles['loader-container']} />
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { Button, Image } = require('stremio/common');
|
||||
const styles = require('./styles');
|
||||
|
||||
const ErrorDialog = ({ className }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [dataCleared, setDataCleared] = React.useState(false);
|
||||
const reload = React.useCallback(() => {
|
||||
window.location.reload();
|
||||
|
|
@ -22,13 +25,19 @@ const ErrorDialog = ({ className }) => {
|
|||
src={require('/images/empty.png')}
|
||||
alt={' '}
|
||||
/>
|
||||
<div className={styles['error-message']}>Something went wrong!</div>
|
||||
<div className={styles['error-message']}>
|
||||
{ t('GENERIC_ERROR_MESSAGE') }
|
||||
</div>
|
||||
<div className={styles['buttons-container']}>
|
||||
<Button className={styles['button-container']} title={'Try again'} onClick={reload}>
|
||||
<div className={styles['label']}>Try again</div>
|
||||
<Button className={styles['button-container']} title={t('TRY_AGAIN')} onClick={reload}>
|
||||
<div className={styles['label']}>
|
||||
{ t('TRY_AGAIN') }
|
||||
</div>
|
||||
</Button>
|
||||
<Button className={styles['button-container']} disabled={dataCleared} title={'Clear data'} onClick={clearData}>
|
||||
<div className={styles['label']}>Clear data</div>
|
||||
<Button className={styles['button-container']} disabled={dataCleared} title={t('CLEAR_DATA')} onClick={clearData}>
|
||||
<div className={styles['label']}>
|
||||
{ t('CLEAR_DATA') }
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@
|
|||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
|
||||
.error-image {
|
||||
flex: none;
|
||||
width: 12rem;
|
||||
height: 12rem;
|
||||
margin-bottom: 1rem;
|
||||
object-fit: contain;
|
||||
object-position: center;
|
||||
opacity: 0.9;
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
font-size: 2rem;
|
||||
max-height: 3.6em;
|
||||
text-align: center;
|
||||
color: @color-surface-light5-90;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
.buttons-container {
|
||||
|
|
@ -36,6 +36,8 @@
|
|||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1.5rem;
|
||||
margin-top: 1rem;
|
||||
|
||||
.button-container {
|
||||
flex-grow: 0;
|
||||
|
|
@ -45,18 +47,23 @@
|
|||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 2rem 1rem 0;
|
||||
padding: 0 1rem;
|
||||
padding: 0 2.5rem;
|
||||
min-width: 8rem;
|
||||
height: 3rem;
|
||||
background-color: @color-accent3;
|
||||
height: 3.5rem;
|
||||
border-radius: 3.5rem;
|
||||
background-color: var(--overlay-color);
|
||||
|
||||
&:hover {
|
||||
background-color: @color-accent3-light1;
|
||||
outline: var(--focus-outline-size) solid var(--primary-foreground-color);
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&:active {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:global(.disabled) {
|
||||
background-color: @color-surface-dark5;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.label {
|
||||
|
|
@ -67,7 +74,7 @@
|
|||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
color: @color-surface-light5-90;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
@import (inline, once, css) '~stremio/common/roboto.css';
|
||||
@import (reference) '~stremio/common/screen-sizes.less';
|
||||
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
|
||||
|
||||
@font-face {
|
||||
font-family: 'PlusJakartaSans';
|
||||
src: url('/fonts/PlusJakartaSans.ttf') format('truetype');
|
||||
}
|
||||
|
||||
:global {
|
||||
@import (once, less) '~stremio/common/animations.less';
|
||||
@import (once, less) '~stremio-router/styles.css';
|
||||
|
|
@ -13,14 +17,28 @@
|
|||
--landscape-shape-ratio: 0.5625;
|
||||
--poster-shape-ratio: 1.464;
|
||||
--scroll-bar-size: 6px;
|
||||
--horizontal-nav-bar-size: 4rem;
|
||||
--vertical-nav-bar-size: 5.2rem;
|
||||
--horizontal-nav-bar-size: 5.5rem;
|
||||
--vertical-nav-bar-size: 6rem;
|
||||
--focus-outline-size: 2px;
|
||||
--color-facebook: #4267b2;
|
||||
--color-twitter: #1DA1F2;
|
||||
--color-facebook: #1877F1;
|
||||
--color-x: #000000;
|
||||
--color-reddit: #FF4500;
|
||||
--color-imdb: #f5c518;
|
||||
--color-trakt: #ED2224;
|
||||
--color-placeholder: #60606080;
|
||||
--color-placeholder-text: @color-surface-50;
|
||||
--color-placeholder-background: @color-surface-dark5-20;
|
||||
--primary-background-color: rgba(12, 11, 17, 1);
|
||||
--secondary-background-color: rgba(26, 23, 62, 1);
|
||||
--primary-foreground-color: rgba(255, 255, 255, 0.9);
|
||||
--secondary-foreground-color: rgb(12, 11, 17, 1);
|
||||
--primary-accent-color: rgb(123, 91, 245);
|
||||
--secondary-accent-color: rgba(34, 179, 101, 1);
|
||||
--tertiary-accent-color: rgba(246, 199, 0, 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);
|
||||
--border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
* {
|
||||
|
|
@ -28,7 +46,6 @@
|
|||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-size: 1rem;
|
||||
line-height: 1.2em;
|
||||
font-family: inherit;
|
||||
border: none;
|
||||
outline: none;
|
||||
|
|
@ -41,7 +58,7 @@
|
|||
overflow: hidden;
|
||||
word-break: break-word;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: @color-secondaryvariant2-light1 @color-background-dark2;
|
||||
scrollbar-color: var(--overlay-color) transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
|
|
@ -50,15 +67,16 @@
|
|||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: @color-secondaryvariant2-light1;
|
||||
border-radius: var(--scroll-bar-size);
|
||||
background-color: var(--overlay-color);
|
||||
|
||||
&:hover {
|
||||
background-color: @color-secondaryvariant2-light2;
|
||||
background-color: var(--primary-accent-color);
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: @color-background-dark2;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
svg {
|
||||
|
|
@ -70,12 +88,13 @@ html {
|
|||
height: 100%;
|
||||
min-width: 640px;
|
||||
min-height: 480px;
|
||||
font-family: 'Roboto', 'sans-serif';
|
||||
font-family: 'PlusJakartaSans', 'sans-serif';
|
||||
overflow: auto;
|
||||
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(41deg, var(--primary-background-color) 0%, var(--secondary-background-color) 100%);
|
||||
|
||||
:global(#app) {
|
||||
position: relative;
|
||||
|
|
@ -100,6 +119,24 @@ html {
|
|||
}
|
||||
}
|
||||
|
||||
.tooltip-container {
|
||||
height: 2.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 1.5rem;
|
||||
font-size: 1rem;
|
||||
color: var(--primary-foreground-color);
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--modal-background-color);
|
||||
box-shadow: var(--outer-glow);
|
||||
transition: opacity 0.1s ease-out;
|
||||
|
||||
&:global(.active) {
|
||||
transition-delay: 0.25s;
|
||||
}
|
||||
}
|
||||
|
||||
.router {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
|
@ -108,7 +145,6 @@ html {
|
|||
.loader-container, .error-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: @color-background-dark2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const Image = require('stremio/common/Image');
|
||||
const styles = require('./styles');
|
||||
|
||||
const AddonDetails = ({ className, id, name, version, logo, description, types, transportUrl, official }) => {
|
||||
const renderLogoFallback = React.useCallback(() => (
|
||||
<Icon className={styles['icon']} icon={'ic_addons'} />
|
||||
<Icon className={styles['icon']} name={'addons'} />
|
||||
), []);
|
||||
return (
|
||||
<div className={classnames(className, styles['addon-details-container'])}>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
height: 5rem;
|
||||
margin-right: 1.5rem;
|
||||
padding: 0.5rem;
|
||||
background-color: @color-background-dark5;
|
||||
}
|
||||
|
||||
.logo {
|
||||
|
|
@ -23,7 +22,7 @@
|
|||
}
|
||||
|
||||
.icon {
|
||||
fill: @color-secondaryvariant1-light3;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
.name-container {
|
||||
|
|
@ -41,7 +40,7 @@
|
|||
flex-basis: auto;
|
||||
margin-right: 0.5rem;
|
||||
font-size: 1.6rem;
|
||||
color: @color-background-dark5-90;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
.version {
|
||||
|
|
@ -49,7 +48,7 @@
|
|||
flex-shrink: 1;
|
||||
flex-basis: auto;
|
||||
margin-top: 0.5rem;
|
||||
color: @color-background-dark5-60;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -59,13 +58,13 @@
|
|||
|
||||
.section-header {
|
||||
font-size: 1.1rem;
|
||||
color: @color-background-dark5-90;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
.section-label {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 300;
|
||||
color: @color-background-dark5-90;
|
||||
color: var(--primary-foreground-color);
|
||||
|
||||
&.transport-url-label {
|
||||
user-select: text;
|
||||
|
|
|
|||
|
|
@ -10,38 +10,37 @@
|
|||
.addon-details-container, .addon-details-message-container {
|
||||
width: 40rem;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.install-button, .uninstall-button, .cancel-button {
|
||||
.label {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.uninstall-button, .cancel-button {
|
||||
&:focus {
|
||||
outline-color: @color-background-dark5;
|
||||
}
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
background-color: transparent;
|
||||
opacity: 0.3;
|
||||
|
||||
&:hover {
|
||||
background-color: @color-surface-light3;
|
||||
outline: var(--focus-outline-size) solid var(--primary-foreground-color);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: @color-surface-dark2;
|
||||
&:focus {
|
||||
outline-color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
.cancel-button-label {
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
.uninstall-button {
|
||||
background-color: @color-accent2;
|
||||
|
||||
background-color: var(--overlay-color);
|
||||
|
||||
&:hover {
|
||||
background-color: @color-accent2-light2;
|
||||
outline: var(--focus-outline-size) solid var(--overlay-color);
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline-color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,6 @@
|
|||
|
||||
&:global(.disabled) {
|
||||
pointer-events: none;
|
||||
opacity: 0.25;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
const CHROMECAST_RECEIVER_APP_ID = '1634F54B';
|
||||
const SUBTITLES_SIZES = [75, 100, 125, 150, 175, 200, 250];
|
||||
const SUBTITLES_FONTS = ['Roboto', 'Arial', 'Halvetica', 'Times New Roman', 'Verdana', 'Courier', 'Lucida Console', 'sans-serif', 'serif', 'monospace'];
|
||||
const SEEK_TIME_DURATIONS = [5000, 10000, 15000, 20000, 25000, 30000];
|
||||
const NEXT_VIDEO_POPUP_DURATIONS = [0, 5000, 10000, 15000, 20000, 25000, 30000, 35000, 40000, 45000, 50000];
|
||||
const SUBTITLES_FONTS = ['PlusJakartaSans', 'Arial', 'Halvetica', 'Times New Roman', 'Verdana', 'Courier', 'Lucida Console', 'sans-serif', 'serif', 'monospace'];
|
||||
const SEEK_TIME_DURATIONS = [3000, 5000, 10000, 15000, 20000, 30000];
|
||||
const NEXT_VIDEO_POPUP_DURATIONS = [0, 5000, 10000, 15000, 20000, 25000, 30000, 35000, 40000, 45000, 50000, 55000, 60000, 65000, 70000, 75000, 80000, 85000, 90000];
|
||||
const CATALOG_PREVIEW_SIZE = 10;
|
||||
const CATALOG_PAGE_SIZE = 100;
|
||||
const NONE_EXTRA_VALUE = 'None';
|
||||
|
|
@ -27,19 +27,62 @@ const TYPE_PRIORITIES = {
|
|||
other: -Infinity
|
||||
};
|
||||
const ICON_FOR_TYPE = new Map([
|
||||
['movie', 'ic_movies'],
|
||||
['series', 'ic_series'],
|
||||
['channel', 'ic_channels'],
|
||||
['tv', 'ic_tv'],
|
||||
['movie', 'movies'],
|
||||
['series', 'series'],
|
||||
['channel', 'channels'],
|
||||
['tv', 'tv'],
|
||||
['book', 'ic_book'],
|
||||
['game', 'ic_games'],
|
||||
['music', 'ic_music'],
|
||||
['adult', 'ic_adult'],
|
||||
['radio', 'ic_radio'],
|
||||
['podcast', 'ic_podcast'],
|
||||
['other', 'ic_movies'],
|
||||
['other', 'movies'],
|
||||
]);
|
||||
|
||||
const EXTERNAL_PLAYERS = [
|
||||
{
|
||||
label: 'EXTERNAL_PLAYER_DISABLED',
|
||||
value: null,
|
||||
platforms: ['ios', 'android', 'windows', 'linux', 'macos'],
|
||||
},
|
||||
{
|
||||
label: 'EXTERNAL_PLAYER_ALLOW_CHOOSING',
|
||||
value: 'choose',
|
||||
platforms: ['android'],
|
||||
},
|
||||
{
|
||||
label: 'VLC',
|
||||
value: 'vlc',
|
||||
platforms: ['ios', 'android'],
|
||||
},
|
||||
{
|
||||
label: 'MPV',
|
||||
value: 'mpv',
|
||||
platforms: ['macos'],
|
||||
},
|
||||
{
|
||||
label: 'IINA',
|
||||
value: 'iina',
|
||||
platforms: ['macos'],
|
||||
},
|
||||
{
|
||||
label: 'MX Player',
|
||||
value: 'mxplayer',
|
||||
platforms: ['android'],
|
||||
},
|
||||
{
|
||||
label: 'Just Player',
|
||||
value: 'justplayer',
|
||||
platforms: ['android'],
|
||||
},
|
||||
{
|
||||
label: 'Outplayer',
|
||||
value: 'outplayer',
|
||||
platforms: ['ios'],
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
CHROMECAST_RECEIVER_APP_ID,
|
||||
SUBTITLES_SIZES,
|
||||
|
|
@ -55,5 +98,6 @@ module.exports = {
|
|||
SHARE_LINK_CATEGORY,
|
||||
WRITERS_LINK_CATEGORY,
|
||||
TYPE_PRIORITIES,
|
||||
ICON_FOR_TYPE
|
||||
ICON_FOR_TYPE,
|
||||
EXTERNAL_PLAYERS,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,21 +3,13 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const Button = require('stremio/common/Button');
|
||||
const styles = require('./styles');
|
||||
|
||||
const Checkbox = React.forwardRef(({ className, checked, children, ...props }, ref) => {
|
||||
return (
|
||||
<Button {...props} ref={ref} className={classnames(className, styles['checkbox-container'], { 'checked': checked })}>
|
||||
{
|
||||
checked ?
|
||||
<svg className={styles['icon']} viewBox={'0 0 100 100'}>
|
||||
<Icon x={'10'} y={'10'} width={'80'} height={'80'} icon={'ic_check'} />
|
||||
</svg>
|
||||
:
|
||||
<Icon className={styles['icon']} icon={'ic_box_empty'} />
|
||||
}
|
||||
<div className={styles['toggle']} />
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,18 +2,43 @@
|
|||
|
||||
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
|
||||
|
||||
@height: 1.7rem;
|
||||
@width: 3.2rem;
|
||||
@thumb-margin: 0.5rem;
|
||||
|
||||
@thumb-size: calc(@height - @thumb-margin);
|
||||
|
||||
.checkbox-container {
|
||||
&:global(.checked) {
|
||||
.icon {
|
||||
fill: @color-surface-light5;
|
||||
background-color: @color-primaryvariant1;
|
||||
position: relative;
|
||||
|
||||
.toggle {
|
||||
position: relative;
|
||||
width: @width;
|
||||
height: @height;
|
||||
border-radius: @height;
|
||||
background-color: var(--overlay-color);
|
||||
transition: background-color 0.1s ease-in-out;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
height: @thumb-size;
|
||||
width: @thumb-size;
|
||||
top: calc(@thumb-margin / 2);
|
||||
left: calc(@thumb-margin / 2);
|
||||
border-radius: 50%;
|
||||
background-color: var(--primary-foreground-color);
|
||||
transition: transform 0.1s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: block;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
fill: @color-surface-light5;
|
||||
&:global(.checked) {
|
||||
.toggle {
|
||||
background-color: var(--secondary-accent-color);
|
||||
|
||||
&::before {
|
||||
transform: translateX(calc(@width - @thumb-size - @thumb-margin));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
72
src/common/ContinueWatchingItem/ContinueWatchingItem.js
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const { useServices } = require('stremio/services');
|
||||
const useNotifications = require('stremio/common/useNotifications');
|
||||
const LibItem = require('stremio/common/LibItem');
|
||||
|
||||
const ContinueWatchingItem = ({ _id, deepLinks, ...props }) => {
|
||||
const { core } = useServices();
|
||||
const notifications = useNotifications();
|
||||
|
||||
const newVideos = React.useMemo(() => {
|
||||
const count = notifications.items?.[_id]?.length ?? 0;
|
||||
return Math.min(Math.max(count, 0), 99);
|
||||
}, [_id, notifications.items]);
|
||||
|
||||
const onClick = React.useCallback(() => {
|
||||
if (deepLinks?.metaDetailsVideos ?? deepLinks?.metaDetailsStreams) {
|
||||
window.location = deepLinks?.metaDetailsVideos ?? deepLinks?.metaDetailsStreams;
|
||||
}
|
||||
}, [deepLinks]);
|
||||
|
||||
const onPlayClick = React.useCallback((event) => {
|
||||
event.stopPropagation();
|
||||
if (deepLinks?.player ?? deepLinks?.metaDetailsStreams ?? deepLinks?.metaDetailsVideos) {
|
||||
window.location = deepLinks?.player ?? deepLinks?.metaDetailsStreams ?? deepLinks?.metaDetailsVideos;
|
||||
}
|
||||
}, [deepLinks]);
|
||||
|
||||
const onDismissClick = React.useCallback((event) => {
|
||||
event.stopPropagation();
|
||||
if (typeof _id === 'string') {
|
||||
core.transport.dispatch({
|
||||
action: 'Ctx',
|
||||
args: {
|
||||
action: 'RewindLibraryItem',
|
||||
args: _id
|
||||
}
|
||||
});
|
||||
core.transport.dispatch({
|
||||
action: 'Ctx',
|
||||
args: {
|
||||
action: 'DismissNotificationItem',
|
||||
args: _id
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [_id]);
|
||||
|
||||
return (
|
||||
<LibItem
|
||||
{...props}
|
||||
posterChangeCursor={true}
|
||||
newVideos={newVideos}
|
||||
onClick={onClick}
|
||||
onPlayClick={onPlayClick}
|
||||
onDismissClick={onDismissClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
ContinueWatchingItem.propTypes = {
|
||||
_id: PropTypes.string,
|
||||
deepLinks: PropTypes.shape({
|
||||
metaDetailsVideos: PropTypes.string,
|
||||
metaDetailsStreams: PropTypes.string,
|
||||
player: PropTypes.string
|
||||
}),
|
||||
};
|
||||
|
||||
module.exports = ContinueWatchingItem;
|
||||
5
src/common/ContinueWatchingItem/index.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const ContineWatchingItem = require('./ContinueWatchingItem');
|
||||
|
||||
module.exports = ContineWatchingItem;
|
||||
90
src/common/EventModal/EventModal.js
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const Button = require('stremio/common/Button');
|
||||
const ModalDialog = require('stremio/common/ModalDialog');
|
||||
const useEvents = require('./useEvents');
|
||||
const styles = require('./styles');
|
||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
|
||||
const EventModal = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { events, pullEvents, dismissEvent } = useEvents();
|
||||
|
||||
const modal = React.useMemo(() => {
|
||||
return events?.modal?.type === 'Ready' ?
|
||||
events.modal.content
|
||||
:
|
||||
null;
|
||||
}, [events]);
|
||||
|
||||
const onClose = React.useCallback(() => {
|
||||
modal?.id && dismissEvent(modal.id);
|
||||
}, [modal]);
|
||||
|
||||
React.useEffect(() => {
|
||||
pullEvents();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
modal !== null ?
|
||||
<ModalDialog className={styles['event-modal']} onCloseRequest={onClose}>
|
||||
{
|
||||
modal.imageUrl ?
|
||||
<img className={styles['image']} src={modal.imageUrl} />
|
||||
:
|
||||
null
|
||||
}
|
||||
<div className={styles['info-container']}>
|
||||
<div className={styles['title-container']}>
|
||||
{
|
||||
modal.title ?
|
||||
<div className={styles['title']}>{modal.title}</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
modal.message ?
|
||||
<div className={styles['label']}>{modal.message}</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
{
|
||||
modal?.addon?.name ?
|
||||
<div className={styles['addon-container']}>
|
||||
<Icon className={styles['icon']} name={'addons'} />
|
||||
<div className={styles['name']}>
|
||||
{ modal.addon.name }
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
modal?.addon?.manifestUrl ?
|
||||
<Button className={styles['action-button']} href={`#/addons?addon=${encodeURIComponent(modal.addon.manifestUrl)}`} onClick={onClose}>
|
||||
<div className={styles['button-label']}>
|
||||
{ t('INSTALL_ADDON') }
|
||||
</div>
|
||||
</Button>
|
||||
:
|
||||
modal.externalUrl ?
|
||||
<Button className={styles['action-button']} href={modal.externalUrl} target={'_blank'}>
|
||||
<div className={styles['button-label']}>
|
||||
{ t('LEARN_MORE') }
|
||||
</div>
|
||||
</Button>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
</ModalDialog>
|
||||
:
|
||||
null
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = EventModal;
|
||||
5
src/common/EventModal/index.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const EventModal = require('./EventModal');
|
||||
|
||||
module.exports = EventModal;
|
||||
119
src/common/EventModal/styles.less
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
@import (reference) '~stremio/common/screen-sizes.less';
|
||||
|
||||
:import('~stremio/common/ModalDialog/styles.less') {
|
||||
modal-dialog-content: modal-dialog-content;
|
||||
modal-dialog-container: modal-dialog-container;
|
||||
}
|
||||
|
||||
.event-modal {
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
.modal-dialog-container {
|
||||
overflow: visible;
|
||||
max-width: 45rem;
|
||||
|
||||
.modal-dialog-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
overflow: visible;
|
||||
|
||||
.image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin-top: -10rem;
|
||||
}
|
||||
|
||||
.info-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 2.5rem;
|
||||
padding: 1rem 4rem;
|
||||
margin-top: -7rem;
|
||||
|
||||
.title-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
|
||||
.title {
|
||||
color: var(--primary-foreground-color);
|
||||
font-size: 1.325rem;
|
||||
text-align: center;
|
||||
padding: 0 6rem;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: var(--primary-foreground-color);
|
||||
font-size: 1rem;
|
||||
text-align: center;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.addon-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
|
||||
.icon {
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
color: var(--primary-accent-color);
|
||||
}
|
||||
|
||||
.name {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
.modal-dialog-container {
|
||||
.modal-dialog-content {
|
||||
.image {
|
||||
height: 125%;
|
||||
width: 125%;
|
||||
}
|
||||
|
||||
.info-container {
|
||||
.title-container {
|
||||
.title {
|
||||
padding: 0rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/common/EventModal/useEvents.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const useModelState = require('stremio/common/useModelState');
|
||||
const { useServices } = require('stremio/services');
|
||||
|
||||
const map = (ctx) => ({
|
||||
...ctx.events,
|
||||
});
|
||||
|
||||
const useEvents = () => {
|
||||
const { core } = useServices();
|
||||
|
||||
const pullEvents = () => {
|
||||
core.transport.dispatch({
|
||||
action: 'Ctx',
|
||||
args: {
|
||||
action: 'GetEvents',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const dismissEvent = (id) => {
|
||||
core.transport.dispatch({
|
||||
action: 'Ctx',
|
||||
args: {
|
||||
action: 'DismissEvent',
|
||||
args: id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const events = useModelState({ model: 'ctx', map });
|
||||
return { events, pullEvents, dismissEvent };
|
||||
};
|
||||
|
||||
module.exports = useEvents;
|
||||
|
|
@ -68,6 +68,13 @@ const LibItem = ({ _id, removable, ...props }) => {
|
|||
args: _id
|
||||
}
|
||||
});
|
||||
core.transport.dispatch({
|
||||
action: 'Ctx',
|
||||
args: {
|
||||
action: 'DismissNotificationItem',
|
||||
args: _id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ const { VerticalNavBar, HorizontalNavBar } = require('stremio/common/NavBar');
|
|||
const styles = require('./styles');
|
||||
|
||||
const TABS = [
|
||||
{ id: 'board', label: 'Board', icon: 'ic_board', href: '#/' },
|
||||
{ id: 'discover', label: 'Discover', icon: 'ic_discover', href: '#/discover' },
|
||||
{ id: 'library', label: 'Library', icon: 'ic_library', href: '#/library' },
|
||||
{ id: 'settings', label: 'SETTINGS', icon: 'ic_settings', href: '#/settings' },
|
||||
{ id: 'addons', label: 'ADDONS', icon: 'ic_addons', href: '#/addons' }
|
||||
{ id: 'board', label: 'Board', icon: 'home', href: '#/' },
|
||||
{ id: 'discover', label: 'Discover', icon: 'discover', href: '#/discover' },
|
||||
{ id: 'library', label: 'Library', icon: 'library', href: '#/library' },
|
||||
{ id: 'addons', label: 'ADDONS', icon: 'addons', href: '#/addons' },
|
||||
{ id: 'settings', label: 'SETTINGS', icon: 'settings', href: '#/settings' },
|
||||
];
|
||||
|
||||
const MainNavBars = React.memo(({ className, route, query, children }) => {
|
||||
|
|
|
|||
|
|
@ -3,17 +3,18 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const filterInvalidDOMProps = require('filter-invalid-dom-props').default;
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const Button = require('stremio/common/Button');
|
||||
const Image = require('stremio/common/Image');
|
||||
const Multiselect = require('stremio/common/Multiselect');
|
||||
const PlayIconCircleCentered = require('stremio/common/PlayIconCircleCentered');
|
||||
const useBinaryState = require('stremio/common/useBinaryState');
|
||||
const { ICON_FOR_TYPE } = require('stremio/common/CONSTANTS');
|
||||
const styles = require('./styles');
|
||||
|
||||
const MetaItem = React.memo(({ className, type, name, poster, posterShape, playIcon, progress, options, deepLinks, dataset, optionOnSelect, ...props }) => {
|
||||
const MetaItem = React.memo(({ className, type, name, poster, posterShape, posterChangeCursor, progress, newVideos, options, deepLinks, dataset, optionOnSelect, onDismissClick, onPlayClick, watched, ...props }) => {
|
||||
const { t } = useTranslation();
|
||||
const [menuOpen, onMenuOpen, onMenuClose] = useBinaryState(false);
|
||||
const href = React.useMemo(() => {
|
||||
return deepLinks ?
|
||||
|
|
@ -56,15 +57,32 @@ const MetaItem = React.memo(({ className, type, name, poster, posterShape, playI
|
|||
const renderPosterFallback = React.useCallback(() => (
|
||||
<Icon
|
||||
className={styles['placeholder-icon']}
|
||||
icon={ICON_FOR_TYPE.has(type) ? ICON_FOR_TYPE.get(type) : ICON_FOR_TYPE.get('other')}
|
||||
name={ICON_FOR_TYPE.has(type) ? ICON_FOR_TYPE.get(type) : ICON_FOR_TYPE.get('other')}
|
||||
/>
|
||||
), [type]);
|
||||
const renderMenuLabelContent = React.useCallback(() => (
|
||||
<Icon className={styles['icon']} icon={'ic_more'} />
|
||||
<Icon className={styles['icon']} name={'more-vertical'} />
|
||||
), []);
|
||||
return (
|
||||
<Button title={name} href={href} {...filterInvalidDOMProps(props)} className={classnames(className, styles['meta-item-container'], styles['poster-shape-poster'], styles[`poster-shape-${posterShape}`], { 'active': menuOpen })} onClick={metaItemOnClick}>
|
||||
<div className={styles['poster-container']}>
|
||||
<div className={classnames(styles['poster-container'], { 'poster-change-cursor': posterChangeCursor })}>
|
||||
{
|
||||
onDismissClick ?
|
||||
<div title={t('LIBRARY_RESUME_DISMISS')} className={styles['dismiss-icon-layer']} onClick={onDismissClick}>
|
||||
<Icon className={styles['dismiss-icon']} name={'close'} />
|
||||
<div className={styles['dismiss-icon-backdrop']} />
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
watched ?
|
||||
<div className={styles['watched-icon-layer']}>
|
||||
<Icon className={styles['watched-icon']} name={'checkmark'} />
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
<div className={styles['poster-image-layer']}>
|
||||
<Image
|
||||
className={styles['poster-image']}
|
||||
|
|
@ -74,9 +92,11 @@ const MetaItem = React.memo(({ className, type, name, poster, posterShape, playI
|
|||
/>
|
||||
</div>
|
||||
{
|
||||
playIcon ?
|
||||
<div className={styles['play-icon-layer']}>
|
||||
<PlayIconCircleCentered className={styles['play-icon']} />
|
||||
onPlayClick ?
|
||||
<div title={t('CONTINUE_WATCHING')} className={styles['play-icon-layer']} onClick={onPlayClick}>
|
||||
<Icon className={styles['play-icon']} name={'play'} />
|
||||
<div className={styles['play-icon-outer']} />
|
||||
<div className={styles['play-icon-background']} />
|
||||
</div>
|
||||
:
|
||||
null
|
||||
|
|
@ -85,6 +105,22 @@ const MetaItem = React.memo(({ className, type, name, poster, posterShape, playI
|
|||
progress > 0 ?
|
||||
<div className={styles['progress-bar-layer']}>
|
||||
<div className={styles['progress-bar']} style={{ width: `${Math.max(0, Math.min(1, progress)) * 100}%` }} />
|
||||
<div className={styles['progress-bar-background']} />
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
newVideos > 0 ?
|
||||
<div className={styles['new-videos']}>
|
||||
<div className={styles['layer']} />
|
||||
<div className={styles['layer']} />
|
||||
<div className={styles['layer']}>
|
||||
<Icon className={styles['icon']} name={'add'} />
|
||||
<div className={styles['label']}>
|
||||
{newVideos}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
null
|
||||
|
|
@ -127,8 +163,9 @@ MetaItem.propTypes = {
|
|||
name: PropTypes.string,
|
||||
poster: PropTypes.string,
|
||||
posterShape: PropTypes.oneOf(['poster', 'landscape', 'square']),
|
||||
playIcon: PropTypes.bool,
|
||||
posterChangeCursor: PropTypes.bool,
|
||||
progress: PropTypes.number,
|
||||
newVideos: PropTypes.number,
|
||||
options: PropTypes.array,
|
||||
deepLinks: PropTypes.shape({
|
||||
metaDetailsVideos: PropTypes.string,
|
||||
|
|
@ -137,7 +174,10 @@ MetaItem.propTypes = {
|
|||
}),
|
||||
dataset: PropTypes.object,
|
||||
optionOnSelect: PropTypes.func,
|
||||
onClick: PropTypes.func
|
||||
onDismissClick: PropTypes.func,
|
||||
onPlayClick: PropTypes.func,
|
||||
onClick: PropTypes.func,
|
||||
watched: PropTypes.bool
|
||||
};
|
||||
|
||||
module.exports = MetaItem;
|
||||
|
|
|
|||
|
|
@ -18,14 +18,44 @@
|
|||
play-icon-circle-centered-icon: icon;
|
||||
}
|
||||
|
||||
@play-icon-size: 4rem;
|
||||
|
||||
.meta-item-container {
|
||||
padding: 1rem;
|
||||
overflow: visible;
|
||||
|
||||
&:hover, &:focus, &:global(.active), &:global(.selected) {
|
||||
outline-style: none;
|
||||
background-color: @color-background-light3;
|
||||
transition: background-color 100ms ease-out;
|
||||
|
||||
.poster-container {
|
||||
box-shadow: 0 0 0 0.2rem var(--primary-foreground-color);
|
||||
|
||||
.dismiss-icon-layer {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.poster-image-layer {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.play-icon-layer {
|
||||
.play-icon-outer {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.play-icon-background {
|
||||
background-color: var(--secondary-accent-color);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title-bar-container {
|
||||
.menu-label-container {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.poster-shape-poster {
|
||||
|
|
@ -49,7 +79,71 @@
|
|||
.poster-container {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
background-color: @color-background;
|
||||
background-color: var(--overlay-color);
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
&:global(.poster-change-cursor) {
|
||||
.poster-image-layer {
|
||||
&:hover {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dismiss-icon-layer {
|
||||
z-index: -2;
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
left: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
border-radius: 100%;
|
||||
opacity: 0;
|
||||
transition: opacity 0.1s ease-in;
|
||||
|
||||
.dismiss-icon {
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
height: 1.25rem;
|
||||
width: 1.25rem;
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.dismiss-icon-backdrop {
|
||||
z-index: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--primary-background-color);
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.watched-icon-layer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
background-color: var(--primary-accent-color);
|
||||
border-radius: 50%;
|
||||
margin: 0.5rem;
|
||||
|
||||
.watched-icon {
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
.poster-image-layer {
|
||||
position: absolute;
|
||||
|
|
@ -62,6 +156,7 @@
|
|||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: transform 0.1s ease-out;
|
||||
|
||||
.poster-image {
|
||||
flex: none;
|
||||
|
|
@ -76,46 +171,138 @@
|
|||
flex: none;
|
||||
width: 80%;
|
||||
height: 50%;
|
||||
fill: @color-background-light3-90;
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
|
||||
.play-icon-layer {
|
||||
position: absolute;
|
||||
top: 30%;
|
||||
right: 0;
|
||||
bottom: 30%;
|
||||
left: 0;
|
||||
z-index: -2;
|
||||
overflow: visible;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: calc(@play-icon-size / -2);
|
||||
margin-left: calc(@play-icon-size / -2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: @play-icon-size;
|
||||
width: @play-icon-size;
|
||||
transition: transform 0.1s ease-out;
|
||||
|
||||
.play-icon {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
filter: drop-shadow(0 0 0.5rem @color-background);
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
height: 2.25rem;
|
||||
width: 2.25rem;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
.play-icon-circle-centered-background {
|
||||
fill: @color-accent4-90;
|
||||
}
|
||||
.play-icon-outer {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
color: var(--primary-foreground-color);
|
||||
box-shadow: 0 0 0 0.15rem currentColor inset;
|
||||
border-radius: 100%;
|
||||
transition: color 0.1s ease-in;
|
||||
}
|
||||
|
||||
.play-icon-circle-centered-icon {
|
||||
fill: @color-surface-light5-90;
|
||||
}
|
||||
.play-icon-background {
|
||||
z-index: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--primary-background-color);
|
||||
border-radius: 100%;
|
||||
opacity: 0.4;
|
||||
transition: all 0.1s ease-in;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
.progress-bar-layer {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
background-color: @color-background-light2;
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
left: 1rem;
|
||||
right: 1rem;
|
||||
height: 0.45rem;
|
||||
border-radius: 0.45rem;
|
||||
overflow: hidden;
|
||||
|
||||
.progress-bar {
|
||||
height: 0.4rem;
|
||||
background-color: @color-primaryvariant1;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
background-color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
.progress-bar-background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--primary-foreground-color);
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
.new-videos {
|
||||
z-index: -1;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
overflow: visible;
|
||||
|
||||
.layer {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 1.25rem;
|
||||
width: 2.25rem;
|
||||
border-radius: 0.25rem;
|
||||
|
||||
&:nth-child(1) {
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
background-color: var(--primary-foreground-color);
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
top: 0.75rem;
|
||||
right: 0.75rem;
|
||||
background-color: var(--primary-foreground-color);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
background-color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
.icon {
|
||||
height: 0.8rem;
|
||||
width: 0.8rem;
|
||||
color: var(--primary-accent-color);
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: var(--primary-accent-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -124,52 +311,60 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 2.8rem;
|
||||
height: 4rem;
|
||||
overflow: visible;
|
||||
|
||||
.title-label {
|
||||
flex: 1;
|
||||
max-height: 2.4em;
|
||||
padding-left: 0.5rem;
|
||||
color: @color-surface-light5-90;
|
||||
padding-left: 1.5rem;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
color: var(--primary-foreground-color);
|
||||
|
||||
&:only-child {
|
||||
padding-right: 0.5rem;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-label-container {
|
||||
z-index: 1;
|
||||
flex: none;
|
||||
width: 1.5rem;
|
||||
height: 2.8rem;
|
||||
height: 4rem;
|
||||
padding: 1rem 0;
|
||||
background-color: transparent;
|
||||
opacity: 0;
|
||||
transform: translateX(1rem);
|
||||
transition: opacity 0.1s ease-out;
|
||||
|
||||
.icon {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
fill: @color-surface-light1-90;
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.popup-menu-container {
|
||||
width: auto;
|
||||
|
||||
.multiselect-menu-container {
|
||||
min-width: 6rem;
|
||||
max-width: 12rem;
|
||||
min-width: 8rem;
|
||||
max-width: 14rem;
|
||||
|
||||
.multiselect-option-container {
|
||||
padding: 0.5rem;
|
||||
background-color: @color-surface-light5;
|
||||
padding: 1rem 1.5rem;
|
||||
|
||||
&:hover, &:focus {
|
||||
outline: none;
|
||||
background-color: @color-surface-light2;
|
||||
background-color: var(--overlay-color);
|
||||
}
|
||||
|
||||
.multiselect-option-label {
|
||||
color: @color-background-dark5-90;
|
||||
font-weight: 500;
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,23 +3,30 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const Button = require('stremio/common/Button');
|
||||
const styles = require('./styles');
|
||||
const { Tooltip } = require('stremio/common/Tooltips');
|
||||
|
||||
const ActionButton = ({ className, icon, label, ...props }) => {
|
||||
const ActionButton = ({ className, icon, label, tooltip, ...props }) => {
|
||||
return (
|
||||
<Button title={label} {...props} className={classnames(className, styles['action-button-container'])}>
|
||||
<Button title={tooltip ? '' : label} {...props} className={classnames(className, styles['action-button-container'], { 'wide': typeof label === 'string' && !tooltip })}>
|
||||
{
|
||||
tooltip === true ?
|
||||
<Tooltip label={label} position={'top'} />
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
typeof icon === 'string' && icon.length > 0 ?
|
||||
<div className={styles['icon-container']}>
|
||||
<Icon className={styles['icon']} icon={icon} />
|
||||
<Icon className={styles['icon']} name={icon} />
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
typeof label === 'string' && label.length > 0 ?
|
||||
!tooltip && typeof label === 'string' && label.length > 0 ?
|
||||
<div className={styles['label-container']}>
|
||||
<div className={styles['label']}>{label}</div>
|
||||
</div>
|
||||
|
|
@ -33,7 +40,8 @@ const ActionButton = ({ className, icon, label, ...props }) => {
|
|||
ActionButton.propTypes = {
|
||||
className: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
label: PropTypes.string
|
||||
label: PropTypes.string,
|
||||
tooltip: PropTypes.bool
|
||||
};
|
||||
|
||||
module.exports = ActionButton;
|
||||
|
|
|
|||
|
|
@ -5,33 +5,34 @@
|
|||
|
||||
.action-button-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
background-color: @color-surface-light5-20;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
border-radius: 100%;
|
||||
background-color: var(--overlay-color);
|
||||
backdrop-filter: blur(5px);
|
||||
transition: background-color 0.1s ease-out;
|
||||
|
||||
&:hover, &:focus {
|
||||
background-color: @color-accent3;
|
||||
outline: var(--focus-outline-size) solid var(--primary-foreground-color);
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
flex: 0 0 50%;
|
||||
align-self: stretch;
|
||||
padding-top: 15%;
|
||||
|
||||
&:only-child {
|
||||
padding: 5% 0;
|
||||
}
|
||||
flex: none;
|
||||
|
||||
.icon {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
fill: @color-surface-light5-90;
|
||||
height: 1.75rem;
|
||||
width: 1.75rem;
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
.label-container {
|
||||
flex: 0 0 50%;
|
||||
flex: none;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
@ -39,11 +40,13 @@
|
|||
|
||||
.label {
|
||||
flex: 1;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
max-height: 2.4em;
|
||||
padding: 0 0.2rem;
|
||||
text-align: center;
|
||||
color: @color-surface-light5-90;
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -54,21 +57,8 @@
|
|||
padding: 0 1rem;
|
||||
|
||||
.icon-container {
|
||||
flex: none;
|
||||
align-self: center;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
padding-top: 0;
|
||||
margin-right: 0.5rem;
|
||||
|
||||
&:only-child {
|
||||
padding: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.label-container {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,10 +4,12 @@
|
|||
|
||||
.meta-links-container {
|
||||
.label-container {
|
||||
margin-bottom: 0.2rem;
|
||||
margin-bottom: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
color: @color-surface-dark3-90;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.links-container {
|
||||
|
|
@ -19,15 +21,18 @@
|
|||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
flex-basis: auto;
|
||||
margin-right: 0.5rem;
|
||||
margin-bottom: 0.2rem;
|
||||
padding: 0.3rem 0.5rem;
|
||||
margin-right: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
padding: 0.4rem 1.25rem;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
border-radius: 2rem;
|
||||
border: var(--focus-outline-size) solid transparent;
|
||||
color: @color-surface-light2-90;
|
||||
background-color: @color-surface-light5-20;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
color: var(--primary-foreground-color);
|
||||
background-color: var(--overlay-color);
|
||||
backdrop-filter: blur(5px);
|
||||
|
||||
&:hover, &:focus {
|
||||
background-color: @color-surface-light5-30;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ const PropTypes = require('prop-types');
|
|||
const classnames = require('classnames');
|
||||
const UrlUtils = require('url');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const Button = require('stremio/common/Button');
|
||||
const Image = require('stremio/common/Image');
|
||||
const ModalDialog = require('stremio/common/ModalDialog');
|
||||
|
|
@ -95,8 +95,8 @@ const MetaPreview = ({ className, compact, name, logo, background, runtime, rele
|
|||
return trailerStreams[0].deepLinks.player;
|
||||
}, [trailerStreams]);
|
||||
const renderLogoFallback = React.useCallback(() => (
|
||||
<div className={styles['logo-placeholder']}>{!compact ? name : null}</div>
|
||||
), [compact, name]);
|
||||
<div className={styles['logo-placeholder']}>{name}</div>
|
||||
), [name]);
|
||||
return (
|
||||
<div className={classnames(className, styles['meta-preview-container'], { [styles['compact']]: compact })}>
|
||||
{
|
||||
|
|
@ -147,8 +147,8 @@ const MetaPreview = ({ className, compact, name, logo, background, runtime, rele
|
|||
target={'_blank'}
|
||||
{...(compact ? { tabIndex: -1 } : null)}
|
||||
>
|
||||
<Icon className={styles['icon']} icon={'ic_imdbnoframe'} />
|
||||
<div className={styles['label']}>{linksGroups.get(CONSTANTS.IMDB_LINK_CATEGORY).label}</div>
|
||||
<Icon className={styles['icon']} name={'imdb'} />
|
||||
</Button>
|
||||
:
|
||||
null
|
||||
|
|
@ -157,17 +157,11 @@ const MetaPreview = ({ className, compact, name, logo, background, runtime, rele
|
|||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
compact && typeof name === 'string' && name.length > 0 ?
|
||||
<div className={styles['name-container']}>
|
||||
{name}
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
compact && typeof description === 'string' && description.length > 0 ?
|
||||
<div className={styles['description-container']}>{description}</div>
|
||||
<div className={styles['description-container']}>
|
||||
{description}
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
|
|
@ -187,14 +181,26 @@ const MetaPreview = ({ className, compact, name, logo, background, runtime, rele
|
|||
/>
|
||||
))
|
||||
}
|
||||
{
|
||||
!compact && typeof description === 'string' && description.length > 0 ?
|
||||
<div className={styles['description-container']}>
|
||||
<div className={styles['label-container']}>
|
||||
{t('SUMMARY')}
|
||||
</div>
|
||||
{description}
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
<div className={styles['action-buttons-container']}>
|
||||
{
|
||||
typeof toggleInLibrary === 'function' ?
|
||||
<ActionButton
|
||||
className={styles['action-button']}
|
||||
icon={inLibrary ? 'ic_removelib' : 'ic_addlib'}
|
||||
icon={inLibrary ? 'remove-from-library' : 'add-to-library'}
|
||||
label={inLibrary ? t('REMOVE_FROM_LIB') : t('ADD_TO_LIB')}
|
||||
tooltip={compact}
|
||||
tabIndex={compact ? -1 : 0}
|
||||
onClick={toggleInLibrary}
|
||||
/>
|
||||
|
|
@ -205,10 +211,11 @@ const MetaPreview = ({ className, compact, name, logo, background, runtime, rele
|
|||
typeof trailerHref === 'string' ?
|
||||
<ActionButton
|
||||
className={styles['action-button']}
|
||||
icon={'ic_movies'}
|
||||
icon={'trailer'}
|
||||
label={t('TRAILER')}
|
||||
tabIndex={compact ? -1 : 0}
|
||||
href={trailerHref}
|
||||
tooltip={compact}
|
||||
/>
|
||||
:
|
||||
null
|
||||
|
|
@ -216,8 +223,8 @@ const MetaPreview = ({ className, compact, name, logo, background, runtime, rele
|
|||
{
|
||||
typeof showHref === 'string' && compact ?
|
||||
<ActionButton
|
||||
className={styles['action-button']}
|
||||
icon={'ic_play'}
|
||||
className={classnames(styles['action-button'], styles['show-button'])}
|
||||
icon={'play'}
|
||||
label={t('SHOW')}
|
||||
tabIndex={compact ? -1 : 0}
|
||||
href={showHref}
|
||||
|
|
@ -230,8 +237,9 @@ const MetaPreview = ({ className, compact, name, logo, background, runtime, rele
|
|||
<React.Fragment>
|
||||
<ActionButton
|
||||
className={styles['action-button']}
|
||||
icon={'ic_share'}
|
||||
icon={'share'}
|
||||
label={t('CTX_SHARE')}
|
||||
tooltip={true}
|
||||
tabIndex={compact ? -1 : 0}
|
||||
onClick={openShareModal}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ const MetaPreviewPlaceholder = ({ className }) => {
|
|||
<div className={styles['duration-container']} />
|
||||
<div className={styles['release-info-container']} />
|
||||
</div>
|
||||
<div className={styles['description-container']}>
|
||||
<div className={styles['description-label-container']} />
|
||||
<div className={styles['description-label-container']} />
|
||||
<div className={styles['genres-container']}>
|
||||
<div className={styles['genres-header-container']} />
|
||||
<div className={styles['genre-label-container']} />
|
||||
</div>
|
||||
<div className={styles['genres-container']}>
|
||||
<div className={styles['genres-header-container']} />
|
||||
|
|
@ -27,20 +27,7 @@ const MetaPreviewPlaceholder = ({ className }) => {
|
|||
<div className={styles['genre-label-container']} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles['action-buttons-container']}>
|
||||
<div className={styles['action-button-container']}>
|
||||
<div className={styles['action-button-icon']} />
|
||||
<div className={styles['action-button-label']} />
|
||||
</div>
|
||||
<div className={styles['action-button-container']}>
|
||||
<div className={styles['action-button-icon']} />
|
||||
<div className={styles['action-button-label']} />
|
||||
</div>
|
||||
<div className={styles['action-button-container']}>
|
||||
<div className={styles['action-button-icon']} />
|
||||
<div className={styles['action-button-label']} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles['action-buttons-container']} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
.meta-preview-placeholder-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 2rem;
|
||||
|
||||
.meta-info-container {
|
||||
flex: 1;
|
||||
|
|
@ -13,6 +12,7 @@
|
|||
width: 20rem;
|
||||
height: 8rem;
|
||||
max-width: 100%;
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--color-placeholder-background);
|
||||
}
|
||||
|
||||
|
|
@ -26,37 +26,18 @@
|
|||
flex-basis: 5rem;
|
||||
height: 1.4rem;
|
||||
margin-right: 1rem;
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--color-placeholder-background);
|
||||
}
|
||||
|
||||
.release-info-container {
|
||||
flex-basis: 5rem;
|
||||
height: 1.4rem;
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--color-placeholder-background);
|
||||
}
|
||||
}
|
||||
|
||||
.description-container {
|
||||
margin: 1rem 0;
|
||||
|
||||
.description-label-container {
|
||||
background-color: var(--color-placeholder-background);
|
||||
|
||||
&:nth-child(1) {
|
||||
width: 20rem;
|
||||
height: 1.4rem;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
width: 26rem;
|
||||
height: 4.6rem;
|
||||
max-width: 80%;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.genres-container {
|
||||
margin: 1rem 0;
|
||||
|
||||
|
|
@ -64,6 +45,7 @@
|
|||
width: 6.5rem;
|
||||
height: 1.6rem;
|
||||
max-width: 100%;
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--color-placeholder-background);
|
||||
}
|
||||
|
||||
|
|
@ -72,6 +54,7 @@
|
|||
height: 1.2rem;
|
||||
max-width: 100%;
|
||||
margin-top: 0.2rem;
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--color-placeholder-background);
|
||||
}
|
||||
}
|
||||
|
|
@ -81,32 +64,9 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
max-height: 6rem;
|
||||
|
||||
.action-button-container {
|
||||
flex: none;
|
||||
width: 6rem;
|
||||
height: 6rem;
|
||||
margin-right: 2rem;
|
||||
background-color: var(--color-placeholder-background);
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.action-button-icon {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
margin: 1rem 2rem 0 2rem;
|
||||
background-color: var(--color-placeholder-background);
|
||||
}
|
||||
|
||||
.action-button-label {
|
||||
width: 4rem;
|
||||
height: 1.2rem;
|
||||
margin: 0.9rem 1rem;
|
||||
background-color: var(--color-placeholder-background);
|
||||
}
|
||||
}
|
||||
height: 4rem;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: 4rem;
|
||||
background-color: var(--color-placeholder-background);
|
||||
}
|
||||
}
|
||||
|
|
@ -13,12 +13,11 @@
|
|||
.meta-info-container {
|
||||
.logo, .logo-placeholder {
|
||||
width: 100%;
|
||||
height: 8rem;
|
||||
background-color: @color-surface-dark5-10;
|
||||
height: 6rem;
|
||||
}
|
||||
|
||||
.runtime-release-info-container {
|
||||
justify-content: space-evenly;
|
||||
justify-content: space-between;
|
||||
|
||||
.runtime-label, .release-info-label {
|
||||
margin: 1rem 0.4rem;
|
||||
|
|
@ -31,8 +30,7 @@
|
|||
}
|
||||
|
||||
.action-buttons-container {
|
||||
justify-content: space-evenly;
|
||||
padding: 0;
|
||||
justify-content: space-between;
|
||||
|
||||
.action-button:not(:last-child) {
|
||||
margin-right: 0;
|
||||
|
|
@ -48,32 +46,20 @@
|
|||
left: -10px;
|
||||
z-index: -1;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
background: @color-background-dark2-60;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.background-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
opacity: 0.9;
|
||||
filter: blur(5px);
|
||||
filter: blur(10px);
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
.meta-info-container {
|
||||
flex: 1;
|
||||
align-self: stretch;
|
||||
padding: 0 2rem;
|
||||
overflow-y: auto;
|
||||
|
||||
&:not(:hover) {
|
||||
|
|
@ -87,11 +73,11 @@
|
|||
.logo, .logo-placeholder {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
margin: 2rem 0;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 8rem;
|
||||
height: 9rem;
|
||||
object-fit: contain;
|
||||
object-position: center;
|
||||
}
|
||||
|
|
@ -110,10 +96,10 @@
|
|||
|
||||
.runtime-label, .release-info-label {
|
||||
flex: 0 1 auto;
|
||||
margin-right: 2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1.4rem;
|
||||
color: @color-surface-light5-90;
|
||||
margin-right: 3rem;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
.imdb-button-container {
|
||||
|
|
@ -121,55 +107,47 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
padding: 0.3rem 1rem;
|
||||
border-radius: 2.5rem;
|
||||
border: var(--focus-outline-size) solid transparent;
|
||||
background-color: @color-surface-light5-20;
|
||||
|
||||
&:hover, &:focus {
|
||||
background-color: @color-surface-light5-30;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border: var(--focus-outline-size) solid @color-surface-light5;
|
||||
}
|
||||
|
||||
.label {
|
||||
flex: 0 1 auto;
|
||||
margin-right: 1rem;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
.icon {
|
||||
flex: none;
|
||||
width: 3rem;
|
||||
height: 1.1rem;
|
||||
margin-right: 1rem;
|
||||
fill: @color-surface-90;
|
||||
}
|
||||
|
||||
.label {
|
||||
flex: 0 1 auto;
|
||||
max-height: 1.2em;
|
||||
font-size: 1.6rem;
|
||||
font-weight: 500;
|
||||
color: @color-surface-light5-90;
|
||||
height: 3rem;
|
||||
color: var(--color-imdb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.name-container {
|
||||
margin-top: 1rem;
|
||||
font-size: 1.5rem;
|
||||
color: @color-surface-light5-90;
|
||||
}
|
||||
|
||||
.description-container {
|
||||
max-height: 6em;
|
||||
margin-top: 1rem;
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.5em;
|
||||
color: @color-surface-light5-90;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 2em;
|
||||
color: var(--primary-foreground-color);
|
||||
.label-container {
|
||||
text-transform: uppercase;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
.meta-links {
|
||||
margin-top: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -178,18 +156,54 @@
|
|||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-end;
|
||||
max-height: 15rem;
|
||||
flex-wrap: wrap;
|
||||
max-height: 10rem;
|
||||
padding: 0 2rem;
|
||||
padding-top: 3.5rem;
|
||||
overflow: visible;
|
||||
|
||||
.label {
|
||||
position: absolute;
|
||||
top: -3rem;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
text-align: center;
|
||||
color: var(--primary-foreground-color);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.label {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.action-button {
|
||||
flex: none;
|
||||
width: 6rem;
|
||||
height: 6rem;
|
||||
margin: 2rem 0;
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
&:global(.wide) {
|
||||
width: auto;
|
||||
padding: 0 2rem;
|
||||
border-radius: 4rem;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 2rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
&.show-button {
|
||||
&:hover, &:focus {
|
||||
background-color: var( --secondary-accent-color);
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -202,34 +216,29 @@
|
|||
@media only screen and (max-width: @minimum) {
|
||||
.meta-preview-container {
|
||||
.meta-info-container {
|
||||
padding: 0 1.5rem;
|
||||
|
||||
.logo {
|
||||
margin: 1em 0;
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons-container {
|
||||
flex-wrap: nowrap;
|
||||
padding: 0 1.5rem;
|
||||
overflow-x: visible;
|
||||
scrollbar-width: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
margin: 2rem auto;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
width: auto;
|
||||
height: 4rem;
|
||||
max-width: 60%;
|
||||
margin: 1rem 0;
|
||||
.runtime-release-info-container {
|
||||
justify-content: space-between;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 1rem;
|
||||
.runtime-label, .release-info-label {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons-container {
|
||||
flex-shrink: 0;
|
||||
margin-top: 3rem;
|
||||
overflow: visible;
|
||||
scrollbar-width: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.share-prompt {
|
||||
|
|
|
|||
|
|
@ -4,39 +4,47 @@ const React = require('react');
|
|||
const ReactIs = require('react-is');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const Button = require('stremio/common/Button');
|
||||
const CONSTANTS = require('stremio/common/CONSTANTS');
|
||||
const useTranslate = require('stremio/common/useTranslate');
|
||||
const MetaRowPlaceholder = require('./MetaRowPlaceholder');
|
||||
const styles = require('./styles');
|
||||
|
||||
const MetaRow = ({ className, title, message, items, itemComponent, deepLinks }) => {
|
||||
const { t } = useTranslation();
|
||||
const MetaRow = ({ className, title, catalog, message, itemComponent }) => {
|
||||
const t = useTranslate();
|
||||
|
||||
const catalogTitle = React.useMemo(() => {
|
||||
return title ?? t.catalogTitle(catalog);
|
||||
}, [title, catalog, t.catalogTitle]);
|
||||
|
||||
const items = React.useMemo(() => {
|
||||
return catalog?.items ?? catalog?.content?.content;
|
||||
}, [catalog]);
|
||||
|
||||
const href = React.useMemo(() => {
|
||||
return catalog?.deepLinks?.discover ?? catalog?.deepLinks?.library;
|
||||
}, [catalog]);
|
||||
|
||||
return (
|
||||
<div className={classnames(className, styles['meta-row-container'])}>
|
||||
{
|
||||
(typeof title === 'string' && title.length > 0) || (deepLinks && (typeof deepLinks.discover === 'string' || typeof deepLinks.library === 'string')) ?
|
||||
<div className={styles['header-container']}>
|
||||
{
|
||||
typeof title === 'string' && title.length > 0 ?
|
||||
<div className={styles['title-container']} title={title}>{title}</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
deepLinks && (typeof deepLinks.discover === 'string' || typeof deepLinks.library === 'string') ?
|
||||
<Button className={styles['see-all-container']} title={t('BUTTON_SEE_ALL')} href={deepLinks.discover || deepLinks.library} tabIndex={-1}>
|
||||
<div className={styles['label']}>{ t('BUTTON_SEE_ALL') }</div>
|
||||
<Icon className={styles['icon']} icon={'ic_arrow_thin_right'} />
|
||||
</Button>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
<div className={styles['header-container']}>
|
||||
{
|
||||
typeof catalogTitle === 'string' && catalogTitle.length > 0 ?
|
||||
<div className={styles['title-container']} title={catalogTitle}>{catalogTitle}</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
href ?
|
||||
<Button className={styles['see-all-container']} title={t.string('BUTTON_SEE_ALL')} href={href} tabIndex={-1}>
|
||||
<div className={styles['label']}>{ t.string('BUTTON_SEE_ALL') }</div>
|
||||
<Icon className={styles['icon']} name={'chevron-forward'} />
|
||||
</Button>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
{
|
||||
typeof message === 'string' && message.length > 0 ?
|
||||
<div className={styles['message-container']} title={message}>{message}</div>
|
||||
|
|
@ -69,14 +77,33 @@ MetaRow.propTypes = {
|
|||
className: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
message: PropTypes.string,
|
||||
items: PropTypes.arrayOf(PropTypes.shape({
|
||||
posterShape: PropTypes.string
|
||||
})),
|
||||
catalog: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
addon: PropTypes.shape({
|
||||
manifest: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
}),
|
||||
}),
|
||||
content: PropTypes.shape({
|
||||
content: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.arrayOf(PropTypes.shape({
|
||||
posterShape: PropTypes.string,
|
||||
})),
|
||||
]),
|
||||
}),
|
||||
items: PropTypes.arrayOf(PropTypes.shape({
|
||||
posterShape: PropTypes.string,
|
||||
})),
|
||||
deepLinks: PropTypes.shape({
|
||||
discover: PropTypes.string,
|
||||
library: PropTypes.string,
|
||||
}),
|
||||
}),
|
||||
itemComponent: PropTypes.elementType,
|
||||
deepLinks: PropTypes.shape({
|
||||
discover: PropTypes.string,
|
||||
library: PropTypes.string
|
||||
})
|
||||
};
|
||||
|
||||
module.exports = MetaRow;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ const React = require('react');
|
|||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const Button = require('stremio/common/Button');
|
||||
const CONSTANTS = require('stremio/common/CONSTANTS');
|
||||
const styles = require('./styles');
|
||||
|
|
@ -21,7 +21,7 @@ const MetaRowPlaceholder = ({ className, title, deepLinks }) => {
|
|||
deepLinks && typeof deepLinks.discover === 'string' ?
|
||||
<Button className={styles['see-all-container']} title={t('BUTTON_SEE_ALL')} href={deepLinks.discover} tabIndex={-1}>
|
||||
<div className={styles['label']}>{ t('BUTTON_SEE_ALL') }</div>
|
||||
<Icon className={styles['icon']} icon={'ic_arrow_thin_right'} />
|
||||
<Icon className={styles['icon']} name={'chevron-forward'} />
|
||||
</Button>
|
||||
:
|
||||
null
|
||||
|
|
|
|||
|
|
@ -9,12 +9,13 @@
|
|||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding: 0 1rem;
|
||||
margin-bottom: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
.title-container {
|
||||
flex: 1;
|
||||
max-height: 2.4em;
|
||||
font-size: 1.8rem;
|
||||
font-size: 1.6rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-placeholder-text);
|
||||
|
||||
&:empty {
|
||||
|
|
@ -39,17 +40,16 @@
|
|||
.label {
|
||||
flex: 0 1 auto;
|
||||
max-height: 1.2em;
|
||||
font-size: 1.3rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-placeholder-text);
|
||||
}
|
||||
|
||||
.icon {
|
||||
flex: none;
|
||||
height: 1.3rem;
|
||||
height: 1rem;
|
||||
margin-left: 0.5rem;
|
||||
fill: var(--color-placeholder-text);
|
||||
color: var(--color-placeholder-text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -68,6 +68,7 @@
|
|||
}
|
||||
|
||||
.poster-container {
|
||||
border-radius: var(--border-radius);
|
||||
padding-bottom: calc(100% * var(--poster-shape-ratio));
|
||||
background-color: var(--color-placeholder-background);
|
||||
}
|
||||
|
|
@ -76,12 +77,14 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 2.8rem;
|
||||
|
||||
.title-label {
|
||||
flex: none;
|
||||
width: 60%;
|
||||
height: 1.2rem;
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--color-placeholder-background);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,13 +12,14 @@
|
|||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding: 0 1rem;
|
||||
margin-bottom: 1rem;
|
||||
margin-bottom: 0.25rem;
|
||||
|
||||
.title-container {
|
||||
flex: 1;
|
||||
max-height: 2.4em;
|
||||
font-size: 1.8rem;
|
||||
color: @color-secondaryvariant2-light1-90;
|
||||
font-size: 1.6rem;
|
||||
font-weight: 500;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
.see-all-container {
|
||||
|
|
@ -27,46 +28,40 @@
|
|||
flex-direction: row;
|
||||
align-items: center;
|
||||
max-width: 12rem;
|
||||
padding: 0.2rem;
|
||||
height: 2.5rem;
|
||||
padding: 0 0.5rem 0 1rem;
|
||||
border-radius: 2.5rem;
|
||||
opacity: 0.6;
|
||||
|
||||
&:focus {
|
||||
&:hover, &:focus {
|
||||
outline: none;
|
||||
background-color: @color-background-light3;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.label {
|
||||
color: @color-secondaryvariant2-light2-90;
|
||||
}
|
||||
|
||||
.icon {
|
||||
fill: @color-secondaryvariant2-light2-90;
|
||||
}
|
||||
background-color: var(--overlay-color);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.label {
|
||||
flex: 0 1 auto;
|
||||
max-height: 1.2em;
|
||||
font-size: 1.3rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
color: @color-secondaryvariant2-light1-90;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
.icon {
|
||||
flex: none;
|
||||
height: 1.3rem;
|
||||
height: 1.5rem;
|
||||
margin-left: 0.5rem;
|
||||
fill: @color-secondaryvariant2-light1-90;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.message-container {
|
||||
max-height: 3.6em;
|
||||
padding: 0 1rem;
|
||||
padding: 0 0.5rem;
|
||||
font-size: 1.3rem;
|
||||
color: @color-secondaryvariant2-light1-90;
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.meta-items-container {
|
||||
|
|
@ -76,10 +71,6 @@
|
|||
overflow: visible;
|
||||
|
||||
.meta-item {
|
||||
&:not(:first-child) {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
&.poster-shape-poster {
|
||||
flex: calc(1 / var(--poster-shape-ratio));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ const PropTypes = require('prop-types');
|
|||
const classnames = require('classnames');
|
||||
const { useRouteFocused, useModalsContainer } = require('stremio-router');
|
||||
const Button = require('stremio/common/Button');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const { Modal } = require('stremio-router');
|
||||
const styles = require('./styles');
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ const ModalDialog = ({ className, title, buttons, children, dataset, onCloseRequ
|
|||
<Modal ref={modalContainerRef} {...props} className={classnames(className, styles['modal-container'])} onMouseDown={onModalContainerMouseDown}>
|
||||
<div className={styles['modal-dialog-container']} onMouseDown={onModalDialogContainerMouseDown}>
|
||||
<Button className={styles['close-button-container']} title={'Close'} onClick={closeButtonOnClick}>
|
||||
<Icon className={styles['icon']} icon={'ic_x'} />
|
||||
<Icon className={styles['icon']} name={'close'} />
|
||||
</Button>
|
||||
{
|
||||
typeof title === 'string' && title.length > 0 ?
|
||||
|
|
@ -78,7 +78,7 @@ const ModalDialog = ({ className, title, buttons, children, dataset, onCloseRequ
|
|||
<Button title={label} {...props} key={index} className={classnames(className, styles['action-button'])}>
|
||||
{
|
||||
typeof icon === 'string' && icon.length > 0 ?
|
||||
<Icon className={styles['icon']} icon={icon} />
|
||||
<Icon className={styles['icon']} name={icon} />
|
||||
:
|
||||
null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,54 +10,61 @@
|
|||
background-color: @color-background-dark5-40;
|
||||
|
||||
.modal-dialog-container {
|
||||
position: relative;
|
||||
flex: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 80%;
|
||||
max-height: 80%;
|
||||
background-color: @color-surface-light5;
|
||||
padding: 0 2rem;
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--modal-background-color);
|
||||
box-shadow: var(--outer-glow);
|
||||
|
||||
.close-button-container {
|
||||
flex: none;
|
||||
align-self: flex-end;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
margin: 0.2rem 0.2rem 0 0;
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
padding: 0.5rem;
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
.icon {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
fill: @color-surface-dark1-90;
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
&:hover, &:focus {
|
||||
.icon {
|
||||
fill: @color-surface-light1-90;
|
||||
opacity: 1;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline-color: @color-background-dark5;
|
||||
outline-color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
.title-container {
|
||||
flex: 1 0 auto;
|
||||
max-height: 2.4em;
|
||||
margin: 0 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 4.5rem;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
color: @color-background-dark5-90;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
.modal-dialog-content {
|
||||
flex: 1;
|
||||
align-self: stretch;
|
||||
margin: 1.5rem 1rem 0;
|
||||
padding: 0 1rem;
|
||||
overflow-y: auto;
|
||||
padding: 2rem 0;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 2rem;
|
||||
|
|
@ -67,13 +74,12 @@
|
|||
.buttons-container {
|
||||
flex: none;
|
||||
align-self: stretch;
|
||||
margin: 2rem 2rem 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 2rem;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -85,15 +91,18 @@
|
|||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 3.5rem;
|
||||
border-radius: 3.5rem;
|
||||
padding: 1.2rem;
|
||||
background-color: @color-accent3;
|
||||
background-color: var(--secondary-accent-color);
|
||||
|
||||
&:hover {
|
||||
background-color: @color-accent3-light1;
|
||||
background-color: transparent;
|
||||
outline: var(--focus-outline-size) solid var(--secondary-accent-color);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline-color: @color-background-dark5;
|
||||
outline-color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
|
|
@ -105,7 +114,7 @@
|
|||
width: 1.2rem;
|
||||
height: 1.2rem;
|
||||
margin-right: .5rem;
|
||||
fill: @color-surface-light5-90;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
.label {
|
||||
|
|
@ -114,9 +123,9 @@
|
|||
flex-basis: auto;
|
||||
max-height: 3.6em;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
color: @color-surface-light5-90;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -127,35 +136,20 @@
|
|||
width: 90%;
|
||||
max-width: initial;
|
||||
z-index: 0;
|
||||
|
||||
.close-button-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: 0.75rem 0.75rem 0 0;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.title-container {
|
||||
max-height: 4.8em;
|
||||
margin: 1rem 3rem 1rem 1.5rem;
|
||||
}
|
||||
|
||||
.modal-dialog-content {
|
||||
margin: 0 0.5rem;
|
||||
padding: 0 0.5rem;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
padding: 0 1.5rem;
|
||||
|
||||
.buttons-container {
|
||||
margin: 1rem 1rem 0;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.action-button {
|
||||
width: 100%;
|
||||
|
||||
.label {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const Button = require('stremio/common/Button');
|
||||
const Popup = require('stremio/common/Popup');
|
||||
const ModalDialog = require('stremio/common/ModalDialog');
|
||||
|
|
@ -15,7 +15,7 @@ const Multiselect = ({ className, mode, direction, title, disabled, dataset, ren
|
|||
const options = React.useMemo(() => {
|
||||
return Array.isArray(props.options) ?
|
||||
props.options.filter((option) => {
|
||||
return option && typeof option.value === 'string';
|
||||
return option && (typeof option.value === 'string' || option.value === null);
|
||||
})
|
||||
:
|
||||
[];
|
||||
|
|
@ -23,7 +23,7 @@ const Multiselect = ({ className, mode, direction, title, disabled, dataset, ren
|
|||
const selected = React.useMemo(() => {
|
||||
return Array.isArray(props.selected) ?
|
||||
props.selected.filter((value) => {
|
||||
return typeof value === 'string';
|
||||
return typeof value === 'string' || value === null;
|
||||
})
|
||||
:
|
||||
[];
|
||||
|
|
@ -104,7 +104,7 @@ const Multiselect = ({ className, mode, direction, title, disabled, dataset, ren
|
|||
title
|
||||
}
|
||||
</div>
|
||||
<Icon className={styles['icon']} icon={'ic_arrow_thin_down'} />
|
||||
<Icon className={styles['icon']} name={'caret-down'} />
|
||||
</React.Fragment>
|
||||
}
|
||||
{children}
|
||||
|
|
@ -161,7 +161,7 @@ Multiselect.propTypes = {
|
|||
direction: PropTypes.any,
|
||||
title: PropTypes.string,
|
||||
options: PropTypes.arrayOf(PropTypes.shape({
|
||||
value: PropTypes.string.isRequired,
|
||||
value: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
label: PropTypes.string
|
||||
})),
|
||||
|
|
|
|||
|
|
@ -11,16 +11,14 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 1rem;
|
||||
background-color: @color-background;
|
||||
height: 2.75rem;
|
||||
padding: 0 1.5rem;
|
||||
border-radius: 2.75rem;
|
||||
background-color: var(--overlay-color);
|
||||
|
||||
&:global(.active) {
|
||||
.label {
|
||||
color: @color-surface-light5-90;
|
||||
}
|
||||
|
||||
.icon {
|
||||
fill: @color-surface-light5-90;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -28,7 +26,7 @@
|
|||
flex: 1;
|
||||
max-height: 2.4em;
|
||||
font-weight: 500;
|
||||
color: @color-secondaryvariant1-90;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
.icon {
|
||||
|
|
@ -36,7 +34,8 @@
|
|||
width: 1rem;
|
||||
height: 1rem;
|
||||
margin-left: 1rem;
|
||||
fill: @color-secondaryvariant1-90;
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.popup-menu-container {
|
||||
|
|
@ -51,7 +50,6 @@
|
|||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
background-color: @color-background;
|
||||
|
||||
&:global(.selected) {
|
||||
.icon {
|
||||
|
|
@ -60,13 +58,13 @@
|
|||
}
|
||||
|
||||
&:hover, &:focus {
|
||||
background-color: @color-background-light2;
|
||||
background-color: var(--overlay-color);
|
||||
}
|
||||
|
||||
.label {
|
||||
flex: 1;
|
||||
max-height: 4.8em;
|
||||
color: @color-surface-light5-90;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
.icon {
|
||||
|
|
@ -76,7 +74,8 @@
|
|||
height: 0.5rem;
|
||||
border-radius: 100%;
|
||||
margin-left: 1rem;
|
||||
background-color: @color-accent3-90;
|
||||
background-color: var(--secondary-accent-color);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const Button = require('stremio/common/Button');
|
||||
const Image = require('stremio/common/Image');
|
||||
const useFullscreen = require('stremio/common/useFullscreen');
|
||||
const usePWA = require('stremio/common/usePWA');
|
||||
const SearchBar = require('./SearchBar');
|
||||
const NavMenu = require('./NavMenu');
|
||||
const styles = require('./styles');
|
||||
|
|
@ -17,9 +18,10 @@ const HorizontalNavBar = React.memo(({ className, route, query, title, backButto
|
|||
window.history.back();
|
||||
}, []);
|
||||
const [fullscreen, requestFullscreen, exitFullscreen] = useFullscreen();
|
||||
const [isIOSPWA] = usePWA();
|
||||
const renderNavMenuLabel = React.useCallback(({ ref, className, onClick, children, }) => (
|
||||
<Button ref={ref} className={classnames(className, styles['button-container'], styles['menu-button-container'])} tabIndex={-1} onClick={onClick}>
|
||||
<Icon className={styles['icon']} icon={'ic_more'} />
|
||||
<Icon className={styles['icon']} name={'person-outline'} />
|
||||
{children}
|
||||
</Button>
|
||||
), []);
|
||||
|
|
@ -28,7 +30,7 @@ const HorizontalNavBar = React.memo(({ className, route, query, title, backButto
|
|||
{
|
||||
backButton ?
|
||||
<Button className={classnames(styles['button-container'], styles['back-button-container'])} tabIndex={-1} onClick={backButtonOnClick}>
|
||||
<Icon className={styles['icon']} icon={'ic_back_ios'} />
|
||||
<Icon className={styles['icon']} name={'chevron-back'} />
|
||||
</Button>
|
||||
:
|
||||
<div className={styles['logo-container']}>
|
||||
|
|
@ -45,36 +47,36 @@ const HorizontalNavBar = React.memo(({ className, route, query, title, backButto
|
|||
:
|
||||
null
|
||||
}
|
||||
<div className={styles['spacing']} />
|
||||
{
|
||||
searchBar ?
|
||||
searchBar && route !== 'addons' ?
|
||||
<SearchBar className={styles['search-bar']} query={query} active={route === 'search'} />
|
||||
:
|
||||
null
|
||||
}
|
||||
<div className={styles['spacing']} />
|
||||
{
|
||||
addonsButton ?
|
||||
<Button className={styles['button-container']} href={'#/addons'} title={t('ADDONS')} tabIndex={-1}>
|
||||
<Icon className={styles['icon']} icon={'ic_addons'} />
|
||||
</Button>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
fullscreenButton ?
|
||||
<Button className={styles['button-container']} title={fullscreen ? t('EXIT_FULLSCREEN') : t('ENTER_FULLSCREEN')} tabIndex={-1} onClick={fullscreen ? exitFullscreen : requestFullscreen}>
|
||||
<Icon className={styles['icon']} icon={fullscreen ? 'ic_exit_fullscreen' : 'ic_fullscreen'} />
|
||||
</Button>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
navMenu ?
|
||||
<NavMenu renderLabel={renderNavMenuLabel} />
|
||||
:
|
||||
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}>
|
||||
<Icon className={styles['icon']} name={fullscreen ? 'minimize' : 'maximize'} />
|
||||
</Button>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
navMenu ?
|
||||
<NavMenu renderLabel={renderNavMenuLabel} />
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ const { useRouteFocused } = require('stremio-router');
|
|||
const Popup = require('stremio/common/Popup');
|
||||
const useBinaryState = require('stremio/common/useBinaryState');
|
||||
const NavMenuContent = require('./NavMenuContent');
|
||||
const styles = require('./styles.less');
|
||||
|
||||
const NavMenu = (props) => {
|
||||
const routeFocused = useRouteFocused();
|
||||
|
|
@ -42,6 +43,7 @@ const NavMenu = (props) => {
|
|||
onCloseRequest={closeMenu}
|
||||
renderLabel={renderLabel}
|
||||
renderMenu={renderMenu}
|
||||
className={styles['nav-menu-popup-label']}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,11 +4,12 @@ const React = require('react');
|
|||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const { useServices } = require('stremio/services');
|
||||
const Button = require('stremio/common/Button');
|
||||
const useFullscreen = require('stremio/common/useFullscreen');
|
||||
const useProfile = require('stremio/common/useProfile');
|
||||
const usePWA = require('stremio/common/usePWA');
|
||||
const useTorrent = require('stremio/common/useTorrent');
|
||||
const { withCoreSuspender } = require('stremio/common/CoreSuspender');
|
||||
const styles = require('./styles');
|
||||
|
|
@ -19,6 +20,7 @@ const NavMenuContent = ({ onClick }) => {
|
|||
const profile = useProfile();
|
||||
const { createTorrentFromMagnet } = useTorrent();
|
||||
const [fullscreen, requestFullscreen, exitFullscreen] = useFullscreen();
|
||||
const [isIOSPWA, isAndroidPWA] = usePWA();
|
||||
const logoutButtonOnClick = React.useCallback(() => {
|
||||
core.transport.dispatch({
|
||||
action: 'Ctx',
|
||||
|
|
@ -50,34 +52,41 @@ const NavMenuContent = ({ onClick }) => {
|
|||
`url('${require('/images/default_avatar.png')}')`
|
||||
}}
|
||||
/>
|
||||
<div className={styles['email-container']}>
|
||||
<div className={styles['email-label']}>{profile.auth === null ? t('ANONYMOUS_USER') : profile.auth.user.email}</div>
|
||||
<div className={styles['user-info-details']}>
|
||||
<div className={styles['email-container']}>
|
||||
<div className={styles['email-label']}>{profile.auth === null ? t('ANONYMOUS_USER') : profile.auth.user.email}</div>
|
||||
</div>
|
||||
<Button className={styles['logout-button-container']} title={profile.auth === null ? `${t('LOG_IN')} / ${t('SIGN_UP')}` : t('LOG_OUT')} href={profile.auth === null ? '#/intro' : null} onClick={profile.auth !== null ? logoutButtonOnClick : null}>
|
||||
<div className={styles['logout-label']}>{profile.auth === null ? `${t('LOG_IN')} / ${t('SIGN_UP')}` : t('LOG_OUT')}</div>
|
||||
</Button>
|
||||
</div>
|
||||
<Button className={styles['logout-button-container']} title={profile.auth === null ? `${t('LOG_IN')} / ${t('SIGN_UP')}` : t('LOG_OUT')} href={profile.auth === null ? '#/intro' : null} onClick={profile.auth !== null ? logoutButtonOnClick : null}>
|
||||
<div className={styles['logout-label']}>{profile.auth === null ? `${t('LOG_IN')} / ${t('SIGN_UP')}` : t('LOG_OUT')}</div>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles['nav-menu-section']}>
|
||||
<Button className={styles['nav-menu-option-container']} title={fullscreen ? t('EXIT_FULLSCREEN') : t('ENTER_FULLSCREEN')} onClick={fullscreen ? exitFullscreen : requestFullscreen}>
|
||||
<Icon className={styles['icon']} icon={fullscreen ? 'ic_exit_fullscreen' : 'ic_fullscreen'} />
|
||||
<div className={styles['nav-menu-option-label']}>{fullscreen ? t('EXIT_FULLSCREEN') : t('ENTER_FULLSCREEN')}</div>
|
||||
</Button>
|
||||
</div>
|
||||
{
|
||||
!isIOSPWA && !isAndroidPWA ?
|
||||
<div className={styles['nav-menu-section']}>
|
||||
<Button className={styles['nav-menu-option-container']} title={fullscreen ? t('EXIT_FULLSCREEN') : t('ENTER_FULLSCREEN')} onClick={fullscreen ? exitFullscreen : requestFullscreen}>
|
||||
<Icon className={styles['icon']} name={fullscreen ? 'minimize' : 'maximize'} />
|
||||
<div className={styles['nav-menu-option-label']}>{fullscreen ? t('EXIT_FULLSCREEN') : t('ENTER_FULLSCREEN')}</div>
|
||||
</Button>
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
<div className={styles['nav-menu-section']}>
|
||||
<Button className={styles['nav-menu-option-container']} title={ t('SETTINGS') } href={'#/settings'}>
|
||||
<Icon className={styles['icon']} icon={'ic_settings'} />
|
||||
<Icon className={styles['icon']} name={'settings'} />
|
||||
<div className={styles['nav-menu-option-label']}>{ t('SETTINGS') }</div>
|
||||
</Button>
|
||||
<Button className={styles['nav-menu-option-container']} title={ t('ADDONS') } href={'#/addons'}>
|
||||
<Icon className={styles['icon']} icon={'ic_addons'} />
|
||||
<Icon className={styles['icon']} name={'addons-outline'} />
|
||||
<div className={styles['nav-menu-option-label']}>{ t('ADDONS') }</div>
|
||||
</Button>
|
||||
<Button className={styles['nav-menu-option-container']} title={ t('PLAY_URL_MAGNET_LINK') } onClick={onPlayMagnetLinkClick}>
|
||||
<Icon className={styles['icon']} icon={'ic_magnet'} />
|
||||
<Icon className={styles['icon']} name={'magnet-link'} />
|
||||
<div className={styles['nav-menu-option-label']}>{ t('PLAY_URL_MAGNET_LINK') }</div>
|
||||
</Button>
|
||||
<Button className={styles['nav-menu-option-container']} title={ t('HELP_FEEDBACK') } href={'https://stremio.zendesk.com/'} target={'_blank'}>
|
||||
<Icon className={styles['icon']} icon={'ic_help'} />
|
||||
<Icon className={styles['icon']} name={'help'} />
|
||||
<div className={styles['nav-menu-option-label']}>{ t('HELP_FEEDBACK') }</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -88,9 +97,14 @@ const NavMenuContent = ({ onClick }) => {
|
|||
<Button className={styles['nav-menu-option-container']} title={ t('PRIVACY_POLICY') } href={'https://www.stremio.com/privacy'} target={'_blank'}>
|
||||
<div className={styles['nav-menu-option-label']}>{ t('PRIVACY_POLICY') }</div>
|
||||
</Button>
|
||||
<Button className={styles['nav-menu-option-container']} title={ t('ABOUT_STREMIO') } href={'https://www.stremio.com/'} target={'_blank'}>
|
||||
<div className={styles['nav-menu-option-label']}>{ t('ABOUT_STREMIO') }</div>
|
||||
</Button>
|
||||
{
|
||||
profile.auth !== null ?
|
||||
<Button className={styles['nav-menu-option-container']} title={ t('USER_PANEL') } href={'https://www.stremio.com/acc-settings'} target={'_blank'}>
|
||||
<div className={styles['nav-menu-option-label']}>{ t('USER_PANEL') }</div>
|
||||
</Button>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,101 +3,104 @@
|
|||
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
|
||||
@import (reference) '~stremio/common/screen-sizes.less';
|
||||
|
||||
:import('~stremio/common/Popup/styles.less') {
|
||||
popup-menu-container: menu-container;
|
||||
}
|
||||
|
||||
.nav-menu-popup-label {
|
||||
.popup-menu-container {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
.nav-menu-container {
|
||||
width: 20rem;
|
||||
width: 22rem;
|
||||
max-height: calc(100vh - var(--horizontal-nav-bar-size));
|
||||
overflow-y: auto;
|
||||
background-color: @color-background-dark1;
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--modal-background-color);
|
||||
|
||||
.user-info-container {
|
||||
display: grid;
|
||||
height: 7rem;
|
||||
grid-template-columns: 7rem 1fr;
|
||||
grid-template-rows: 50% 50%;
|
||||
grid-template-areas:
|
||||
"avatar-area email-area"
|
||||
"avatar-area logout-button-area";
|
||||
display: flex;
|
||||
padding: 1.5rem 1rem;
|
||||
|
||||
.avatar-container {
|
||||
grid-area: avatar-area;
|
||||
padding: 1rem;
|
||||
flex: none;
|
||||
height: 4rem;
|
||||
width: 4rem;
|
||||
border-radius: 50%;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-origin: content-box;
|
||||
background-clip: content-box;
|
||||
background-color: var(--primary-foreground-color);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.email-container {
|
||||
grid-area: email-area;
|
||||
.user-info-details {
|
||||
flex: auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 1rem 1rem 0 0;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin-left: 1rem;
|
||||
|
||||
.email-label {
|
||||
flex: 1;
|
||||
max-height: 2.4em;
|
||||
color: @color-surface-light5-90;
|
||||
}
|
||||
}
|
||||
.email-container {
|
||||
flex: none;
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
.logout-button-container {
|
||||
grid-area: logout-button-area;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 1rem 1rem 0;
|
||||
|
||||
&:hover, &:focus {
|
||||
outline: none;
|
||||
|
||||
.logout-label {
|
||||
color: @color-surface-light5-90;
|
||||
text-decoration: underline;
|
||||
.email-label {
|
||||
flex: 1;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
.logout-label {
|
||||
flex: 1;
|
||||
max-height: 2.4em;
|
||||
color: @color-surface-light3-90;
|
||||
.logout-button-container {
|
||||
flex: none;
|
||||
|
||||
&:hover, &:focus {
|
||||
outline: none;
|
||||
|
||||
.logout-label {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.logout-label {
|
||||
flex: 1;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-menu-section {
|
||||
border-top: thin solid @color-surface-light5-20;
|
||||
border-top: thin solid var(--overlay-color);
|
||||
|
||||
.nav-menu-option-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 4rem;
|
||||
padding: 0 1.5rem;
|
||||
|
||||
&:hover {
|
||||
background-color: @color-background-light2;
|
||||
background-color: var(--overlay-color);
|
||||
}
|
||||
|
||||
.icon {
|
||||
flex: none;
|
||||
width: 1.4rem;
|
||||
height: 1.4rem;
|
||||
margin: 1.3rem;
|
||||
fill: @color-secondaryvariant2-light1-90;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
margin-right: 1rem;
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.nav-menu-option-label {
|
||||
flex: 1;
|
||||
max-height: 2.4em;
|
||||
padding-right: 1.3rem;
|
||||
color: @color-surface-light5-90;
|
||||
|
||||
&:only-child {
|
||||
padding-left: 1.3rem;
|
||||
}
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,44 +3,100 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const debounce = require('lodash.debounce');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const { useRouteFocused } = require('stremio-router');
|
||||
const Button = require('stremio/common/Button');
|
||||
const TextInput = require('stremio/common/TextInput');
|
||||
const useTorrent = require('stremio/common/useTorrent');
|
||||
const { withCoreSuspender } = require('stremio/common/CoreSuspender');
|
||||
const useSearchHistory = require('./useSearchHistory');
|
||||
const useLocalSearch = require('./useLocalSearch');
|
||||
const styles = require('./styles');
|
||||
const useBinaryState = require('stremio/common/useBinaryState');
|
||||
|
||||
const SearchBar = ({ className, query, active }) => {
|
||||
const SearchBar = React.memo(({ className, query, active }) => {
|
||||
const { t } = useTranslation();
|
||||
const routeFocused = useRouteFocused();
|
||||
const searchHistory = useSearchHistory();
|
||||
const localSearch = useLocalSearch();
|
||||
const { createTorrentFromMagnet } = useTorrent();
|
||||
|
||||
const [historyOpen, openHistory, closeHistory, ] = useBinaryState(false);
|
||||
const [currentQuery, setCurrentQuery] = React.useState(query || '');
|
||||
|
||||
const searchInputRef = React.useRef(null);
|
||||
const containerRef = React.useRef(null);
|
||||
|
||||
const searchBarOnClick = React.useCallback(() => {
|
||||
if (!active) {
|
||||
window.location = '#/search';
|
||||
}
|
||||
}, [active]);
|
||||
|
||||
const searchHistoryOnClose = React.useCallback((event) => {
|
||||
if (historyOpen && containerRef.current && !containerRef.current.contains(event.target)) {
|
||||
closeHistory();
|
||||
}
|
||||
}, [historyOpen]);
|
||||
|
||||
React.useEffect(() => {
|
||||
document.addEventListener('mousedown', searchHistoryOnClose);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', searchHistoryOnClose);
|
||||
};
|
||||
}, [searchHistoryOnClose]);
|
||||
|
||||
const queryInputOnChange = React.useCallback(() => {
|
||||
const value = searchInputRef.current.value;
|
||||
setCurrentQuery(value);
|
||||
openHistory();
|
||||
try {
|
||||
createTorrentFromMagnet(searchInputRef.current.value);
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch { }
|
||||
}, []);
|
||||
const queryInputOnSubmit = React.useCallback(() => {
|
||||
if (searchInputRef.current !== null) {
|
||||
const queryParams = new URLSearchParams([['search', searchInputRef.current.value]]);
|
||||
window.location = `#/search?${queryParams.toString()}`;
|
||||
createTorrentFromMagnet(value);
|
||||
} catch (error) {
|
||||
console.error('Failed to create torrent from magnet:', error);
|
||||
}
|
||||
}, [createTorrentFromMagnet]);
|
||||
|
||||
const queryInputOnSubmit = React.useCallback((event) => {
|
||||
event.preventDefault();
|
||||
const searchValue = `/search?search=${event.target.value}`;
|
||||
setCurrentQuery(searchValue);
|
||||
if (searchInputRef.current && searchValue) {
|
||||
window.location.hash = searchValue;
|
||||
closeHistory();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const queryInputClear = React.useCallback(() => {
|
||||
searchInputRef.current.value = '';
|
||||
setCurrentQuery('');
|
||||
window.location.hash = '/search';
|
||||
}, []);
|
||||
|
||||
const updateLocalSearchDebounced = React.useCallback(debounce((query) => {
|
||||
localSearch.search(query);
|
||||
}, 250), []);
|
||||
|
||||
React.useEffect(() => {
|
||||
updateLocalSearchDebounced(currentQuery);
|
||||
}, [currentQuery]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (routeFocused && active) {
|
||||
searchInputRef.current.focus();
|
||||
}
|
||||
}, [routeFocused, active, query]);
|
||||
}, [routeFocused, active]);
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
updateLocalSearchDebounced.cancel();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<label className={classnames(className, styles['search-bar-container'], { 'active': active })} onClick={searchBarOnClick}>
|
||||
<div className={classnames(className, styles['search-bar-container'], { 'active': active })} onClick={searchBarOnClick} ref={containerRef}>
|
||||
{
|
||||
active ?
|
||||
<TextInput
|
||||
|
|
@ -53,18 +109,72 @@ const SearchBar = ({ className, query, active }) => {
|
|||
tabIndex={-1}
|
||||
onChange={queryInputOnChange}
|
||||
onSubmit={queryInputOnSubmit}
|
||||
onClick={openHistory}
|
||||
/>
|
||||
:
|
||||
<div className={styles['search-input']}>
|
||||
<div className={styles['placeholder-label']}>{ t('SEARCH_OR_PASTE_LINK') }</div>
|
||||
</div>
|
||||
}
|
||||
<Button className={styles['submit-button-container']} tabIndex={-1} onClick={queryInputOnSubmit}>
|
||||
<Icon className={styles['icon']} icon={'ic_search_link'} />
|
||||
</Button>
|
||||
</label>
|
||||
{
|
||||
currentQuery.length > 0 ?
|
||||
<Button className={styles['submit-button-container']} onClick={queryInputClear}>
|
||||
<Icon className={styles['icon']} name={'close'} />
|
||||
</Button>
|
||||
:
|
||||
<Button className={styles['submit-button-container']}>
|
||||
<Icon className={styles['icon']} name={'search'} />
|
||||
</Button>
|
||||
}
|
||||
{
|
||||
historyOpen && (searchHistory?.items?.length || localSearch?.items?.length) ?
|
||||
<div className={styles['menu-container']}>
|
||||
{
|
||||
searchHistory?.items?.length > 0 ?
|
||||
<div className={styles['items']}>
|
||||
<div className={styles['title']}>
|
||||
<div className={styles['label']}>{ t('STREMIO_TV_SEARCH_HISTORY_TITLE') }</div>
|
||||
<button className={styles['search-history-clear']} onClick={searchHistory.clear}>
|
||||
{ t('CLEAR_HISTORY') }
|
||||
</button>
|
||||
</div>
|
||||
{
|
||||
searchHistory.items.slice(0, 8).map(({ query, deepLinks }, index) => (
|
||||
<Button key={index} className={styles['item']} href={deepLinks.search} onClick={closeHistory}>
|
||||
{query}
|
||||
</Button>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
localSearch?.items?.length ?
|
||||
<div className={styles['items']}>
|
||||
<div className={styles['title']}>
|
||||
<div className={styles['label']}>{ t('Recommendations') }</div>
|
||||
</div>
|
||||
{
|
||||
localSearch.items.map(({ query, deepLinks }, index) => (
|
||||
<Button key={index} className={styles['item']} href={deepLinks.search} onClick={closeHistory}>
|
||||
{query}
|
||||
</Button>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
SearchBar.displayName = 'SearchBar';
|
||||
|
||||
SearchBar.propTypes = {
|
||||
className: PropTypes.string,
|
||||
|
|
@ -80,7 +190,7 @@ const SearchBarFallback = ({ className }) => {
|
|||
<div className={styles['placeholder-label']}>{ t('SEARCH_OR_PASTE_LINK') }</div>
|
||||
</div>
|
||||
<Button className={styles['submit-button-container']} tabIndex={-1}>
|
||||
<Icon className={styles['icon']} icon={'ic_search_link'} />
|
||||
<Icon className={styles['icon']} name={'search'} />
|
||||
</Button>
|
||||
</label>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,16 +3,14 @@
|
|||
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
|
||||
|
||||
.search-bar-container {
|
||||
--search-bar-size: calc(var(--horizontal-nav-bar-size) - 1.2rem);
|
||||
--search-bar-size: 3.25rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: var(--search-bar-size);
|
||||
border-radius: var(--search-bar-size);
|
||||
background-color: @color-background-light2;
|
||||
|
||||
&:hover {
|
||||
background-color: @color-background-light3;
|
||||
}
|
||||
background-color: var(--overlay-color);
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
|
|
@ -20,15 +18,16 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 0.5rem 0 1.5rem;
|
||||
padding: 0 0.5rem 0 2rem;
|
||||
font-weight: 500;
|
||||
color: @color-secondaryvariant1-light1;
|
||||
color: var(--primary-foreground-color);
|
||||
cursor: text;
|
||||
|
||||
&::placeholder, .placeholder-label {
|
||||
max-height: 1.2em;
|
||||
opacity: 1;
|
||||
color: @color-secondaryvariant1-light1-90;
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -38,20 +37,81 @@
|
|||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: var(--search-bar-size);
|
||||
height: var(--search-bar-size);
|
||||
|
||||
&:hover {
|
||||
.icon {
|
||||
fill: @color-secondaryvariant2-light1-90;
|
||||
}
|
||||
}
|
||||
padding: 0 1.5rem;
|
||||
|
||||
.icon {
|
||||
flex: none;
|
||||
width: 1.7rem;
|
||||
height: 1.7rem;
|
||||
fill: @color-secondaryvariant1-90;
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-container {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
z-index: 10;
|
||||
padding: 1rem;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
background-color: var(--modal-background-color);
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
.label {
|
||||
font-size: 0.9rem;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
opacity: 0.8;
|
||||
padding-bottom: 1rem;
|
||||
|
||||
.search-history-clear {
|
||||
cursor: pointer;
|
||||
color: var(--primary-foreground-color);
|
||||
font-size: 0.9rem;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.items {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
|
||||
.item {
|
||||
width: 90%;
|
||||
color: var(--primary-foreground-color);
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: var(--border-radius);
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--secondary-background-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
src/common/NavBar/HorizontalNavBar/SearchBar/useLocalSearch.d.ts
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
declare const useLocalSearch: () => { items: LocalSearchItem[], search: (query: string) => void };
|
||||
export = useLocalSearch;
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const { useServices } = require('stremio/services');
|
||||
const useModelState = require('stremio/common/useModelState');
|
||||
|
||||
const useLocalSearch = () => {
|
||||
const { core } = useServices();
|
||||
|
||||
const action = React.useMemo(() => ({
|
||||
action: 'Load',
|
||||
args: {
|
||||
model: 'LocalSearch',
|
||||
}
|
||||
}), []);
|
||||
|
||||
const { items } = useModelState({ model: 'local_search', action });
|
||||
|
||||
const search = React.useCallback((query) => {
|
||||
core.transport.dispatch({
|
||||
action: 'Search',
|
||||
args: {
|
||||
action: 'Search',
|
||||
args: {
|
||||
searchQuery: query,
|
||||
maxResults: 5
|
||||
}
|
||||
},
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
items,
|
||||
search,
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = useLocalSearch;
|
||||
2
src/common/NavBar/HorizontalNavBar/SearchBar/useSearchHistory.d.ts
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
declare const useSearchHistory: () => { items: SearchHistory, clear: () => void };
|
||||
export = useSearchHistory;
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const useModelState = require('stremio/common/useModelState');
|
||||
const { useServices } = require('stremio/services');
|
||||
|
||||
const useSearchHistory = () => {
|
||||
const { core } = useServices();
|
||||
const { searchHistory: items } = useModelState({ model: 'ctx' });
|
||||
|
||||
const clear = React.useCallback(() => {
|
||||
core.transport.dispatch({
|
||||
action: 'Ctx',
|
||||
args: {
|
||||
action: 'ClearSearchHistory',
|
||||
},
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
items,
|
||||
clear,
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = useSearchHistory;
|
||||
|
|
@ -7,9 +7,10 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: var(--horizontal-nav-bar-size);
|
||||
padding-right: 1rem;
|
||||
background-color: @color-background;
|
||||
background-color: transparent;
|
||||
overflow: visible;
|
||||
|
||||
.logo-container {
|
||||
|
|
@ -30,8 +31,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
.spacing {
|
||||
flex: 1 0 0;
|
||||
.back-button-container {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
|
|
@ -44,19 +45,18 @@
|
|||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
color: @color-secondaryvariant2-light1-90;
|
||||
|
||||
&+.spacing {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
flex: 2 0 9.5rem;
|
||||
max-width: 30rem;
|
||||
height: 3.25rem;
|
||||
width: 30rem;
|
||||
}
|
||||
|
||||
&+.spacing {
|
||||
max-width: 11rem;
|
||||
}
|
||||
.buttons-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
|
|
@ -64,31 +64,27 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: var(--horizontal-nav-bar-size);
|
||||
height: var(--horizontal-nav-bar-size);
|
||||
|
||||
&.back-button-container {
|
||||
width: var(--vertical-nav-bar-size);
|
||||
height: var(--horizontal-nav-bar-size);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @color-background-light2;
|
||||
}
|
||||
|
||||
&:global(.active) {
|
||||
background-color: @color-background-light3;
|
||||
|
||||
.icon {
|
||||
fill: @color-surface-light5-90;
|
||||
}
|
||||
}
|
||||
width: 3.5rem;
|
||||
height: 3.5rem;
|
||||
border-radius: 0.75rem;
|
||||
opacity: 0.4;
|
||||
|
||||
.icon {
|
||||
flex: none;
|
||||
width: 1.7rem;
|
||||
height: 1.7rem;
|
||||
fill: @color-secondaryvariant2-light1-90;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&:hover, &:global(.active) {
|
||||
background-color: var(--overlay-color);
|
||||
opacity: 1;
|
||||
|
||||
.icon {
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -102,16 +98,12 @@
|
|||
width: var(--horizontal-nav-bar-size);
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
.button-container {
|
||||
margin: 0 1rem;
|
||||
|
||||
.spacing {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.button-container:not(.back-button-container):not(.menu-button-container) {
|
||||
display: none;
|
||||
&:not(.back-button-container):not(.menu-button-container) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const Button = require('stremio/common/Button');
|
||||
const Image = require('stremio/common/Image');
|
||||
const styles = require('./styles');
|
||||
|
|
@ -11,7 +11,7 @@ const styles = require('./styles');
|
|||
const NavTabButton = ({ className, logo, icon, label, href, selected, onClick }) => {
|
||||
const renderLogoFallback = React.useCallback(() => (
|
||||
typeof icon === 'string' && icon.length > 0 ?
|
||||
<Icon className={styles['icon']} icon={icon} />
|
||||
<Icon className={styles['icon']} name={icon} />
|
||||
:
|
||||
null
|
||||
), [icon]);
|
||||
|
|
@ -27,7 +27,7 @@ const NavTabButton = ({ className, logo, icon, label, href, selected, onClick })
|
|||
/>
|
||||
:
|
||||
typeof icon === 'string' && icon.length > 0 ?
|
||||
<Icon className={styles['icon']} icon={icon} />
|
||||
<Icon className={styles['icon']} name={selected ? icon : `${icon}-outline`} />
|
||||
:
|
||||
null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,50 +1,71 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
|
||||
@import (reference) '~stremio/common/screen-sizes.less';
|
||||
|
||||
.nav-tab-button-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: @color-background-dark1;
|
||||
background-color: transparent;
|
||||
border-radius: 0.75rem;
|
||||
|
||||
&:hover {
|
||||
background-color: @color-background-light2;
|
||||
background-color: var(--overlay-color);
|
||||
|
||||
.label {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
&:global(.selected) {
|
||||
background-color: @color-secondaryvariant1-dark5;
|
||||
|
||||
&:hover {
|
||||
background-color: @color-secondaryvariant1-dark4;
|
||||
}
|
||||
|
||||
.icon {
|
||||
fill: @color-surface-light5-90;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: @color-surface-light5-90;
|
||||
opacity: 1;
|
||||
color: var(--primary-accent-color);
|
||||
}
|
||||
}
|
||||
|
||||
.icon, .logo {
|
||||
flex: none;
|
||||
width: 1.7rem;
|
||||
height: 1.7rem;
|
||||
width: 2.2rem;
|
||||
height: 2.2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
fill: @color-secondary-light5-90;
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.label {
|
||||
flex: none;
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
max-height: 2.4em;
|
||||
padding: 0 0.2rem;
|
||||
font-size: 0.9rem;
|
||||
padding: 0 0.5rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.01rem;
|
||||
text-align: center;
|
||||
color: @color-secondaryvariant1-90;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
.nav-tab-button-container {
|
||||
.label {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
&:global(.selected) {
|
||||
.label {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,12 @@
|
|||
@import (reference) '~stremio/common/screen-sizes.less';
|
||||
|
||||
.vertical-nav-bar-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
width: var(--vertical-nav-bar-size);
|
||||
background-color: @color-background-dark1;
|
||||
background-color: transparent;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
|
||||
|
|
@ -14,8 +18,8 @@
|
|||
}
|
||||
|
||||
.nav-tab-button {
|
||||
width: var(--vertical-nav-bar-size);
|
||||
height: var(--vertical-nav-bar-size);
|
||||
width: calc(var(--vertical-nav-bar-size) - 1.5rem);
|
||||
height: calc(var(--vertical-nav-bar-size) - 1.5rem);
|
||||
|
||||
&:first-child {
|
||||
margin-top: 1rem;
|
||||
|
|
@ -29,11 +33,12 @@
|
|||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
.vertical-nav-bar-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
gap: 0;
|
||||
height: var(--vertical-nav-bar-size);
|
||||
width: 100%;
|
||||
padding: 0 1rem;
|
||||
overflow-y: hidden;
|
||||
overflow-x: auto;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const Button = require('stremio/common/Button');
|
||||
const styles = require('./styles');
|
||||
|
||||
|
|
@ -22,13 +22,13 @@ const PaginationInput = ({ className, label, dataset, onSelect, ...props }) => {
|
|||
return (
|
||||
<div {...props} className={classnames(className, styles['pagination-input-container'])} >
|
||||
<Button className={styles['prev-button-container']} title={'Previous page'} data-value={'prev'} onClick={prevNextButtonOnClick}>
|
||||
<Icon className={styles['icon']} icon={'ic_arrow_left'} />
|
||||
<Icon className={styles['icon']} name={'chevron-back'} />
|
||||
</Button>
|
||||
<div className={styles['label-container']} title={label}>
|
||||
<div className={styles['label']}>{label}</div>
|
||||
</div>
|
||||
<Button className={styles['next-button-container']} title={'Next page'} data-value={'next'} onClick={prevNextButtonOnClick}>
|
||||
<Icon className={styles['icon']} icon={'ic_arrow_right'} />
|
||||
<Icon className={styles['icon']} name={'chevron-forward'} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,17 +5,18 @@
|
|||
.pagination-input-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
.prev-button-container, .next-button-container {
|
||||
flex: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: @color-background;
|
||||
background-color: var(--overlay-color);
|
||||
|
||||
.icon {
|
||||
display: block;
|
||||
fill: @color-secondaryvariant1-90;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -25,7 +26,7 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: @color-background-dark1;
|
||||
background-color: var(--overlay-color);
|
||||
|
||||
.label {
|
||||
flex: none;
|
||||
|
|
@ -35,7 +36,7 @@
|
|||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
color: @color-secondaryvariant1-90;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,10 +13,11 @@
|
|||
.menu-container {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
overflow: visible;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
box-shadow: 0 1.35rem 2.7rem @color-background-dark5-40,
|
||||
0 1.1rem 0.85rem @color-background-dark5-20;
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--modal-background-color);
|
||||
box-shadow: var(--outer-glow);
|
||||
cursor: auto;
|
||||
|
||||
&.menu-direction-top-left {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const TextInput = require('stremio/common/TextInput');
|
||||
const SearchBarPlaceholder = require('./SearchBarPlaceholder');
|
||||
const styles = require('./styles');
|
||||
|
|
@ -18,7 +18,7 @@ const SearchBar = ({ className, title, value, onChange }) => {
|
|||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Icon className={styles['icon']} icon={'ic_search'} />
|
||||
<Icon className={styles['icon']} name={'search'} />
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const styles = require('./styles');
|
||||
|
||||
const SearchBarPlaceholder = ({ className, title }) => {
|
||||
return (
|
||||
<div className={classnames(className, styles['search-bar-container'])}>
|
||||
<div className={styles['search-input']}>{title}</div>
|
||||
<Icon className={styles['icon']} icon={'ic_search'} />
|
||||
<Icon className={styles['icon']} name={'search'} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,6 +22,6 @@
|
|||
flex: none;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
fill: var(--color-placeholder-background);
|
||||
color: var(--color-placeholder-background);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,31 +6,26 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 3.5rem;
|
||||
padding: 0 1rem;
|
||||
border-radius: 3.5rem;
|
||||
height: 3rem;
|
||||
padding: 0 1.5rem;
|
||||
border-radius: 3rem;
|
||||
border: var(--focus-outline-size) solid transparent;
|
||||
background-color: @color-background;
|
||||
background-color: var(--overlay-color);
|
||||
cursor: text;
|
||||
|
||||
&:hover, &:focus-within {
|
||||
background-color: @color-background-light1;
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
border: var(--focus-outline-size) solid @color-surface-light5;
|
||||
border: var(--focus-outline-size) solid var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
margin-right: 1rem;
|
||||
font-size: 1.1rem;
|
||||
color: @color-surface-light5;
|
||||
font-size: 1rem;
|
||||
color: var(--primary-foreground-color);
|
||||
|
||||
&::placeholder {
|
||||
max-height: 1.2em;
|
||||
opacity: 1;
|
||||
color: @color-secondaryvariant1-light1-90;
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -38,6 +33,7 @@
|
|||
flex: none;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
fill: @color-secondaryvariant1-90;
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,9 +4,10 @@ const React = require('react');
|
|||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const { useRouteFocused } = require('stremio-router');
|
||||
const { useServices } = require('stremio/services');
|
||||
const useToast = require('stremio/common/Toast/useToast');
|
||||
const Button = require('stremio/common/Button');
|
||||
const TextInput = require('stremio/common/TextInput');
|
||||
const styles = require('./styles');
|
||||
|
|
@ -14,6 +15,7 @@ const styles = require('./styles');
|
|||
const SharePrompt = ({ className, url }) => {
|
||||
const { t } = useTranslation();
|
||||
const { core } = useServices();
|
||||
const toast = useToast();
|
||||
const inputRef = React.useRef(null);
|
||||
const routeFocused = useRouteFocused();
|
||||
const selectInputContent = React.useCallback(() => {
|
||||
|
|
@ -25,6 +27,11 @@ const SharePrompt = ({ className, url }) => {
|
|||
if (inputRef.current !== null) {
|
||||
inputRef.current.select();
|
||||
document.execCommand('copy');
|
||||
toast.show({
|
||||
type: 'success',
|
||||
title: 'Copied to clipboard',
|
||||
timeout: 3000,
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
React.useEffect(() => {
|
||||
|
|
@ -44,12 +51,13 @@ const SharePrompt = ({ className, url }) => {
|
|||
<div className={classnames(className, styles['share-prompt-container'])}>
|
||||
<div className={styles['buttons-container']}>
|
||||
<Button className={classnames(styles['button-container'], styles['facebook-button'])} title={'Facebook'} href={`https://www.facebook.com/sharer/sharer.php?u=${url}`} target={'_blank'}>
|
||||
<Icon className={styles['icon']} icon={'ic_facebook'} />
|
||||
<div className={styles['label']}>Facebook</div>
|
||||
<Icon className={styles['icon']} name={'facebook'} />
|
||||
</Button>
|
||||
<Button className={classnames(styles['button-container'], styles['twitter-button'])} title={'Twitter'} href={`https://twitter.com/home?status=${url}`} target={'_blank'}>
|
||||
<Icon className={styles['icon']} icon={'ic_twitter'} />
|
||||
<div className={styles['label']}>Twitter</div>
|
||||
<Button className={classnames(styles['button-container'], styles['x-button'])} title={'X (Twitter)'} href={`https://twitter.com/intent/tweet?text=${url}`} target={'_blank'}>
|
||||
<Icon className={styles['icon']} name={'x'} />
|
||||
</Button>
|
||||
<Button className={classnames(styles['button-container'], styles['reddit-button'])} title={'Reddit'} href={`https://www.reddit.com/submit?url=${url}`} target={'_blank'}>
|
||||
<Icon className={styles['icon']} name={'reddit'} />
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles['url-container']}>
|
||||
|
|
@ -63,7 +71,7 @@ const SharePrompt = ({ className, url }) => {
|
|||
tabIndex={-1}
|
||||
/>
|
||||
<Button className={styles['copy-button']} title={'Copy to clipboard'} onClick={copyToClipboard}>
|
||||
<Icon className={styles['icon']} icon={'ic_link'} />
|
||||
<Icon className={styles['icon']} name={'link'} />
|
||||
<div className={styles['label']}>{ t('COPY') }</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -21,26 +21,16 @@
|
|||
|
||||
.icon {
|
||||
flex: none;
|
||||
height: 1.2rem;
|
||||
margin-right: 1rem;
|
||||
fill: @color-surface-light5-90;
|
||||
}
|
||||
|
||||
.label {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
flex-basis: auto;
|
||||
max-height: 2.4em;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
color: @color-surface-light5-90;
|
||||
height: 1.5rem;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
.facebook-button, .twitter-button {
|
||||
.facebook-button, .x-button, .reddit-button {
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
&:focus {
|
||||
outline-color: @color-background-dark5;
|
||||
outline-color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -48,8 +38,12 @@
|
|||
background-color: var(--color-facebook);
|
||||
}
|
||||
|
||||
.twitter-button {
|
||||
background-color: var(--color-twitter);
|
||||
.x-button {
|
||||
background-color: var(--color-x);
|
||||
}
|
||||
|
||||
.reddit-button {
|
||||
background-color: var(--color-reddit);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -57,7 +51,8 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-top: 1rem;
|
||||
background-color: @color-surface-light2;
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--overlay-color);
|
||||
|
||||
.url-text-input {
|
||||
flex: 1;
|
||||
|
|
@ -65,7 +60,7 @@
|
|||
padding: 1rem;
|
||||
font-size: 1.1rem;
|
||||
text-align: center;
|
||||
color: @color-background-dark5;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
|
|
@ -77,14 +72,16 @@
|
|||
justify-content: center;
|
||||
width: 8rem;
|
||||
padding: 1rem;
|
||||
background-color: @color-accent3;
|
||||
border-radius: 0 var(--border-radius) var(--border-radius) 0;
|
||||
background-color: var(--secondary-accent-color);
|
||||
|
||||
&:hover {
|
||||
background-color: @color-accent3-light1;
|
||||
outline: var(--focus-outline-size) solid var(--secondary-accent-color);
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline-color: @color-background-dark5;
|
||||
outline-color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
.icon {
|
||||
|
|
@ -92,7 +89,7 @@
|
|||
width: 1.2rem;
|
||||
height: 1.2rem;
|
||||
margin-right: 0.5rem;
|
||||
fill: @color-surface-light5-90;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
.label {
|
||||
|
|
@ -101,7 +98,7 @@
|
|||
flex-basis: auto;
|
||||
max-height: 2.4em;
|
||||
font-size: 1.1rem;
|
||||
color: @color-surface-light5-90;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,9 +109,7 @@ const Slider = ({ className, value, buffered, minimumValue, maximumValue, disabl
|
|||
<div className={styles['track-after']} style={{ width: `calc(100% * ${thumbPosition})` }} />
|
||||
</div>
|
||||
<div className={styles['layer']}>
|
||||
<svg className={styles['thumb']} style={{ marginLeft: `calc(100% * ${thumbPosition})` }} viewBox={'0 0 10 10'}>
|
||||
<circle cx={'5'} cy={'5'} r={'5'} />
|
||||
</svg>
|
||||
<div className={styles['thumb']} style={{ marginLeft: `calc(100% * ${thumbPosition})` }} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -16,26 +16,9 @@ html.active-slider-within {
|
|||
overflow: visible;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover, &:global(.active) {
|
||||
.track-after {
|
||||
background-color: @color-primary-light5;
|
||||
}
|
||||
}
|
||||
|
||||
&:global(.disabled) {
|
||||
pointer-events: none;
|
||||
|
||||
.track {
|
||||
background-color: @color-surface-dark5;
|
||||
}
|
||||
|
||||
.track-after {
|
||||
background-color: @color-surface;
|
||||
}
|
||||
|
||||
.thumb {
|
||||
fill: @color-surface;
|
||||
}
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.layer {
|
||||
|
|
@ -55,21 +38,24 @@ html.active-slider-within {
|
|||
z-index: 0;
|
||||
flex: 1;
|
||||
height: var(--track-size);
|
||||
background-color: @color-surface-light5-20;
|
||||
border-radius: var(--track-size);
|
||||
background-color: var(--overlay-color);
|
||||
}
|
||||
|
||||
.track-before {
|
||||
z-index: 1;
|
||||
flex: none;
|
||||
height: var(--track-size);
|
||||
background-color: @color-surface-light5-10;
|
||||
border-radius: var(--track-size);
|
||||
background-color: var(--overlay-color);
|
||||
}
|
||||
|
||||
.track-after {
|
||||
z-index: 2;
|
||||
flex: none;
|
||||
height: var(--track-size);
|
||||
background-color: @color-primary-light3;
|
||||
border-radius: var(--track-size);
|
||||
background-color: var(--primary-foreground-color);
|
||||
}
|
||||
|
||||
.thumb {
|
||||
|
|
@ -78,6 +64,7 @@ html.active-slider-within {
|
|||
width: var(--thumb-size);
|
||||
height: var(--thumb-size);
|
||||
transform: translateX(-50%);
|
||||
fill: @color-surface-light5;
|
||||
background-color: var(--primary-foreground-color);
|
||||
border-radius: 100%;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const Button = require('stremio/common/Button');
|
||||
const styles = require('./styles');
|
||||
|
||||
|
|
@ -16,8 +16,8 @@ const ToastItem = ({ title, message, dataset, onSelect, onClose, ...props }) =>
|
|||
}, [props.type]);
|
||||
const icon = React.useMemo(() => {
|
||||
return typeof props.icon === 'string' ? props.icon :
|
||||
type === 'success' ? 'ic_check' :
|
||||
type === 'error' ? 'ic_warning' :
|
||||
type === 'success' ? 'checkmark' :
|
||||
type === 'error' ? 'warning' :
|
||||
null;
|
||||
}, [type, props.icon]);
|
||||
const toastOnClick = React.useCallback((event) => {
|
||||
|
|
@ -54,7 +54,7 @@ const ToastItem = ({ title, message, dataset, onSelect, onClose, ...props }) =>
|
|||
{
|
||||
typeof icon === 'string' && icon.length > 0 ?
|
||||
<div className={styles['icon-container']}>
|
||||
<Icon className={styles['icon']} icon={icon} />
|
||||
<Icon className={styles['icon']} name={icon} />
|
||||
</div>
|
||||
:
|
||||
null
|
||||
|
|
@ -74,7 +74,7 @@ const ToastItem = ({ title, message, dataset, onSelect, onClose, ...props }) =>
|
|||
}
|
||||
</div>
|
||||
<Button className={styles['close-button-container']} title={'Close'} tabIndex={-1} onClick={closeButtonOnClick}>
|
||||
<Icon className={styles['icon']} icon={'ic_x'} />
|
||||
<Icon className={styles['icon']} name={'close'} />
|
||||
</Button>
|
||||
</Button>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
background-color: @color-accent3;
|
||||
|
||||
.icon {
|
||||
fill: @color-surface-light5-90;
|
||||
color: @color-surface-light5-90;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
background-color: @color-accent2;
|
||||
|
||||
.icon {
|
||||
fill: @color-surface-light5-90;
|
||||
color: @color-surface-light5-90;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
fill: @color-background-dark5-90;
|
||||
color: @color-background-dark5-90;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||