Merge branch 'development' into refactor/install-addons-button-stream-list

This commit is contained in:
Timothy Z. 2025-01-10 20:42:51 +02:00
commit 5700c3ab6e
220 changed files with 914 additions and 561 deletions

23
package-lock.json generated
View file

@ -1,18 +1,18 @@
{
"name": "stremio",
"version": "5.0.0-beta.15",
"version": "5.0.0-beta.16",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "stremio",
"version": "5.0.0-beta.15",
"version": "5.0.0-beta.16",
"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.3",
"@stremio/stremio-core-web": "0.48.4",
"@stremio/stremio-icons": "5.4.1",
"@stremio/stremio-video": "0.0.48",
"a-color-picker": "1.2.1",
@ -23,7 +23,7 @@
"filter-invalid-dom-props": "3.0.1",
"hat": "^0.0.3",
"i18next": "^24.0.5",
"langs": "^2.0.0",
"langs": "github:Stremio/nodejs-langs",
"lodash.debounce": "4.0.8",
"lodash.intersection": "4.4.0",
"lodash.isequal": "4.5.0",
@ -36,7 +36,7 @@
"react-i18next": "^15.1.3",
"react-is": "18.3.1",
"spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
"stremio-translations": "github:Stremio/stremio-translations#f666d9a97cafa5aa150878b5c51a2896b5f4f1b2",
"stremio-translations": "github:Stremio/stremio-translations#a0f50634202f748a57907b645d2cd92fbaa479dd",
"url": "0.11.4",
"use-long-press": "^3.2.0"
},
@ -3371,9 +3371,10 @@
"integrity": "sha512-dYlPgu9W/H7c9s1zmW5tiDnRenaUa4Hg1QCyOg1lhOcgSfM/bVTi5nnqX+IfvGTTUNA0zgzh8hI3o3miwnZxTg=="
},
"node_modules/@stremio/stremio-core-web": {
"version": "0.48.3",
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.48.3.tgz",
"integrity": "sha512-JL8pOLOEVACYG+33Dtp/mrB2/vuc7RoYZdxX1BQa5MPR8EzsODjpvL5uETmdxo/swgtMZyx2A6/e1B53eKA4oQ==",
"version": "0.48.4",
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.48.4.tgz",
"integrity": "sha512-848OLm0dtP75aAlYhUB0KoOqwosJIj+ubB8/abuaAzH/N3dtxs40vu2AezmMpGjwR4V60rlOUkUZeWFvrUOjrw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "7.24.1"
}
@ -10263,6 +10264,7 @@
},
"node_modules/langs": {
"version": "2.0.0",
"resolved": "git+ssh://git@github.com/Stremio/nodejs-langs.git#584beab4032469f6b76be1d119a0ab25f31f4ab6",
"license": "MIT"
},
"node_modules/launch-editor": {
@ -13372,8 +13374,9 @@
},
"node_modules/stremio-translations": {
"version": "1.44.9",
"resolved": "git+ssh://git@github.com/Stremio/stremio-translations.git#f666d9a97cafa5aa150878b5c51a2896b5f4f1b2",
"integrity": "sha512-SzaIGUMqQuMAq58sI9L/RKSs5O4eF8VKPMqnWFddBSg/tZOU9xuNYqjRPKT07cp8MRfzzGQmCKMByozTYfjdIA=="
"resolved": "git+ssh://git@github.com/Stremio/stremio-translations.git#a0f50634202f748a57907b645d2cd92fbaa479dd",
"integrity": "sha512-JJpd1JJet3T6/VTNdZ2NZ7uvHJ4zkuyqo5BnTcDGqLVNO/OpicGqKhZjE4WGSgmuhsfPBU8T0ICCfzKu2xpvKg==",
"license": "MIT"
},
"node_modules/string_decoder": {
"version": "1.1.1",

View file

@ -1,7 +1,7 @@
{
"name": "stremio",
"displayName": "Stremio",
"version": "5.0.0-beta.15",
"version": "5.0.0-beta.16",
"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.3",
"@stremio/stremio-core-web": "0.48.4",
"@stremio/stremio-icons": "5.4.1",
"@stremio/stremio-video": "0.0.48",
"a-color-picker": "1.2.1",
@ -27,7 +27,7 @@
"filter-invalid-dom-props": "3.0.1",
"hat": "^0.0.3",
"i18next": "^24.0.5",
"langs": "^2.0.0",
"langs": "github:Stremio/nodejs-langs",
"lodash.debounce": "4.0.8",
"lodash.intersection": "4.4.0",
"lodash.isequal": "4.5.0",
@ -40,7 +40,7 @@
"react-i18next": "^15.1.3",
"react-is": "18.3.1",
"spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
"stremio-translations": "github:Stremio/stremio-translations#f666d9a97cafa5aa150878b5c51a2896b5f4f1b2",
"stremio-translations": "github:Stremio/stremio-translations#a0f50634202f748a57907b645d2cd92fbaa479dd",
"url": "0.11.4",
"use-long-press": "^3.2.0"
},

View file

@ -4,7 +4,7 @@ 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 { Image, Button } = require('stremio/components');
const styles = require('./styles');
const ErrorDialog = ({ className }) => {

View file

@ -1,4 +1,4 @@
// Copyright (C) 2017-2023 Smart code 203358507
// Copyright (C) 2017-2024 Smart code 203358507
@import (reference) '~stremio/common/screen-sizes.less';
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
@ -13,6 +13,20 @@
@import (once, less) '~stremio-router/styles.css';
}
// iOS pads the bottom inset more than needed, so we deduce the actual inset size when using the webapp
@calculated-bottom-safe-inset: ~"min(env(safe-area-inset-bottom, 0rem), max(1rem, calc(100lvh - 100svh - env(safe-area-inset-top, 0rem))))";
@html-width: ~"calc(max(100svw, 100dvw))";
@html-height: ~"calc(max(100svh, 100dvh))";
@safe-area-inset-top: env(safe-area-inset-top, 0rem);
@safe-area-inset-right: env(safe-area-inset-right, 0rem);
@safe-area-inset-bottom: env(safe-area-inset-bottom, 0rem);
@safe-area-inset-left: env(safe-area-inset-left, 0rem);
@top-overlay-size: 5.25rem;
@bottom-overlay-size: 0rem;
@overlap-size: 3rem;
@transparency-grandient-pad: 6rem;
:root {
--landscape-shape-ratio: 0.5625;
--poster-shape-ratio: 1.464;
@ -40,6 +54,15 @@
--modal-background-color: rgba(15, 13, 32, 1);
--outer-glow: 0px 0px 15px rgba(123, 91, 245, 0.37);
--border-radius: 0.75rem;
--calculated-bottom-safe-inset: @calculated-bottom-safe-inset;
--top-overlay-size: @top-overlay-size;
--bottom-overlay-size: @bottom-overlay-size;
--overlap-size: @overlap-size;
--transparency-grandient-pad: @transparency-grandient-pad;
--safe-area-inset-top: @safe-area-inset-top;
--safe-area-inset-right: @safe-area-inset-right;
--safe-area-inset-bottom: @safe-area-inset-bottom;
--safe-area-inset-left: @safe-area-inset-left;
}
* {
@ -85,13 +108,16 @@ svg {
}
html {
width: 100%;
height: 100%;
width: @html-width;
height: @html-height;
min-width: 640px;
min-height: 480px;
font-family: 'PlusJakartaSans', 'sans-serif';
overflow: auto;
overscroll-behavior: none;
user-select: none;
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
body {
width: 100%;
@ -106,9 +132,9 @@ html {
.toasts-container {
position: absolute;
top: calc(1.2 * var(--horizontal-nav-bar-size));
right: 0;
bottom: calc(1.2 * var(--horizontal-nav-bar-size));
top: calc(1.2 * var(--horizontal-nav-bar-size) + var(--safe-area-inset-top));
right: var(--safe-area-inset-right);
bottom: calc(1.2 * var(--horizontal-nav-bar-size) + var(--calculated-bottom-safe-inset, 0rem));
left: auto;
z-index: 1;
padding: 0 calc(0.5 * var(--horizontal-nav-bar-size));
@ -193,4 +219,10 @@ html {
}
}
}
}
@media only screen and (max-width: @minimum) {
:root {
--bottom-overlay-size: 6rem;
}
}

View file

@ -1,65 +0,0 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const styles = require('./styles');
const { useLongPress } = require('use-long-press');
const Button = React.forwardRef(({ className, href, disabled, children, onLongPress, onDoubleClick, ...props }, ref) => {
const longPress = useLongPress(onLongPress, { detect: 'pointer' });
const onKeyDown = React.useCallback((event) => {
if (typeof props.onKeyDown === 'function') {
props.onKeyDown(event);
}
if (event.key === 'Enter') {
event.preventDefault();
if (!event.nativeEvent.buttonClickPrevented) {
event.currentTarget.click();
}
}
}, [props.onKeyDown]);
const onMouseDown = React.useCallback((event) => {
if (typeof props.onMouseDown === 'function') {
props.onMouseDown(event);
}
if (!event.nativeEvent.buttonBlurPrevented) {
event.preventDefault();
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
}
}, [props.onMouseDown]);
return React.createElement(
typeof href === 'string' && href.length > 0 ? 'a' : 'div',
{
tabIndex: 0,
...props,
ref,
className: classnames(className, styles['button-container'], { 'disabled': disabled }),
href,
onKeyDown,
onMouseDown,
onDoubleClick,
...longPress()
},
children
);
});
Button.displayName = 'Button';
Button.propTypes = {
className: PropTypes.string,
href: PropTypes.string,
disabled: PropTypes.bool,
children: PropTypes.node,
onKeyDown: PropTypes.func,
onMouseDown: PropTypes.func,
onLongPress: PropTypes.func,
onDoubleClick: PropTypes.func
};
module.exports = Button;

View file

@ -1,5 +0,0 @@
// Copyright (C) 2017-2023 Smart code 203358507
const Button = require('./Button');
module.exports = Button;

View file

@ -1,5 +0,0 @@
// Copyright (C) 2017-2023 Smart code 203358507
const Image = require('./Image');
module.exports = Image;

View file

@ -1,5 +0,0 @@
// Copyright (C) 2017-2023 Smart code 203358507
const MainNavBars = require('./MainNavBars');
module.exports = MainNavBars;

View file

@ -1,54 +0,0 @@
// Copyright (C) 2017-2024 Smart code 203358507
import React from 'react';
import Button from 'stremio/common/Button';
import { useTranslation } from 'react-i18next';
import classNames from 'classnames';
import Option from './Option';
import Icon from '@stremio/stremio-icons/react';
import styles from './Dropdown.less';
type Props = {
options: MultiselectMenuOption[];
selectedOption?: MultiselectMenuOption | null;
menuOpen: boolean | (() => void);
level: number;
setLevel: (level: number) => void;
onSelect: (value: number) => void;
};
const Dropdown = ({ level, setLevel, options, onSelect, selectedOption, menuOpen }: Props) => {
const { t } = useTranslation();
const onBackButtonClick = () => {
setLevel(level - 1);
};
return (
<div className={classNames(styles['dropdown'], { [styles['open']]: menuOpen })} role={'listbox'}>
{
level > 0 ?
<Button className={styles['back-button']} onClick={onBackButtonClick}>
<Icon name={'caret-left'} className={styles['back-button-icon']} />
{t('BACK')}
</Button>
: null
}
{
options
.filter((option: MultiselectMenuOption) => !option.hidden)
.map((option: MultiselectMenuOption, index) => (
<Option
key={index}
option={option}
onSelect={onSelect}
selectedOption={selectedOption}
/>
))
}
</div>
);
};
export default Dropdown;

View file

@ -4,7 +4,7 @@ const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const { default: Icon } = require('@stremio/stremio-icons/react');
const Button = require('stremio/common/Button');
const { Button } = require('stremio/components');
const styles = require('./styles');
const ToastItem = ({ title, message, dataset, onSelect, onClose, ...props }) => {

View file

@ -1,42 +1,15 @@
// Copyright (C) 2017-2023 Smart code 203358507
const AddonDetailsModal = require('./AddonDetailsModal');
const { default: BottomSheet } = require('./BottomSheet');
const Button = require('./Button');
const Toggle = require('./Toggle');
const { default: Chips } = require('./Chips');
const ColorInput = require('./ColorInput');
const ContinueWatchingItem = require('./ContinueWatchingItem');
const DelayedRenderer = require('./DelayedRenderer');
const Image = require('./Image');
const LibItem = require('./LibItem');
const MainNavBars = require('./MainNavBars');
const MetaItem = require('./MetaItem');
const MetaPreview = require('./MetaPreview');
const MetaRow = require('./MetaRow');
const ModalDialog = require('./ModalDialog');
const Multiselect = require('./Multiselect');
const { default: MultiselectMenu } = require('./MultiselectMenu');
const { HorizontalNavBar, VerticalNavBar } = require('./NavBar');
const { default: HorizontalScroll } = require('./HorizontalScroll');
const { PlatformProvider, usePlatform } = require('./Platform');
const PlayIconCircleCentered = require('./PlayIconCircleCentered');
const Popup = require('./Popup');
const SearchBar = require('./SearchBar');
const StreamingServerWarning = require('./StreamingServerWarning');
const SharePrompt = require('./SharePrompt');
const Slider = require('./Slider');
const { default: TextInput } = require('./TextInput');
const { ToastProvider, useToast } = require('./Toast');
const { TooltipProvider, Tooltip } = require('./Tooltips');
const { default: Transition } = require('./Transition');
const Video = require('./Video');
const comparatorWithPriorities = require('./comparatorWithPriorities');
const CONSTANTS = require('./CONSTANTS');
const { withCoreSuspender, useCoreSuspender } = require('./CoreSuspender');
const getVisibleChildrenRange = require('./getVisibleChildrenRange');
const interfaceLanguages = require('./interfaceLanguages.json');
const languageNames = require('./languageNames.json');
const languages = require('./languages');
const routesRegexp = require('./routesRegexp');
const useAnimationFrame = require('./useAnimationFrame');
const useBinaryState = require('./useBinaryState');
@ -49,44 +22,14 @@ const useProfile = require('./useProfile');
const useStreamingServer = require('./useStreamingServer');
const useTorrent = require('./useTorrent');
const useTranslate = require('./useTranslate');
const EventModal = require('./EventModal');
module.exports = {
AddonDetailsModal,
BottomSheet,
Button,
Toggle,
Chips,
ColorInput,
ContinueWatchingItem,
DelayedRenderer,
Image,
LibItem,
MainNavBars,
MetaItem,
MetaPreview,
MetaRow,
ModalDialog,
Multiselect,
MultiselectMenu,
HorizontalNavBar,
HorizontalScroll,
VerticalNavBar,
PlatformProvider,
usePlatform,
PlayIconCircleCentered,
Popup,
SearchBar,
StreamingServerWarning,
SharePrompt,
Slider,
TextInput,
ToastProvider,
useToast,
TooltipProvider,
Tooltip,
Transition,
Video,
comparatorWithPriorities,
CONSTANTS,
withCoreSuspender,
@ -94,6 +37,7 @@ module.exports = {
getVisibleChildrenRange,
interfaceLanguages,
languageNames,
languages,
routesRegexp,
useAnimationFrame,
useBinaryState,
@ -106,5 +50,4 @@ module.exports = {
useStreamingServer,
useTorrent,
useTranslate,
EventModal,
};

25
src/common/languages.ts Normal file
View file

@ -0,0 +1,25 @@
import langs from 'langs';
const all = langs.all().map((lang) => ({
...lang,
code: lang['2'],
label: lang.local,
alpha2: lang['1'],
alpha3: [lang['2'], lang['2B'], lang['2T'], lang['3']],
locale: lang['locale'],
}));
const find = (code: string) => {
return all.find(({ alpha2, alpha3, locale }) => [alpha2, ...alpha3, locale].includes(code));
};
const label = (code: string) => {
const language = find(code);
return language?.label ?? code;
};
export {
all,
find,
label,
};

View file

@ -4,7 +4,7 @@ const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const { default: Icon } = require('@stremio/stremio-icons/react');
const Image = require('stremio/common/Image');
const { default: Image } = require('stremio/components/Image');
const styles = require('./styles');
const AddonDetails = ({ className, id, name, version, logo, description, types, transportUrl, official }) => {

View file

@ -2,7 +2,7 @@
const React = require('react');
const PropTypes = require('prop-types');
const ModalDialog = require('stremio/common/ModalDialog');
const ModalDialog = require('stremio/components/ModalDialog');
const { withCoreSuspender } = require('stremio/common/CoreSuspender');
const { usePlatform } = require('stremio/common/Platform');
const { useServices } = require('stremio/services');

View file

@ -2,10 +2,6 @@
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
:import('~stremio/common/ModalDialog/styles.less') {
label: label;
}
.addon-details-modal-container {
.addon-details-container, .addon-details-message-container {
width: 40rem;

View file

@ -23,7 +23,6 @@
opacity: 0.8;
transition: opacity 0.1s ease-out;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
.container {

View file

@ -11,7 +11,7 @@ const CLOSE_THRESHOLD = 100;
type Props = {
children: JSX.Element,
title: string,
show?: boolean,
show: boolean,
onClose: () => void,
};

View file

@ -1,4 +1,4 @@
// Copyright (C) 2017-2023 Smart code 203358507
// Copyright (C) 2017-2024 Smart code 203358507
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
@ -6,7 +6,6 @@
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

@ -0,0 +1,71 @@
// Copyright (C) 2017-2023 Smart code 203358507
import { createElement, forwardRef, useCallback } from 'react';
import classNames from 'classnames';
import { LongPressEventType, useLongPress } from 'use-long-press';
import styles from './Button.less';
type Props = {
className?: string,
href?: string,
title?: string,
disabled?: boolean,
tabIndex?: number,
children: React.ReactNode,
onKeyDown?: (event: React.KeyboardEvent) => void,
onMouseDown?: (event: React.MouseEvent) => void,
onLongPress?: () => void,
onClick?: (event: React.MouseEvent<HTMLDivElement>) => void,
onDoubleClick?: () => void,
};
const Button = forwardRef(({ className, href, disabled, children, onLongPress, onDoubleClick, ...props }: Props, ref) => {
const longPress = useLongPress(onLongPress!, { detect: LongPressEventType.Pointer });
const onKeyDown = useCallback((event: React.KeyboardEvent<HTMLDivElement>) => {
if (typeof props.onKeyDown === 'function') {
props.onKeyDown(event);
}
if (event.key === 'Enter') {
event.preventDefault();
// @ts-expect-error: Property 'buttonClickPrevented' does not exist on type 'KeyboardEvent'.
if (!event.nativeEvent.buttonClickPrevented) {
event.currentTarget.click();
}
}
}, [props.onKeyDown]);
const onMouseDown = useCallback((event: React.MouseEvent<HTMLDivElement>) => {
if (typeof props.onMouseDown === 'function') {
props.onMouseDown(event);
}
// @ts-expect-error: Property 'buttonBlurPrevented' does not exist on type 'MouseEvent'.
if (!event.nativeEvent.buttonBlurPrevented) {
event.preventDefault();
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
}
}, [props.onMouseDown]);
return createElement(
typeof href === 'string' && href.length > 0 ? 'a' : 'div',
{
tabIndex: 0,
...props,
ref,
className: classNames(className, styles['button-container'], { 'disabled': disabled }),
href,
onKeyDown,
onMouseDown,
onDoubleClick,
...longPress()
},
children
);
});
export default Button;

View file

@ -0,0 +1,6 @@
// Copyright (C) 2017-2023 Smart code 203358507
import Button from './Button';
export default Button;

View file

@ -16,7 +16,6 @@
text-transform: capitalize;
padding: 0 1.75rem;
border-radius: @height;
-webkit-tap-highlight-color: transparent;
background-color: transparent;
user-select: none;
overflow: hidden;

View file

@ -2,7 +2,7 @@
import React, { MouseEvent, memo, useCallback, useEffect, useRef } from 'react';
import classNames from 'classnames';
import Button from 'stremio/common/Button';
import { Button } from 'stremio/components';
import styles from './Chip.less';
type Props = {

View file

@ -1,7 +1,7 @@
// Copyright (C) 2017-2024 Smart code 203358507
import React, { memo } from 'react';
import HorizontalScroll from '../HorizontalScroll';
import { HorizontalScroll } from 'stremio/components';
import Chip from './Chip';
import styles from './Chips.less';

View file

@ -5,8 +5,7 @@ const PropTypes = require('prop-types');
const classnames = require('classnames');
const AColorPicker = require('a-color-picker');
const { useTranslation } = require('react-i18next');
const Button = require('stremio/common/Button');
const ModalDialog = require('stremio/common/ModalDialog');
const { Button, ModalDialog } = require('stremio/components');
const useBinaryState = require('stremio/common/useBinaryState');
const ColorPicker = require('./ColorPicker');
const styles = require('./styles');

View file

@ -3,7 +3,7 @@
const React = require('react');
const PropTypes = require('prop-types');
const { useServices } = require('stremio/services');
const LibItem = require('stremio/common/LibItem');
const LibItem = require('stremio/components/LibItem');
const ContinueWatchingItem = ({ _id, notifications, deepLinks, ...props }) => {
const { core } = useServices();

View file

@ -2,8 +2,7 @@
const React = require('react');
const { useTranslation } = require('react-i18next');
const Button = require('stremio/common/Button');
const ModalDialog = require('stremio/common/ModalDialog');
const { Button, ModalDialog } = require('stremio/components');
const useEvents = require('./useEvents');
const styles = require('./styles');
const { default: Icon } = require('@stremio/stremio-icons/react');

View file

@ -2,7 +2,7 @@
@import (reference) '~stremio/common/screen-sizes.less';
:import('~stremio/common/ModalDialog/styles.less') {
:import('~stremio/components/ModalDialog/styles.less') {
modal-dialog-content: modal-dialog-content;
modal-dialog-container: modal-dialog-container;
}

View file

@ -1,20 +1,30 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
const PropTypes = require('prop-types');
import React, { useCallback, useLayoutEffect, useState } from 'react';
const Image = ({ className, src, alt, fallbackSrc, renderFallback, ...props }) => {
const [broken, setBroken] = React.useState(false);
const onError = React.useCallback((event) => {
type Props = {
className: string,
src: string,
alt: string,
fallbackSrc: string,
renderFallback: () => void,
onError: (event: React.SyntheticEvent<HTMLImageElement>) => void,
};
const Image = ({ className, src, alt, fallbackSrc, renderFallback, ...props }: Props) => {
const [broken, setBroken] = useState(false);
const onError = useCallback((event: React.SyntheticEvent<HTMLImageElement>) => {
if (typeof props.onError === 'function') {
props.onError(event);
}
setBroken(true);
}, [props.onError]);
React.useLayoutEffect(() => {
useLayoutEffect(() => {
setBroken(false);
}, [src]);
return (broken || typeof src !== 'string' || src.length === 0) && (typeof renderFallback === 'function' || typeof fallbackSrc === 'string') ?
typeof renderFallback === 'function' ?
renderFallback()
@ -24,13 +34,4 @@ const Image = ({ className, src, alt, fallbackSrc, renderFallback, ...props }) =
<img {...props} className={className} src={src} alt={alt} loading='lazy' onError={onError} />;
};
Image.propTypes = {
className: PropTypes.string,
src: PropTypes.string,
alt: PropTypes.string,
fallbackSrc: PropTypes.string,
renderFallback: PropTypes.func,
onError: PropTypes.func
};
module.exports = Image;
export default Image;

View file

@ -0,0 +1,5 @@
// Copyright (C) 2017-2023 Smart code 203358507
import Image from './Image';
export default Image;

View file

@ -3,7 +3,7 @@
const React = require('react');
const { useServices } = require('stremio/services');
const PropTypes = require('prop-types');
const MetaItem = require('stremio/common/MetaItem');
const MetaItem = require('stremio/components/MetaItem');
const { t } = require('i18next');
const LibItem = ({ _id, removable, notifications, watched, ...props }) => {

View file

@ -1,10 +1,15 @@
// Copyright (C) 2017-2023 Smart code 203358507
// Copyright (C) 2017-2024 Smart code 203358507
@import (reference) '~stremio/common/screen-sizes.less';
.main-nav-bars-container {
position: relative;
z-index: 0;
overflow: clip;
margin-left: env(safe-area-inset-left, 0px);
margin-right: env(safe-area-inset-right, 0px);
width: calc(100% - env(safe-area-inset-left, 0px) - env(safe-area-inset-right, 0px));
height: 100%;
.horizontal-nav-bar {
position: absolute;
@ -17,18 +22,20 @@
.vertical-nav-bar {
position: absolute;
top: var(--horizontal-nav-bar-size);
bottom: 0;
bottom: var(--calculated-bottom-safe-inset);
left: 0;
z-index: 1;
}
.nav-content-container {
position: absolute;
top: var(--horizontal-nav-bar-size);
padding-top: calc(var(--horizontal-nav-bar-size) + env(safe-area-inset-top, 0px));
top: 0;
right: 0;
bottom: 0;
left: var(--vertical-nav-bar-size);
z-index: 0;
overflow: scroll;
}
}
@ -36,7 +43,7 @@
.main-nav-bars-container {
.nav-content-container {
left: 0;
bottom: var(--vertical-nav-bar-size);
padding-bottom: var(--vertical-nav-bar-size);
}
.vertical-nav-bar {

View file

@ -1,10 +1,9 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const { VerticalNavBar, HorizontalNavBar } = require('stremio/common/NavBar');
const styles = require('./styles');
import React, { memo } from 'react';
import classnames from 'classnames';
import { VerticalNavBar, HorizontalNavBar } from 'stremio/components/NavBar';
import styles from './MainNavBars.less';
const TABS = [
{ id: 'board', label: 'Board', icon: 'home', href: '#/' },
@ -15,7 +14,14 @@ const TABS = [
{ id: 'settings', label: 'SETTINGS', icon: 'settings', href: '#/settings' },
];
const MainNavBars = React.memo(({ className, route, query, children }) => {
type Props = {
className: string,
route?: string,
query?: string,
children?: React.ReactNode,
};
const MainNavBars = memo(({ className, route, query, children }: Props) => {
return (
<div className={classnames(className, styles['main-nav-bars-container'])}>
<HorizontalNavBar
@ -37,13 +43,5 @@ const MainNavBars = React.memo(({ className, route, query, children }) => {
);
});
MainNavBars.displayName = 'MainNavBars';
export default MainNavBars;
MainNavBars.propTypes = {
className: PropTypes.string,
route: PropTypes.string,
query: PropTypes.string,
children: PropTypes.node
};
module.exports = MainNavBars;

View file

@ -0,0 +1,6 @@
// Copyright (C) 2017-2023 Smart code 203358507
import MainNavBars from './MainNavBars';
export default MainNavBars;

View file

@ -6,9 +6,9 @@ const classnames = require('classnames');
const { useTranslation } = require('react-i18next');
const filterInvalidDOMProps = require('filter-invalid-dom-props').default;
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 { default: Button } = require('stremio/components/Button');
const { default: Image } = require('stremio/components/Image');
const Multiselect = require('stremio/components/Multiselect');
const useBinaryState = require('stremio/common/useBinaryState');
const { ICON_FOR_TYPE } = require('stremio/common/CONSTANTS');
const styles = require('./styles');

View file

@ -1,29 +1,23 @@
// Copyright (C) 2017-2023 Smart code 203358507
// Copyright (C) 2017-2024 Smart code 203358507
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
@import (reference) '~stremio/common/screen-sizes.less';
:import('~stremio/common/Popup/styles.less') {
:import('~stremio/components/Popup/styles.less') {
popup-menu-container: menu-container;
}
:import('~stremio/common/Multiselect/styles.less') {
:import('~stremio/components/Multiselect/styles.less') {
multiselect-menu-container: menu-container;
multiselect-option-container: option-container;
multiselect-option-label: label;
}
:import('~stremio/common/PlayIconCircleCentered/styles.less') {
play-icon-circle-centered-background: background;
play-icon-circle-centered-icon: icon;
}
@play-icon-size: 4rem;
.meta-item-container {
padding: 1rem;
overflow: visible;
-webkit-tap-highlight-color: transparent;
&:hover, &:focus, &:global(.active), &:global(.selected) {
outline-style: none;

View file

@ -4,7 +4,7 @@ const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const { default: Icon } = require('@stremio/stremio-icons/react');
const Button = require('stremio/common/Button');
const { Button } = require('stremio/components');
const styles = require('./styles');
const { Tooltip } = require('stremio/common/Tooltips');

View file

@ -4,7 +4,7 @@ const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const { useTranslation } = require('react-i18next');
const Button = require('stremio/common/Button');
const { Button } = require('stremio/components');
const styles = require('./styles');
const MetaLinks = ({ className, label, links }) => {

View file

@ -6,10 +6,10 @@ const classnames = require('classnames');
const UrlUtils = require('url');
const { useTranslation } = require('react-i18next');
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');
const SharePrompt = require('stremio/common/SharePrompt');
const { default: Button } = require('stremio/components/Button');
const { default: Image } = require('stremio/components/Image');
const ModalDialog = require('stremio/components/ModalDialog');
const SharePrompt = require('stremio/components/SharePrompt');
const CONSTANTS = require('stremio/common/CONSTANTS');
const routesRegexp = require('stremio/common/routesRegexp');
const useBinaryState = require('stremio/common/useBinaryState');

View file

@ -5,7 +5,7 @@ const ReactIs = require('react-is');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const { default: Icon } = require('@stremio/stremio-icons/react');
const Button = require('stremio/common/Button');
const { Button } = require('stremio/components');
const CONSTANTS = require('stremio/common/CONSTANTS');
const useTranslate = require('stremio/common/useTranslate');
const MetaRowPlaceholder = require('./MetaRowPlaceholder');

View file

@ -5,7 +5,7 @@ const PropTypes = require('prop-types');
const classnames = require('classnames');
const { useTranslation } = require('react-i18next');
const { default: Icon } = require('@stremio/stremio-icons/react');
const Button = require('stremio/common/Button');
const { Button } = require('stremio/components');
const CONSTANTS = require('stremio/common/CONSTANTS');
const styles = require('./styles');

View file

@ -4,7 +4,7 @@ const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const { useRouteFocused, useModalsContainer } = require('stremio-router');
const Button = require('stremio/common/Button');
const { default: Button } = require('stremio/components/Button');
const { default: Icon } = require('@stremio/stremio-icons/react');
const { Modal } = require('stremio-router');
const styles = require('./styles');

View file

@ -4,9 +4,9 @@ const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
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');
const { Button } = require('stremio/components');
const Popup = require('stremio/components/Popup');
const ModalDialog = require('stremio/components/ModalDialog');
const useBinaryState = require('stremio/common/useBinaryState');
const styles = require('./styles');

View file

@ -3,7 +3,7 @@
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
@import (reference) '~stremio/common/screen-sizes.less';
:import('~stremio/common/Popup/styles.less') {
:import('~stremio/components/Popup/styles.less') {
popup-menu-container: menu-container;
}

View file

@ -0,0 +1,78 @@
// Copyright (C) 2017-2024 Smart code 203358507
import React, { useRef, useEffect, useCallback } from 'react';
import { Button } from 'stremio/components';
import { useTranslation } from 'react-i18next';
import classNames from 'classnames';
import Option from './Option';
import Icon from '@stremio/stremio-icons/react';
import styles from './Dropdown.less';
type Props = {
options: MultiselectMenuOption[];
selectedOption?: MultiselectMenuOption | null;
menuOpen: boolean | (() => void);
level: number;
setLevel: (level: number) => void;
onSelect: (value: number) => void;
};
const Dropdown = ({ level, setLevel, options, onSelect, selectedOption, menuOpen }: Props) => {
const { t } = useTranslation();
const optionsRef = useRef(new Map());
const containerRef = useRef(null);
const handleSetOptionRef = useCallback((value: number) => (node: HTMLButtonElement | null) => {
if (node) {
optionsRef.current.set(value, node);
} else {
optionsRef.current.delete(value);
}
}, []);
const handleBackClick = useCallback(() => {
setLevel(level - 1);
}, [setLevel, level]);
useEffect(() => {
if (menuOpen && selectedOption && containerRef.current) {
const selectedNode = optionsRef.current.get(selectedOption.value);
if (selectedNode) {
selectedNode.scrollIntoView({
behavior: 'smooth',
block: 'nearest'
});
}
}
}, [menuOpen, selectedOption]);
return (
<div
className={classNames(styles['dropdown'], { [styles['open']]: menuOpen })}
role={'listbox'}
ref={containerRef}
>
{level > 0 ?
<Button className={styles['back-button']} onClick={handleBackClick}>
<Icon name={'caret-left'} className={styles['back-button-icon']} />
{t('BACK')}
</Button>
: null
}
{options
.filter((option: MultiselectMenuOption) => !option.hidden)
.map((option: MultiselectMenuOption) => (
<Option
key={option.id}
ref={handleSetOptionRef(option.value)}
option={option}
onSelect={onSelect}
selectedOption={selectedOption}
/>
))
}
</div>
);
};
export default Dropdown;

View file

@ -1,8 +1,8 @@
// Copyright (C) 2017-2024 Smart code 203358507
import React, { useCallback, useMemo } from 'react';
import React, { useCallback, useMemo, forwardRef } from 'react';
import classNames from 'classnames';
import Button from 'stremio/common/Button';
import { Button } from 'stremio/components';
import styles from './Option.less';
import Icon from '@stremio/stremio-icons/react';
@ -12,7 +12,7 @@ type Props = {
onSelect: (value: number) => void;
};
const Option = ({ option, selectedOption, onSelect }: Props) => {
const Option = forwardRef<HTMLButtonElement, Props>(({ option, selectedOption, onSelect }, ref) => {
// consider using option.id === selectedOption?.id instead
const selected = useMemo(() => option?.value === selectedOption?.value, [option, selectedOption]);
@ -22,6 +22,7 @@ const Option = ({ option, selectedOption, onSelect }: Props) => {
return (
<Button
ref={ref}
className={classNames(styles['option'], { [styles['selected']]: selected })}
key={option.id}
onClick={handleClick}
@ -32,7 +33,6 @@ const Option = ({ option, selectedOption, onSelect }: Props) => {
selected && !option.level ?
<div className={styles['icon']} />
: null
}
{
option.level ?
@ -41,6 +41,8 @@ const Option = ({ option, selectedOption, onSelect }: Props) => {
}
</Button>
);
};
});
Option.displayName = 'Option';
export default Option;

View file

@ -1,7 +1,7 @@
// Copyright (C) 2017-2024 Smart code 203358507
import React from 'react';
import Button from 'stremio/common/Button';
import { Button } from 'stremio/components';
import useBinaryState from 'stremio/common/useBinaryState';
import Dropdown from './Dropdown';
import classNames from 'classnames';

View file

@ -4,8 +4,7 @@ const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const { default: Icon } = require('@stremio/stremio-icons/react');
const Button = require('stremio/common/Button');
const Image = require('stremio/common/Image');
const { Button, Image } = require('stremio/components');
const useFullscreen = require('stremio/common/useFullscreen');
const usePWA = require('stremio/common/usePWA');
const SearchBar = require('./SearchBar');

View file

@ -4,7 +4,7 @@ const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const { useRouteFocused } = require('stremio-router');
const Popup = require('stremio/common/Popup');
const Popup = require('stremio/components/Popup');
const useBinaryState = require('stremio/common/useBinaryState');
const NavMenuContent = require('./NavMenuContent');
const styles = require('./styles.less');

View file

@ -6,7 +6,7 @@ const classnames = require('classnames');
const { useTranslation } = require('react-i18next');
const { default: Icon } = require('@stremio/stremio-icons/react');
const { useServices } = require('stremio/services');
const Button = require('stremio/common/Button');
const { Button } = require('stremio/components');
const useFullscreen = require('stremio/common/useFullscreen');
const useProfile = require('stremio/common/useProfile');
const usePWA = require('stremio/common/usePWA');

View file

@ -3,7 +3,7 @@
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
@import (reference) '~stremio/common/screen-sizes.less';
:import('~stremio/common/Popup/styles.less') {
:import('~stremio/components/Popup/styles.less') {
popup-menu-container: menu-container;
}

View file

@ -7,14 +7,13 @@ const debounce = require('lodash.debounce');
const { useTranslation } = require('react-i18next');
const { default: Icon } = require('@stremio/stremio-icons/react');
const { useRouteFocused } = require('stremio-router');
const Button = require('stremio/common/Button');
const { Button, TextInput } = require('stremio/components');
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 { default: TextInput } = require('stremio/common/TextInput');
const SearchBar = React.memo(({ className, query, active }) => {
const { t } = useTranslation();

Some files were not shown because too many files have changed in this diff Show more