Merge branch 'refactor/player-audio-menu' of https://github.com/Stremio/stremio-web into refactor/player-audio-menu

This commit is contained in:
Tim 2024-12-20 20:23:26 +01:00
commit caeacf1bc1
45 changed files with 378 additions and 144 deletions

13
package-lock.json generated
View file

@ -1,18 +1,18 @@
{
"name": "stremio",
"version": "5.0.0-beta.13",
"version": "5.0.0-beta.14",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "stremio",
"version": "5.0.0-beta.13",
"version": "5.0.0-beta.14",
"license": "gpl-2.0",
"dependencies": {
"@babel/runtime": "7.26.0",
"@sentry/browser": "8.42.0",
"@stremio/stremio-colors": "5.2.0",
"@stremio/stremio-core-web": "0.48.1",
"@stremio/stremio-core-web": "0.48.2",
"@stremio/stremio-icons": "5.4.0",
"@stremio/stremio-video": "0.0.46",
"a-color-picker": "1.2.1",
@ -3379,9 +3379,10 @@
"license": "MIT"
},
"node_modules/@stremio/stremio-core-web": {
"version": "0.48.1",
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.48.1.tgz",
"integrity": "sha512-bdWxBuuOOC0NdG1Mg60lEhpK7Bw/Ea6D89bRcvIvM3WnJrUpGA4jbx4xWj3KQRM08PM3WWCY9/FzctlWCxFMRg==",
"version": "0.48.2",
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.48.2.tgz",
"integrity": "sha512-zkz4ftGMoK9RmIDlGLd/DYLtaXuf4HxnMEN1NduKNXDlYPSJ4Q/b1hCbXrVqVK/nx6s+8X4XyYr9IOwFLaT5ew==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "7.24.1"
}

View file

@ -1,7 +1,7 @@
{
"name": "stremio",
"displayName": "Stremio",
"version": "5.0.0-beta.13",
"version": "5.0.0-beta.14",
"author": "Smart Code OOD",
"private": true,
"license": "gpl-2.0",
@ -16,7 +16,7 @@
"@babel/runtime": "7.26.0",
"@sentry/browser": "8.42.0",
"@stremio/stremio-colors": "5.2.0",
"@stremio/stremio-core-web": "0.48.1",
"@stremio/stremio-core-web": "0.48.2",
"@stremio/stremio-icons": "5.4.0",
"@stremio/stremio-video": "0.0.46",
"a-color-picker": "1.2.1",

View file

@ -38,7 +38,7 @@
--quaternary-accent-color: rgba(18, 69, 166, 1);
--overlay-color: rgba(255, 255, 255, 0.05);
--modal-background-color: rgba(15, 13, 32, 1);
--outer-glow: 0px 0px 30px rgba(123, 91, 245, 0.37);
--outer-glow: 0px 0px 15px rgba(123, 91, 245, 0.37);
--border-radius: 0.75rem;
}
@ -112,7 +112,7 @@ html {
left: auto;
z-index: 1;
padding: 0 calc(0.5 * var(--horizontal-nav-bar-size));
overflow-y: auto;
overflow: visible;
scrollbar-width: none;
pointer-events: none;

View file

@ -87,7 +87,13 @@
}
}
@media only screen and (min-width: @xsmall) {
@media only screen and (min-width: @small) and (orientation: portait) {
.bottom-sheet {
display: none;
}
}
@media only screen and (min-width: @xsmall) and (orientation: landscape) {
.bottom-sheet {
display: none;
}

View file

@ -6,6 +6,7 @@
outline-width: var(--focus-outline-size);
outline-color: @color-surface-light5;
outline-offset: calc(-1 * var(--focus-outline-size));
-webkit-tap-highlight-color: transparent;
cursor: pointer;
&:focus {

View file

@ -17,7 +17,6 @@
align-items: center;
justify-content: center;
padding: 0 0.5rem;
border: thin solid @color-surface-light5-20;
pointer-events: none;
.transparent-label {

View file

@ -19,26 +19,30 @@
flex-direction: column;
align-items: center;
overflow: visible;
position: relative;
.modal-dialog-content {
.body-container {
overflow-y: visible;
}
.image {
width: 100%;
height: 100%;
margin-top: -10rem;
position: absolute;
top: -10rem;
left: 50%;
transform: translateX(-50%);
object-fit: cover;
width: 30rem;
height: 30rem;
}
.info-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2.5rem;
padding: 1rem 4rem;
margin-top: -7rem;
padding: 10rem 4rem 0;
.title-container {
display: flex;
flex-direction: column;
@ -50,7 +54,7 @@
text-align: center;
padding: 0 6rem;
}
.label {
color: var(--primary-foreground-color);
font-size: 1rem;
@ -58,7 +62,7 @@
opacity: 0.5;
}
}
.addon-container {
display: flex;
align-items: center;
@ -76,19 +80,19 @@
color: var(--primary-foreground-color);
}
}
.action-button {
background-color: var(--primary-foreground-color);
border: 2px solid var(--primary-foreground-color);
padding: 0.8rem 2rem;
border-radius: 2rem;
.button-label {
color: var(--primary-accent-color);
font-size: 1rem;
font-weight: 700;
}
&:hover {
background-color: transparent;
}
@ -96,15 +100,34 @@
}
}
}
}
@media only screen and (max-width: @minimum) {
@media (orientation: landscape) and (max-height: @xsmall) {
.event-modal {
.modal-dialog-container {
.modal-dialog-content {
.image {
height: 125%;
width: 125%;
overflow-y: auto;
.body-container {
overflow-y: auto;
}
.image {
display: none;
}
.info-container {
padding: 1rem 4rem 0;
}
}
}
}
}
@media only screen and (max-width: @minimum) {
.event-modal {
.modal-dialog-container {
.modal-dialog-content {
.info-container {
.title-container {
.title {
@ -120,4 +143,4 @@
}
}
}
}
}

View file

@ -24,7 +24,6 @@ const MainNavBars = React.memo(({ className, route, query, children }) => {
query={query}
backButton={false}
searchBar={true}
addonsButton={true}
fullscreenButton={true}
navMenu={true}
/>

View file

@ -82,7 +82,7 @@
.body-container {
flex: 1;
align-self: stretch;
overflow-y: auto;
overflow: visible;
padding: 2rem 0;
&:last-child {

View file

@ -7,6 +7,8 @@
popup-menu-container: menu-container;
}
@parent-height: 10rem;
.label-container {
display: flex;
flex-direction: row;
@ -48,6 +50,8 @@
.modal-container, .popup-menu-container {
.menu-container {
max-height: calc(3.2rem * 7);
.option-container {
display: flex;
flex-direction: row;
@ -101,4 +105,12 @@
}
}
}
}
@media (orientation: landscape) and (max-width: @xsmall) {
.modal-container, .popup-menu-container {
.menu-container {
max-height: calc(100dvh - var(--horizontal-nav-bar-size) - @parent-height);
}
}
}

View file

@ -2,6 +2,8 @@
@import (reference) '~stremio/common/screen-sizes.less';
@parent-height: 10rem;
.dropdown {
background: var(--modal-background-color);
display: none;
@ -16,7 +18,7 @@
&.open {
display: block;
max-height: calc(3.2rem * 10);
max-height: calc(3.3rem * 7);
overflow: auto;
}
@ -33,10 +35,10 @@
}
}
@media only screen and (max-width: @minimum) {
@media (orientation: landscape) and (max-width: @xsmall) {
.dropdown {
&.open {
max-height: calc(3.2rem * 7);
max-height: calc(100dvh - var(--horizontal-nav-bar-size) - @parent-height);
}
}
}

View file

@ -13,7 +13,7 @@ const NavMenu = require('./NavMenu');
const styles = require('./styles');
const { t } = require('i18next');
const HorizontalNavBar = React.memo(({ className, route, query, title, backButton, searchBar, addonsButton, fullscreenButton, navMenu, ...props }) => {
const HorizontalNavBar = React.memo(({ className, route, query, title, backButton, searchBar, fullscreenButton, navMenu, ...props }) => {
const backButtonOnClick = React.useCallback(() => {
window.history.back();
}, []);
@ -54,14 +54,6 @@ const HorizontalNavBar = React.memo(({ className, route, query, title, backButto
null
}
<div className={styles['buttons-container']}>
{
addonsButton ?
<Button className={styles['button-container']} href={'#/addons'} title={t('ADDONS')} tabIndex={-1}>
<Icon className={styles['icon']} name={'addons-outline'} />
</Button>
:
null
}
{
!isIOSPWA && fullscreenButton ?
<Button className={styles['button-container']} title={fullscreen ? t('EXIT_FULLSCREEN') : t('ENTER_FULLSCREEN')} tabIndex={-1} onClick={fullscreen ? exitFullscreen : requestFullscreen}>
@ -90,7 +82,6 @@ HorizontalNavBar.propTypes = {
title: PropTypes.string,
backButton: PropTypes.bool,
searchBar: PropTypes.bool,
addonsButton: PropTypes.bool,
fullscreenButton: PropTypes.bool,
navMenu: PropTypes.bool
};

View file

@ -14,7 +14,7 @@
}
.nav-menu-container {
width: 22rem;
max-height: calc(100vh - var(--horizontal-nav-bar-size));
max-height: calc(100vh - var(--horizontal-nav-bar-size) - 1rem);
overflow-y: auto;
border-radius: var(--border-radius);
background-color: var(--modal-background-color);

View file

@ -61,7 +61,7 @@ const SearchBar = React.memo(({ className, query, active }) => {
const queryInputOnSubmit = React.useCallback((event) => {
event.preventDefault();
const searchValue = `/search?search=${event.target.value}`;
const searchValue = `/search?search=${encodeURIComponent(event.target.value)}`;
setCurrentQuery(searchValue);
if (searchInputRef.current && searchValue) {
window.location.hash = searchValue;

View file

@ -21,10 +21,10 @@
cursor: pointer;
outline: none;
user-select: none;
margin-right: 0.75rem;
outline-width: var(--focus-outline-size);
outline-color: @color-surface-light5;
outline-offset: calc(-1 * var(--focus-outline-size));
-webkit-tap-highlight-color: transparent;
input[type='radio'] {
opacity: 0;

View file

@ -1,32 +1,25 @@
// Copyright (C) 2017-2024 Smart code 203358507
import React from 'react';
import React, { forwardRef, useCallback } from 'react';
import { type KeyboardEvent, type InputHTMLAttributes } from 'react';
import classnames from 'classnames';
import styles from './styles.less';
type Props = React.InputHTMLAttributes<HTMLInputElement> & {
type Props = InputHTMLAttributes<HTMLInputElement> & {
className?: string;
disabled?: boolean;
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
onSubmit?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
onSubmit?: (event: KeyboardEvent<HTMLInputElement>) => void;
};
const TextInput = React.forwardRef<HTMLInputElement, Props>((props, ref) => {
const { onSubmit, className, disabled, ...rest } = props;
const TextInput = forwardRef<HTMLInputElement, Props>((props, ref) => {
const onKeyDown = useCallback((event: KeyboardEvent<HTMLInputElement>) => {
props.onKeyDown && props.onKeyDown(event);
const onKeyDown = React.useCallback((event: React.KeyboardEvent<HTMLInputElement>) => {
if (typeof props.onKeyDown === 'function') {
props.onKeyDown(event);
if (event.key === 'Enter' ) {
props.onSubmit && props.onSubmit(event);
}
if (
event.key === 'Enter' &&
!(event.nativeEvent as any).submitPrevented &&
typeof onSubmit === 'function'
) {
onSubmit(event);
}
}, [props.onKeyDown, onSubmit]);
}, [props.onKeyDown, props.onSubmit]);
return (
<input
@ -36,10 +29,10 @@ const TextInput = React.forwardRef<HTMLInputElement, Props>((props, ref) => {
autoComplete={'off'}
spellCheck={false}
tabIndex={0}
{...props}
ref={ref}
className={classnames(className, styles['text-input'], { disabled })}
className={classnames(props.className, styles['text-input'], { 'disabled': props.disabled })}
onKeyDown={onKeyDown}
{...rest}
/>
);
});

View file

@ -10,14 +10,8 @@ const TooltipItem = React.memo(({ className, active, label, position, margin, pa
const [style, setStyle] = React.useState(null);
const onTransitionEnd = React.useCallback(() => {
if (!active) {
setStyle(null);
}
}, [active]);
React.useEffect(() => {
if (!ref.current) return setStyle(null);
if (!ref.current || !active) return setStyle(null);
const tooltipBounds = ref.current.getBoundingClientRect();
const parentBounds = parent.getBoundingClientRect();
@ -47,7 +41,7 @@ const TooltipItem = React.memo(({ className, active, label, position, margin, pa
}, [active, position, margin, parent, label]);
return (
<div ref={ref} className={classNames(className, styles['tooltip-item'], { 'active': active })} style={style} onTransitionEnd={onTransitionEnd}>
<div ref={ref} className={classNames(className, styles['tooltip-item'], { 'active': active })} style={style}>
{ label }
</div>
);

View file

@ -4,15 +4,16 @@ const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const { t } = require('i18next');
const { useServices } = require('stremio/services');
const { useRouteFocused } = require('stremio-router');
const { default: Icon } = require('@stremio/stremio-icons/react');
const { Button, Image, Popup, useBinaryState } = require('stremio/common');
const Button = require('stremio/common/Button');
const Image = require('stremio/common/Image');
const Popup = require('stremio/common/Popup');
const useBinaryState = require('stremio/common/useBinaryState');
const VideoPlaceholder = require('./VideoPlaceholder');
const styles = require('./styles');
const Video = ({ className, id, title, thumbnail, episode, released, upcoming, watched, progress, scheduled, deepLinks, ...props }) => {
const { core } = useServices();
const Video = ({ className, id, title, thumbnail, episode, released, upcoming, watched, progress, scheduled, deepLinks, onMarkVideoAsWatched, ...props }) => {
const routeFocused = useRouteFocused();
const [menuOpen, , closeMenu, toggleMenu] = useBinaryState(false);
const popupLabelOnMouseUp = React.useCallback((event) => {
@ -49,13 +50,7 @@ const Video = ({ className, id, title, thumbnail, episode, released, upcoming, w
event.preventDefault();
event.stopPropagation();
closeMenu();
core.transport.dispatch({
action: 'MetaDetails',
args: {
action: 'MarkVideoAsWatched',
args: [{ id, released }, !watched]
}
});
onMarkVideoAsWatched({ id, released }, watched);
}, [id, released, watched]);
const videoButtonOnClick = React.useCallback(() => {
if (deepLinks) {
@ -198,7 +193,8 @@ Video.propTypes = {
deepLinks: PropTypes.shape({
metaDetailsStreams: PropTypes.string,
player: PropTypes.string
})
}),
onMarkVideoAsWatched: PropTypes.func,
};
module.exports = Video;

View file

@ -29,6 +29,7 @@ const Slider = require('./Slider');
const { default: TextInput } = require('./TextInput');
const { ToastProvider, useToast } = require('./Toast');
const { TooltipProvider, Tooltip } = require('./Tooltips');
const Video = require('./Video');
const comparatorWithPriorities = require('./comparatorWithPriorities');
const CONSTANTS = require('./CONSTANTS');
const { withCoreSuspender, useCoreSuspender } = require('./CoreSuspender');
@ -83,6 +84,7 @@ module.exports = {
useToast,
TooltipProvider,
Tooltip,
Video,
comparatorWithPriorities,
CONSTANTS,
withCoreSuspender,

View file

@ -0,0 +1,143 @@
// Copyright (C) 2017-2024 Smart code 203358507
@import (reference) '~stremio/common/screen-sizes.less';
@placeholder-opacity: 0.1;
@padding: 1.5rem;
@small-padding: 1rem;
@logo-size: 8rem;
.placeholder-pill(@width: 100%, @height: 1.3rem) {
background-color: var(--primary-foreground-color);
border-radius: var(--border-radius);
opacity: @placeholder-opacity;
width: @width;
height: @height;
}
.placeholder-logo(@size: @logo-size) {
width: @size;
height: @size;
border-radius: 50%;
background-color: var(--primary-foreground-color);
opacity: @placeholder-opacity;
}
.addon-container {
display: flex;
flex-direction: row;
align-items: flex-start;
padding: @padding;
border-radius: var(--border-radius);
background-color: var(--overlay-color);
cursor: inherit;
.content {
display: flex;
flex: 1;
.logo-container {
flex: none;
.placeholder-logo(@logo-size);
}
.info-container {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: @small-padding;
flex: 1;
.placeholder-pill:nth-child(1) {
.placeholder-pill(40%);
}
.placeholder-pill:nth-child(2) {
.placeholder-pill(60%);
}
.placeholder-pill:nth-child(3) {
.placeholder-pill(80%);
}
.placeholder-pill:nth-child(4) {
display: none;
}
}
}
.buttons-container {
flex: none;
display: flex;
flex-direction: column;
gap: @small-padding;
width: 30%;
max-width: 18rem;
.action-buttons-container {
display: flex;
flex-direction: row;
gap: @small-padding;
.placeholder-pill:nth-child(1), .placeholder-pill:nth-child(2) {
.placeholder-pill(50%, 3.5rem);
}
}
.placeholder-pill:last-child {
.placeholder-pill(100%, 3.5rem);
}
}
}
@media screen and (max-width: @minimum) {
.addon-container {
flex-direction: column;
align-items: stretch;
width: 100%;
gap: 1rem;
.content {
flex-direction: row;
width: 100%;
.logo-container {
margin: 0 auto;
}
.info-container {
padding: 1rem;
width: 100%;
.placeholder-pill:nth-child(1) {
.placeholder-pill(60%);
}
.placeholder-pill:nth-child(2) {
.placeholder-pill(40%);
}
.placeholder-pill:nth-child(3) {
.placeholder-pill(80%, 0.8rem);
}
.placeholder-pill:nth-child(4) {
display: block;
.placeholder-pill(20%, 0.8rem);
}
}
}
.buttons-container {
flex-direction: column;
align-items: stretch;
width: 100%;
gap: 0.5rem;
max-width: none;
.action-buttons-container {
display: none;
}
}
}
}

View file

@ -0,0 +1,34 @@
// Copyright (C) 2017-2024 Smart code 203358507
import React from 'react';
import classnames from 'classnames';
import styles from './AddonPlaceholder.less';
type Props = {
className?: string;
};
export const AddonPlaceholder = ({ className }: Props) => {
return (
<div className={classnames(className, styles['addon-container'])}>
<div className={styles['content']}>
<div className={styles['logo-container']}>
<div className={styles['placeholder-logo']} />
</div>
<div className={styles['info-container']}>
<div className={styles['placeholder-pill']} />
<div className={styles['placeholder-pill']} />
<div className={styles['placeholder-pill']} />
<div className={styles['placeholder-pill']} />
</div>
</div>
<div className={styles['buttons-container']}>
<div className={styles['action-buttons-container']}>
<div className={styles['placeholder-pill']} />
<div className={styles['placeholder-pill']} />
</div>
<div className={styles['placeholder-pill']} />
</div>
</div>
);
};

View file

@ -0,0 +1,7 @@
// Copyright (C) 2017-2024 Smart code 203358507
import { AddonPlaceholder } from './AddonPlaceholder';
export {
AddonPlaceholder
};

View file

@ -12,6 +12,7 @@ const useRemoteAddons = require('./useRemoteAddons');
const useAddonDetailsTransportUrl = require('./useAddonDetailsTransportUrl');
const useSelectableInputs = require('./useSelectableInputs');
const styles = require('./styles');
const { AddonPlaceholder } = require('./AddonPlaceholder');
const Addons = ({ urlParams, queryParams }) => {
const { t } = useTranslation();
@ -94,7 +95,7 @@ const Addons = ({ urlParams, queryParams }) => {
<div className={styles['spacing']} />
<Button className={styles['add-button-container']} title={t('ADD_ADDON')} onClick={openAddAddonModal}>
<Icon className={styles['icon']} name={'add'} />
<div className={styles['add-button-label']}>{ t('ADD_ADDON') }</div>
<div className={styles['add-button-label']}>{t('ADD_ADDON')}</div>
</Button>
<SearchBar
className={styles['search-bar']}
@ -150,8 +151,10 @@ const Addons = ({ urlParams, queryParams }) => {
</div>
:
remoteAddons.catalog.content.type === 'Loading' ?
<div className={styles['message-container']}>
Loading!
<div className={styles['addons-list-container']}>
{Array.from({ length: 6 }).map((_, index) => (
<AddonPlaceholder key={index} className={styles['addon']} />
))}
</div>
:
<div className={styles['addons-list-container']}>
@ -179,8 +182,10 @@ const Addons = ({ urlParams, queryParams }) => {
}
</div>
:
<div className={styles['message-container']}>
No select
<div className={styles['addons-list-container']}>
{Array.from({ length: 6 }).map((_, index) => (
<AddonPlaceholder key={index} className={styles['addon']} />
))}
</div>
}
</div>
@ -205,7 +210,7 @@ const Addons = ({ urlParams, queryParams }) => {
title={t('ADD_ADDON')}
buttons={addAddonModalButtons}
onCloseRequest={closeAddAddonModal}>
<div className={styles['notice']}>{ t('ADD_ADDON_DESCRIPTION') }</div>
<div className={styles['notice']}>{t('ADD_ADDON_DESCRIPTION')}</div>
<TextInput
ref={addAddonUrlInputRef}
className={styles['addon-url-input']}

View file

@ -89,7 +89,6 @@
margin-right: 1.5rem;
.multiselect-menu-container {
max-height: calc(3.2rem * 7);
overflow: auto;
}
}
@ -156,6 +155,10 @@
.select-input-container {
height: 3rem;
.multiselect-menu-container {
overflow: auto;
}
&:not(:last-child) {
margin-bottom: 1rem;
}

View file

@ -128,8 +128,10 @@
border-color: var(--primary-foreground-color);
}
&:not(.active):hover {
border-color: var(--overlay-color);
@media (pointer: fine) {
&:not(.active):hover {
border-color: var(--overlay-color);
}
}
}

View file

@ -37,7 +37,7 @@ const Cell = ({ selected, monthInfo, date, items, onClick }: Props) => {
<HorizontalScroll className={styles['items']}>
{
items.map(({ id, name, poster, deepLinks }) => (
<Button key={id} className={styles['item']} href={deepLinks.metaDetailsStreams}>
<Button key={id} className={styles['item']} href={deepLinks.metaDetailsStreams} tabIndex={-1}>
<Icon className={styles['icon']} name={'play'} />
<Image
className={styles['poster']}

View file

@ -57,7 +57,6 @@
}
.multiselect-menu-container {
max-height: calc(3.2rem * 7);
overflow: auto;
}
}
@ -220,7 +219,7 @@
}
.multiselect-menu-container {
max-height: calc(3.2rem * 4);
max-height: calc(3.2rem * 3);
overflow: auto;
}
}

View file

@ -42,7 +42,6 @@
}
.multiselect-menu-container {
max-height: calc(3.2rem * 7);
overflow: auto;
}
}

View file

@ -81,7 +81,6 @@ const MetaDetails = ({ urlParams, queryParams }) => {
<HorizontalNavBar
className={styles['nav-bar']}
backButton={true}
addonsButton={true}
fullscreenButton={true}
navMenu={true}
/>
@ -210,7 +209,6 @@ const MetaDetailsFallback = () => (
<HorizontalNavBar
className={styles['nav-bar']}
backButton={true}
addonsButton={true}
fullscreenButton={true}
navMenu={true}
/>

View file

@ -124,7 +124,6 @@
}
.multiselect-menu-container {
max-height: calc(3.2rem * 7);
overflow: auto;
}
}

View file

@ -77,7 +77,6 @@
}
.multiselect-menu-container {
max-height: calc(3.2rem * 7);
overflow: auto;
}
}
@ -86,11 +85,5 @@
@media only screen and (max-width: @minimum) {
.seasons-bar-container {
height: 6rem;
.seasons-popup-label-container {
.multiselect-menu-container {
max-height: calc(3.2rem * 3);
}
}
}
}

View file

@ -4,12 +4,13 @@ const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const { t } = require('i18next');
const { Image, SearchBar, Toggle } = require('stremio/common');
const { useServices } = require('stremio/services');
const { Image, SearchBar, Toggle, Video } = require('stremio/common');
const SeasonsBar = require('./SeasonsBar');
const Video = require('./Video');
const styles = require('./styles');
const VideosList = ({ className, metaItem, libraryItem, season, seasonOnSelect, toggleNotifications }) => {
const { core } = useServices();
const showNotificationsToggle = React.useMemo(() => {
return metaItem?.content?.content?.inLibrary && metaItem?.content?.content?.videos?.length;
}, [metaItem]);
@ -59,6 +60,17 @@ const VideosList = ({ className, metaItem, libraryItem, season, seasonOnSelect,
const searchInputOnChange = React.useCallback((event) => {
setSearch(event.currentTarget.value);
}, []);
const onMarkVideoAsWatched = (video, watched) => {
core.transport.dispatch({
action: 'MetaDetails',
args: {
action: 'MarkVideoAsWatched',
args: [video, !watched]
}
});
};
return (
<div className={classnames(className, styles['videos-list-container'])}>
{
@ -130,6 +142,7 @@ const VideosList = ({ className, metaItem, libraryItem, season, seasonOnSelect,
progress={video.progress}
deepLinks={video.deepLinks}
scheduled={video.scheduled}
onMarkVideoAsWatched={onMarkVideoAsWatched}
/>
))
}

View file

@ -277,10 +277,9 @@ const Player = ({ urlParams, queryParams }) => {
React.useEffect(() => {
setError(null);
if (player.selected === null) {
video.unload();
} else if (streamingServer.settings !== null && streamingServer.settings.type !== 'Loading' &&
(player.selected.metaRequest === null || (player.metaItem !== null && player.metaItem.type !== 'Loading'))) {
video.unload();
if (player.selected && streamingServer.settings?.type !== 'Loading') {
video.load({
stream: {
...player.selected.stream,
@ -315,7 +314,7 @@ const Player = ({ urlParams, queryParams }) => {
shellTransport: shell.active ? shell.transport : null,
});
}
}, [streamingServer.baseUrl, player.selected, player.metaItem, forceTranscoding, casting]);
}, [streamingServer.baseUrl, player.selected, forceTranscoding, casting]);
React.useEffect(() => {
if (video.state.stream !== null) {
const tracks = player.subtitles.map((subtitles) => ({

View file

@ -1,10 +1,9 @@
// Copyright (C) 2017-2023 Smart code 203358507
// Copyright (C) 2017-2024 Smart code 203358507
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
.speed-menu-container {
width: 14rem;
overflow: visible !important;
.title {
flex: none;
@ -18,7 +17,6 @@
flex: 0 1 auto;
max-height: calc(3.2rem * 8);
padding: 0 1rem 0.5rem;
overflow-y: auto;
.option {
height: 3.2rem;

View file

@ -3,19 +3,34 @@
const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const Video = require('../../MetaDetails/VideosList/Video');
const { useServices } = require('stremio/services');
const { Video } = require('stremio/common');
const styles = require('./styles');
const VideosMenu = ({ className, metaItem, seriesInfo }) => {
const { core } = useServices();
const onMouseDown = React.useCallback((event) => {
event.nativeEvent.videosMenuClosePrevented = true;
}, []);
const videos = React.useMemo(() => {
return seriesInfo && typeof seriesInfo.season === 'number' && Array.isArray(metaItem.videos) ?
metaItem.videos.filter(({ season }) => season === seriesInfo.season)
:
metaItem.videos;
}, [metaItem, seriesInfo]);
const onMarkVideoAsWatched = (video, watched) => {
core.transport.dispatch({
action: 'Player',
args: {
action: 'MarkVideoAsWatched',
args: [video, !watched]
}
});
};
return (
<div className={classnames(className, styles['videos-menu-container'])} onMouseDown={onMouseDown}>
{
@ -32,6 +47,7 @@ const VideosMenu = ({ className, metaItem, seriesInfo }) => {
progress={video.progress}
deepLinks={video.deepLinks}
scheduled={video.scheduled}
onMarkVideoAsWatched={onMarkVideoAsWatched}
/>
))
}

View file

@ -4,11 +4,10 @@
.add-item {
display: flex;
padding: 0.5rem 1.5rem;
gap: 1rem;
border-radius: var(--border-radius);
padding: 0.35rem 1.5rem;
border-radius: 2.5rem;
transition: 0.3s all ease-in-out;
background-color: transparent;
background-color: var(--overlay-color);
border: 2px solid transparent;
justify-content: space-between;
position: relative;
@ -77,13 +76,12 @@
}
&:hover {
border: 2px solid transparent;
background-color: var(--overlay-color);
border: 2px solid var(--overlay-color);
}
}
@media only screen and (max-width: @minimum) {
.add-item {
padding: 0.5rem;
padding: 0.35rem 0.5rem;
}
}

View file

@ -4,10 +4,10 @@
.item {
display: flex;
padding: 1rem 1.5rem;
border-radius: var(--border-radius);
padding: 0.7rem 1.5rem;
border-radius: 2.5rem;
transition: 0.3s all ease-in-out;
background-color: transparent;
background-color: var(--overlay-color);
border: 2px solid transparent;
justify-content: space-between;
position: relative;
@ -34,7 +34,7 @@
.actions {
display: flex;
gap: 1rem;
margin-right: 5rem;
padding-right: 4rem;
.status {
display: flex;
@ -100,7 +100,7 @@
}
&:hover {
background-color: var(--overlay-color);
border: 2px solid var(--overlay-color);
.actions {
.delete {
@ -114,13 +114,11 @@
@media only screen and (max-width: @minimum) {
.item {
padding: 1rem 0.5rem;
padding: 0.7rem 1rem;
.actions {
margin-right: 4rem;
.delete {
right: 0.5rem;
right: 1rem;
.icon {
opacity: 0.6;

View file

@ -27,8 +27,8 @@ const Item = ({ url }: Props) => {
const handleDelete = useCallback(() => {
deleteServerUrl(url);
selectServerUrl(DEFAULT_STREAMING_SERVER_URL);
}, [url]);
selected && selectServerUrl(DEFAULT_STREAMING_SERVER_URL);
}, [url, selected]);
const handleSelect = useCallback(() => {
selectServerUrl(url);

View file

@ -8,7 +8,8 @@
.header {
display: flex;
justify-content: space-around;
justify-content: space-between;
padding: 0 3rem;
align-items: center;
.label {
@ -16,6 +17,10 @@
color: var(--primary-foreground-color);
font-weight: 400;
opacity: 0.6;
&:last-of-type {
padding-right: 3rem;
}
}
}

View file

@ -286,7 +286,6 @@
}
.multiselect-menu-container {
max-height: calc(3.2rem * 7);
overflow: auto;
}
}
@ -316,7 +315,14 @@
}
&.color-input-container {
padding: 1.75rem 1rem;
padding: 1.3rem 1rem;
border-radius: 3rem;
border: 2px solid transparent;
transition: 0.3s all ease-in-out;
&:hover {
border-color: var(--overlay-color);
}
}
&.info-container {