Compare commits

..

No commits in common. "development" and "v5.0.0-alpha21" have entirely different histories.

656 changed files with 31135 additions and 31038 deletions

99
.eslintrc Normal file
View file

@ -0,0 +1,99 @@
{
"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
}
]
}
}

View file

@ -1,82 +0,0 @@
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

View file

@ -1,42 +0,0 @@
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

View file

@ -1,8 +0,0 @@
version: 2
# Check for outdated actions
updates:
- package-ecosystem: "github-actions"
directory: "/"
# Check for updates every Monday
schedule:
interval: "weekly"

View file

@ -1,66 +0,0 @@
name: PR and Issue Workflow
on:
pull_request:
types: [opened, reopened]
issues:
types: [opened]
jobs:
auto-assign-and-label:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
# Auto assign PR to author
- name: Auto Assign PR to Author
if: github.event.pull_request.head.repo.fork == false && github.event_name == 'pull_request' && github.actor != 'dependabot[bot]'
uses: actions/github-script@v9
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const pr = context.payload.pull_request;
if (pr) {
await github.rest.issues.addAssignees({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
assignees: [pr.user.login]
});
console.log(`Assigned PR #${pr.number} to author @${pr.user.login}`);
}
# Dynamic labeling based on PR/Issue title
- name: Label PRs and Issues
if: github.event.pull_request.head.repo.fork == false && github.actor != 'dependabot[bot]'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: actions/github-script@v9
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prTitle = context.payload.pull_request ? context.payload.pull_request.title : context.payload.issue.title;
const issueNumber = context.payload.pull_request ? context.payload.pull_request.number : context.payload.issue.number;
const isIssue = context.payload.issue !== undefined;
const labelMappings = [
{ pattern: /^feat(ure)?/i, label: 'feature' },
{ pattern: /^fix/i, label: 'bug' },
{ pattern: /^refactor/i, label: 'refactor' },
{ pattern: /^chore/i, label: 'chore' },
{ pattern: /^docs?/i, label: 'documentation' },
{ pattern: /^perf(ormance)?/i, label: 'performance' },
{ pattern: /^test/i, label: 'testing' }
];
let labelsToAdd = [];
for (const mapping of labelMappings) {
if (mapping.pattern.test(prTitle)) {
labelsToAdd.push(mapping.label);
}
}
if (labelsToAdd.length > 0) {
github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
labels: labelsToAdd
});
}

View file

@ -1,54 +1,19 @@
name: Build name: Build
on: on: push
push:
branches:
- development
tags-ignore:
- "**"
pull_request:
branches:
- development
# Allow manual dispatch in GH
workflow_dispatch:
permissions:
contents: write
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v2
- name: Install pnpm
uses: pnpm/action-setup@v6
with:
version: 10
run_install: false
- name: Setup node
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: "pnpm"
- name: Install NPM dependencies - name: Install NPM dependencies
run: pnpm install run: npm install
- name: Build - name: Build
run: pnpm build run: npm run build
- name: Test - name: Upload build artifact
run: pnpm test uses: actions/upload-artifact@v2
- name: Lint
run: pnpm lint
# Create recursively the destination dir with
# "--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.event.pull_request.head.repo.fork == false && github.actor != 'dependabot[bot]'
uses: peaceiris/actions-gh-pages@v4
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} name: stremio-web
publish_dir: ./build path: build
# in stremio, we use `feat/features-name` or `fix/this-bug`
# so we need a recursive creation of the destination dir
destination_dir: ${{ github.head_ref || github.ref_name }}
allow_empty_commit: true

View file

@ -1,53 +0,0 @@
name: GitHub Pages Cleanup
on:
schedule:
- cron: '0 0 * * 0'
workflow_dispatch:
permissions:
contents: write
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: gh-pages
fetch-depth: 0
- name: Delete directories that don't have existing branch
run: |
branches=( $(git branch -r | grep origin | grep -v HEAD | sed 's|origin/||') )
declare -p branches
find . -mindepth 1 -maxdepth 2 -type d -not -path '*/\.*' | while read -r dir; do
path="${dir#./}"
if [[ " ${branches[*]} " =~ " $path " ]]; then
continue
fi
keep_parent=false
for branch in "${branches[@]}"; do
if [[ "$branch" == "$path/"* ]]; then
keep_parent=true
break
fi
done
if ! $keep_parent; then
echo "Deleting $dir"
rm -rf "$dir"
fi
done
- name: Commit and push
run: |
git config --global user.name 'GitHub Pages Cleanup'
git config --global user.email 'actions@stremio.com'
git add -A
git diff --cached --quiet || git commit -m "cleanup"
git push origin gh-pages

View file

@ -9,25 +9,26 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v2
- name: Install pnpm - name: Install NPM dependencies
uses: pnpm/action-setup@v6 run: npm install
with:
version: 10
run_install: false
- name: Install dependencies
run: pnpm install
- name: Build - name: Build
env: env:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }} SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
run: pnpm build run: npm run build
- name: Zip build artifact - name: Zip build artifact
run: zip -r stremio-web.zip ./build run: zip -r stremio-web.zip ./build
- name: Upload build artifact to GitHub release assets - name: Upload build artifact to GitHub release assets
uses: svenstaro/upload-release-action@2.11.5 uses: svenstaro/upload-release-action@v1-release
with: with:
repo_token: ${{ secrets.GITHUB_TOKEN }} repo_token: ${{ secrets.GITHUB_TOKEN }}
file: stremio-web.zip file: stremio-web.zip
asset_name: stremio-web.zip asset_name: stremio-web.zip
tag: ${{ github.ref }} tag: ${{ github.ref }}
overwrite: true overwrite: true
- name: Upload build artifact to Netlify
run: |
curl -H "Content-Type: application/zip" \
-H "Authorization: Bearer ${{ secrets.netlify_access_token }}" \
--data-binary "@stremio-web.zip" \
https://api.netlify.com/api/v1/sites/stremio-development.netlify.com/deploys

16
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,16 @@
name: Test
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install NPM dependencies
run: npm install
- name: Test
run: npm test
- name: Lint
run: npm run lint

1
.gitignore vendored
View file

@ -3,4 +3,3 @@
/yarn.lock /yarn.lock
/npm-debug.log /npm-debug.log
.DS_Store .DS_Store
.prettierignore

1
.nvmrc
View file

@ -1 +0,0 @@
20

View file

@ -1,26 +0,0 @@
{
"applinks": {
"apps": [],
"details": [
{
"appIDs": [
"9EWRZ4QP3J.com.stremio.one"
],
"appID": "9EWRZ4QP3J.com.stremio.one",
"paths": [
"*"
]
}
]
},
"activitycontinuation": {
"apps": [
"9EWRZ4QP3J.com.stremio.one"
]
},
"webcredentials": {
"apps": [
"9EWRZ4QP3J.com.stremio.one"
]
}
}

View file

@ -1,44 +0,0 @@
# Code of Conduct
## Our Pledge
We as contributors and maintainers want to make contributing to our project and community a nice experience for everyone.
## Our Standards
Examples of positive behavior:
- Using welcoming language.
- Being respectful.
- Accepting constructive criticism.
Examples of bad behavior:
- Use of sexualized language.
- Trolling, insulting comments, and personal or political attacks.
- Public or private harassment.
- Publishing others private information, such as a physical or electronic address, without explicit permission.
- Submitting entirely generated by AI PRs with agents such as Devin, Claude Code, Cursor Agent etc.
- Submitting PRs which in majority contain only AI generated code (including docs & comments) and do not solve an actual issue.
- Spamming issues because of no ETAs on issues.
## Our Responsibilities
Project maintainers are responsible for enforcing this code of conduct. They can remove or edit comments, code, and other contributions that don't follow these rules. They can also ban users who behave inappropriately.
## Suggestions for newbies
- Contributors are welcomed to use AI models as "help" in solving issues, but you must always double check the code that you're submitting.
- Refrain from excessive comments generated by AI.
- Refrain from docs generated entirely by AI.
- Always check what files you are committing and submitting to the PR when you are using any agent for help or an AI model.
- If you don't know how to tackle a problem and AI can't help you, please just ask or look in Stack Overlflow, Google, Medium etc.
- Learning how to code is fun and easier when using AI, but sometimes it might be just too much ... what are you going to learn, if AI does everything for you and you don't know what the code you are submitting actually does?!
## Scope
This Code of Conduct applies everywhere in `stremio-web` repository, and also applies when an individual is officially representing the project or its community in other spaces.
## Enforcement
Pls be nice or we will ban you `:)`

View file

@ -1,41 +1,22 @@
# Stremio Node 20.x # Stremio Node 14.x
# the node version for running Stremio Web FROM stremio/node-base:fermium
ARG NODE_VERSION=20-alpine
FROM node:$NODE_VERSION AS base # Meta
LABEL Description="Stremio Web" Vendor="Smart Code OOD" Version="1.0.0"
# Setup pnpm
ENV PNPM_HOME="/pnpm" # Create app directory
ENV PATH="$PNPM_HOME:$PATH" RUN mkdir -p /var/www/stremio-web
RUN corepack enable # Install app dependencies
RUN apk add --no-cache git WORKDIR /var/www/stremio-web
COPY . /var/www/stremio-web
# Meta RUN npm install
LABEL Description="Stremio Web" Vendor="Smart Code OOD" Version="1.0.0" RUN npm install -g http-server
RUN mkdir -p /var/www/stremio-web # Bundle app source
WORKDIR /var/www/stremio-web WORKDIR /var/www/stremio-web
# Setup app RUN npm run build
FROM base AS app
EXPOSE 8080
COPY package.json pnpm-lock.yaml /var/www/stremio-web CMD ["http-server", "/var/www/stremio-web/build/", "-p", "8080", "-d", "false"]
RUN pnpm i --frozen-lockfile
COPY . /var/www/stremio-web
RUN pnpm build
# Setup server
FROM base AS server
RUN pnpm i express@4
# Finalize
FROM base
COPY http_server.js /var/www/stremio-web
COPY --from=server /var/www/stremio-web/node_modules /var/www/stremio-web/node_modules
COPY --from=app /var/www/stremio-web/build /var/www/stremio-web/build
EXPOSE 8080
CMD ["node", "http_server.js"]

View file

@ -1,7 +1,7 @@
# Stremio - Freedom to Stream # Stremio - Freedom to Stream
[![Build](https://github.com/Stremio/stremio-web/actions/workflows/build.yml/badge.svg)](https://github.com/Stremio/stremio-web/actions/workflows/build.yml) ![Build](https://github.com/stremio/stremio-web/workflows/Build/badge.svg?branch=development)
[![Github Page](https://img.shields.io/website?label=Page&logo=github&up_message=online&down_message=offline&url=https%3A%2F%2Fstremio.github.io%2Fstremio-web%2F)](https://stremio.github.io/stremio-web/development) [![Netlify](https://api.netlify.com/api/v1/badges/ac26d7ae-d08b-4cc4-a14d-a83ba7c3e8ca/deploy-status)](https://stremio-development.netlify.app)
Stremio is a modern media center that's a one-stop solution for your video entertainment. You discover, watch and organize video content from easy to install addons. Stremio is a modern media center that's a one-stop solution for your video entertainment. You discover, watch and organize video content from easy to install addons.
@ -10,47 +10,40 @@ Stremio is a modern media center that's a one-stop solution for your video enter
### Prerequisites ### Prerequisites
* Node.js 12 or higher * Node.js 12 or higher
* [pnpm](https://pnpm.io/installation) 10 or higher * npm 6 or higher
### Install dependencies ### Install dependencies
```bash ```bash
pnpm install npm install
``` ```
### Start development server ### Start development server
```bash ```bash
pnpm start npm start
``` ```
### Production build ### Production build
```bash ```bash
pnpm run build npm run build
```
### Run with Docker
```bash
docker build -t stremio-web .
docker run -p 8080:8080 stremio-web
``` ```
## Screenshots ## Screenshots
### Board ### Board
![Board](/assets/screenshots/board.png) ![Board](/screenshots/board.png)
### Discover ### Discover
![Discover](/assets/screenshots/discover.png) ![Discover](/screenshots/discover.png)
### Meta Details ### Meta Details
![Meta Details](/assets/screenshots/metadetails.png) ![Meta Details](/screenshots/metadetails.png)
## License ## License
Stremio is copyright 2017-2023 Smart code and available under GPLv2 license. See the [LICENSE](/LICENSE.md) file in the project for more information. Stremio is copyright 2017-2022 Smart code and available under GPLv2 license. See the [LICENSE](/LICENSE.md) file in the project for more information.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 652 KiB

View file

@ -1,15 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="968" height="565" viewBox="0 0 968 565">
<defs>
<clipPath id="clip-path">
<rect id="Rectangle_1144" data-name="Rectangle 1144" width="968" height="565" transform="translate(0 262)" fill="#fff" stroke="#707070" stroke-width="1"/>
</clipPath>
</defs>
<g id="Mask_Group_31" data-name="Mask Group 31" transform="translate(0 -262)" clip-path="url(#clip-path)">
<g id="Group_2309" data-name="Group 2309">
<path id="Path_983" data-name="Path 983" d="M410.951-49.5c337,24.76,699.788,308.381,792,500.579S897.064,762.814,577.9,762.814,0,593.971,0,385.694,73.955-74.26,410.951-49.5Z" transform="translate(-301.147 411.907)" fill="#362565" opacity="0.8"/>
<path id="Path_979" data-name="Path 979" d="M360.91-73.97c324,27.3,638,301.633,720.932,474.48S806.748,680.86,519.716,680.86,0,529.016,0,341.708,36.91-101.27,360.91-73.97Z" transform="translate(-231.91 594.67)" fill="rgba(123,91,245,0.83)" opacity="0.8"/>
<path id="Path_984" data-name="Path 984" d="M262.171-10C444.7-10,659.821,73.865,660.993,203.729S513.025,402.667,330.5,402.667,0,313.6,0,203.729,79.643-10,262.171-10Z" transform="translate(-69 681.267)" fill="#5126ed"/>
<path id="Path_980" data-name="Path 980" d="M262.171-10C444.7-10,659.821,66.535,660.993,185.049S513.025,366.6,330.5,366.6,0,285.317,0,185.049,79.643-10,262.171-10Z" transform="translate(-69 762.333)" fill="#4516fc"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -1,13 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="718" height="356" viewBox="0 0 718 356">
<defs>
<clipPath id="clip-path">
<rect id="Rectangle_1144" data-name="Rectangle 1144" width="718" height="356" transform="translate(602 -8)" fill="#fff" stroke="#707070" stroke-width="1"/>
</clipPath>
</defs>
<g id="Mask_Group_31" data-name="Mask Group 31" transform="translate(-602 8)" clip-path="url(#clip-path)">
<g id="Group_2308" data-name="Group 2308" transform="translate(-49.883 86.23)">
<path id="Path_982" data-name="Path 982" d="M264.138,0C470.016,0,780.486,131.36,775.97,319.553S578.654,535.889,372.776,535.889,0,418.717,0,274.178,58.26,0,264.138,0Z" transform="translate(1521.635 173.714) rotate(180)" fill="rgba(137,91,245,0.64)" opacity="0.52"/>
<path id="Path_981" data-name="Path 981" d="M177.9,0C301.753,0,447.725,59.059,448.52,150.512s-100.4,140.1-224.26,140.1S0,227.885,0,150.512,54.042,0,177.9,0Z" transform="translate(1366.094 26.124) rotate(180)" fill="#4722d2"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

View file

@ -1,102 +0,0 @@
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/function-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
favicons/icon-96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
fonts/Roboto-Bold.ttf Normal file

Binary file not shown.

BIN
fonts/Roboto-BoldItalic.ttf Normal file

Binary file not shown.

BIN
fonts/Roboto-Light.ttf Normal file

Binary file not shown.

Binary file not shown.

BIN
fonts/Roboto-Medium.ttf Normal file

Binary file not shown.

Binary file not shown.

BIN
fonts/Roboto-Regular.ttf Normal file

Binary file not shown.

Binary file not shown.

View file

@ -1,23 +0,0 @@
#!/usr/bin/env node
// Copyright (C) 2017-2023 Smart code 203358507
const INDEX_CACHE = 7200;
const ASSETS_CACHE = 2629744;
const HTTP_PORT = 8080;
const express = require('express');
const path = require('path');
const build_path = path.resolve(__dirname, 'build');
const index_path = path.join(build_path, 'index.html');
express().use(express.static(build_path, {
setHeaders: (res, path) => {
if (path === index_path) res.set('cache-control', `public, max-age: ${INDEX_CACHE}`);
else res.set('cache-control', `public, max-age: ${ASSETS_CACHE}`);
}
})).all('*', (_req, res) => {
// TODO: better 404 page
res.status(404).send('<h1>404! Page not found</h1>');
}).listen(HTTP_PORT, () => console.info(`Server listening on port: ${HTTP_PORT}`));

BIN
images/anonymous.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
images/default_avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

BIN
images/empty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

BIN
images/intro_background.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

BIN
images/stremio_symbol.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View file

@ -1,59 +0,0 @@
{
"name": "Stremio Web",
"short_name": "Stremio",
"description": "Freedom To Stream",
"background_color": "#161523",
"theme_color": "#2a2843",
"orientation": "any",
"display": "standalone",
"display_override": ["standalone"],
"scope": "./",
"start_url": "./",
"icons": [
{
"src": "favicons/icon_256x256.ico",
"sizes": "256x256",
"type": "image/vnd.microsoft.icon"
},
{
"src": "images/maskable_icon_512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "images/maskable_icon_196x196.png",
"sizes": "196x196",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "images/icon_512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "images/icon_196x196.png",
"sizes": "196x196",
"type": "image/png",
"purpose": "any"
}
],
"screenshots": [
{
"src": "screenshots/board_wide.webp",
"sizes": "1440x900",
"type": "image/webp",
"form_factor": "wide",
"label": "Homescreen of Stremio"
},
{
"src": "screenshots/board_narrow.webp",
"sizes": "414x896",
"type": "image/webp",
"form_factor": "narrow",
"label": "Homescreen of Stremio"
}
]
}

24288
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

106
package.json Normal file → Executable file
View file

@ -1,85 +1,65 @@
{ {
"name": "stremio", "name": "stremio",
"displayName": "Stremio", "displayName": "Stremio",
"version": "5.0.0-beta.34", "version": "5.0.0",
"author": "Smart Code OOD", "author": "Smart Code OOD",
"private": true, "private": true,
"license": "gpl-2.0", "license": "gpl-2.0",
"scripts": { "scripts": {
"start": "webpack serve --mode development", "start": "webpack serve --mode development",
"start-prod": "webpack serve --mode production",
"build": "webpack --mode production", "build": "webpack --mode production",
"test": "jest", "test": "jest",
"lint": "eslint src", "lint": "eslint src"
"scan-translations": "pnpx jest ./tests/i18nScan.test.js"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "7.26.0", "@babel/runtime": "7.16.0",
"@sentry/browser": "8.42.0", "@sentry/browser": "6.13.3",
"@stremio/stremio-colors": "5.2.0", "@stremio/stremio-colors": "4.0.1",
"@stremio/stremio-core-web": "0.56.3", "@stremio/stremio-core-web": "0.44.3",
"@stremio/stremio-icons": "5.8.0", "@stremio/stremio-icons": "3.0.5",
"@stremio/stremio-video": "0.0.73", "@stremio/stremio-video": "0.0.20-rc.7",
"a-color-picker": "1.2.1", "a-color-picker": "1.2.1",
"bowser": "2.11.0", "bowser": "2.11.0",
"buffer": "6.0.3", "buffer": "6.0.3",
"classnames": "2.5.1", "classnames": "2.3.1",
"eventemitter3": "5.0.1", "eventemitter3": "4.0.7",
"fast-equals": "^6.0.0", "filter-invalid-dom-props": "2.1.0",
"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.debounce": "4.0.8",
"lodash.intersection": "4.4.0", "lodash.intersection": "4.4.0",
"lodash.isequal": "4.5.0",
"lodash.throttle": "4.1.1", "lodash.throttle": "4.1.1",
"magnet-uri": "6.2.0", "prop-types": "15.7.2",
"prop-types": "15.8.1", "react": "18.2.0",
"react": "18.3.1", "react-dom": "18.2.0",
"react-dom": "18.3.1", "react-is": "18.2.0",
"react-focus-lock": "2.13.2", "react-focus-lock": "2.9.1",
"react-i18next": "^15.1.3", "spatial-navigation-polyfill": "git+https://git@github.com/Stremio/spatial-navigation.git#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
"react-is": "18.3.1", "url": "0.11.0"
"spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
"stremio-translations": "github:Stremio/stremio-translations#90ea718c18750a0e9cd6824b0ef7c512a41cb90b",
"url": "0.11.4",
"use-long-press": "^3.2.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.26.0", "@babel/core": "7.16.0",
"@babel/preset-env": "7.26.0", "@babel/plugin-proposal-class-properties": "7.16.0",
"@babel/preset-react": "7.26.3", "@babel/plugin-proposal-object-rest-spread": "7.16.0",
"@eslint/js": "^9.16.0", "@babel/preset-env": "7.16.0",
"@stylistic/eslint-plugin": "^5.4.0", "@babel/preset-react": "7.16.0",
"@stylistic/eslint-plugin-jsx": "^4.4.1", "babel-loader": "8.2.3",
"@types/hat": "^0.0.4", "clean-webpack-plugin": "4.0.0",
"@types/lodash.throttle": "^4.1.9", "copy-webpack-plugin": "9.0.1",
"@types/react": "^18.3.13", "css-loader": "6.5.0",
"@types/react-dom": "^18.3.1", "cssnano": "5.0.8",
"babel-loader": "9.2.1", "cssnano-preset-advanced": "5.1.4",
"copy-webpack-plugin": "12.0.2", "eslint": "7.32.0",
"css-loader": "6.11.0", "eslint-plugin-react": "7.26.1",
"cssnano": "7.0.6", "html-webpack-plugin": "5.5.0",
"cssnano-preset-advanced": "7.0.6", "jest": "27.3.1",
"eslint": "^9.16.0", "less": "4.1.2",
"eslint-plugin-react": "^7.37.2", "less-loader": "10.2.0",
"globals": "^15.13.0", "mini-css-extract-plugin": "2.4.3",
"html-webpack-plugin": "5.6.3", "postcss-loader": "6.2.0",
"jest": "29.7.0", "readdirp": "3.6.0",
"less": "4.2.1", "terser-webpack-plugin": "5.2.4",
"less-loader": "12.2.0", "webpack": "5.61.0",
"mini-css-extract-plugin": "2.9.2", "webpack-cli": "4.9.1",
"postcss-loader": "8.1.1", "webpack-dev-server": "4.7.4"
"readdirp": "4.0.2",
"recast": "0.23.11",
"terser-webpack-plugin": "5.3.10",
"thread-loader": "^4.0.4",
"ts-loader": "^9.5.1",
"typescript": "^5.7.2",
"typescript-eslint": "^8.17.0",
"webpack": "5.97.0",
"webpack-cli": "5.1.4",
"webpack-dev-server": "^5.1.0",
"workbox-webpack-plugin": "^7.3.0"
} }
} }

File diff suppressed because it is too large Load diff

BIN
screenshots/board.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 976 KiB

BIN
screenshots/discover.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
screenshots/metadetails.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View file

@ -1,52 +1,30 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2022 Smart code 203358507
require('spatial-navigation-polyfill'); require('spatial-navigation-polyfill');
const React = require('react'); const React = require('react');
const { useTranslation } = require('react-i18next');
const { Router } = require('stremio-router'); const { Router } = require('stremio-router');
const { Core, Shell, Chromecast, DragAndDrop, KeyboardShortcuts, ServicesProvider } = require('stremio/services'); const { Core, Shell, Chromecast, KeyboardShortcuts, ServicesProvider } = require('stremio/services');
const { NotFound } = require('stremio/routes'); const { NotFound } = require('stremio/routes');
const { FileDropProvider, PlatformProvider, ToastProvider, TooltipProvider, ShortcutsProvider, CONSTANTS, withCoreSuspender, useShell, useBinaryState } = require('stremio/common'); const { ToastProvider, CONSTANTS } = require('stremio/common');
const ServicesToaster = require('./ServicesToaster'); const CoreEventsToaster = require('./CoreEventsToaster');
const DeepLinkHandler = require('./DeepLinkHandler');
const SearchParamsHandler = require('./SearchParamsHandler');
const { default: UpdaterBanner } = require('./UpdaterBanner');
const { default: ShortcutsModal } = require('./ShortcutsModal');
const ErrorDialog = require('./ErrorDialog'); const ErrorDialog = require('./ErrorDialog');
const withProtectedRoutes = require('./withProtectedRoutes');
const routerViewsConfig = require('./routerViewsConfig'); const routerViewsConfig = require('./routerViewsConfig');
const styles = require('./styles'); const styles = require('./styles');
const RouterWithProtectedRoutes = withCoreSuspender(withProtectedRoutes(Router));
const App = () => { const App = () => {
const { i18n } = useTranslation();
const shell = useShell();
const onPathNotMatch = React.useCallback(() => { const onPathNotMatch = React.useCallback(() => {
return NotFound; return NotFound;
}, []); }, []);
const services = React.useMemo(() => { const services = React.useMemo(() => ({
const core = new Core({ core: new Core({
appVersion: process.env.VERSION, appVersion: process.env.VERSION,
shellVersion: null shellVersion: null
}); }),
return { shell: new Shell(),
core, chromecast: new Chromecast(),
shell: new Shell(), keyboardShortcuts: new KeyboardShortcuts()
chromecast: new Chromecast(), }), []);
keyboardShortcuts: new KeyboardShortcuts(),
dragAndDrop: new DragAndDrop({ core })
};
}, []);
const [initialized, setInitialized] = React.useState(false); const [initialized, setInitialized] = React.useState(false);
const [shortcutModalOpen,, closeShortcutsModal, toggleShortcutModal] = useBinaryState(false);
const onShortcut = React.useCallback((name) => {
if (name === 'shortcuts') {
toggleShortcutModal();
}
}, [toggleShortcutModal]);
React.useEffect(() => { React.useEffect(() => {
let prevPath = window.location.hash.slice(1); let prevPath = window.location.hash.slice(1);
const onLocationHashChange = () => { const onLocationHashChange = () => {
@ -82,8 +60,7 @@ const App = () => {
receiverApplicationId: CONSTANTS.CHROMECAST_RECEIVER_APP_ID, receiverApplicationId: CONSTANTS.CHROMECAST_RECEIVER_APP_ID,
autoJoinPolicy: chrome.cast.AutoJoinPolicy.PAGE_SCOPED, autoJoinPolicy: chrome.cast.AutoJoinPolicy.PAGE_SCOPED,
resumeSavedSession: false, resumeSavedSession: false,
language: null, language: null
androidReceiverCompatible: true
}); });
} }
}; };
@ -94,71 +71,19 @@ const App = () => {
services.shell.start(); services.shell.start();
services.chromecast.start(); services.chromecast.start();
services.keyboardShortcuts.start(); services.keyboardShortcuts.start();
services.dragAndDrop.start();
window.services = services; window.services = services;
return () => { return () => {
services.core.stop(); services.core.stop();
services.shell.stop(); services.shell.stop();
services.chromecast.stop(); services.chromecast.stop();
services.keyboardShortcuts.stop(); services.keyboardShortcuts.stop();
services.dragAndDrop.stop();
services.core.off('stateChanged', onCoreStateChanged); services.core.off('stateChanged', onCoreStateChanged);
services.shell.off('stateChanged', onShellStateChanged); services.shell.off('stateChanged', onShellStateChanged);
services.chromecast.off('stateChanged', onChromecastStateChange); services.chromecast.off('stateChanged', onChromecastStateChange);
}; };
}, []); }, []);
// Handle shell events
React.useEffect(() => { React.useEffect(() => {
const onOpenMedia = (data) => { if (services.core.active) {
try {
const { protocol, hostname, pathname, searchParams } = new URL(data);
if (protocol === CONSTANTS.PROTOCOL) {
if (hostname.length) {
const transportUrl = `https://${hostname}${pathname}`;
window.location.href = `#/addons?addon=${encodeURIComponent(transportUrl)}`;
} else {
window.location.href = `#${pathname}?${searchParams.toString()}`;
}
}
} catch (e) {
console.error('Failed to open media:', e);
}
};
shell.on('open-media', onOpenMedia);
return () => {
shell.off('open-media', onOpenMedia);
};
}, []);
React.useEffect(() => {
const onCoreEvent = ({ event, args }) => {
switch (event) {
case 'SettingsUpdated': {
if (args && args.settings && typeof args.settings.interfaceLanguage === 'string') {
i18n.changeLanguage(args.settings.interfaceLanguage);
}
if (args?.settings?.quitOnClose && shell.windowClosed) {
shell.send('quit');
}
break;
}
}
};
const onCtxState = (state) => {
if (state && state.profile && state.profile.settings && typeof state.profile.settings.interfaceLanguage === 'string') {
i18n.changeLanguage(state.profile.settings.interfaceLanguage);
}
if (state?.profile?.settings?.quitOnClose && shell.windowClosed) {
shell.send('quit');
}
};
const onWindowFocus = () => {
services.core.transport.dispatch({ services.core.transport.dispatch({
action: 'Ctx', action: 'Ctx',
args: { args: {
@ -168,39 +93,11 @@ const App = () => {
services.core.transport.dispatch({ services.core.transport.dispatch({
action: 'Ctx', action: 'Ctx',
args: { args: {
action: 'PullUserFromAPI', action: 'PullUserFromAPI'
args: {}
} }
}); });
services.core.transport.dispatch({
action: 'Ctx',
args: {
action: 'SyncLibraryWithAPI'
}
});
services.core.transport.dispatch({
action: 'Ctx',
args: {
action: 'PullNotifications'
}
});
};
if (services.core.active) {
onWindowFocus();
window.addEventListener('focus', onWindowFocus);
services.core.transport.on('CoreEvent', onCoreEvent);
services.core.transport
.getState('ctx')
.then(onCtxState)
.catch(console.error);
} }
return () => { }, [initialized]);
if (services.core.active) {
window.removeEventListener('focus', onWindowFocus);
services.core.transport.off('CoreEvent', onCoreEvent);
}
};
}, [initialized, shell.windowClosed]);
return ( return (
<React.StrictMode> <React.StrictMode>
<ServicesProvider services={services}> <ServicesProvider services={services}>
@ -209,28 +106,14 @@ const App = () => {
services.core.error instanceof Error ? services.core.error instanceof Error ?
<ErrorDialog className={styles['error-container']} /> <ErrorDialog className={styles['error-container']} />
: :
<PlatformProvider> <ToastProvider className={styles['toasts-container']}>
<ToastProvider className={styles['toasts-container']}> <CoreEventsToaster />
<TooltipProvider className={styles['tooltip-container']}> <Router
<FileDropProvider className={styles['file-drop-container']}> className={styles['router']}
<ShortcutsProvider onShortcut={onShortcut}> viewsConfig={routerViewsConfig}
{ onPathNotMatch={onPathNotMatch}
shortcutModalOpen && <ShortcutsModal onClose={closeShortcutsModal}/> />
} </ToastProvider>
<ServicesToaster />
<DeepLinkHandler />
<SearchParamsHandler />
<UpdaterBanner className={styles['updater-banner-container']} />
<RouterWithProtectedRoutes
className={styles['router']}
viewsConfig={routerViewsConfig}
onPathNotMatch={onPathNotMatch}
/>
</ShortcutsProvider>
</FileDropProvider>
</TooltipProvider>
</ToastProvider>
</PlatformProvider>
: :
<div className={styles['loader-container']} /> <div className={styles['loader-container']} />
} }

View file

@ -0,0 +1,29 @@
// Copyright (C) 2017-2022 Smart code 203358507
const React = require('react');
const { useServices } = require('stremio/services');
const { useToast } = require('stremio/common');
const CoreEventsToaster = () => {
const { core } = useServices();
const toast = useToast();
React.useEffect(() => {
const onCoreEvent = ({ event, args }) => {
if (event === 'Error') {
toast.show({
type: 'error',
title: args.source.event,
message: args.error.message,
timeout: 4000
});
}
};
core.transport.on('CoreEvent', onCoreEvent);
return () => {
core.transport.off('CoreEvent', onCoreEvent);
};
}, []);
return null;
};
module.exports = CoreEventsToaster;

View file

@ -1,22 +0,0 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
const { withCoreSuspender, useStreamingServer } = require('stremio/common');
const DeepLinkHandler = () => {
const streamingServer = useStreamingServer();
React.useEffect(() => {
if (streamingServer.torrent !== null) {
const [, { type, content }] = streamingServer.torrent;
if (type === 'Ready') {
const [, deepLinks] = content;
if (typeof deepLinks.metaDetailsVideos === 'string') {
window.location = deepLinks.metaDetailsVideos;
}
}
}
}, [streamingServer.torrent]);
return null;
};
module.exports = withCoreSuspender(DeepLinkHandler);

View file

@ -1,15 +1,12 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2022 Smart code 203358507
const React = require('react'); const React = require('react');
const { useTranslation } = require('react-i18next');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const classnames = require('classnames'); const classnames = require('classnames');
const { Image, Button } = require('stremio/components'); const { Button, Image } = require('stremio/common');
const styles = require('./styles'); const styles = require('./styles');
const ErrorDialog = ({ className }) => { const ErrorDialog = ({ className }) => {
const { t } = useTranslation();
const [dataCleared, setDataCleared] = React.useState(false); const [dataCleared, setDataCleared] = React.useState(false);
const reload = React.useCallback(() => { const reload = React.useCallback(() => {
window.location.reload(); window.location.reload();
@ -22,22 +19,16 @@ const ErrorDialog = ({ className }) => {
<div className={classnames(className, styles['error-container'])}> <div className={classnames(className, styles['error-container'])}>
<Image <Image
className={styles['error-image']} className={styles['error-image']}
src={require('/assets/images/empty.png')} src={require('/images/empty.png')}
alt={' '} alt={' '}
/> />
<div className={styles['error-message']}> <div className={styles['error-message']}>Something went wrong!</div>
{ t('GENERIC_ERROR_MESSAGE') }
</div>
<div className={styles['buttons-container']}> <div className={styles['buttons-container']}>
<Button className={styles['button-container']} title={t('TRY_AGAIN')} onClick={reload}> <Button className={styles['button-container']} title={'Try again'} onClick={reload}>
<div className={styles['label']}> <div className={styles['label']}>Try again</div>
{ t('TRY_AGAIN') }
</div>
</Button> </Button>
<Button className={styles['button-container']} disabled={dataCleared} title={t('CLEAR_DATA')} onClick={clearData}> <Button className={styles['button-container']} disabled={dataCleared} title={'Clear data'} onClick={clearData}>
<div className={styles['label']}> <div className={styles['label']}>Clear data</div>
{ t('CLEAR_DATA') }
</div>
</Button> </Button>
</div> </div>
</div> </div>

View file

@ -1,4 +1,4 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2022 Smart code 203358507
const ErrorDialog = require('./ErrorDialog'); const ErrorDialog = require('./ErrorDialog');

View file

@ -1,4 +1,4 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2022 Smart code 203358507
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less'; @import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
@ -7,12 +7,12 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 1rem;
.error-image { .error-image {
flex: none; flex: none;
width: 12rem; width: 12rem;
height: 12rem; height: 12rem;
margin-bottom: 1rem;
object-fit: contain; object-fit: contain;
object-position: center; object-position: center;
opacity: 0.9; opacity: 0.9;
@ -24,7 +24,7 @@
font-size: 2rem; font-size: 2rem;
max-height: 3.6em; max-height: 3.6em;
text-align: center; text-align: center;
color: var(--primary-foreground-color); color: @color-surface-light5-90;
} }
.buttons-container { .buttons-container {
@ -36,8 +36,6 @@
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 1.5rem;
margin-top: 1rem;
.button-container { .button-container {
flex-grow: 0; flex-grow: 0;
@ -47,23 +45,18 @@
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 0 2.5rem; margin: 2rem 1rem 0;
padding: 0 1rem;
min-width: 8rem; min-width: 8rem;
height: 3.5rem; height: 3rem;
border-radius: 3.5rem; background-color: @color-accent3;
background-color: var(--overlay-color);
&:hover { &:hover {
outline: var(--focus-outline-size) solid var(--primary-foreground-color); background-color: @color-accent3-light1;
background-color: transparent;
}
&:active {
outline: none;
} }
&:global(.disabled) { &:global(.disabled) {
opacity: 0.3; background-color: @color-surface-dark5;
} }
.label { .label {
@ -74,7 +67,7 @@
font-size: 1.1rem; font-size: 1.1rem;
font-weight: 500; font-weight: 500;
text-align: center; text-align: center;
color: var(--primary-foreground-color); color: @color-surface-light5-90;
} }
} }
} }

View file

@ -1,63 +0,0 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
const { deepEqual } = require('fast-equals');
const { withCoreSuspender, useProfile, useToast } = require('stremio/common');
const { useServices } = require('stremio/services');
const SearchParamsHandler = () => {
const { core } = useServices();
const profile = useProfile();
const toast = useToast();
const [searchParams, setSearchParams] = React.useState({});
const onLocationChange = () => {
const { origin, hash, search } = window.location;
const { searchParams } = new URL(`${origin}${hash.replace('#', '')}${search}`);
setSearchParams((previousSearchParams) => {
const currentSearchParams = Object.fromEntries(searchParams.entries());
return deepEqual(previousSearchParams, currentSearchParams) ? previousSearchParams : currentSearchParams;
});
};
React.useEffect(() => {
const { streamingServerUrl } = searchParams;
if (streamingServerUrl) {
core.transport.dispatch({
action: 'Ctx',
args: {
action: 'UpdateSettings',
args: {
...profile.settings,
streamingServerUrl,
},
},
});
core.transport.dispatch({
action: 'Ctx',
args: {
action: 'AddServerUrl',
args: streamingServerUrl,
},
});
toast.show({
type: 'success',
title: `Using streaming server at ${streamingServerUrl}`,
timeout: 4000,
});
}
}, [searchParams]);
React.useEffect(() => {
onLocationChange();
window.addEventListener('hashchange', onLocationChange);
return () => window.removeEventListener('hashchange', onLocationChange);
}, []);
return null;
};
module.exports = withCoreSuspender(SearchParamsHandler);

View file

@ -1,81 +0,0 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
const { useServices } = require('stremio/services');
const { useToast } = require('stremio/common');
const ServicesToaster = () => {
const { core, dragAndDrop } = useServices();
const toast = useToast();
React.useEffect(() => {
const onCoreEvent = ({ event, args }) => {
switch (event) {
case 'Error': {
if (args.source.event === 'UserPulledFromAPI' && args.source.args.uid === null) {
break;
}
if (args.source.event === 'LibrarySyncWithAPIPlanned' && args.source.args.uid === null) {
break;
}
if (args.error.type === 'Other' && args.error.code === 3 && args.source.event === 'AddonInstalled' && args.source.args.transport_url.startsWith('https://www.strem.io/trakt/addon')) {
break;
}
toast.show({
type: 'error',
title: args.source.event,
message: args.error.message,
timeout: 4000,
dataset: {
type: 'CoreEvent'
}
});
break;
}
case 'TorrentParsed': {
toast.show({
type: 'success',
title: 'Torrent file parsed',
timeout: 4000
});
break;
}
case 'MagnetParsed': {
toast.show({
type: 'info',
title: 'Magnet link parsed',
timeout: 4000
});
break;
}
case 'PlayingOnDevice': {
toast.show({
type: 'success',
title: `Stream opened in ${args.device}`,
timeout: 4000
});
break;
}
}
};
const onDragAndDropError = (error) => {
toast.show({
type: 'error',
title: error.message,
message: error.file?.name,
timeout: 4000
});
};
core.transport.on('CoreEvent', onCoreEvent);
dragAndDrop.on('error', onDragAndDropError);
return () => {
core.transport.off('CoreEvent', onCoreEvent);
dragAndDrop.off('error', onDragAndDropError);
};
}, []);
return null;
};
module.exports = ServicesToaster;

View file

@ -1,59 +0,0 @@
// Copyright (C) 2017-2023 Smart code 203358507
import React, { useEffect } from 'react';
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
import Icon from '@stremio/stremio-icons/react';
import { useShortcuts } from 'stremio/common';
import { Button, ShortcutsGroup } from 'stremio/components';
import styles from './styles.less';
type Props = {
onClose: () => void,
};
const ShortcutsModal = ({ onClose }: Props) => {
const { t } = useTranslation();
const { grouped } = useShortcuts();
useEffect(() => {
const onKeyDown = ({ key }: KeyboardEvent) => {
key === 'Escape' && onClose();
};
document.addEventListener('keydown', onKeyDown);
return () => document.removeEventListener('keydown', onKeyDown);
}, []);
return createPortal((
<div className={styles['shortcuts-modal']}>
<div className={styles['backdrop']} onClick={onClose} />
<div className={styles['container']}>
<div className={styles['header']}>
<div className={styles['title']}>
{t('SETTINGS_NAV_SHORTCUTS')}
</div>
<Button className={styles['close-button']} title={t('BUTTON_CLOSE')} onClick={onClose}>
<Icon className={styles['icon']} name={'close'} />
</Button>
</div>
<div className={styles['content']}>
{
grouped.map(({ name, label, shortcuts }) => (
<ShortcutsGroup
key={name}
label={label}
shortcuts={shortcuts}
/>
))
}
</div>
</div>
</div>
), document.body);
};
export default ShortcutsModal;

View file

@ -1,2 +0,0 @@
import ShortcutsModal from './ShortcutsModal';
export default ShortcutsModal;

View file

@ -1,91 +0,0 @@
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
.shortcuts-modal {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: center;
.backdrop {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: @color-background-dark5-40;
cursor: pointer;
}
.container {
position: relative;
display: flex;
flex-direction: column;
gap: 1rem;
max-height: 80%;
max-width: 80%;
border-radius: var(--border-radius);
background-color: var(--modal-background-color);
box-shadow: var(--outer-glow);
overflow-y: auto;
.header {
flex: none;
display: flex;
justify-content: space-between;
align-items: center;
height: 5rem;
padding-left: 2.5rem;
padding-right: 1rem;
.title {
position: relative;
font-size: 1.5rem;
font-weight: 500;
color: var(--primary-foreground-color);
}
.close-button {
position: relative;
width: 3rem;
height: 3rem;
padding: 0.5rem;
border-radius: var(--border-radius);
z-index: 2;
.icon {
display: block;
width: 100%;
height: 100%;
color: var(--primary-foreground-color);
opacity: 0.4;
}
&:hover, &:focus {
.icon {
opacity: 1;
color: var(--primary-foreground-color);
}
}
&:focus {
outline-color: var(--primary-foreground-color);
}
}
}
.content {
position: relative;
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 3rem;
padding: 0 2.5rem;
padding-bottom: 2rem;
overflow-y: auto;
}
}
}

View file

@ -1,46 +0,0 @@
.updater-banner {
height: 4rem;
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
padding: 0 1rem;
font-size: 1rem;
font-weight: bold;
color: var(--primary-foreground-color);
background-color: var(--primary-accent-color);
.button {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
height: 2.5rem;
padding: 0 1rem;
border-radius: var(--border-radius);
color: var(--primary-background-color);
background-color: var(--primary-foreground-color);
transition: all 0.1s ease-out;
&:hover {
color: var(--primary-foreground-color);
background-color: transparent;
box-shadow: inset 0 0 0 0.15rem var(--primary-foreground-color);
}
}
.close {
position: absolute;
right: 0;
height: 4rem;
width: 4rem;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
.icon {
height: 2rem;
}
}
}

View file

@ -1,50 +0,0 @@
import React, { useEffect } from 'react';
import Icon from '@stremio/stremio-icons/react';
import { useTranslation } from 'react-i18next';
import { useServices } from 'stremio/services';
import { useBinaryState, useShell } from 'stremio/common';
import { Button, Transition } from 'stremio/components';
import styles from './UpdaterBanner.less';
type Props = {
className: string,
};
const UpdaterBanner = ({ className }: Props) => {
const { t } = useTranslation();
const { shell } = useServices();
const shellTransport = useShell();
const [visible, show, hide] = useBinaryState(false);
const onInstallClick = () => {
shellTransport.send('autoupdater-notif-clicked');
};
useEffect(() => {
shell.transport && shell.transport.on('autoupdater-show-notif', show);
return () => {
shell.transport && shell.transport.off('autoupdater-show-notif', show);
};
}, []);
return (
<div className={className}>
<Transition when={visible} name={'slide-up'}>
<div className={styles['updater-banner']}>
<div className={styles['label']}>
{ t('UPDATER_TITLE') }
</div>
<Button className={styles['button']} onClick={onInstallClick}>
{ t('UPDATER_INSTALL_BUTTON') }
</Button>
<Button className={styles['close']} onClick={hide}>
<Icon className={styles['icon']} name={'close'} />
</Button>
</div>
</Transition>
</div>
);
};
export default UpdaterBanner;

View file

@ -1,2 +0,0 @@
import UpdaterBanner from './UpdaterBanner';
export default UpdaterBanner;

View file

@ -1,4 +1,4 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2022 Smart code 203358507
const App = require('./App'); const App = require('./App');

View file

@ -1,4 +1,4 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2022 Smart code 203358507
const routes = require('stremio/routes'); const routes = require('stremio/routes');
const { routesRegexp } = require('stremio/common'); const { routesRegexp } = require('stremio/common');
@ -23,10 +23,6 @@ const routerViewsConfig = [
...routesRegexp.library, ...routesRegexp.library,
component: routes.Library component: routes.Library
}, },
{
...routesRegexp.calendar,
component: routes.Calendar
},
{ {
...routesRegexp.continuewatching, ...routesRegexp.continuewatching,
component: routes.Library component: routes.Library

View file

@ -1,115 +1,26 @@
// Copyright (C) 2017-2024 Smart code 203358507 // Copyright (C) 2017-2022 Smart code 203358507
@import (inline, once, css) '~stremio/common/roboto.css';
@import (reference) '~stremio/common/screen-sizes.less'; @import (reference) '~stremio/common/screen-sizes.less';
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less'; @import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
@font-face {
font-family: 'PlusJakartaSans';
src: url('/assets/fonts/PlusJakartaSans.ttf') format('truetype');
}
@font-face {
font-family: 'TwemojiFlags';
src: url('/assets/fonts/TwemojiFlags.woff2') format('woff2');
unicode-range: U+1F1E6-1F1FF;
}
:global { :global {
@import (once, less) '~stremio/common/animations.less'; @import (once, less) '~stremio/common/animations.less';
@import (once, less) '~stremio-router/styles.css'; @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(var(--viewport-height-diff) - env(safe-area-inset-top, 0rem))))";
// Viewport sizes
@viewport-width: ~"100vw";
@viewport-height: ~"100vh";
// HTML sizes
@html-width: ~"calc(max(var(--small-viewport-width), var(--dynamic-viewport-width)))";
@html-height: ~"calc(max(var(--small-viewport-height), var(--dynamic-viewport-height)))";
@html-standalone-width: ~"calc(max(100%, var(--large-viewport-width)))";
@html-standalone-height: ~"calc(max(100%, var(--large-viewport-height)))";
// Safe area insets
@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-gradient-pad: 6rem;
:root { :root {
--landscape-shape-ratio: 0.5625; --landscape-shape-ratio: 0.5625;
--poster-shape-ratio: 1.464; --poster-shape-ratio: 1.464;
--scroll-bar-size: 6px; --scroll-bar-size: 6px;
--horizontal-nav-bar-size: 5.5rem; --horizontal-nav-bar-size: 4rem;
--vertical-nav-bar-size: 6rem; --vertical-nav-bar-size: 5.2rem;
--focus-outline-size: 2px; --focus-outline-size: 2px;
--color-facebook: #1877F1; --color-facebook: #4267b2;
--color-x: #000000; --color-twitter: #1DA1F2;
--color-reddit: #FF4500;
--color-imdb: #f5c518;
--color-trakt: rgb(255, 255, 255);
--color-placeholder: #60606080; --color-placeholder: #60606080;
--color-placeholder-text: @color-surface-50; --color-placeholder-text: @color-surface-50;
--color-placeholder-background: @color-surface-dark5-20; --color-placeholder-background: @color-surface-dark5-20;
--primary-background-color: rgba(12, 11, 17, 1);
--secondary-background-color: rgba(26, 23, 62, 1);
--primary-foreground-color: rgba(255, 255, 255, 0.9);
--secondary-foreground-color: rgb(12, 11, 17, 1);
--primary-accent-color: rgb(123, 91, 245);
--secondary-accent-color: rgba(34, 179, 101, 1);
--tertiary-accent-color: rgba(246, 199, 0, 1);
--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 15px rgba(123, 91, 245, 0.37);
--warning-accent-color: rgba(255, 165, 0, 1);
--danger-accent-color: rgba(220, 38, 38, 1);
--border-radius: 0.75rem;
--top-overlay-size: @top-overlay-size;
--bottom-overlay-size: @bottom-overlay-size;
--overlap-size: @overlap-size;
--transparency-gradient-pad: @transparency-gradient-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;
--dynamic-viewport-width: @viewport-width;
--dynamic-viewport-height: @viewport-height;
--large-viewport-width: @viewport-width;
--large-viewport-height: @viewport-height;
--small-viewport-width: @viewport-width;
--small-viewport-height: @viewport-height;
--viewport-height-diff: calc(100vh - 100vh);
@supports (height: 100dvh) {
--dynamic-viewport-width: 100dvw;
--dynamic-viewport-height: 100dvh;
}
@supports (height: 100lvh) {
--large-viewport-width: 100lvw;
--large-viewport-height: 100lvh;
}
@supports (height: 100svh) {
--small-viewport-width: 100svw;
--small-viewport-height: 100svh;
}
@supports (height: 100lvh) and (height: 100svh) {
--viewport-height-diff: calc(100lvh - 100svh);
}
@media (display-mode: standalone) {
--safe-area-inset-bottom: @calculated-bottom-safe-inset;
}
} }
* { * {
@ -117,6 +28,7 @@
padding: 0; padding: 0;
box-sizing: border-box; box-sizing: border-box;
font-size: 1rem; font-size: 1rem;
line-height: 1.2em;
font-family: inherit; font-family: inherit;
border: none; border: none;
outline: none; outline: none;
@ -129,7 +41,7 @@
overflow: hidden; overflow: hidden;
word-break: break-word; word-break: break-word;
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: var(--overlay-color) transparent; scrollbar-color: @color-secondaryvariant2-light1 @color-background-dark2;
} }
::-webkit-scrollbar { ::-webkit-scrollbar {
@ -138,16 +50,15 @@
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
border-radius: var(--scroll-bar-size); background-color: @color-secondaryvariant2-light1;
background-color: var(--overlay-color);
&:hover { &:hover {
background-color: var(--primary-accent-color); background-color: @color-secondaryvariant2-light2;
} }
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
background-color: transparent; background-color: @color-background-dark2;
} }
svg { svg {
@ -155,26 +66,16 @@ svg {
} }
html { html {
width: @html-width; width: 100%;
height: @html-height; height: 100%;
font-family: 'PlusJakartaSans', 'Arial', 'Helvetica', 'TwemojiFlags', 'sans-serif'; min-width: 640px;
min-height: 480px;
font-family: 'Roboto', 'sans-serif';
overflow: auto; overflow: auto;
overscroll-behavior: none;
user-select: none;
touch-action: manipulation;
background-color: var(--primary-background-color);
-webkit-tap-highlight-color: transparent;
@media (display-mode: standalone) {
width: @html-standalone-width;
height: @html-standalone-height;
}
body { body {
width: 100%; width: 100%;
height: 100%; height: 100%;
background: linear-gradient(41deg, var(--primary-background-color) 0%, var(--secondary-background-color) 100%);
-webkit-font-smoothing: antialiased;
:global(#app) { :global(#app) {
position: relative; position: relative;
@ -184,13 +85,13 @@ html {
.toasts-container { .toasts-container {
position: absolute; position: absolute;
top: calc(1.2 * var(--horizontal-nav-bar-size) + var(--safe-area-inset-top)); top: calc(1.2 * var(--horizontal-nav-bar-size));
right: var(--safe-area-inset-right); right: 0;
bottom: calc(1.2 * var(--horizontal-nav-bar-size) + var(--safe-area-inset-bottom, 0rem)); bottom: calc(1.2 * var(--horizontal-nav-bar-size));
left: auto; left: auto;
z-index: 1; z-index: 1;
padding: 0 calc(0.5 * var(--horizontal-nav-bar-size)); padding: 0 calc(0.5 * var(--horizontal-nav-bar-size));
overflow: visible; overflow-y: auto;
scrollbar-width: none; scrollbar-width: none;
pointer-events: none; pointer-events: none;
@ -199,44 +100,6 @@ html {
} }
} }
.tooltip-container {
height: 2.5rem;
display: flex;
align-items: center;
justify-content: center;
padding: 0 1.5rem;
font-size: 1rem;
color: var(--primary-foreground-color);
border-radius: var(--border-radius);
background-color: var(--modal-background-color);
box-shadow: var(--outer-glow);
transition: opacity 0.1s ease-out;
}
.file-drop-container {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
border-radius: 1rem;
border: 0.5rem dashed transparent;
pointer-events: none;
transition: border-color 0.25s ease-out;
&:global(.active) {
border-color: var(--primary-accent-color);
}
}
.updater-banner-container {
z-index: 1;
position: absolute;
left: 0;
right: 0;
bottom: 0;
}
.router { .router {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -245,6 +108,7 @@ html {
.loader-container, .error-container { .loader-container, .error-container {
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: @color-background-dark2;
} }
} }
} }
@ -276,22 +140,7 @@ html {
@media only screen and (max-width: @xsmall) { @media only screen and (max-width: @xsmall) {
html { html {
body { min-width: inherit;
:global(#app) { min-height: inherit;
.toasts-container {
padding: 0 1rem;
}
.tooltip-container {
display: none;
}
}
}
}
}
@media only screen and (max-width: @minimum) {
:root {
--bottom-overlay-size: 6rem;
} }
} }

View file

@ -1,29 +0,0 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
const { Intro } = require('stremio/routes');
const { useProfile } = require('stremio/common');
const withProtectedRoutes = (Component) => {
return function withProtectedRoutes(props) {
const profile = useProfile();
const previousAuthRef = React.useRef(profile.auth);
React.useEffect(() => {
if (previousAuthRef.current !== null && profile.auth === null) {
window.location = '#/intro';
}
previousAuthRef.current = profile.auth;
}, [profile]);
const onRouteChange = React.useCallback((routeConfig) => {
if (profile.auth !== null && routeConfig.component === Intro) {
window.location.replace('#/');
return true;
}
}, [profile]);
return (
<Component {...props} onRouteChange={onRouteChange} />
);
};
};
module.exports = withProtectedRoutes;

View file

@ -1,17 +1,15 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2022 Smart code 203358507
const React = require('react'); const React = require('react');
const { useTranslation } = require('react-i18next');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const classnames = require('classnames'); const classnames = require('classnames');
const { default: Icon } = require('@stremio/stremio-icons/react'); const Icon = require('@stremio/stremio-icons/dom');
const { default: Image } = require('stremio/components/Image'); const Image = require('stremio/common/Image');
const styles = require('./styles'); const styles = require('./styles');
const AddonDetails = ({ className, id, name, version, logo, description, types, transportUrl, official }) => { const AddonDetails = ({ className, id, name, version, logo, description, types, transportUrl, official }) => {
const { t } = useTranslation();
const renderLogoFallback = React.useCallback(() => ( const renderLogoFallback = React.useCallback(() => (
<Icon className={styles['icon']} name={'addons'} /> <Icon className={styles['icon']} icon={'ic_addons'} />
), []); ), []);
return ( return (
<div className={classnames(className, styles['addon-details-container'])}> <div className={classnames(className, styles['addon-details-container'])}>
@ -26,7 +24,7 @@ const AddonDetails = ({ className, id, name, version, logo, description, types,
<span className={styles['name']}>{typeof name === 'string' && name.length > 0 ? name : id}</span> <span className={styles['name']}>{typeof name === 'string' && name.length > 0 ? name : id}</span>
{ {
typeof version === 'string' && version.length > 0 ? typeof version === 'string' && version.length > 0 ?
<span className={styles['version']}>{t('ADDON_VERSION_SHORT', {version})}</span> <span className={styles['version']}>v. {version}</span>
: :
null null
} }
@ -43,7 +41,7 @@ const AddonDetails = ({ className, id, name, version, logo, description, types,
{ {
typeof transportUrl === 'string' && transportUrl.length > 0 ? typeof transportUrl === 'string' && transportUrl.length > 0 ?
<div className={styles['section-container']}> <div className={styles['section-container']}>
<span className={styles['section-header']}>{`${t('URL')}:`}</span> <span className={styles['section-header']}>URL: </span>
<span className={classnames(styles['section-label'], styles['transport-url-label'])}>{transportUrl}</span> <span className={classnames(styles['section-label'], styles['transport-url-label'])}>{transportUrl}</span>
</div> </div>
: :
@ -52,7 +50,7 @@ const AddonDetails = ({ className, id, name, version, logo, description, types,
{ {
Array.isArray(types) && types.length > 0 ? Array.isArray(types) && types.length > 0 ?
<div className={styles['section-container']}> <div className={styles['section-container']}>
<span className={styles['section-header']}>{`${t('ADDON_SUPPORTED_TYPES')}:`} </span> <span className={styles['section-header']}>Supported types: </span>
<span className={styles['section-label']}> <span className={styles['section-label']}>
{ {
types.length === 1 ? types.length === 1 ?
@ -68,7 +66,7 @@ const AddonDetails = ({ className, id, name, version, logo, description, types,
{ {
!official ? !official ?
<div className={styles['section-container']}> <div className={styles['section-container']}>
<div className={classnames(styles['section-label'], styles['disclaimer-label'])}>{t('ADDON_DISCLAIMER')}</div> <div className={classnames(styles['section-label'], styles['disclaimer-label'])}>Using third-party add-ons will always be subject to your responsibility and the governing law of the jurisdiction you are located.</div>
</div> </div>
: :
null null

View file

@ -1,4 +1,4 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2022 Smart code 203358507
const AddonDetails = require('./AddonDetails'); const AddonDetails = require('./AddonDetails');

View file

@ -1,4 +1,4 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2022 Smart code 203358507
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less'; @import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
@ -14,6 +14,7 @@
height: 5rem; height: 5rem;
margin-right: 1.5rem; margin-right: 1.5rem;
padding: 0.5rem; padding: 0.5rem;
background-color: @color-background-dark5;
} }
.logo { .logo {
@ -22,7 +23,7 @@
} }
.icon { .icon {
color: var(--primary-foreground-color); fill: @color-secondaryvariant1-light3;
} }
.name-container { .name-container {
@ -40,7 +41,7 @@
flex-basis: auto; flex-basis: auto;
margin-right: 0.5rem; margin-right: 0.5rem;
font-size: 1.6rem; font-size: 1.6rem;
color: var(--primary-foreground-color); color: @color-background-dark5-90;
} }
.version { .version {
@ -48,7 +49,7 @@
flex-shrink: 1; flex-shrink: 1;
flex-basis: auto; flex-basis: auto;
margin-top: 0.5rem; margin-top: 0.5rem;
color: var(--primary-foreground-color); color: @color-background-dark5-60;
} }
} }
} }
@ -58,13 +59,13 @@
.section-header { .section-header {
font-size: 1.1rem; font-size: 1.1rem;
color: var(--primary-foreground-color); color: @color-background-dark5-90;
} }
.section-label { .section-label {
font-size: 1.1rem; font-size: 1.1rem;
font-weight: 300; font-weight: 300;
color: var(--primary-foreground-color); color: @color-background-dark5-90;
&.transport-url-label { &.transport-url-label {
user-select: text; user-select: text;

View file

@ -1,11 +1,9 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2022 Smart code 203358507
const React = require('react'); const React = require('react');
const { useTranslation } = require('react-i18next');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const ModalDialog = require('stremio/components/ModalDialog'); const ModalDialog = require('stremio/common/ModalDialog');
const { withCoreSuspender } = require('stremio/common/CoreSuspender'); const { withCoreSuspender } = require('stremio/common/CoreSuspender');
const { usePlatform } = require('stremio/common/Platform');
const { useServices } = require('stremio/services'); const { useServices } = require('stremio/services');
const AddonDetailsWithRemoteAndLocalAddon = withRemoteAndLocalAddon(require('./AddonDetails')); const AddonDetailsWithRemoteAndLocalAddon = withRemoteAndLocalAddon(require('./AddonDetails'));
const useAddonDetails = require('./useAddonDetails'); const useAddonDetails = require('./useAddonDetails');
@ -30,7 +28,6 @@ function withRemoteAndLocalAddon(AddonDetails) {
id={addon.manifest.id} id={addon.manifest.id}
name={addon.manifest.name} name={addon.manifest.name}
version={addon.manifest.version} version={addon.manifest.version}
background={addon.manifest.background}
logo={addon.manifest.logo} logo={addon.manifest.logo}
description={addon.manifest.description} description={addon.manifest.description}
types={addon.manifest.types} types={addon.manifest.types}
@ -44,14 +41,12 @@ function withRemoteAndLocalAddon(AddonDetails) {
} }
const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => { const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => {
const { t } = useTranslation();
const { core } = useServices(); const { core } = useServices();
const platform = usePlatform();
const addonDetails = useAddonDetails(transportUrl); const addonDetails = useAddonDetails(transportUrl);
const modalButtons = React.useMemo(() => { const modalButtons = React.useMemo(() => {
const cancelButton = { const cancelButton = {
className: styles['cancel-button'], className: styles['cancel-button'],
label: t('BUTTON_CANCEL'), label: 'Cancel',
props: { props: {
onClick: (event) => { onClick: (event) => {
if (typeof onCloseRequest === 'function') { if (typeof onCloseRequest === 'function') {
@ -64,31 +59,10 @@ const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => {
} }
} }
}; };
const configureButton = addonDetails.remoteAddon !== null &&
addonDetails.remoteAddon.content.type === 'Ready' &&
addonDetails.remoteAddon.content.content.manifest.behaviorHints.configurable ?
{
className: styles['configure-button'],
label: t('ADDON_CONFIGURE'),
props: {
onClick: (event) => {
platform.openExternal(transportUrl.replace('manifest.json', 'configure'));
if (typeof onCloseRequest === 'function') {
onCloseRequest({
type: 'configure',
reactEvent: event,
nativeEvent: event.nativeEvent
});
}
}
}
}
:
null;
const toggleButton = addonDetails.localAddon !== null ? const toggleButton = addonDetails.localAddon !== null ?
{ {
className: styles['uninstall-button'], className: styles['uninstall-button'],
label: t('ADDON_UNINSTALL'), label: 'Uninstall',
props: { props: {
onClick: (event) => { onClick: (event) => {
core.transport.dispatch({ core.transport.dispatch({
@ -109,13 +83,11 @@ const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => {
} }
} }
: :
addonDetails.remoteAddon !== null && addonDetails.remoteAddon !== null && addonDetails.remoteAddon.content.type === 'Ready' ?
addonDetails.remoteAddon.content.type === 'Ready' &&
!addonDetails.remoteAddon.content.content.manifest.behaviorHints.configurationRequired ?
{ {
className: styles['install-button'], className: styles['install-button'],
label: t('ADDON_INSTALL'), label: 'Install',
props: { props: {
onClick: (event) => { onClick: (event) => {
core.transport.dispatch({ core.transport.dispatch({
@ -137,27 +109,24 @@ const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => {
} }
: :
null; null;
return configureButton && toggleButton ? [cancelButton, configureButton, toggleButton] : configureButton ? [cancelButton, configureButton] : toggleButton ? [cancelButton, toggleButton] : [cancelButton]; return toggleButton !== null ? [cancelButton, toggleButton] : [cancelButton];
}, [addonDetails, onCloseRequest]); }, [addonDetails, onCloseRequest]);
const modalBackground = React.useMemo(() => {
return addonDetails.remoteAddon?.content.type === 'Ready' ? addonDetails.remoteAddon.content.content.manifest.background : null;
}, [addonDetails.remoteAddon]);
return ( return (
<ModalDialog className={styles['addon-details-modal-container']} title={t('STREMIO_COMMUNITY_ADDON')} buttons={modalButtons} background={modalBackground} onCloseRequest={onCloseRequest}> <ModalDialog className={styles['addon-details-modal-container']} title={'Stremio addon'} buttons={modalButtons} onCloseRequest={onCloseRequest}>
{ {
addonDetails.selected === null ? addonDetails.selected === null ?
<div className={styles['addon-details-message-container']}> <div className={styles['addon-details-message-container']}>
{t('ADDON_LOADING_MANIFEST')} Loading addon manifest
</div> </div>
: :
addonDetails.remoteAddon === null || addonDetails.remoteAddon.content.type === 'Loading' ? addonDetails.remoteAddon === null || addonDetails.remoteAddon.content.type === 'Loading' ?
<div className={styles['addon-details-message-container']}> <div className={styles['addon-details-message-container']}>
{t('ADDON_LOADING_MANIFEST_FROM', { origin: addonDetails.selected.transportUrl})} Loading addon manifest from {addonDetails.selected.transportUrl}
</div> </div>
: :
addonDetails.remoteAddon.content.type === 'Err' && addonDetails.localAddon === null ? addonDetails.remoteAddon.content.type === 'Err' && addonDetails.localAddon === null ?
<div className={styles['addon-details-message-container']}> <div className={styles['addon-details-message-container']}>
{t('ADDON_LOADING_MANIFEST_FAILED', {origin: addonDetails.selected.transportUrl})} Failed to get addon manifest from {addonDetails.selected.transportUrl}
<div>{addonDetails.remoteAddon.content.content.message}</div> <div>{addonDetails.remoteAddon.content.content.message}</div>
</div> </div>
: :
@ -176,18 +145,17 @@ AddonDetailsModal.propTypes = {
onCloseRequest: PropTypes.func onCloseRequest: PropTypes.func
}; };
const AddonDetailsModalFallback = ({ onCloseRequest }) => { const AddonDetailsModalFallback = ({ onCloseRequest }) => (
const { t } = useTranslation(); <ModalDialog
return <ModalDialog
className={styles['addon-details-modal-container']} className={styles['addon-details-modal-container']}
title={t('STREMIO_COMMUNITY_ADDON')} title={'Stremio addon'}
onCloseRequest={onCloseRequest} onCloseRequest={onCloseRequest}
> >
<div className={styles['addon-details-message-container']}> <div className={styles['addon-details-message-container']}>
{t('ADDON_LOADING_MANIFEST')} Loading addon manifest
</div> </div>
</ModalDialog>; </ModalDialog>
}; );
AddonDetailsModalFallback.propTypes = AddonDetailsModal.propTypes; AddonDetailsModalFallback.propTypes = AddonDetailsModal.propTypes;

View file

@ -1,4 +1,4 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2022 Smart code 203358507
const AddonDetailsModal = require('./AddonDetailsModal'); const AddonDetailsModal = require('./AddonDetailsModal');

View file

@ -0,0 +1,47 @@
// Copyright (C) 2017-2022 Smart code 203358507
@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;
max-width: 100%;
}
.install-button, .uninstall-button, .cancel-button {
.label {
font-size: 1.2rem;
font-weight: 500;
}
}
.uninstall-button, .cancel-button {
&:focus {
outline-color: @color-background-dark5;
}
}
.cancel-button {
background-color: transparent;
&:hover {
background-color: @color-surface-light3;
}
.label {
color: @color-surface-dark2;
}
}
.uninstall-button {
background-color: @color-accent2;
&:hover {
background-color: @color-accent2-light2;
}
}
}

View file

@ -1,4 +1,4 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2022 Smart code 203358507
const React = require('react'); const React = require('react');
const useModelState = require('stremio/common/useModelState'); const useModelState = require('stremio/common/useModelState');

View file

@ -0,0 +1,56 @@
// Copyright (C) 2017-2022 Smart code 203358507
const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const styles = require('./styles');
const Button = React.forwardRef(({ className, href, disabled, children, ...props }, ref) => {
const onKeyDown = React.useCallback((event) => {
if (typeof props.onKeyDown === 'function') {
props.onKeyDown(event);
}
if (event.key === 'Enter' && !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
},
children
);
});
Button.displayName = 'Button';
Button.propTypes = {
className: PropTypes.string,
href: PropTypes.string,
disabled: PropTypes.bool,
children: PropTypes.node,
onKeyDown: PropTypes.func,
onMouseDown: PropTypes.func
};
module.exports = Button;

View file

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

View file

@ -1,4 +1,4 @@
// Copyright (C) 2017-2024 Smart code 203358507 // Copyright (C) 2017-2022 Smart code 203358507
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less'; @import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
@ -14,6 +14,5 @@
&:global(.disabled) { &:global(.disabled) {
pointer-events: none; pointer-events: none;
opacity: 0.5;
} }
} }

View file

@ -1,13 +1,9 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2022 Smart code 203358507
const CHROMECAST_RECEIVER_APP_ID = '1634F54B'; const CHROMECAST_RECEIVER_APP_ID = '1634F54B';
const DEFAULT_STREAMING_SERVER_URL = 'http://127.0.0.1:11470/';
const DEFAULT_SUBTITLES_LANGUAGE = 'eng';
const LOCAL_SUBTITLES_LANGUAGE = 'local';
const SUBTITLES_SIZES = [75, 100, 125, 150, 175, 200, 250]; 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 SUBTITLES_FONTS = ['Roboto', 'Arial', 'Halvetica', 'Times New Roman', 'Verdana', 'Courier', 'Lucida Console', 'sans-serif', 'serif', 'monospace'];
const SEEK_TIME_DURATIONS = [3000, 5000, 10000, 15000, 20000, 30000]; const SEEK_TIME_DURATIONS = [5000, 10000, 15000, 20000, 25000, 30000];
const NEXT_VIDEO_POPUP_DURATIONS = [0, 5000, 10000, 15000, 20000, 25000, 30000, 35000, 40000, 45000, 50000, 55000, 60000, 65000, 70000, 75000, 80000, 85000, 90000];
const CATALOG_PREVIEW_SIZE = 10; const CATALOG_PREVIEW_SIZE = 10;
const CATALOG_PAGE_SIZE = 100; const CATALOG_PAGE_SIZE = 100;
const NONE_EXTRA_VALUE = 'None'; const NONE_EXTRA_VALUE = 'None';
@ -29,106 +25,12 @@ const TYPE_PRIORITIES = {
adult: 1, adult: 1,
other: -Infinity other: -Infinity
}; };
const ICON_FOR_TYPE = new Map([
['movie', 'movies'],
['series', 'series'],
['channel', 'channels'],
['tv', 'tv'],
['book', 'ic_book'],
['game', 'ic_games'],
['music', 'ic_music'],
['adult', 'ic_adult'],
['radio', 'ic_radio'],
['podcast', 'ic_podcast'],
['other', 'movies'],
]);
const MIME_SIGNATURES = {
'application/x-subrip': ['310D0A', '310A'],
'text/vtt': ['574542565454'],
};
const SUPPORTED_LOCAL_SUBTITLES = [
'application/x-subrip',
'text/vtt',
];
const EXTERNAL_PLAYERS = [
{
label: 'EXTERNAL_PLAYER_DISABLED',
value: null,
platforms: ['ios', 'visionos', 'android', 'windows', 'linux', 'macos'],
},
{
label: 'EXTERNAL_PLAYER_ALLOW_CHOOSING',
value: 'choose',
platforms: ['android'],
},
{
label: 'VLC',
value: 'vlc',
platforms: ['ios', 'visionos', 'android'],
},
{
label: 'MPV',
value: 'mpv',
platforms: ['macos'],
},
{
label: 'IINA',
value: 'iina',
platforms: ['macos'],
},
{
label: 'MX Player',
value: 'mxplayer',
platforms: ['android'],
},
{
label: 'Just Player',
value: 'justplayer',
platforms: ['android'],
},
{
label: 'Outplayer',
value: 'outplayer',
platforms: ['ios', 'visionos'],
},
{
label: 'Moonplayer (VisionOS)',
value: 'moonplayer',
platforms: ['visionos'],
},
{
label: 'Infuse',
value: 'infuse',
platforms: ['ios', 'visionos', 'macos'],
},
{
label: 'Vidhub',
value: 'vidhub',
platforms: ['ios'],
},
{
label: 'M3U Playlist',
value: 'm3u',
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'];
const PROTOCOL = 'stremio:';
module.exports = { module.exports = {
CHROMECAST_RECEIVER_APP_ID, CHROMECAST_RECEIVER_APP_ID,
DEFAULT_STREAMING_SERVER_URL,
DEFAULT_SUBTITLES_LANGUAGE,
LOCAL_SUBTITLES_LANGUAGE,
SUBTITLES_SIZES, SUBTITLES_SIZES,
SUBTITLES_FONTS, SUBTITLES_FONTS,
SEEK_TIME_DURATIONS, SEEK_TIME_DURATIONS,
NEXT_VIDEO_POPUP_DURATIONS,
CATALOG_PREVIEW_SIZE, CATALOG_PREVIEW_SIZE,
CATALOG_PAGE_SIZE, CATALOG_PAGE_SIZE,
NONE_EXTRA_VALUE, NONE_EXTRA_VALUE,
@ -137,11 +39,5 @@ module.exports = {
IMDB_LINK_CATEGORY, IMDB_LINK_CATEGORY,
SHARE_LINK_CATEGORY, SHARE_LINK_CATEGORY,
WRITERS_LINK_CATEGORY, WRITERS_LINK_CATEGORY,
TYPE_PRIORITIES, TYPE_PRIORITIES
ICON_FOR_TYPE,
MIME_SIGNATURES,
SUPPORTED_LOCAL_SUBTITLES,
EXTERNAL_PLAYERS,
WHITELISTED_HOSTS,
PROTOCOL,
}; };

View file

@ -0,0 +1,34 @@
// Copyright (C) 2017-2022 Smart code 203358507
const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const Icon = require('@stremio/stremio-icons/dom');
const Button = require('stremio/common/Button');
const styles = require('./styles');
const Checkbox = React.forwardRef(({ className, checked, children, ...props }, ref) => {
return (
<Button {...props} ref={ref} className={classnames(className, styles['checkbox-container'], { 'checked': checked })}>
{
checked ?
<svg className={styles['icon']} viewBox={'0 0 100 100'}>
<Icon x={'10'} y={'10'} width={'80'} height={'80'} icon={'ic_check'} />
</svg>
:
<Icon className={styles['icon']} icon={'ic_box_empty'} />
}
{children}
</Button>
);
});
Checkbox.displayName = 'Checkbox';
Checkbox.propTypes = {
className: PropTypes.string,
checked: PropTypes.bool,
children: PropTypes.node
};
module.exports = Checkbox;

View file

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

View file

@ -0,0 +1,19 @@
// Copyright (C) 2017-2022 Smart code 203358507
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
.checkbox-container {
&:global(.checked) {
.icon {
fill: @color-surface-light5;
background-color: @color-primaryvariant1;
}
}
.icon {
display: block;
width: 1rem;
height: 1rem;
fill: @color-surface-light5;
}
}

View file

@ -0,0 +1,101 @@
// Copyright (C) 2017-2022 Smart code 203358507
const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const AColorPicker = require('a-color-picker');
const Button = require('stremio/common/Button');
const ModalDialog = require('stremio/common/ModalDialog');
const useBinaryState = require('stremio/common/useBinaryState');
const ColorPicker = require('./ColorPicker');
const styles = require('./styles');
const parseColor = (value) => {
const color = AColorPicker.parseColor(value, 'hexcss4');
return typeof color === 'string' ? color : '#ffffffff';
};
const ColorInput = ({ className, value, dataset, onChange, ...props }) => {
const [modalOpen, openModal, closeModal] = useBinaryState(false);
const [tempValue, setTempValue] = React.useState(() => {
return parseColor(value);
});
const labelButtonStyle = React.useMemo(() => ({
backgroundColor: value
}), [value]);
const isTransparent = React.useMemo(() => {
return parseColor(value).endsWith('00');
}, [value]);
const labelButtonOnClick = React.useCallback((event) => {
if (typeof props.onClick === 'function') {
props.onClick(event);
}
if (!event.nativeEvent.openModalPrevented) {
openModal();
}
}, [props.onClick]);
const modalDialogOnClick = React.useCallback((event) => {
event.nativeEvent.openModalPrevented = true;
}, []);
const modalButtons = React.useMemo(() => {
const selectButtonOnClick = (event) => {
if (typeof onChange === 'function') {
onChange({
type: 'change',
value: tempValue,
dataset: dataset,
reactEvent: event,
nativeEvent: event.nativeEvent
});
}
closeModal();
};
return [
{
label: 'Select',
props: {
'data-autofocus': true,
onClick: selectButtonOnClick
}
}
];
}, [tempValue, dataset, onChange]);
const colorPickerOnInput = React.useCallback((event) => {
setTempValue(parseColor(event.value));
}, []);
React.useLayoutEffect(() => {
setTempValue(parseColor(value));
}, [value, modalOpen]);
return (
<Button title={isTransparent ? 'Transparent' : value} {...props} style={labelButtonStyle} className={classnames(className, styles['color-input-container'])} onClick={labelButtonOnClick}>
{
isTransparent ?
<div className={styles['transparent-label-container']}>
<div className={styles['transparent-label']}>Transparent</div>
</div>
:
null
}
{
modalOpen ?
<ModalDialog title={'Choose a color:'} buttons={modalButtons} onCloseRequest={closeModal} onClick={modalDialogOnClick}>
<ColorPicker className={styles['color-picker-container']} value={tempValue} onInput={colorPickerOnInput} />
</ModalDialog>
:
null
}
</Button>
);
};
ColorInput.propTypes = {
className: PropTypes.string,
value: PropTypes.string,
dataset: PropTypes.object,
onChange: PropTypes.func,
onClick: PropTypes.func
};
module.exports = ColorInput;

View file

@ -1,4 +1,4 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2022 Smart code 203358507
const React = require('react'); const React = require('react');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
@ -21,7 +21,7 @@ const ColorPicker = ({ className, value, onInput }) => {
showRGB: false, showRGB: false,
showAlpha: true showAlpha: true
}); });
const pickerClipboard = pickerElementRef.current.querySelector('.a-color-picker-clipboard'); const pickerClipboard = pickerElementRef.current.querySelector('.a-color-picker-clipbaord');
if (pickerClipboard instanceof HTMLElement) { if (pickerClipboard instanceof HTMLElement) {
pickerClipboard.tabIndex = -1; pickerClipboard.tabIndex = -1;
} }
@ -29,7 +29,10 @@ const ColorPicker = ({ className, value, onInput }) => {
React.useLayoutEffect(() => { React.useLayoutEffect(() => {
if (typeof onInput === 'function') { if (typeof onInput === 'function') {
pickerRef.current.on('change', (picker, value) => { pickerRef.current.on('change', (picker, value) => {
onInput(parseColor(value)); onInput({
type: 'input',
value: parseColor(value)
});
}); });
} }
return () => { return () => {

View file

@ -1,4 +1,4 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2022 Smart code 203358507
const ColorPicker = require('./ColorPicker'); const ColorPicker = require('./ColorPicker');

View file

@ -1,4 +1,4 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2022 Smart code 203358507
.color-picker-container { .color-picker-container {
overflow: visible; overflow: visible;
@ -16,7 +16,7 @@
box-shadow: 0 0 .2rem var(--color-surfacedark); box-shadow: 0 0 .2rem var(--color-surfacedark);
} }
:global(.a-color-picker-clipboard) { :global(.a-color-picker-clipbaord) {
pointer-events: none; pointer-events: none;
} }
} }

View file

@ -0,0 +1,6 @@
// Copyright (C) 2017-2022 Smart code 203358507
const ColorInput = require('./ColorInput');
module.exports = ColorInput;

View file

@ -1,4 +1,4 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2022 Smart code 203358507
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less'; @import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
@ -17,6 +17,7 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 0 0.5rem; padding: 0 0.5rem;
border: thin solid @color-surface-light5-20;
pointer-events: none; pointer-events: none;
.transparent-label { .transparent-label {

View file

@ -1,4 +1,4 @@
// Copyright (C) 2017-2023 Smart code 203358507 // Copyright (C) 2017-2022 Smart code 203358507
const React = require('react'); const React = require('react');
const { useServices } = require('stremio/services'); const { useServices } = require('stremio/services');
@ -37,7 +37,6 @@ const useCoreSuspender = () => {
return React.useContext(CoreSuspenderContext); return React.useContext(CoreSuspenderContext);
}; };
// eslint-disable-next-line @typescript-eslint/no-empty-function
const withCoreSuspender = (Component, Fallback = () => { }) => { const withCoreSuspender = (Component, Fallback = () => { }) => {
return function withCoreSuspender(props) { return function withCoreSuspender(props) {
const { core } = useServices(); const { core } = useServices();

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