mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-03-11 17:15:48 +00:00
Merge branch 'development' into feat/captions-shortkey
This commit is contained in:
commit
ea5d05c31d
345 changed files with 11654 additions and 7042 deletions
99
.eslintrc
99
.eslintrc
|
|
@ -1,99 +0,0 @@
|
|||
{
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended"
|
||||
],
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
},
|
||||
"globals": {
|
||||
"YT": "readonly",
|
||||
"FB": "readonly",
|
||||
"cast": "readonly",
|
||||
"chrome": "readonly"
|
||||
},
|
||||
"env": {
|
||||
"node": true,
|
||||
"commonjs": true,
|
||||
"browser": true,
|
||||
"es6": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 11,
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"/*",
|
||||
"!/src"
|
||||
],
|
||||
"rules": {
|
||||
"arrow-parens": "error",
|
||||
"arrow-spacing": "error",
|
||||
"block-spacing": "error",
|
||||
"comma-spacing": "error",
|
||||
"eol-last": "error",
|
||||
"eqeqeq": "error",
|
||||
"func-call-spacing": "error",
|
||||
"indent": [
|
||||
"error",
|
||||
4,
|
||||
{
|
||||
"SwitchCase": 1
|
||||
}
|
||||
],
|
||||
"no-console": [
|
||||
"error",
|
||||
{
|
||||
"allow": [
|
||||
"warn",
|
||||
"error"
|
||||
]
|
||||
}
|
||||
],
|
||||
"no-extra-semi": "error",
|
||||
"no-eq-null": "error",
|
||||
"no-multi-spaces": "error",
|
||||
"no-multiple-empty-lines": [
|
||||
"error",
|
||||
{
|
||||
"max": 1
|
||||
}
|
||||
],
|
||||
"no-prototype-builtins": "off",
|
||||
"no-template-curly-in-string": "error",
|
||||
"no-trailing-spaces": "error",
|
||||
"no-useless-concat": "error",
|
||||
"no-unreachable": "error",
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"varsIgnorePattern": "_"
|
||||
}
|
||||
],
|
||||
"prefer-const": "error",
|
||||
"quotes": [
|
||||
"error",
|
||||
"single"
|
||||
],
|
||||
"quote-props": [
|
||||
"error",
|
||||
"as-needed",
|
||||
{
|
||||
"unnecessary": false
|
||||
}
|
||||
],
|
||||
"semi": "error",
|
||||
"semi-spacing": "error",
|
||||
"space-before-blocks": "error",
|
||||
"valid-typeof": [
|
||||
"error",
|
||||
{
|
||||
"requireStringLiterals": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
82
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
82
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
name: Bug report
|
||||
description: Report a bug in Stremio-Web
|
||||
title: "[Bug]: "
|
||||
labels:
|
||||
- bug
|
||||
body:
|
||||
- type: dropdown
|
||||
id: stremio_web_version
|
||||
attributes:
|
||||
label: "Stremio-Web Version"
|
||||
description: "Select the version of the Stremio-Web app you are using"
|
||||
options:
|
||||
- /development branch
|
||||
- web.stremio.com
|
||||
- web.strem.io
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: browser
|
||||
attributes:
|
||||
label: "Browser"
|
||||
description: "Which browser are you using?"
|
||||
options:
|
||||
- Chrome
|
||||
- Brave
|
||||
- Firefox
|
||||
- Arc
|
||||
- Opera
|
||||
- Safari
|
||||
- Edge
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: platform
|
||||
attributes:
|
||||
label: "Platform / Device type"
|
||||
description: "Which platform / device type are you using?"
|
||||
options:
|
||||
- Windows
|
||||
- Linux
|
||||
- MacOS
|
||||
- Android Web
|
||||
- Android PWA
|
||||
- iOS Web
|
||||
- iOS PWA
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: what_happened
|
||||
attributes:
|
||||
label: "What Happened?"
|
||||
description: "Describe the issue you encountered"
|
||||
placeholder: "Explain what you were doing, what you expected to happen, and what actually happened."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: "Logs"
|
||||
description: "Paste any relevant logs here (optional)"
|
||||
render: shell
|
||||
|
||||
- type: textarea
|
||||
id: notes
|
||||
attributes:
|
||||
label: "Notes"
|
||||
description: "Any additional information (optional)"
|
||||
|
||||
- type: checkboxes
|
||||
id: code_of_conduct
|
||||
attributes:
|
||||
label: "Code of Conduct"
|
||||
description: "Please confirm you have read and agree to the Code of Conduct"
|
||||
options:
|
||||
- label: "I agree"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
42
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
42
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
name: Feature request
|
||||
description: Suggest a new feature or enhancement for Stremio-Web
|
||||
title: "[Feature]: "
|
||||
labels:
|
||||
- enhancement
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "Thank you for your interest in improving Stremio-Web! Please provide as much detail as possible."
|
||||
|
||||
- type: textarea
|
||||
id: feature_description
|
||||
attributes:
|
||||
label: "Feature Description"
|
||||
description: "Describe the feature you would like to see implemented. What problem does it solve, or what functionality does it add?"
|
||||
placeholder: "Describe your idea in detail..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: proposed_solution
|
||||
attributes:
|
||||
label: "Proposed Solution"
|
||||
description: "If you have any thoughts on how this could be implemented or approached, share them here."
|
||||
placeholder: "Suggest possible approaches or solutions..."
|
||||
|
||||
- type: textarea
|
||||
id: additional_context
|
||||
attributes:
|
||||
label: "Additional Context or Screenshots"
|
||||
description: "Add any other context, screenshots, or references that may help us understand the request."
|
||||
placeholder: "Any extra info that might help..."
|
||||
|
||||
- type: checkboxes
|
||||
id: code_of_conduct
|
||||
attributes:
|
||||
label: "Code of Conduct"
|
||||
description: "Please confirm you have read and agree to the Code of Conduct"
|
||||
options:
|
||||
- label: "I agree"
|
||||
validations:
|
||||
required: true
|
||||
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
|
|
@ -21,6 +21,10 @@ jobs:
|
|||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Install NPM dependencies
|
||||
run: npm ci
|
||||
- name: Build
|
||||
|
|
@ -33,7 +37,7 @@ jobs:
|
|||
# "--parrents where no error if existing, make parent directories as needed."
|
||||
- run: mkdir -p ./build/${{ github.head_ref || github.ref_name }}
|
||||
- name: Deploy to GitHub Pages
|
||||
if: ${{ github.actor != 'dependabot[bot]' }}
|
||||
if: github.event.pull_request.head.repo.fork == false && github.actor != 'dependabot[bot]'
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
|||
1
.nvmrc
Normal file
1
.nvmrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
20
|
||||
37
CODE_OF_CONDUCT.md
Normal file
37
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and community a harassment-free experience for everyone, regardless of age, level of experience, education, nationality or race.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
- Using welcoming and inclusive language.
|
||||
- Being respectful of differing viewpoints and experiences.
|
||||
- Accepting constructive criticism.
|
||||
- Focusing on what is best for the community.
|
||||
- Showing empathy towards other community members.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or advances.
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks.
|
||||
- Public or private harassment.
|
||||
- Publishing others’ private information, such as a physical or electronic address, without explicit permission.
|
||||
- Other conduct which could reasonably be considered inappropriate in a professional setting.
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, pull requests, and other contributions that do not align with this Code of Conduct, as well as to temporarily or permanently ban any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all `stremio-web` spaces, and also applies when an individual is officially representing the project or its community in public spaces.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Pls be nice or we will ban you `:)`
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# Stremio Node 14.x
|
||||
# Stremio Node 20.x
|
||||
# the node version for running Stremio Web
|
||||
ARG NODE_VERSION=20-alpine
|
||||
FROM node:$NODE_VERSION AS base
|
||||
|
|
|
|||
102
eslint.config.mjs
Normal file
102
eslint.config.mjs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import globals from 'globals';
|
||||
import pluginJs from '@eslint/js';
|
||||
import tseslint from 'typescript-eslint';
|
||||
import pluginReact from 'eslint-plugin-react';
|
||||
import stylistic from '@stylistic/eslint-plugin';
|
||||
|
||||
export default [
|
||||
pluginJs.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
...tseslint.configs.stylistic,
|
||||
pluginReact.configs.flat.recommended,
|
||||
{
|
||||
plugins: {
|
||||
'@stylistic': stylistic
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.{js,mjs,cjs,ts,jsx,tsx}']
|
||||
},
|
||||
{
|
||||
files: ['**/*.js'],
|
||||
languageOptions: {
|
||||
sourceType: 'commonjs',
|
||||
ecmaVersion: 'latest',
|
||||
}
|
||||
},
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
YT: 'readonly',
|
||||
FB: 'readonly',
|
||||
cast: 'readonly',
|
||||
chrome: 'readonly',
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'no-redeclare': 'off',
|
||||
'eol-last': 'error',
|
||||
'eqeqeq': 'error',
|
||||
'no-console': ['error', {
|
||||
allow: [
|
||||
'warn',
|
||||
'error'
|
||||
]
|
||||
}],
|
||||
}
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'@typescript-eslint/no-redeclare': 'off',
|
||||
'@typescript-eslint/no-require-imports': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-empty-object-type': 'off',
|
||||
'@typescript-eslint/no-unused-expressions': 'off',
|
||||
'@typescript-eslint/consistent-type-definitions': 'off',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
'varsIgnorePattern': '_',
|
||||
'caughtErrorsIgnorePattern': '_',
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'@stylistic/arrow-parens': 'error',
|
||||
'@stylistic/arrow-spacing': 'error',
|
||||
'@stylistic/block-spacing': 'error',
|
||||
'@stylistic/comma-spacing': 'error',
|
||||
'@stylistic/semi-spacing': 'error',
|
||||
'@stylistic/space-before-blocks': 'error',
|
||||
'@stylistic/no-trailing-spaces': 'error',
|
||||
'@stylistic/func-call-spacing': 'error',
|
||||
'@stylistic/semi': 'error',
|
||||
'@stylistic/no-extra-semi': 'error',
|
||||
'@stylistic/eol-last': 'error',
|
||||
'@stylistic/no-multi-spaces': 'error',
|
||||
'@stylistic/no-multiple-empty-lines': ['error', {
|
||||
max: 1
|
||||
}],
|
||||
'@stylistic/indent': ['error', 4],
|
||||
'@stylistic/quotes': ['error', 'single'],
|
||||
}
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'react/display-name': 'off',
|
||||
}
|
||||
}
|
||||
];
|
||||
BIN
images/calendar_placeholder.png
Normal file
BIN
images/calendar_placeholder.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 202 KiB |
11799
package-lock.json
generated
11799
package-lock.json
generated
File diff suppressed because it is too large
Load diff
103
package.json
Executable file → Normal file
103
package.json
Executable file → Normal file
|
|
@ -1,77 +1,84 @@
|
|||
{
|
||||
"name": "stremio",
|
||||
"displayName": "Stremio",
|
||||
"version": "5.0.0-beta.8",
|
||||
"version": "5.0.0-beta.16",
|
||||
"author": "Smart Code OOD",
|
||||
"private": true,
|
||||
"license": "gpl-2.0",
|
||||
"scripts": {
|
||||
"start": "webpack serve --mode development",
|
||||
"start-prod": "webpack serve --mode production",
|
||||
"build": "webpack --mode production",
|
||||
"test": "jest",
|
||||
"lint": "eslint src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.16.0",
|
||||
"@sentry/browser": "6.13.3",
|
||||
"@stremio/stremio-colors": "5.0.1",
|
||||
"@stremio/stremio-core-web": "0.47.7",
|
||||
"@stremio/stremio-icons": "5.2.0",
|
||||
"@stremio/stremio-video": "0.0.38",
|
||||
"@babel/runtime": "7.26.0",
|
||||
"@sentry/browser": "8.42.0",
|
||||
"@stremio/stremio-colors": "5.2.0",
|
||||
"@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",
|
||||
"bowser": "2.11.0",
|
||||
"buffer": "6.0.3",
|
||||
"classnames": "2.3.1",
|
||||
"eventemitter3": "4.0.7",
|
||||
"filter-invalid-dom-props": "2.1.0",
|
||||
"hat": "0.0.3",
|
||||
"i18next": "^22.4.3",
|
||||
"langs": "^2.0.0",
|
||||
"classnames": "2.5.1",
|
||||
"eventemitter3": "5.0.1",
|
||||
"filter-invalid-dom-props": "3.0.1",
|
||||
"hat": "^0.0.3",
|
||||
"i18next": "^24.0.5",
|
||||
"langs": "github:Stremio/nodejs-langs",
|
||||
"lodash.debounce": "4.0.8",
|
||||
"lodash.intersection": "4.4.0",
|
||||
"lodash.isequal": "4.5.0",
|
||||
"lodash.throttle": "4.1.1",
|
||||
"magnet-uri": "6.2.0",
|
||||
"prop-types": "15.7.2",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-focus-lock": "2.9.1",
|
||||
"react-i18next": "^12.1.1",
|
||||
"react-is": "18.2.0",
|
||||
"prop-types": "15.8.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-focus-lock": "2.13.2",
|
||||
"react-i18next": "^15.1.3",
|
||||
"react-is": "18.3.1",
|
||||
"spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
|
||||
"stremio-translations": "github:Stremio/stremio-translations#b13b3e2653bd0dcf644d2a20ffa32074fe6532dd",
|
||||
"url": "0.11.0",
|
||||
"use-long-press": "^3.1.5"
|
||||
"stremio-translations": "github:Stremio/stremio-translations#a0f50634202f748a57907b645d2cd92fbaa479dd",
|
||||
"url": "0.11.4",
|
||||
"use-long-press": "^3.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.16.0",
|
||||
"@babel/plugin-proposal-class-properties": "7.16.0",
|
||||
"@babel/plugin-proposal-object-rest-spread": "7.16.0",
|
||||
"@babel/preset-env": "7.16.0",
|
||||
"@babel/preset-react": "7.16.0",
|
||||
"@types/react": "^18.2.9",
|
||||
"babel-loader": "8.2.3",
|
||||
"@babel/core": "7.26.0",
|
||||
"@babel/preset-env": "7.26.0",
|
||||
"@babel/preset-react": "7.26.3",
|
||||
"@eslint/js": "^9.16.0",
|
||||
"@stylistic/eslint-plugin": "^2.11.0",
|
||||
"@stylistic/eslint-plugin-jsx": "^2.11.0",
|
||||
"@types/hat": "^0.0.4",
|
||||
"@types/react": "^18.3.13",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"babel-loader": "9.2.1",
|
||||
"clean-webpack-plugin": "4.0.0",
|
||||
"copy-webpack-plugin": "9.0.1",
|
||||
"css-loader": "6.5.0",
|
||||
"cssnano": "5.0.8",
|
||||
"cssnano-preset-advanced": "5.1.4",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-plugin-react": "7.26.1",
|
||||
"html-webpack-plugin": "5.5.0",
|
||||
"jest": "27.3.1",
|
||||
"less": "4.1.2",
|
||||
"less-loader": "10.2.0",
|
||||
"mini-css-extract-plugin": "2.4.3",
|
||||
"postcss-loader": "6.2.0",
|
||||
"readdirp": "3.6.0",
|
||||
"terser-webpack-plugin": "5.2.4",
|
||||
"copy-webpack-plugin": "12.0.2",
|
||||
"css-loader": "6.11.0",
|
||||
"cssnano": "7.0.6",
|
||||
"cssnano-preset-advanced": "7.0.6",
|
||||
"eslint": "^9.16.0",
|
||||
"eslint-plugin-react": "^7.37.2",
|
||||
"globals": "^15.13.0",
|
||||
"html-webpack-plugin": "5.6.3",
|
||||
"jest": "29.7.0",
|
||||
"less": "4.2.1",
|
||||
"less-loader": "12.2.0",
|
||||
"mini-css-extract-plugin": "2.9.2",
|
||||
"postcss-loader": "8.1.1",
|
||||
"readdirp": "4.0.2",
|
||||
"terser-webpack-plugin": "5.3.10",
|
||||
"thread-loader": "^4.0.4",
|
||||
"ts-loader": "^9.5.1",
|
||||
"typescript": "^5.4.2",
|
||||
"webpack": "5.61.0",
|
||||
"webpack-cli": "4.9.1",
|
||||
"webpack-dev-server": "^4.7.4",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript-eslint": "^8.17.0",
|
||||
"webpack": "5.97.0",
|
||||
"webpack-cli": "5.1.4",
|
||||
"webpack-dev-server": "^5.1.0",
|
||||
"webpack-pwa-manifest": "^4.3.0",
|
||||
"workbox-webpack-plugin": "^6.5.3"
|
||||
"workbox-webpack-plugin": "^7.3.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ const { useTranslation } = require('react-i18next');
|
|||
const { Router } = require('stremio-router');
|
||||
const { Core, Shell, Chromecast, DragAndDrop, KeyboardShortcuts, ServicesProvider } = require('stremio/services');
|
||||
const { NotFound } = require('stremio/routes');
|
||||
const { ToastProvider, TooltipProvider, CONSTANTS, withCoreSuspender } = require('stremio/common');
|
||||
const { PlatformProvider, ToastProvider, TooltipProvider, CONSTANTS, withCoreSuspender } = require('stremio/common');
|
||||
const ServicesToaster = require('./ServicesToaster');
|
||||
const DeepLinkHandler = require('./DeepLinkHandler');
|
||||
const SearchParamsHandler = require('./SearchParamsHandler');
|
||||
|
|
@ -162,18 +162,20 @@ const App = () => {
|
|||
services.core.error instanceof Error ?
|
||||
<ErrorDialog className={styles['error-container']} />
|
||||
:
|
||||
<ToastProvider className={styles['toasts-container']}>
|
||||
<TooltipProvider className={styles['tooltip-container']}>
|
||||
<ServicesToaster />
|
||||
<DeepLinkHandler />
|
||||
<SearchParamsHandler />
|
||||
<RouterWithProtectedRoutes
|
||||
className={styles['router']}
|
||||
viewsConfig={routerViewsConfig}
|
||||
onPathNotMatch={onPathNotMatch}
|
||||
/>
|
||||
</TooltipProvider>
|
||||
</ToastProvider>
|
||||
<PlatformProvider>
|
||||
<ToastProvider className={styles['toasts-container']}>
|
||||
<TooltipProvider className={styles['tooltip-container']}>
|
||||
<ServicesToaster />
|
||||
<DeepLinkHandler />
|
||||
<SearchParamsHandler />
|
||||
<RouterWithProtectedRoutes
|
||||
className={styles['router']}
|
||||
viewsConfig={routerViewsConfig}
|
||||
onPathNotMatch={onPathNotMatch}
|
||||
/>
|
||||
</TooltipProvider>
|
||||
</ToastProvider>
|
||||
</PlatformProvider>
|
||||
:
|
||||
<div className={styles['loader-container']} />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }) => {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ const routerViewsConfig = [
|
|||
...routesRegexp.library,
|
||||
component: routes.Library
|
||||
},
|
||||
{
|
||||
...routesRegexp.calendar,
|
||||
component: routes.Calendar
|
||||
},
|
||||
{
|
||||
...routesRegexp.continuewatching,
|
||||
component: routes.Library
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -38,8 +52,17 @@
|
|||
--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;
|
||||
--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,12 +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%;
|
||||
|
|
@ -105,13 +132,13 @@ 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));
|
||||
overflow-y: auto;
|
||||
overflow: visible;
|
||||
scrollbar-width: none;
|
||||
pointer-events: none;
|
||||
|
||||
|
|
@ -192,4 +219,10 @@ html {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
:root {
|
||||
--bottom-overlay-size: 6rem;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,63 +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, ...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,
|
||||
...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,
|
||||
};
|
||||
|
||||
module.exports = Button;
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const Button = require('./Button');
|
||||
|
||||
module.exports = Button;
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const CHROMECAST_RECEIVER_APP_ID = '1634F54B';
|
||||
const DEFAULT_STREAMING_SERVER_URL = 'http://127.0.0.1:11470/';
|
||||
const SUBTITLES_SIZES = [75, 100, 125, 150, 175, 200, 250];
|
||||
const SUBTITLES_FONTS = ['PlusJakartaSans', 'Arial', 'Halvetica', 'Times New Roman', 'Verdana', 'Courier', 'Lucida Console', 'sans-serif', 'serif', 'monospace'];
|
||||
const SEEK_TIME_DURATIONS = [3000, 5000, 10000, 15000, 20000, 30000];
|
||||
|
|
@ -44,7 +45,7 @@ const EXTERNAL_PLAYERS = [
|
|||
{
|
||||
label: 'EXTERNAL_PLAYER_DISABLED',
|
||||
value: null,
|
||||
platforms: ['ios', 'android', 'windows', 'linux', 'macos'],
|
||||
platforms: ['ios', 'visionos', 'android', 'windows', 'linux', 'macos'],
|
||||
},
|
||||
{
|
||||
label: 'EXTERNAL_PLAYER_ALLOW_CHOOSING',
|
||||
|
|
@ -54,7 +55,7 @@ const EXTERNAL_PLAYERS = [
|
|||
{
|
||||
label: 'VLC',
|
||||
value: 'vlc',
|
||||
platforms: ['ios', 'android'],
|
||||
platforms: ['ios', 'visionos', 'android'],
|
||||
},
|
||||
{
|
||||
label: 'MPV',
|
||||
|
|
@ -79,17 +80,25 @@ const EXTERNAL_PLAYERS = [
|
|||
{
|
||||
label: 'Outplayer',
|
||||
value: 'outplayer',
|
||||
platforms: ['ios'],
|
||||
platforms: ['ios', 'visionos'],
|
||||
},
|
||||
{
|
||||
label: 'Moonplayer (VisionOS)',
|
||||
value: 'moonplayer',
|
||||
platforms: ['visionos'],
|
||||
},
|
||||
{
|
||||
label: 'M3U Playlist',
|
||||
value: 'm3u',
|
||||
platforms: ['ios', 'android', 'windows', 'linux', 'macos'],
|
||||
platforms: ['ios', 'visionos', 'android', 'windows', 'linux', 'macos'],
|
||||
},
|
||||
];
|
||||
|
||||
const WHITELISTED_HOSTS = ['stremio.com', 'strem.io', 'stremio.zendesk.com', 'google.com', 'youtube.com', 'twitch.tv', 'twitter.com', 'x.com', 'netflix.com', 'adex.network', 'amazon.com', 'forms.gle'];
|
||||
|
||||
module.exports = {
|
||||
CHROMECAST_RECEIVER_APP_ID,
|
||||
DEFAULT_STREAMING_SERVER_URL,
|
||||
SUBTITLES_SIZES,
|
||||
SUBTITLES_FONTS,
|
||||
SEEK_TIME_DURATIONS,
|
||||
|
|
@ -105,4 +114,5 @@ module.exports = {
|
|||
TYPE_PRIORITIES,
|
||||
ICON_FOR_TYPE,
|
||||
EXTERNAL_PLAYERS,
|
||||
WHITELISTED_HOSTS,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const Checkbox = require('./Checkbox');
|
||||
|
||||
module.exports = Checkbox;
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React, { memo, useEffect, useRef, useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Chip from './Chip';
|
||||
import styles from './Chips.less';
|
||||
|
||||
type Option = {
|
||||
label: string,
|
||||
value: string,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
options: Option[],
|
||||
selected: string[],
|
||||
onSelect: (value: string) => {},
|
||||
};
|
||||
|
||||
const SCROLL_THRESHOLD = 1;
|
||||
|
||||
const Chips = memo(({ options, selected, onSelect }: Props) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [scrollPosition, setScrollPosition] = useState('left');
|
||||
|
||||
useEffect(() => {
|
||||
const onScroll = ({ target }: Event) => {
|
||||
const { scrollLeft, scrollWidth, offsetWidth} = target as HTMLDivElement;
|
||||
const position =
|
||||
(scrollLeft - SCROLL_THRESHOLD) <= 0 ? 'left' :
|
||||
(scrollLeft + offsetWidth + SCROLL_THRESHOLD) >= scrollWidth ? 'right' :
|
||||
'center';
|
||||
setScrollPosition(position);
|
||||
};
|
||||
|
||||
ref.current?.addEventListener('scroll', onScroll);
|
||||
return () => ref.current?.removeEventListener('scroll', onScroll);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={ref} className={classNames(styles['chips'], [styles[scrollPosition]])}>
|
||||
{
|
||||
options.map(({ label, value }) => (
|
||||
<Chip
|
||||
key={value}
|
||||
label={label}
|
||||
value={value}
|
||||
active={selected.includes(value)}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default Chips;
|
||||
|
|
@ -37,6 +37,7 @@ const useCoreSuspender = () => {
|
|||
return React.useContext(CoreSuspenderContext);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
const withCoreSuspender = (Component, Fallback = () => { }) => {
|
||||
return function withCoreSuspender(props) {
|
||||
const { core } = useServices();
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const Image = require('./Image');
|
||||
|
||||
module.exports = Image;
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const MainNavBars = require('./MainNavBars');
|
||||
|
||||
module.exports = MainNavBars;
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
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 styles = require('./styles');
|
||||
|
||||
const PaginationInput = ({ className, label, dataset, onSelect, ...props }) => {
|
||||
const prevNextButtonOnClick = React.useCallback((event) => {
|
||||
if (typeof onSelect === 'function') {
|
||||
onSelect({
|
||||
type: 'change-page',
|
||||
value: event.currentTarget.dataset.value,
|
||||
dataset: dataset,
|
||||
reactEvent: event,
|
||||
nativeEvent: event.nativeEvent
|
||||
});
|
||||
}
|
||||
}, [dataset, onSelect]);
|
||||
return (
|
||||
<div {...props} className={classnames(className, styles['pagination-input-container'])} >
|
||||
<Button className={styles['prev-button-container']} title={'Previous page'} data-value={'prev'} onClick={prevNextButtonOnClick}>
|
||||
<Icon className={styles['icon']} name={'chevron-back'} />
|
||||
</Button>
|
||||
<div className={styles['label-container']} title={label}>
|
||||
<div className={styles['label']}>{label}</div>
|
||||
</div>
|
||||
<Button className={styles['next-button-container']} title={'Next page'} data-value={'next'} onClick={prevNextButtonOnClick}>
|
||||
<Icon className={styles['icon']} name={'chevron-forward'} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
PaginationInput.propTypes = {
|
||||
className: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
dataset: PropTypes.object,
|
||||
onSelect: PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = PaginationInput;
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const PaginationInput = require('./PaginationInput');
|
||||
|
||||
module.exports = PaginationInput;
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
|
||||
|
||||
.pagination-input-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
.prev-button-container, .next-button-container {
|
||||
flex: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--overlay-color);
|
||||
|
||||
.icon {
|
||||
display: block;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
.label-container {
|
||||
flex: 1;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--overlay-color);
|
||||
|
||||
.label {
|
||||
flex: none;
|
||||
min-width: 1.2rem;
|
||||
max-width: 3rem;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
51
src/common/Platform/Platform.tsx
Normal file
51
src/common/Platform/Platform.tsx
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import React, { createContext, useContext } from 'react';
|
||||
import { WHITELISTED_HOSTS } from 'stremio/common/CONSTANTS';
|
||||
import useShell from './useShell';
|
||||
import { name, isMobile } from './device';
|
||||
|
||||
interface PlatformContext {
|
||||
name: string;
|
||||
isMobile: boolean;
|
||||
openExternal: (url: string) => void;
|
||||
}
|
||||
|
||||
const PlatformContext = createContext<PlatformContext>({} as PlatformContext);
|
||||
|
||||
type Props = {
|
||||
children: JSX.Element;
|
||||
};
|
||||
|
||||
const PlatformProvider = ({ children }: Props) => {
|
||||
const shell = useShell();
|
||||
|
||||
const openExternal = (url: string) => {
|
||||
try {
|
||||
const { hostname } = new URL(url);
|
||||
const isWhitelisted = WHITELISTED_HOSTS.some((host: string) => hostname.endsWith(host));
|
||||
const finalUrl = !isWhitelisted ? `https://www.stremio.com/warning#${encodeURIComponent(url)}` : url;
|
||||
|
||||
if (shell.active) {
|
||||
shell.send('open-external', finalUrl);
|
||||
} else {
|
||||
window.open(finalUrl, '_blank');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to parse external url:', e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<PlatformContext.Provider value={{ openExternal, name, isMobile }}>
|
||||
{children}
|
||||
</PlatformContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const usePlatform = () => {
|
||||
return useContext(PlatformContext);
|
||||
};
|
||||
|
||||
export {
|
||||
PlatformProvider,
|
||||
usePlatform
|
||||
};
|
||||
31
src/common/Platform/device.ts
Normal file
31
src/common/Platform/device.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import Bowser from 'bowser';
|
||||
|
||||
const APPLE_MOBILE_DEVICES = [
|
||||
'iPad Simulator',
|
||||
'iPhone Simulator',
|
||||
'iPod Simulator',
|
||||
'iPad',
|
||||
'iPhone',
|
||||
'iPod',
|
||||
];
|
||||
|
||||
const { userAgent, platform, maxTouchPoints } = globalThis.navigator;
|
||||
|
||||
// this detects ipad properly in safari
|
||||
// while bowser does not
|
||||
const isIOS = APPLE_MOBILE_DEVICES.includes(platform) || (userAgent.includes('Mac') && 'ontouchend' in document);
|
||||
|
||||
// Edge case: iPad is included in this function
|
||||
// Keep in mind maxTouchPoints for Vision Pro might change in the future
|
||||
const isVisionOS = userAgent.includes('Macintosh') && maxTouchPoints === 5;
|
||||
|
||||
const bowser = Bowser.getParser(userAgent);
|
||||
const os = bowser.getOSName().toLowerCase();
|
||||
|
||||
const name = isVisionOS ? 'visionos' : isIOS ? 'ios' : os || 'unknown';
|
||||
const isMobile = ['ios', 'android'].includes(name);
|
||||
|
||||
export {
|
||||
name,
|
||||
isMobile,
|
||||
};
|
||||
5
src/common/Platform/index.ts
Normal file
5
src/common/Platform/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { PlatformProvider, usePlatform } from './Platform';
|
||||
export {
|
||||
PlatformProvider,
|
||||
usePlatform,
|
||||
};
|
||||
22
src/common/Platform/useShell.ts
Normal file
22
src/common/Platform/useShell.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
const createId = () => Math.floor(Math.random() * 9999) + 1;
|
||||
|
||||
const useShell = () => {
|
||||
const transport = globalThis?.qt?.webChannelTransport;
|
||||
|
||||
const send = (method: string, ...args: (string | number)[]) => {
|
||||
transport?.send(JSON.stringify({
|
||||
id: createId(),
|
||||
type: 6,
|
||||
object: 'transport',
|
||||
method: 'onEvent',
|
||||
args: [method, ...args],
|
||||
}));
|
||||
};
|
||||
|
||||
return {
|
||||
active: !!transport,
|
||||
send,
|
||||
};
|
||||
};
|
||||
|
||||
export default useShell;
|
||||
|
|
@ -1,43 +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 TextInput = React.forwardRef((props, ref) => {
|
||||
const onKeyDown = React.useCallback((event) => {
|
||||
if (typeof props.onKeyDown === 'function') {
|
||||
props.onKeyDown(event);
|
||||
}
|
||||
|
||||
if (event.key === 'Enter' && !event.nativeEvent.submitPrevented && typeof props.onSubmit === 'function') {
|
||||
props.onSubmit(event);
|
||||
}
|
||||
}, [props.onKeyDown, props.onSubmit]);
|
||||
return (
|
||||
<input
|
||||
size={1}
|
||||
autoCorrect={'off'}
|
||||
autoCapitalize={'off'}
|
||||
autoComplete={'off'}
|
||||
spellCheck={false}
|
||||
tabIndex={0}
|
||||
{...props}
|
||||
ref={ref}
|
||||
className={classnames(props.className, styles['text-input'], { 'disabled': props.disabled })}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
TextInput.displayName = 'TextInput';
|
||||
|
||||
TextInput.propTypes = {
|
||||
className: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
onKeyDown: PropTypes.func,
|
||||
onSubmit: PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = TextInput;
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const TextInput = require('./TextInput');
|
||||
|
||||
module.exports = TextInput;
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
|
||||
const React = require('react');
|
||||
|
||||
const ToastContext = React.createContext({
|
||||
|
|
|
|||
|
|
@ -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 }) => {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -19,4 +19,36 @@
|
|||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
:global(.animation-slide-up) {
|
||||
:local {
|
||||
animation-name: slide-up;
|
||||
}
|
||||
|
||||
animation-timing-function: ease-out;
|
||||
animation-duration: 0.1s;
|
||||
}
|
||||
|
||||
@keyframes slide-up {
|
||||
0% {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(0%);
|
||||
}
|
||||
}
|
||||
|
||||
.slide-left-enter {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
.slide-left-active {
|
||||
transform: translateX(0%);
|
||||
transition: transform 0.3s cubic-bezier(0.32, 0, 0.67, 0);
|
||||
}
|
||||
|
||||
.slide-left-exit {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
|
@ -1,29 +1,6 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const AddonDetailsModal = require('./AddonDetailsModal');
|
||||
const Button = require('./Button');
|
||||
const Checkbox = require('./Checkbox');
|
||||
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 { HorizontalNavBar, VerticalNavBar } = require('./NavBar');
|
||||
const PaginationInput = require('./PaginationInput');
|
||||
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 TextInput = require('./TextInput');
|
||||
const { PlatformProvider, usePlatform } = require('./Platform');
|
||||
const { ToastProvider, useToast } = require('./Toast');
|
||||
const { TooltipProvider, Tooltip } = require('./Tooltips');
|
||||
const comparatorWithPriorities = require('./comparatorWithPriorities');
|
||||
|
|
@ -32,6 +9,7 @@ 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');
|
||||
|
|
@ -44,35 +22,10 @@ const useProfile = require('./useProfile');
|
|||
const useStreamingServer = require('./useStreamingServer');
|
||||
const useTorrent = require('./useTorrent');
|
||||
const useTranslate = require('./useTranslate');
|
||||
const platform = require('./platform');
|
||||
const EventModal = require('./EventModal');
|
||||
|
||||
module.exports = {
|
||||
AddonDetailsModal,
|
||||
Button,
|
||||
Checkbox,
|
||||
Chips,
|
||||
ColorInput,
|
||||
ContinueWatchingItem,
|
||||
DelayedRenderer,
|
||||
Image,
|
||||
LibItem,
|
||||
MainNavBars,
|
||||
MetaItem,
|
||||
MetaPreview,
|
||||
MetaRow,
|
||||
ModalDialog,
|
||||
Multiselect,
|
||||
HorizontalNavBar,
|
||||
VerticalNavBar,
|
||||
PaginationInput,
|
||||
PlayIconCircleCentered,
|
||||
Popup,
|
||||
SearchBar,
|
||||
StreamingServerWarning,
|
||||
SharePrompt,
|
||||
Slider,
|
||||
TextInput,
|
||||
PlatformProvider,
|
||||
usePlatform,
|
||||
ToastProvider,
|
||||
useToast,
|
||||
TooltipProvider,
|
||||
|
|
@ -84,6 +37,7 @@ module.exports = {
|
|||
getVisibleChildrenRange,
|
||||
interfaceLanguages,
|
||||
languageNames,
|
||||
languages,
|
||||
routesRegexp,
|
||||
useAnimationFrame,
|
||||
useBinaryState,
|
||||
|
|
@ -96,6 +50,4 @@ module.exports = {
|
|||
useStreamingServer,
|
||||
useTorrent,
|
||||
useTranslate,
|
||||
platform,
|
||||
EventModal,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
"name": "български език",
|
||||
"codes": ["bg-BG", "bul"]
|
||||
},
|
||||
{
|
||||
"name": "বাংলা",
|
||||
"codes": ["bn-Bd", "ben"]
|
||||
},
|
||||
{
|
||||
"name": "català",
|
||||
"codes": ["ca-CA", "cat"]
|
||||
|
|
@ -75,6 +79,14 @@
|
|||
"name": "italiano",
|
||||
"codes": ["it-IT", "ita"]
|
||||
},
|
||||
{
|
||||
"name": "日本語 (にほんご)",
|
||||
"codes": ["ja-JP", "jpn"]
|
||||
},
|
||||
{
|
||||
"name": "한국어",
|
||||
"codes": ["ko-KR", "kor"]
|
||||
},
|
||||
{
|
||||
"name": "македонски јазик",
|
||||
"codes": ["mk-MK", "mkd"]
|
||||
|
|
@ -135,6 +147,10 @@
|
|||
"name": "українська мова",
|
||||
"codes": ["uk-UA", "ukr"]
|
||||
},
|
||||
{
|
||||
"name": "Tiếng Việt",
|
||||
"codes": ["vi-VN", "vie"]
|
||||
},
|
||||
{
|
||||
"name": "中文(中华人民共和国)",
|
||||
"codes": ["zh-CN", "zho"]
|
||||
|
|
@ -147,4 +163,4 @@
|
|||
"name": "中文(台灣)",
|
||||
"codes": ["zh-TW", "zho"]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
|
|||
25
src/common/languages.ts
Normal file
25
src/common/languages.ts
Normal 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,
|
||||
};
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
// this detects ipad properly in safari
|
||||
// while bowser does not
|
||||
function iOS() {
|
||||
return [
|
||||
'iPad Simulator',
|
||||
'iPhone Simulator',
|
||||
'iPod Simulator',
|
||||
'iPad',
|
||||
'iPhone',
|
||||
'iPod'
|
||||
].includes(navigator.platform)
|
||||
|| (navigator.userAgent.includes('Mac') && 'ontouchend' in document);
|
||||
}
|
||||
|
||||
const Bowser = require('bowser');
|
||||
|
||||
const browser = Bowser.parse(window.navigator?.userAgent || '');
|
||||
|
||||
const name = iOS() ? 'ios' : (browser?.os?.name || 'unknown').toLowerCase();
|
||||
|
||||
module.exports = {
|
||||
name,
|
||||
isMobile: () => {
|
||||
return name === 'ios' || name === 'android';
|
||||
}
|
||||
};
|
||||
|
|
@ -17,6 +17,10 @@ const routesRegexp = {
|
|||
regexp: /^\/library(?:\/([^/]*))?$/,
|
||||
urlParamsNames: ['type']
|
||||
},
|
||||
calendar: {
|
||||
regexp: /^\/calendar(?:\/([^/]*)\/([^/]*))?$/,
|
||||
urlParamsNames: ['year', 'month']
|
||||
},
|
||||
continuewatching: {
|
||||
regexp: /^\/continuewatching(?:\/([^/]*))?$/,
|
||||
urlParamsNames: ['type']
|
||||
|
|
|
|||
8
src/common/useBinaryState.d.ts
vendored
Normal file
8
src/common/useBinaryState.d.ts
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
declare const useBinaryState: (initialValue?: boolean) => [
|
||||
boolean,
|
||||
() => void,
|
||||
() => void,
|
||||
() => void,
|
||||
];
|
||||
|
||||
export = useBinaryState;
|
||||
2
src/common/useNotifications.d.ts
vendored
2
src/common/useNotifications.d.ts
vendored
|
|
@ -1,2 +1,2 @@
|
|||
declare const useNotifcations: () => Notifications;
|
||||
export = useNotifcations;
|
||||
export = useNotifcations;
|
||||
|
|
|
|||
27
src/common/useOutsideClick.ts
Normal file
27
src/common/useOutsideClick.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
const useOutsideClick = (callback: () => void) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent | TouchEvent) => {
|
||||
if (ref.current && !ref.current.contains(event.target as Node)) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mouseup', handleClickOutside);
|
||||
document.addEventListener('touchend', handleClickOutside);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mouseup', handleClickOutside);
|
||||
document.removeEventListener('touchend', handleClickOutside);
|
||||
};
|
||||
}, [callback]);
|
||||
|
||||
return ref;
|
||||
};
|
||||
|
||||
export default useOutsideClick;
|
||||
2
src/common/useProfile.d.ts
vendored
2
src/common/useProfile.d.ts
vendored
|
|
@ -1,2 +1,2 @@
|
|||
declare const useProfile: () => Profile;
|
||||
export = useProfile;
|
||||
export = useProfile;
|
||||
|
|
|
|||
2
src/common/useStreamingServer.d.ts
vendored
2
src/common/useStreamingServer.d.ts
vendored
|
|
@ -1,2 +1,2 @@
|
|||
declare const useStreamingServer: () => StreamingServer;
|
||||
export = useStreamingServer;
|
||||
export = useStreamingServer;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ const useTranslate = () => {
|
|||
|
||||
const catalogTitle = useCallback(({ addon, id, name, type } = {}, withType = true) => {
|
||||
if (addon && id && name) {
|
||||
const partialKey = `${addon.manifest.id.replaceAll('.', '_')}_${id}`;
|
||||
const partialKey = `${addon.manifest.id.split('.').join('_')}_${id}`;
|
||||
const translatedName = stringWithPrefix(partialKey, 'CATALOG_', name);
|
||||
|
||||
if (type && withType) {
|
||||
|
|
|
|||
|
|
@ -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 }) => {
|
||||
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
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');
|
||||
const AddonDetailsWithRemoteAndLocalAddon = withRemoteAndLocalAddon(require('./AddonDetails'));
|
||||
const useAddonDetails = require('./useAddonDetails');
|
||||
|
|
@ -43,6 +44,7 @@ function withRemoteAndLocalAddon(AddonDetails) {
|
|||
|
||||
const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => {
|
||||
const { core } = useServices();
|
||||
const platform = usePlatform();
|
||||
const addonDetails = useAddonDetails(transportUrl);
|
||||
const modalButtons = React.useMemo(() => {
|
||||
const cancelButton = {
|
||||
|
|
@ -68,7 +70,7 @@ const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => {
|
|||
label: 'Configure',
|
||||
props: {
|
||||
onClick: (event) => {
|
||||
window.open(transportUrl.replace('manifest.json', 'configure'));
|
||||
platform.openExternal(transportUrl.replace('manifest.json', 'configure'));
|
||||
if (typeof onCloseRequest === 'function') {
|
||||
onCloseRequest({
|
||||
type: 'configure',
|
||||
|
|
@ -105,7 +107,9 @@ const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => {
|
|||
}
|
||||
}
|
||||
:
|
||||
addonDetails.remoteAddon !== null && addonDetails.remoteAddon.content.type === 'Ready' ?
|
||||
addonDetails.remoteAddon !== null &&
|
||||
addonDetails.remoteAddon.content.type === 'Ready' &&
|
||||
!addonDetails.remoteAddon.content.content.manifest.behaviorHints.configurationRequired ?
|
||||
{
|
||||
|
||||
className: styles['install-button'],
|
||||
|
|
@ -131,7 +135,7 @@ const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => {
|
|||
}
|
||||
:
|
||||
null;
|
||||
return toggleButton !== null ? configureButton ? [cancelButton, configureButton, toggleButton] : [cancelButton, toggleButton] : [cancelButton];
|
||||
return configureButton && toggleButton ? [cancelButton, configureButton, toggleButton] : configureButton ? [cancelButton, configureButton] : toggleButton ? [cancelButton, toggleButton] : [cancelButton];
|
||||
}, [addonDetails, onCloseRequest]);
|
||||
const modalBackground = React.useMemo(() => {
|
||||
return addonDetails.remoteAddon?.content.type === 'Ready' ? addonDetails.remoteAddon.content.content.manifest.background : null;
|
||||
|
|
@ -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;
|
||||
107
src/components/BottomSheet/BottomSheet.less
Normal file
107
src/components/BottomSheet/BottomSheet.less
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
@import (reference) '~stremio/common/screen-sizes.less';
|
||||
|
||||
.bottom-sheet {
|
||||
z-index: 99;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
.backdrop {
|
||||
z-index: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--primary-background-color);
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.1s ease-out;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.container {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
max-height: calc(100% - var(--horizontal-nav-bar-size));
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
padding-bottom: 1rem;
|
||||
border-radius: 2rem 2rem 0 0;
|
||||
background-color: var(--modal-background-color);
|
||||
box-shadow: var(--outer-glow);
|
||||
overflow: hidden;
|
||||
|
||||
&:not(.dragging) {
|
||||
transition: transform 0.1s ease-out;
|
||||
}
|
||||
|
||||
.heading {
|
||||
position: relative;
|
||||
|
||||
.handle {
|
||||
position: relative;
|
||||
height: 2.5rem;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
height: 0.3rem;
|
||||
width: 3rem;
|
||||
border-radius: 1rem;
|
||||
background-color: var(--primary-foreground-color);
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 1rem;
|
||||
padding-left: 1.5rem;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (orientation: landscape) {
|
||||
.bottom-sheet {
|
||||
.container {
|
||||
max-width: 90%;
|
||||
}
|
||||
}
|
||||
}
|
||||
87
src/components/BottomSheet/BottomSheet.tsx
Normal file
87
src/components/BottomSheet/BottomSheet.tsx
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import classNames from 'classnames';
|
||||
import useBinaryState from 'stremio/common/useBinaryState';
|
||||
import styles from './BottomSheet.less';
|
||||
|
||||
const CLOSE_THRESHOLD = 100;
|
||||
|
||||
type Props = {
|
||||
children: JSX.Element,
|
||||
title: string,
|
||||
show: boolean,
|
||||
onClose: () => void,
|
||||
};
|
||||
|
||||
const BottomSheet = ({ children, title, show, onClose }: Props) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [startOffset, setStartOffset] = useState(0);
|
||||
const [offset, setOffset] = useState(0);
|
||||
|
||||
const [opened, open, close] = useBinaryState();
|
||||
|
||||
const containerStyle = useMemo(() => ({
|
||||
transform: `translateY(${offset}px)`
|
||||
}), [offset]);
|
||||
|
||||
const containerHeight = () => containerRef.current?.offsetHeight ?? 0;
|
||||
|
||||
const onCloseRequest = () => setOffset(containerHeight());
|
||||
|
||||
const onTouchStart = ({ touches }: React.TouchEvent<HTMLDivElement>) => {
|
||||
const { clientY } = touches[0];
|
||||
setStartOffset(clientY);
|
||||
};
|
||||
|
||||
const onTouchMove = useCallback(({ touches }: React.TouchEvent<HTMLDivElement>) => {
|
||||
const { clientY } = touches[0];
|
||||
setOffset(Math.max(0, clientY - startOffset));
|
||||
}, [startOffset]);
|
||||
|
||||
const onTouchEnd = () => {
|
||||
setOffset((offset) => offset > CLOSE_THRESHOLD ? containerHeight() : 0);
|
||||
setStartOffset(0);
|
||||
};
|
||||
|
||||
const onTransitionEnd = useCallback(() => {
|
||||
(offset === containerHeight()) && close();
|
||||
}, [offset]);
|
||||
|
||||
useEffect(() => {
|
||||
setOffset(0);
|
||||
show ? open() : close();
|
||||
}, [show]);
|
||||
|
||||
useEffect(() => {
|
||||
!opened && onClose();
|
||||
}, [opened]);
|
||||
|
||||
return opened && createPortal((
|
||||
<div className={styles['bottom-sheet']}>
|
||||
<div className={styles['backdrop']} onClick={onCloseRequest} />
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={classNames(styles['container'], { [styles['dragging']]: startOffset }, 'animation-slide-up')}
|
||||
style={containerStyle}
|
||||
onTouchStart={onTouchStart}
|
||||
onTouchMove={onTouchMove}
|
||||
onTouchEnd={onTouchEnd}
|
||||
onTransitionEnd={onTransitionEnd}
|
||||
>
|
||||
<div className={styles['heading']}>
|
||||
<div className={styles['handle']} />
|
||||
<div className={styles['title']}>
|
||||
{title}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles['content']} onClick={onCloseRequest}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
), document.body);
|
||||
};
|
||||
|
||||
export default BottomSheet;
|
||||
4
src/components/BottomSheet/index.ts
Normal file
4
src/components/BottomSheet/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import BottomSheet from './BottomSheet';
|
||||
export default BottomSheet;
|
||||
|
|
@ -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';
|
||||
|
||||
71
src/components/Button/Button.tsx
Normal file
71
src/components/Button/Button.tsx
Normal 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;
|
||||
|
||||
6
src/components/Button/index.ts
Normal file
6
src/components/Button/index.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
import Button from './Button';
|
||||
|
||||
export default Button;
|
||||
|
||||
|
|
@ -16,18 +16,19 @@
|
|||
text-transform: capitalize;
|
||||
padding: 0 1.75rem;
|
||||
border-radius: @height;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
background-color: transparent;
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
opacity: 0.6;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--overlay-color);
|
||||
transition: background-color 0.1s ease-out;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.active {
|
||||
font-weight: 700;
|
||||
opacity: 1;
|
||||
background-color: var(--quaternary-accent-color);
|
||||
transition: background-color 0.1s ease-in;
|
||||
}
|
||||
|
|
@ -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 = {
|
||||
|
|
@ -42,4 +42,4 @@ const Chip = memo(({ label, value, active, onSelect }: Props) => {
|
|||
);
|
||||
});
|
||||
|
||||
export default Chip;
|
||||
export default Chip;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import Chip from './Chip';
|
||||
export default Chip;
|
||||
export default Chip;
|
||||
10
src/components/Chips/Chips.less
Normal file
10
src/components/Chips/Chips.less
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
.chips {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 1rem;
|
||||
}
|
||||
37
src/components/Chips/Chips.tsx
Normal file
37
src/components/Chips/Chips.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { HorizontalScroll } from 'stremio/components';
|
||||
import Chip from './Chip';
|
||||
import styles from './Chips.less';
|
||||
|
||||
type Option = {
|
||||
label: string,
|
||||
value: string,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
options: Option[],
|
||||
selected: string[],
|
||||
onSelect: (value: string) => {},
|
||||
};
|
||||
|
||||
const Chips = memo(({ options, selected, onSelect }: Props) => {
|
||||
return (
|
||||
<HorizontalScroll className={styles['chips']}>
|
||||
{
|
||||
options.map(({ label, value }) => (
|
||||
<Chip
|
||||
key={value}
|
||||
label={label}
|
||||
value={value}
|
||||
active={selected.includes(value)}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</HorizontalScroll>
|
||||
);
|
||||
});
|
||||
|
||||
export default Chips;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import Chips from './Chips';
|
||||
export default Chips;
|
||||
export default Chips;
|
||||
|
|
@ -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');
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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();
|
||||
|
|
@ -17,7 +17,8 @@ const DelayedRenderer = ({ children, delay }) => {
|
|||
};
|
||||
|
||||
DelayedRenderer.propTypes = {
|
||||
children: PropTypes.node
|
||||
children: PropTypes.node,
|
||||
delay: PropTypes.number,
|
||||
};
|
||||
|
||||
module.exports = DelayedRenderer;
|
||||
|
|
@ -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');
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -19,26 +19,30 @@
|
|||
flex-direction: column;
|
||||
align-items: center;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
|
||||
.modal-dialog-content {
|
||||
.body-container {
|
||||
overflow-y: visible;
|
||||
}
|
||||
|
||||
|
||||
.image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin-top: -10rem;
|
||||
position: absolute;
|
||||
top: -10rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
object-fit: cover;
|
||||
width: 30rem;
|
||||
height: 30rem;
|
||||
}
|
||||
|
||||
|
||||
.info-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 2.5rem;
|
||||
padding: 1rem 4rem;
|
||||
margin-top: -7rem;
|
||||
|
||||
padding: 10rem 4rem 0;
|
||||
|
||||
.title-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -50,7 +54,7 @@
|
|||
text-align: center;
|
||||
padding: 0 6rem;
|
||||
}
|
||||
|
||||
|
||||
.label {
|
||||
color: var(--primary-foreground-color);
|
||||
font-size: 1rem;
|
||||
|
|
@ -58,7 +62,7 @@
|
|||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.addon-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -76,19 +80,19 @@
|
|||
color: var(--primary-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.action-button {
|
||||
background-color: var(--primary-foreground-color);
|
||||
border: 2px solid var(--primary-foreground-color);
|
||||
padding: 0.8rem 2rem;
|
||||
border-radius: 2rem;
|
||||
|
||||
|
||||
.button-label {
|
||||
color: var(--primary-accent-color);
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
|
@ -96,15 +100,34 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
@media (orientation: landscape) and (max-height: @minimum) {
|
||||
.event-modal {
|
||||
.modal-dialog-container {
|
||||
.modal-dialog-content {
|
||||
.image {
|
||||
height: 125%;
|
||||
width: 125%;
|
||||
overflow-y: auto;
|
||||
|
||||
.body-container {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.image {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.info-container {
|
||||
padding: 1rem 4rem 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: @minimum) {
|
||||
.event-modal {
|
||||
.modal-dialog-container {
|
||||
.modal-dialog-content {
|
||||
.info-container {
|
||||
.title-container {
|
||||
.title {
|
||||
|
|
@ -120,4 +143,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,13 +2,8 @@
|
|||
|
||||
@mask-width: 10%;
|
||||
|
||||
.chips {
|
||||
.horizontal-scroll {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 1rem;
|
||||
overflow-x: auto;
|
||||
|
||||
&.left {
|
||||
|
|
@ -22,4 +17,4 @@
|
|||
&.center {
|
||||
mask-image: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) @mask-width, rgba(0, 0, 0, 1) calc(100% - @mask-width), rgba(0, 0, 0, 0) 100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
40
src/components/HorizontalScroll/HorizontalScroll.tsx
Normal file
40
src/components/HorizontalScroll/HorizontalScroll.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React, { useRef, useEffect, useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import styles from './HorizontalScroll.less';
|
||||
|
||||
const SCROLL_THRESHOLD = 1;
|
||||
|
||||
type Props = {
|
||||
className: string,
|
||||
children: React.ReactNode,
|
||||
};
|
||||
|
||||
const HorizontalScroll = ({ className, children }: Props) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [scrollPosition, setScrollPosition] = useState('left');
|
||||
|
||||
useEffect(() => {
|
||||
const onScroll = ({ target }: Event) => {
|
||||
const { scrollLeft, scrollWidth, offsetWidth } = target as HTMLDivElement;
|
||||
|
||||
setScrollPosition(() => (
|
||||
(scrollLeft - SCROLL_THRESHOLD) <= 0 ? 'left' :
|
||||
(scrollLeft + offsetWidth + SCROLL_THRESHOLD) >= scrollWidth ? 'right' :
|
||||
'center'
|
||||
));
|
||||
};
|
||||
|
||||
ref.current?.addEventListener('scroll', onScroll);
|
||||
return () => ref.current?.removeEventListener('scroll', onScroll);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={ref} className={classNames(styles['horizontal-scroll'], className, [styles[scrollPosition]])}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HorizontalScroll;
|
||||
4
src/components/HorizontalScroll/index.ts
Normal file
4
src/components/HorizontalScroll/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import HorizontalScroll from './HorizontalScroll';
|
||||
export default HorizontalScroll;
|
||||
|
|
@ -1,36 +1,37 @@
|
|||
// 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()
|
||||
:
|
||||
<img {...props} className={className} src={fallbackSrc} alt={alt} />
|
||||
<img {...props} className={className} src={fallbackSrc} alt={alt} loading='lazy'/>
|
||||
:
|
||||
<img {...props} className={className} src={src} alt={alt} onError={onError} />;
|
||||
<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;
|
||||
5
src/components/Image/index.ts
Normal file
5
src/components/Image/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
import Image from './Image';
|
||||
|
||||
export default Image;
|
||||
|
|
@ -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 }) => {
|
||||
|
|
@ -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 {
|
||||
|
|
@ -1,20 +1,27 @@
|
|||
// 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: '#/' },
|
||||
{ id: 'discover', label: 'Discover', icon: 'discover', href: '#/discover' },
|
||||
{ id: 'library', label: 'Library', icon: 'library', href: '#/library' },
|
||||
{ id: 'calendar', label: 'Calendar', icon: 'calendar', href: '#/calendar' },
|
||||
{ id: 'addons', label: 'ADDONS', icon: 'addons', href: '#/addons' },
|
||||
{ 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
|
||||
|
|
@ -23,7 +30,6 @@ const MainNavBars = React.memo(({ className, route, query, children }) => {
|
|||
query={query}
|
||||
backButton={false}
|
||||
searchBar={true}
|
||||
addonsButton={true}
|
||||
fullscreenButton={true}
|
||||
navMenu={true}
|
||||
/>
|
||||
|
|
@ -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;
|
||||
6
src/components/MainNavBars/index.ts
Normal file
6
src/components/MainNavBars/index.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
import MainNavBars from './MainNavBars';
|
||||
|
||||
export default MainNavBars;
|
||||
|
||||
|
|
@ -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');
|
||||
|
|
@ -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;
|
||||
|
|
@ -166,6 +160,7 @@
|
|||
object-position: center;
|
||||
object-fit: cover;
|
||||
opacity: 0.9;
|
||||
overflow-clip-margin: unset;
|
||||
}
|
||||
|
||||
.placeholder-icon {
|
||||
|
|
@ -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');
|
||||
|
||||
|
|
@ -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 }) => {
|
||||
|
|
@ -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');
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue