mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-20 02:22:09 +00:00
commit
a81482692e
49 changed files with 14331 additions and 404 deletions
14022
package-lock.json
generated
14022
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -15,10 +15,11 @@
|
|||
"@babel/runtime": "7.16.0",
|
||||
"@sentry/browser": "6.13.3",
|
||||
"@stremio/stremio-colors": "4.0.1",
|
||||
"@stremio/stremio-core-web": "0.38.0",
|
||||
"@stremio/stremio-core-web": "0.42.0",
|
||||
"@stremio/stremio-icons": "3.0.5",
|
||||
"@stremio/stremio-video": "0.0.19-rc.1",
|
||||
"@stremio/stremio-video": "0.0.20-rc.4",
|
||||
"a-color-picker": "1.2.1",
|
||||
"bowser": "2.11.0",
|
||||
"buffer": "6.0.3",
|
||||
"classnames": "2.3.1",
|
||||
"eventemitter3": "4.0.7",
|
||||
|
|
@ -31,7 +32,7 @@
|
|||
"react": "16.12.0",
|
||||
"react-dom": "16.12.0",
|
||||
"react-focus-lock": "2.2.1",
|
||||
"spatial-navigation-polyfill": "git+https://git@github.com/Stremio/spatial-navigation.git#40204ad9942fe786794c62f99ea5ab2b52b24096"
|
||||
"spatial-navigation-polyfill": "git+https://git@github.com/Stremio/spatial-navigation.git#64871b1422466f5f45d24ebc8bbd315b2ebab6a6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.16.0",
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ html {
|
|||
|
||||
@media only screen and (max-width: @xsmall) {
|
||||
html {
|
||||
min-width: inherit !important;
|
||||
min-height: inherit !important;
|
||||
min-width: inherit;
|
||||
min-height: inherit;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
.color-picker-container {
|
||||
overflow: visible;
|
||||
text-align: center;
|
||||
|
||||
* {
|
||||
overflow: visible;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright (C) 2017-2022 Smart code 203358507
|
||||
|
||||
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
|
||||
@import (reference) '~stremio/common/screen-sizes.less';
|
||||
|
||||
.color-input-container {
|
||||
position: relative;
|
||||
|
|
@ -33,10 +32,4 @@
|
|||
|
||||
.color-picker-container {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
.color-picker-container {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
|
@ -19,9 +19,9 @@ const Image = ({ className, src, alt, fallbackSrc, renderFallback, ...props }) =
|
|||
typeof renderFallback === 'function' ?
|
||||
renderFallback()
|
||||
:
|
||||
<img {...props} className={className} src={fallbackSrc} alt={alt} loading={'lazy'} />
|
||||
<img {...props} className={className} src={fallbackSrc} alt={alt} />
|
||||
:
|
||||
<img {...props} className={className} src={src} alt={alt} loading={'lazy'} onError={onError} />;
|
||||
<img {...props} className={className} src={src} alt={alt} onError={onError} />;
|
||||
};
|
||||
|
||||
Image.propTypes = {
|
||||
|
|
|
|||
|
|
@ -35,12 +35,12 @@
|
|||
@media only screen and (max-width: @minimum) {
|
||||
.main-nav-bars-container {
|
||||
.nav-content-container {
|
||||
left: 0 !important;
|
||||
bottom: var(--vertical-nav-bar-size) !important;
|
||||
left: 0;
|
||||
bottom: var(--vertical-nav-bar-size);
|
||||
}
|
||||
|
||||
.vertical-nav-bar {
|
||||
top: initial !important;
|
||||
top: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -180,24 +180,10 @@
|
|||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
.meta-item-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75em;
|
||||
padding: 0.5em;
|
||||
|
||||
.poster-container {
|
||||
flex: auto;
|
||||
}
|
||||
padding: 0.5rem;
|
||||
|
||||
.title-bar-container {
|
||||
height: 2.5rem;
|
||||
flex: auto;
|
||||
align-items: flex-start;
|
||||
|
||||
.title-label {
|
||||
padding-left: 0;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -51,17 +51,24 @@
|
|||
@media only screen and (max-width: @minimum) {
|
||||
.action-button-container {
|
||||
flex-direction: row;
|
||||
padding: 0 1rem;
|
||||
|
||||
.icon-container {
|
||||
flex: none;
|
||||
align-self: center;
|
||||
flex: 0 0 auto;
|
||||
height: 2em;
|
||||
width: 2em;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
padding-top: 0;
|
||||
margin-right: 0.5rem;
|
||||
|
||||
&:only-child {
|
||||
padding: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.label-container {
|
||||
flex: 0 0 auto;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -116,7 +116,7 @@ const MetaPreview = ({ className, compact, name, logo, background, runtime, rele
|
|||
renderFallback={renderLogoFallback}
|
||||
/>
|
||||
:
|
||||
null
|
||||
renderLogoFallback()
|
||||
}
|
||||
{
|
||||
(typeof releaseInfo === 'string' && releaseInfo.length > 0) || (released instanceof Date && !isNaN(released.getTime())) || (typeof runtime === 'string' && runtime.length > 0) || linksGroups.has(CONSTANTS.IMDB_LINK_CATEGORY) ?
|
||||
|
|
|
|||
|
|
@ -210,9 +210,7 @@
|
|||
}
|
||||
|
||||
.action-buttons-container {
|
||||
max-height: 9.5rem;
|
||||
flex-wrap: nowrap;
|
||||
gap: 1em;
|
||||
padding: 0 1.5rem;
|
||||
overflow-x: visible;
|
||||
scrollbar-width: none;
|
||||
|
|
@ -222,18 +220,19 @@
|
|||
}
|
||||
|
||||
.action-button {
|
||||
height: 4rem;
|
||||
width: auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.75em;
|
||||
padding: 0 1.5em;
|
||||
height: 4rem;
|
||||
max-width: 60%;
|
||||
margin: 1rem 0;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 0;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.share-prompt {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ const MetaRow = ({ className, title, message, items, itemComponent, deepLinks })
|
|||
}
|
||||
{
|
||||
deepLinks && (typeof deepLinks.discover === 'string' || typeof deepLinks.library === 'string') ?
|
||||
<Button className={styles['see-all-container']} title={'SEE ALL'} href={deepLinks.discover || deepLinks.library}>
|
||||
<Button className={styles['see-all-container']} title={'SEE ALL'} href={deepLinks.discover || deepLinks.library} tabIndex={-1}>
|
||||
<div className={styles['label']}>SEE ALL</div>
|
||||
<Icon className={styles['icon']} icon={'ic_arrow_thin_right'} />
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ const MetaRowPlaceholder = ({ className, title, deepLinks }) => {
|
|||
</div>
|
||||
{
|
||||
deepLinks && typeof deepLinks.discover === 'string' ?
|
||||
<Button className={styles['see-all-container']} title={'SEE ALL'} href={deepLinks.discover}>
|
||||
<Button className={styles['see-all-container']} title={'SEE ALL'} href={deepLinks.discover} tabIndex={-1}>
|
||||
<div className={styles['label']}>SEE ALL</div>
|
||||
<Icon className={styles['icon']} icon={'ic_arrow_thin_right'} />
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -93,7 +93,15 @@
|
|||
.meta-row-placeholder-container {
|
||||
.meta-items-container {
|
||||
.meta-item {
|
||||
margin: 0;
|
||||
margin: 0.5rem;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.title-bar-container {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,8 +76,6 @@
|
|||
overflow: visible;
|
||||
|
||||
.meta-item {
|
||||
padding: 1rem;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
|
@ -100,10 +98,10 @@
|
|||
@media only screen and (max-width: @minimum) {
|
||||
.meta-row-container {
|
||||
.header-container {
|
||||
gap: 0.5em;
|
||||
padding: 0 0.5em;
|
||||
padding: 0 0.5rem;
|
||||
|
||||
.title-container {
|
||||
margin-right: 0.5rem;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
|
@ -111,8 +109,6 @@
|
|||
|
||||
.meta-items-container {
|
||||
.meta-item {
|
||||
padding: 0.5em;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,33 +123,38 @@
|
|||
@media only screen and (max-width: @minimum) {
|
||||
.modal-container {
|
||||
.modal-dialog-container {
|
||||
position: relative;
|
||||
width: 90%;
|
||||
max-width: initial;
|
||||
gap: 0.5em;
|
||||
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: initial;
|
||||
margin: 0;
|
||||
padding: 1em 1.5em;
|
||||
max-height: 4.8em;
|
||||
margin: 1rem 3rem 1rem 1.5rem;
|
||||
}
|
||||
|
||||
.modal-dialog-content {
|
||||
margin: 0;
|
||||
padding: 0 1.5rem;
|
||||
margin: 0 0.5rem;
|
||||
padding: 0 0.5rem;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.buttons-container {
|
||||
margin: 0;
|
||||
padding: 1.5em;
|
||||
margin: 1rem 1rem 0;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,13 +99,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
.modal-container, .popup-menu-container {
|
||||
.menu-container {
|
||||
max-height: 19em;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ const HorizontalNavBar = React.memo(({ className, route, query, title, backButto
|
|||
}, []);
|
||||
const [fullscreen, requestFullscreen, exitFullscreen] = useFullscreen();
|
||||
const renderNavMenuLabel = React.useCallback(({ ref, className, onClick, children, }) => (
|
||||
<Button ref={ref} className={classnames(className, styles['button-container'])} tabIndex={-1} onClick={onClick}>
|
||||
<Button ref={ref} className={classnames(className, styles['button-container'], styles['menu-button-container'])} tabIndex={-1} onClick={onClick}>
|
||||
<Icon className={styles['icon']} icon={'ic_more'} />
|
||||
{children}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -105,6 +105,6 @@
|
|||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
.nav-menu-container {
|
||||
max-height: calc(100vh - var(--horizontal-nav-bar-size) - var(--vertical-nav-bar-size) - 1em);
|
||||
max-height: calc(100vh - var(--horizontal-nav-bar-size) - var(--vertical-nav-bar-size) - 1rem);
|
||||
}
|
||||
}
|
||||
|
|
@ -96,21 +96,22 @@
|
|||
@media only screen and (max-width: @minimum) {
|
||||
.horizontal-nav-bar-container {
|
||||
justify-content: space-between;
|
||||
gap: 0.5em;
|
||||
padding-right: 0;
|
||||
|
||||
.logo-container {
|
||||
width: 4em;
|
||||
width: var(--horizontal-nav-bar-size);
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
|
||||
.spacing {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
&:not(:last-child):not(.back-button-container) {
|
||||
display: none;
|
||||
}
|
||||
.button-container:not(.back-button-container):not(.menu-button-container) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -32,16 +32,20 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
height: var(--vertical-nav-bar-size) !important;
|
||||
width: 100% !important;
|
||||
overflow: hidden !important;
|
||||
overflow-x: auto !important;
|
||||
height: var(--vertical-nav-bar-size);
|
||||
width: 100%;
|
||||
overflow-y: hidden;
|
||||
overflow-x: auto;
|
||||
|
||||
.nav-tab-button {
|
||||
flex: none;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0 !important;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ const Popup = ({ open, direction, renderLabel, renderMenu, dataset, onCloseReque
|
|||
ref: labelRef,
|
||||
className: classnames(styles['label-container'], props.className, { 'active': open }),
|
||||
children: open ?
|
||||
<FocusLock ref={menuRef} className={classnames(styles['menu-container'], styles[`menu-direction-${autoDirection}`], styles[`menu-direction-${direction}`])} autoFocus={false} lockProps={{ onMouseDown: menuOnMouseDown }}>
|
||||
<FocusLock ref={menuRef} className={classnames(styles['menu-container'], { [styles[`menu-direction-${autoDirection}`]]: !direction }, { [styles[`menu-direction-${direction}`]]: direction })} autoFocus={false} lockProps={{ onMouseDown: menuOnMouseDown }}>
|
||||
{renderMenu()}
|
||||
</FocusLock>
|
||||
:
|
||||
|
|
|
|||
|
|
@ -42,10 +42,10 @@ const StreamingServerWarning = ({ className }) => {
|
|||
return (
|
||||
<div className={classnames(className, styles['warning-container'])}>
|
||||
<div className={styles['warning-statement']}>Streaming server is not available.</div>
|
||||
<Button className={styles['warning-button']} title={'Later'} onClick={onLaterClick}>
|
||||
<Button className={styles['warning-button']} title={'Later'} onClick={onLaterClick} tabIndex={-1}>
|
||||
<div className={styles['warning-label']}>Later</div>
|
||||
</Button>
|
||||
<Button className={styles['warning-button']} title={'Dismiss'} onClick={onDismissClick}>
|
||||
<Button className={styles['warning-button']} title={'Dismiss'} onClick={onDismissClick} tabIndex={-1}>
|
||||
<div className={styles['warning-label']}>Dismiss</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
// Copyright (C) 2017-2022 Smart code 203358507
|
||||
|
||||
const isChildVisible = (container, element, threshold) => {
|
||||
const isChildVisible = (container, element) => {
|
||||
const elementTop = element.offsetTop;
|
||||
const elementBottom = element.offsetTop + element.clientHeight;
|
||||
const containerTop = container.scrollTop - threshold;
|
||||
const containerBottom = container.scrollTop + container.clientHeight + threshold;
|
||||
const containerTop = container.scrollTop;
|
||||
const containerBottom = container.scrollTop + container.clientHeight;
|
||||
return (elementTop >= containerTop && elementBottom <= containerBottom) ||
|
||||
(elementTop < containerTop && containerTop < elementBottom) ||
|
||||
(elementTop < containerBottom && containerBottom < elementBottom);
|
||||
};
|
||||
|
||||
const getVisibleChildrenRange = (container, threshold) => {
|
||||
const getVisibleChildrenRange = (container) => {
|
||||
return Array.from(container.children).reduce((result, child, index) => {
|
||||
if (isChildVisible(container, child, threshold)) {
|
||||
if (isChildVisible(container, child)) {
|
||||
if (result === null) {
|
||||
result = {
|
||||
start: index,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,12 @@ if (typeof process.env.SENTRY_DSN === 'string') {
|
|||
Sentry.init({ dsn: process.env.SENTRY_DSN });
|
||||
}
|
||||
|
||||
const Bowser = require('bowser');
|
||||
const browser = Bowser.parse(window.navigator?.userAgent || '');
|
||||
if (browser?.platform?.type === 'desktop') {
|
||||
document.querySelector('meta[name="viewport"]')?.setAttribute('content', '');
|
||||
}
|
||||
|
||||
const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
const App = require('./App');
|
||||
|
|
|
|||
|
|
@ -171,22 +171,21 @@
|
|||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
.addon-container {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2em;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.info-container {
|
||||
flex-basis: auto;
|
||||
margin-left: 0.5rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.buttons-container {
|
||||
flex: 0 1 100%;
|
||||
width: auto;
|
||||
margin-top: 1rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1.5em;
|
||||
width: 100%;
|
||||
|
||||
.install-button-container, .uninstall-button-container, .share-button-container {
|
||||
&:not(:first-child) {
|
||||
|
|
@ -206,6 +205,7 @@
|
|||
|
||||
.install-button-container, .uninstall-button-container {
|
||||
flex-basis: 100%;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,15 +81,13 @@ const Addons = ({ urlParams, queryParams }) => {
|
|||
<Icon className={styles['icon']} icon={'ic_plus'} />
|
||||
<div className={styles['add-button-label']}>Add addon</div>
|
||||
</Button>
|
||||
<div className={styles['multiselect-inputs-container']}>
|
||||
{selectInputs.map((selectInput, index) => (
|
||||
<Multiselect
|
||||
{...selectInput}
|
||||
key={index}
|
||||
className={styles['select-input-container']}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{selectInputs.map((selectInput, index) => (
|
||||
<Multiselect
|
||||
{...selectInput}
|
||||
key={index}
|
||||
className={styles['select-input-container']}
|
||||
/>
|
||||
))}
|
||||
<div className={styles['spacing']} />
|
||||
<SearchBar
|
||||
className={styles['search-bar']}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
multiselect-menu-container: menu-container;
|
||||
}
|
||||
|
||||
:import('~stremio/common/ModalDialog/styles.less') {
|
||||
share-modal-content: modal-dialog-content;
|
||||
}
|
||||
|
||||
:import('~stremio/common/ModalDialog/styles.less') {
|
||||
addon-modal-content: modal-dialog-content;
|
||||
cancel-button-label: label;
|
||||
|
|
@ -76,23 +80,16 @@
|
|||
}
|
||||
}
|
||||
|
||||
.multiselect-inputs-container {
|
||||
flex: auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: visible;
|
||||
.select-input-container {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
flex-basis: 15rem;
|
||||
height: 3.5rem;
|
||||
margin-right: 1.5rem;
|
||||
|
||||
.select-input-container {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
flex-basis: 15rem;
|
||||
height: 3.5rem;
|
||||
margin-right: 1.5rem;
|
||||
|
||||
.multiselect-menu-container {
|
||||
max-height: calc(3.2rem * 7);
|
||||
overflow: auto;
|
||||
}
|
||||
.multiselect-menu-container {
|
||||
max-height: calc(3.2rem * 7);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -151,13 +148,15 @@
|
|||
|
||||
.filters-modal-content {
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
flex-direction: column;
|
||||
overflow-y: visible !important;
|
||||
overflow: visible;
|
||||
|
||||
.select-input-container {
|
||||
height: 3.5em;
|
||||
height: 3.5rem;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -208,7 +207,7 @@
|
|||
}
|
||||
|
||||
.share-modal-container {
|
||||
.addon-modal-content {
|
||||
.share-modal-content {
|
||||
width: 30rem;
|
||||
|
||||
.title-container {
|
||||
|
|
@ -271,21 +270,15 @@
|
|||
.addons-container {
|
||||
.addons-content {
|
||||
.selectable-inputs-container {
|
||||
gap: 1em;
|
||||
|
||||
.add-button-container {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: calc(3em + var(--horizontal-nav-bar-size));
|
||||
bottom: calc(3rem + var(--horizontal-nav-bar-size));
|
||||
box-shadow: 0 1.35rem 2.7rem @color-background-dark5-40,
|
||||
0 1.1rem 0.85rem @color-background-dark5-20;
|
||||
}
|
||||
|
||||
.select-input-container {
|
||||
flex: 0 1 3.5em;
|
||||
}
|
||||
|
||||
.multiselect-inputs-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
@ -295,6 +288,7 @@
|
|||
|
||||
.search-bar {
|
||||
flex-basis: 100%;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.filter-button {
|
||||
|
|
@ -304,7 +298,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
.add-addon-modal-container, .share-modal-container {
|
||||
.share-modal-container {
|
||||
.share-modal-content {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.add-addon-modal-container {
|
||||
.addon-modal-content {
|
||||
width: auto;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const useBoard = require('./useBoard');
|
|||
const useContinueWatchingPreview = require('./useContinueWatchingPreview');
|
||||
const styles = require('./styles');
|
||||
|
||||
const THRESHOLD = 300;
|
||||
const THRESHOLD = 5;
|
||||
|
||||
const Board = () => {
|
||||
const profile = useProfile();
|
||||
|
|
@ -18,13 +18,13 @@ const Board = () => {
|
|||
const boardCatalogsOffset = continueWatchingPreview.libraryItems.length > 0 ? 1 : 0;
|
||||
const scrollContainerRef = React.useRef();
|
||||
const onVisibleRangeChange = React.useCallback(() => {
|
||||
const range = getVisibleChildrenRange(scrollContainerRef.current, THRESHOLD);
|
||||
const range = getVisibleChildrenRange(scrollContainerRef.current);
|
||||
if (range === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const start = Math.max(0, range.start - boardCatalogsOffset);
|
||||
const end = range.end - boardCatalogsOffset;
|
||||
const start = Math.max(0, range.start - boardCatalogsOffset - THRESHOLD);
|
||||
const end = range.end - boardCatalogsOffset + THRESHOLD;
|
||||
if (end < start) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
overflow-y: auto;
|
||||
|
||||
.board-row {
|
||||
padding: 2rem;
|
||||
margin: 4rem 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -185,28 +185,47 @@
|
|||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
.board-container {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
|
||||
.board-content-container {
|
||||
&:only-child {
|
||||
.board-content {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.board-content {
|
||||
padding: 1em 0;
|
||||
height: calc(100% - 4rem);
|
||||
|
||||
.board-row {
|
||||
padding: 1em;
|
||||
margin: 2rem 1rem;
|
||||
}
|
||||
|
||||
.board-row-poster, .board-row-square {
|
||||
.board-row-poster, .board-row-square, .continue-watching-row {
|
||||
.meta-item, .meta-item-placeholder {
|
||||
&:nth-child(n+4) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.board-row-landscape {
|
||||
.meta-item, .meta-item-placeholder {
|
||||
&:nth-child(n+3) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.board-warning-container {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: var(--vertical-nav-bar-size);
|
||||
height: 4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -80,19 +80,17 @@ const Discover = ({ urlParams, queryParams }) => {
|
|||
{
|
||||
discover.defaultRequest ?
|
||||
<div className={styles['selectable-inputs-container']}>
|
||||
<div className={styles['multiselect-inputs-container']}>
|
||||
{selectInputs.map(({ title, options, selected, renderLabelText, onSelect }, index) => (
|
||||
<Multiselect
|
||||
key={index}
|
||||
className={styles['select-input']}
|
||||
title={title}
|
||||
options={options}
|
||||
selected={selected}
|
||||
renderLabelText={renderLabelText}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{selectInputs.map(({ title, options, selected, renderLabelText, onSelect }, index) => (
|
||||
<Multiselect
|
||||
key={index}
|
||||
className={styles['select-input']}
|
||||
title={title}
|
||||
options={options}
|
||||
selected={selected}
|
||||
renderLabelText={renderLabelText}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
))}
|
||||
<Button className={styles['filter-container']} title={'All filters'} onClick={openInputsModal}>
|
||||
<Icon className={styles['filter-icon']} icon={'ic_filter'} />
|
||||
</Button>
|
||||
|
|
@ -188,19 +186,17 @@ const Discover = ({ urlParams, queryParams }) => {
|
|||
{
|
||||
inputsModalOpen && discover.defaultRequest ?
|
||||
<ModalDialog title={'Catalog filters'} className={styles['selectable-inputs-modal']} onCloseRequest={closeInputsModal}>
|
||||
<div className={styles['selectable-input-container']}>
|
||||
{selectInputs.map(({ title, options, selected, renderLabelText, onSelect }, index) => (
|
||||
<Multiselect
|
||||
key={index}
|
||||
className={styles['select-input']}
|
||||
title={title}
|
||||
options={options}
|
||||
selected={selected}
|
||||
renderLabelText={renderLabelText}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{selectInputs.map(({ title, options, selected, renderLabelText, onSelect }, index) => (
|
||||
<Multiselect
|
||||
key={index}
|
||||
className={styles['select-input']}
|
||||
title={title}
|
||||
options={options}
|
||||
selected={selected}
|
||||
renderLabelText={renderLabelText}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
))}
|
||||
</ModalDialog>
|
||||
:
|
||||
null
|
||||
|
|
|
|||
|
|
@ -48,32 +48,25 @@
|
|||
padding: 1.5rem;
|
||||
overflow: visible;
|
||||
|
||||
.multiselect-inputs-container {
|
||||
flex: auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: visible;
|
||||
.select-input {
|
||||
flex: 0 1 15rem;
|
||||
height: 3.5rem;
|
||||
|
||||
.select-input {
|
||||
flex: 0 1 15rem;
|
||||
height: 3.5rem;
|
||||
&:not(:first-child) {
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-left: 1.5rem;
|
||||
&:nth-child(n+4) {
|
||||
display: none;
|
||||
|
||||
&~.filter-container {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(n+4) {
|
||||
display: none;
|
||||
|
||||
&~.filter-container {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect-menu-container {
|
||||
max-height: calc(3.2rem * 7);
|
||||
overflow: auto;
|
||||
}
|
||||
.multiselect-menu-container {
|
||||
max-height: calc(3.2rem * 7);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -84,6 +77,7 @@
|
|||
justify-content: center;
|
||||
width: 3.5rem;
|
||||
height: 3.5rem;
|
||||
margin-left: 1.5rem;
|
||||
background-color: @color-background;
|
||||
|
||||
.filter-icon {
|
||||
|
|
@ -131,10 +125,6 @@
|
|||
width: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.missing-addon-warning-container {
|
||||
|
|
@ -255,20 +245,21 @@
|
|||
overflow: visible;
|
||||
|
||||
.selectable-inputs-modal-content {
|
||||
overflow-y: visible !important;
|
||||
overflow: visible;
|
||||
|
||||
.selectable-input-container {
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
flex-direction: column;
|
||||
overflow: visible;
|
||||
.select-input {
|
||||
height: 3.5rem;
|
||||
|
||||
.select-input {
|
||||
height: 3.5em;
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.multiselect-menu-container {
|
||||
max-height: calc(3.2rem * 4);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -438,17 +429,8 @@
|
|||
.selectable-inputs-container {
|
||||
flex-direction: row-reverse;
|
||||
justify-content: space-between;
|
||||
gap: 1em;
|
||||
|
||||
.select-input {
|
||||
flex: 0 1 3.5em;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect-inputs-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
.intro-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background:
|
||||
|
|
@ -239,12 +240,10 @@
|
|||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
.intro-container {
|
||||
justify-content: center;
|
||||
|
||||
.form-container {
|
||||
flex: 0 1 auto;
|
||||
width: 100%;
|
||||
padding: 2rem 1.5em;
|
||||
padding: 2rem 1.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -54,10 +54,8 @@ const Library = ({ model, urlParams, queryParams }) => {
|
|||
{
|
||||
model === 'continue_watching' || profile.auth !== null ?
|
||||
<div className={styles['selectable-inputs-container']}>
|
||||
<div className={styles['multiselect-inputs-container']}>
|
||||
<Multiselect {...typeSelect} className={styles['select-input-container']} />
|
||||
<Multiselect {...sortSelect} className={styles['select-input-container']} />
|
||||
</div>
|
||||
<Multiselect {...typeSelect} className={styles['select-input-container']} />
|
||||
<Multiselect {...sortSelect} className={styles['select-input-container']} />
|
||||
<div className={styles['spacing']} />
|
||||
{
|
||||
paginationInput !== null ?
|
||||
|
|
@ -116,10 +114,8 @@ const Library = ({ model, urlParams, queryParams }) => {
|
|||
{
|
||||
inputsModalOpen ?
|
||||
<ModalDialog title={'Library filters'} className={styles['selectable-inputs-modal']} onCloseRequest={closeInputsModal}>
|
||||
<div className={styles['selectable-input-container']}>
|
||||
<Multiselect {...typeSelect} className={styles['select-input-container']} />
|
||||
<Multiselect {...sortSelect} className={styles['select-input-container']} />
|
||||
</div>
|
||||
<Multiselect {...typeSelect} className={styles['select-input-container']} />
|
||||
<Multiselect {...sortSelect} className={styles['select-input-container']} />
|
||||
</ModalDialog>
|
||||
:
|
||||
null
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@
|
|||
selectable-inputs-modal-content: modal-dialog-content;
|
||||
}
|
||||
|
||||
|
||||
.library-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
|
@ -39,26 +38,19 @@
|
|||
padding: 1.5rem;
|
||||
overflow: visible;
|
||||
|
||||
.multiselect-inputs-container {
|
||||
flex: auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: visible;
|
||||
.select-input-container {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
flex-basis: 15rem;
|
||||
height: 3.5rem;
|
||||
|
||||
.select-input-container {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
flex-basis: 15rem;
|
||||
height: 3.5rem;
|
||||
&:not(:last-child) {
|
||||
margin-right: 1.5rem;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 1.5rem;
|
||||
}
|
||||
|
||||
.multiselect-menu-container {
|
||||
max-height: calc(3.2rem * 7);
|
||||
overflow: auto;
|
||||
}
|
||||
.multiselect-menu-container {
|
||||
max-height: calc(3.2rem * 7);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -116,10 +108,6 @@
|
|||
width: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.message-container {
|
||||
|
|
@ -201,17 +189,13 @@
|
|||
overflow: visible;
|
||||
|
||||
.selectable-inputs-modal-content {
|
||||
overflow-y: visible !important;
|
||||
overflow: visible;
|
||||
|
||||
.selectable-input-container {
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
flex-direction: column;
|
||||
overflow: visible;
|
||||
.select-input-container {
|
||||
height: 3.5rem;
|
||||
|
||||
.select-input-container {
|
||||
height: 3.5em;
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -293,24 +277,11 @@
|
|||
.library-content {
|
||||
.selectable-inputs-container {
|
||||
justify-content: space-between;
|
||||
gap: 1em;
|
||||
|
||||
.select-input-container {
|
||||
flex-basis: 3.5em;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect-inputs-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.spacing {
|
||||
display: none;
|
||||
}
|
||||
|
|
@ -318,6 +289,10 @@
|
|||
.pagination-input {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.meta-items-container {
|
||||
|
|
|
|||
|
|
@ -78,6 +78,8 @@
|
|||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
.streams-list-container {
|
||||
overflow: visible;
|
||||
|
||||
.streams-container {
|
||||
margin-top: 0;
|
||||
scrollbar-color: @color-surface-light5-20 transparent;
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ const SeasonsBar = ({ className, seasons, season, onSelect }) => {
|
|||
<Multiselect
|
||||
className={styles['seasons-popup-label-container']}
|
||||
title={season > 0 ? `Season ${season}` : 'Specials'}
|
||||
direction={'bottom-left'}
|
||||
options={options}
|
||||
selected={selected}
|
||||
onSelect={seasonOnSelect}
|
||||
|
|
|
|||
|
|
@ -86,5 +86,11 @@
|
|||
@media only screen and (max-width: @minimum) {
|
||||
.seasons-bar-container {
|
||||
padding-top: 0;
|
||||
|
||||
.seasons-popup-label-container {
|
||||
.multiselect-menu-container {
|
||||
max-height: calc(3.2rem * 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,15 +14,27 @@ const Video = ({ className, id, title, thumbnail, episode, released, upcoming, w
|
|||
const { core } = useServices();
|
||||
const routeFocused = useRouteFocused();
|
||||
const [menuOpen, , closeMenu, toggleMenu] = useBinaryState(false);
|
||||
const popupLabelOnClick = React.useCallback((event) => {
|
||||
if (!event.nativeEvent.togglePopupPrevented && event.nativeEvent.ctrlKey) {
|
||||
event.preventDefault();
|
||||
toggleMenu();
|
||||
}
|
||||
}, []);
|
||||
const popupLabelOnKeyDown = React.useCallback((event) => {
|
||||
event.nativeEvent.buttonClickPrevented = true;
|
||||
}, []);
|
||||
const popupLabelOnContextMenu = React.useCallback((event) => {
|
||||
if (!event.nativeEvent.togglePopupPrevented && !event.nativeEvent.ctrlKey) {
|
||||
event.preventDefault();
|
||||
toggleMenu();
|
||||
event.nativeEvent.preventDefault();
|
||||
}
|
||||
}, [toggleMenu]);
|
||||
const popupMenuOnContextMenu = React.useCallback((event) => {
|
||||
event.nativeEvent.togglePopupPrevented = true;
|
||||
}, []);
|
||||
const popupMenuOnClick = React.useCallback((event) => {
|
||||
event.nativeEvent.togglePopupPrevented = true;
|
||||
}, []);
|
||||
const toggleWatchedOnClick = React.useCallback((event) => {
|
||||
event.preventDefault();
|
||||
closeMenu();
|
||||
|
|
@ -120,7 +132,7 @@ const Video = ({ className, id, title, thumbnail, episode, released, upcoming, w
|
|||
}, []);
|
||||
const renderMenu = React.useMemo(() => function renderMenu() {
|
||||
return (
|
||||
<div className={styles['context-menu-content']} onContextMenu={popupMenuOnContextMenu}>
|
||||
<div className={styles['context-menu-content']} onContextMenu={popupMenuOnContextMenu} onClick={popupMenuOnClick}>
|
||||
<Button className={styles['context-menu-option-container']} title={'Watch'}>
|
||||
<div className={styles['context-menu-option-label']}>Watch</div>
|
||||
</Button>
|
||||
|
|
@ -149,6 +161,8 @@ const Video = ({ className, id, title, thumbnail, episode, released, upcoming, w
|
|||
scheduled={scheduled}
|
||||
href={href}
|
||||
{...props}
|
||||
onClick={popupLabelOnClick}
|
||||
onKeyDown={popupLabelOnKeyDown}
|
||||
onContextMenu={popupLabelOnContextMenu}
|
||||
open={menuOpen}
|
||||
onCloseRequest={closeMenu}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@
|
|||
.released-container {
|
||||
flex: 1;
|
||||
margin-right: 0.5rem;
|
||||
padding: 0.2rem 0;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
|
|
@ -128,53 +129,54 @@
|
|||
background-color: @color-primaryvariant1;
|
||||
}
|
||||
}
|
||||
|
||||
.context-menu-container {
|
||||
max-width: calc(90% - 1.5rem);
|
||||
|
||||
&.menu-direction-top-left, &.menu-direction-bottom-left {
|
||||
right: 1.5rem;
|
||||
}
|
||||
|
||||
&.menu-direction-top-right, &.menu-direction-bottom-right {
|
||||
left: 1.5rem;
|
||||
}
|
||||
|
||||
&.menu-direction-top-left, &.menu-direction-top-right {
|
||||
bottom: 90%;
|
||||
}
|
||||
|
||||
&.menu-direction-bottom-left, &.menu-direction-bottom-right {
|
||||
top: 90%;
|
||||
}
|
||||
|
||||
.context-menu-content {
|
||||
background-color: @color-background-dark1;
|
||||
|
||||
.context-menu-option-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0.5rem 1rem;
|
||||
|
||||
&:hover, &:focus {
|
||||
background-color: @color-background;
|
||||
}
|
||||
|
||||
.context-menu-option-label {
|
||||
font-size: 1rem;
|
||||
max-height: 2.4em;
|
||||
color: @color-surface-light5-90;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
.video-container {
|
||||
&:hover, &:focus {
|
||||
&:hover, &:focus, &:global(.active) {
|
||||
background-color: @color-surface-light5-20;
|
||||
}
|
||||
|
||||
.context-menu-container {
|
||||
max-width: calc(90% - 1.5rem);
|
||||
|
||||
&.menu-direction-top-left, &.menu-direction-bottom-left {
|
||||
right: 1.5rem;
|
||||
}
|
||||
|
||||
&.menu-direction-top-right, &.menu-direction-bottom-right {
|
||||
left: 1.5rem;
|
||||
}
|
||||
|
||||
&.menu-direction-top-left, &.menu-direction-top-right {
|
||||
bottom: 90%;
|
||||
}
|
||||
|
||||
&.menu-direction-bottom-left, &.menu-direction-bottom-right {
|
||||
top: 90%;
|
||||
}
|
||||
|
||||
.context-menu-content {
|
||||
--spatial-navigation-contain: contain;
|
||||
background-color: @color-background-dark1;
|
||||
|
||||
.context-menu-option-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0.5rem 1rem;
|
||||
|
||||
&:hover, &:focus {
|
||||
background-color: @color-background;
|
||||
}
|
||||
|
||||
.context-menu-option-label {
|
||||
font-size: 1rem;
|
||||
max-height: 2.4em;
|
||||
color: @color-surface-light5-90;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (C) 2017-2022 Smart code 203358507
|
||||
|
||||
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
|
||||
@import (reference) '~stremio/common/screen-sizes.less';
|
||||
|
||||
.videos-list-container {
|
||||
display: flex;
|
||||
|
|
@ -54,4 +55,10 @@
|
|||
align-self: stretch;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
.videos-list-container {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
|
@ -129,12 +129,24 @@
|
|||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
.metadetails-container {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
|
||||
.metadetails-content {
|
||||
flex-direction: column;
|
||||
display: block;
|
||||
position: static;
|
||||
z-index: initial;
|
||||
overflow-y: auto;
|
||||
|
||||
.spacing {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.background-image-layer {
|
||||
top: var(--horizontal-nav-bar-size);
|
||||
}
|
||||
|
||||
.videos-list, .streams-list {
|
||||
flex-basis: auto;
|
||||
max-height: 50vh;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ const ControlBar = ({
|
|||
}) => {
|
||||
const { chromecast } = useServices();
|
||||
const [chromecastServiceActive, setChromecastServiceActive] = React.useState(() => chromecast.active);
|
||||
const [buttonsMenuOpen,,, toogleButtonsMenu] = useBinaryState(false);
|
||||
const [buttonsMenuOpen, , , toogleButtonsMenu] = useBinaryState(false);
|
||||
const onSubtitlesButtonMouseDown = React.useCallback((event) => {
|
||||
event.nativeEvent.subtitlesMenuClosePrevented = true;
|
||||
}, []);
|
||||
|
|
@ -115,7 +115,7 @@ const ControlBar = ({
|
|||
/>
|
||||
<div className={styles['spacing']} />
|
||||
<Button className={styles['control-bar-buttons-menu-button']} onClick={toogleButtonsMenu}>
|
||||
<Icon className={styles['icon']} icon="ic_more"/>
|
||||
<Icon className={styles['icon']} icon={'ic_more'} />
|
||||
</Button>
|
||||
<div className={classnames(styles['control-bar-buttons-menu-container'], { 'open': buttonsMenuOpen })}>
|
||||
<Button className={classnames(styles['control-bar-button'], 'disabled')} tabIndex={-1}>
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@
|
|||
}
|
||||
|
||||
.control-bar-buttons-menu-container {
|
||||
flex: none;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
|
@ -82,12 +83,13 @@
|
|||
padding: 0;
|
||||
|
||||
.seek-bar {
|
||||
padding: 0 1.5em;
|
||||
margin: 0 1.5rem;
|
||||
}
|
||||
|
||||
.control-bar-buttons-container {
|
||||
gap: 0.5em;
|
||||
padding: 0 0.5em;
|
||||
position: relative;
|
||||
padding: 0 0.5rem;
|
||||
overflow: visible;
|
||||
|
||||
.control-bar-buttons-menu-button {
|
||||
display: flex;
|
||||
|
|
@ -95,10 +97,10 @@
|
|||
|
||||
.control-bar-buttons-menu-container {
|
||||
position: absolute;
|
||||
right: 0.15em;
|
||||
right: 0.15rem;
|
||||
bottom: 4.5rem;
|
||||
flex-direction: column;
|
||||
padding: 0.5em;
|
||||
padding: 0.5rem;
|
||||
background-color: @color-background-dark1;
|
||||
box-shadow: 0 1.35rem 2.7rem @color-background-dark5-40,
|
||||
0 1.1rem 0.85rem @color-background-dark5-20;
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ const Video = React.forwardRef(({ className, ...props }, ref) => {
|
|||
});
|
||||
}
|
||||
return () => {
|
||||
dispatch({ type: 'command', commandName: 'destroy' });
|
||||
videoRef.current.destroy();
|
||||
};
|
||||
}, []);
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
overflow-y: auto;
|
||||
|
||||
.search-row {
|
||||
padding: 2rem;
|
||||
margin: 4rem 2rem;
|
||||
}
|
||||
|
||||
.search-hints-container {
|
||||
|
|
@ -236,7 +236,7 @@
|
|||
}
|
||||
|
||||
.search-hints-container {
|
||||
padding: 4rem 2em;
|
||||
padding: 4rem 2rem;
|
||||
|
||||
.search-hint-container {
|
||||
padding: 0 1.5rem;
|
||||
|
|
|
|||
|
|
@ -367,7 +367,7 @@
|
|||
|
||||
.side-menu-container {
|
||||
width: 100%;
|
||||
padding: 0 1em;
|
||||
padding: 0 1rem;
|
||||
|
||||
.side-menu-button {
|
||||
display: none;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ function Chromecast() {
|
|||
starting = false;
|
||||
onStateChanged();
|
||||
}
|
||||
function onTransportError(args) {
|
||||
function onTransportInitError(args) {
|
||||
console.error(args);
|
||||
active = false;
|
||||
error = new Error('Google Cast API not available');
|
||||
|
|
@ -68,7 +68,7 @@ function Chromecast() {
|
|||
starting = true;
|
||||
transport = new ChromecastTransport();
|
||||
transport.on('init', onTransportInit);
|
||||
transport.on('error', onTransportError);
|
||||
transport.on('init-error', onTransportInitError);
|
||||
onStateChanged();
|
||||
};
|
||||
this.stop = function() {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
const EventEmitter = require('eventemitter3');
|
||||
|
||||
const MESSAGE_NAMESPACE = 'urn:x-cast:com.stremio';
|
||||
const CHUNK_SIZE = 20000;
|
||||
|
||||
let castAPIAvailable = null;
|
||||
const castAPIEvents = new EventEmitter();
|
||||
|
|
@ -32,6 +33,7 @@ const initialize = () => {
|
|||
|
||||
function ChromecastTransport() {
|
||||
const events = new EventEmitter();
|
||||
const chunks = [];
|
||||
|
||||
initialize()
|
||||
.then(() => {
|
||||
|
|
@ -52,11 +54,31 @@ function ChromecastTransport() {
|
|||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
events.emit('error', error);
|
||||
events.emit('init-error', error);
|
||||
});
|
||||
|
||||
function onMessage(_, message) {
|
||||
events.emit('message', JSON.parse(message));
|
||||
try {
|
||||
const { chunk, last } = JSON.parse(message);
|
||||
chunks.push(chunk);
|
||||
if (!last) {
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
chunks.splice(0, chunks.length);
|
||||
events.emit('message-error', error);
|
||||
return;
|
||||
}
|
||||
|
||||
let parsedMessage;
|
||||
try {
|
||||
parsedMessage = JSON.parse(chunks.splice(0, chunks.length).join(''));
|
||||
} catch (error) {
|
||||
events.emit('message-error', error);
|
||||
return;
|
||||
}
|
||||
|
||||
events.emit('message', parsedMessage);
|
||||
}
|
||||
function onApplicationStatusChanged(event) {
|
||||
events.emit(cast.framework.CastSession.APPLICATION_STATUS_CHANGED, event);
|
||||
|
|
@ -135,7 +157,21 @@ function ChromecastTransport() {
|
|||
this.sendMessage = function(message) {
|
||||
const castSession = cast.framework.CastContext.getInstance().getCurrentSession();
|
||||
if (castSession !== null) {
|
||||
return castSession.sendMessage(MESSAGE_NAMESPACE, message);
|
||||
const serializedMessage = JSON.stringify(message);
|
||||
const chunksCount = Math.ceil(serializedMessage.length / CHUNK_SIZE);
|
||||
const chunks = [];
|
||||
for (let i = 0; i < chunksCount; i++) {
|
||||
const start = i * CHUNK_SIZE;
|
||||
const chunk = serializedMessage.slice(start, start + CHUNK_SIZE);
|
||||
chunks.push(chunk);
|
||||
}
|
||||
|
||||
return Promise.all(chunks.map((chunk, index) => {
|
||||
return castSession.sendMessage(MESSAGE_NAMESPACE, {
|
||||
chunk,
|
||||
last: index === chunks.length - 1,
|
||||
});
|
||||
}));
|
||||
} else {
|
||||
return Promise.reject(new Error('Session not started'));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue