diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d728715a2..f61e32b6f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,14 +20,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install pnpm uses: pnpm/action-setup@v4 with: version: 10 run_install: false - name: Setup node - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc cache: "pnpm" diff --git a/.github/workflows/pages_cleanup.yml b/.github/workflows/pages_cleanup.yml index 2147b25d6..ef4f1db25 100644 --- a/.github/workflows/pages_cleanup.yml +++ b/.github/workflows/pages_cleanup.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: gh-pages fetch-depth: 0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index afbf92001..ce392e134 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,8 +9,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 - - name: Install NPM dependencies + uses: actions/checkout@v6 + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + run_install: false + - name: Install dependencies run: pnpm install - name: Build env: @@ -19,7 +24,7 @@ jobs: - name: Zip build artifact run: zip -r stremio-web.zip ./build - name: Upload build artifact to GitHub release assets - uses: svenstaro/upload-release-action@2.11.2 + uses: svenstaro/upload-release-action@2.11.3 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: stremio-web.zip diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index b4c749870..ae01c05f5 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -29,7 +29,7 @@ Project maintainers are responsible for enforcing this code of conduct. They can ## 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 excesive comments generated by AI. +- 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. diff --git a/Dockerfile b/Dockerfile index a0a3c597a..c9aa2cb23 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,29 +3,39 @@ ARG NODE_VERSION=20-alpine FROM node:$NODE_VERSION AS base +# Setup pnpm +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" + +RUN corepack enable +RUN apk add --no-cache git + # Meta LABEL Description="Stremio Web" Vendor="Smart Code OOD" Version="1.0.0" RUN mkdir -p /var/www/stremio-web WORKDIR /var/www/stremio-web -# Install app dependencies -FROM base AS prebuild +# Setup app +FROM base AS app -RUN apk update && apk upgrade && \ - apk add --no-cache git -WORKDIR /var/www/stremio-web -COPY . . -RUN npm install -RUN npm run build +COPY package.json pnpm-lock.yaml /var/www/stremio-web +RUN pnpm i --frozen-lockfile -# Bundle app source -FROM base AS final +COPY . /var/www/stremio-web +RUN pnpm build -WORKDIR /var/www/stremio-web -COPY . . -COPY --from=prebuild /var/www/stremio-web/node_modules ./node_modules -COPY --from=prebuild /var/www/stremio-web/build ./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"] diff --git a/README.md b/README.md index b7cd999dc..ecc03a921 100644 --- a/README.md +++ b/README.md @@ -41,15 +41,15 @@ docker run -p 8080:8080 stremio-web ### Board -![Board](/screenshots/board.png) +![Board](/assets/screenshots/board.png) ### Discover -![Discover](/screenshots/discover.png) +![Discover](/assets/screenshots/discover.png) ### Meta Details -![Meta Details](/screenshots/metadetails.png) +![Meta Details](/assets/screenshots/metadetails.png) ## License diff --git a/favicons/favicon.ico b/assets/favicons/favicon.ico similarity index 100% rename from favicons/favicon.ico rename to assets/favicons/favicon.ico diff --git a/fonts/PlusJakartaSans.ttf b/assets/fonts/PlusJakartaSans.ttf similarity index 100% rename from fonts/PlusJakartaSans.ttf rename to assets/fonts/PlusJakartaSans.ttf diff --git a/images/anonymous.png b/assets/images/anonymous.png similarity index 100% rename from images/anonymous.png rename to assets/images/anonymous.png diff --git a/images/background_1.svg b/assets/images/background_1.svg similarity index 100% rename from images/background_1.svg rename to assets/images/background_1.svg diff --git a/images/background_2.svg b/assets/images/background_2.svg similarity index 100% rename from images/background_2.svg rename to assets/images/background_2.svg diff --git a/images/calendar_placeholder.png b/assets/images/calendar_placeholder.png similarity index 100% rename from images/calendar_placeholder.png rename to assets/images/calendar_placeholder.png diff --git a/images/default_avatar.png b/assets/images/default_avatar.png similarity index 100% rename from images/default_avatar.png rename to assets/images/default_avatar.png diff --git a/images/empty.png b/assets/images/empty.png similarity index 100% rename from images/empty.png rename to assets/images/empty.png diff --git a/images/icon.png b/assets/images/icon.png similarity index 100% rename from images/icon.png rename to assets/images/icon.png diff --git a/assets/images/icon_196x196.png b/assets/images/icon_196x196.png new file mode 100644 index 000000000..d5bc3e90f Binary files /dev/null and b/assets/images/icon_196x196.png differ diff --git a/assets/images/icon_512x512.png b/assets/images/icon_512x512.png new file mode 100644 index 000000000..9569431ed Binary files /dev/null and b/assets/images/icon_512x512.png differ diff --git a/images/library_placeholder.png b/assets/images/library_placeholder.png similarity index 100% rename from images/library_placeholder.png rename to assets/images/library_placeholder.png diff --git a/images/logo.png b/assets/images/logo.png similarity index 100% rename from images/logo.png rename to assets/images/logo.png diff --git a/images/maskable_icon.png b/assets/images/maskable_icon.png similarity index 100% rename from images/maskable_icon.png rename to assets/images/maskable_icon.png diff --git a/assets/images/maskable_icon_196x196.png b/assets/images/maskable_icon_196x196.png new file mode 100644 index 000000000..eb84355ad Binary files /dev/null and b/assets/images/maskable_icon_196x196.png differ diff --git a/assets/images/maskable_icon_512x512.png b/assets/images/maskable_icon_512x512.png new file mode 100644 index 000000000..804b24a20 Binary files /dev/null and b/assets/images/maskable_icon_512x512.png differ diff --git a/images/stremio_symbol.png b/assets/images/stremio_symbol.png similarity index 100% rename from images/stremio_symbol.png rename to assets/images/stremio_symbol.png diff --git a/screenshots/board.png b/assets/screenshots/board.png similarity index 100% rename from screenshots/board.png rename to assets/screenshots/board.png diff --git a/screenshots/board_narrow.webp b/assets/screenshots/board_narrow.webp similarity index 100% rename from screenshots/board_narrow.webp rename to assets/screenshots/board_narrow.webp diff --git a/screenshots/board_wide.webp b/assets/screenshots/board_wide.webp similarity index 100% rename from screenshots/board_wide.webp rename to assets/screenshots/board_wide.webp diff --git a/screenshots/discover.png b/assets/screenshots/discover.png similarity index 100% rename from screenshots/discover.png rename to assets/screenshots/discover.png diff --git a/screenshots/metadetails.png b/assets/screenshots/metadetails.png similarity index 100% rename from screenshots/metadetails.png rename to assets/screenshots/metadetails.png diff --git a/manifest.json b/manifest.json new file mode 100644 index 000000000..3505fb7c4 --- /dev/null +++ b/manifest.json @@ -0,0 +1,59 @@ +{ + "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" + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json index c824ad411..0c9ca54ec 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "stremio", "displayName": "Stremio", - "version": "5.0.0-beta.27", + "version": "5.0.0-beta.29", "author": "Smart Code OOD", "private": true, "license": "gpl-2.0", @@ -17,21 +17,21 @@ "@babel/runtime": "7.26.0", "@sentry/browser": "8.42.0", "@stremio/stremio-colors": "5.2.0", - "@stremio/stremio-core-web": "0.49.4", - "@stremio/stremio-icons": "5.7.1", - "@stremio/stremio-video": "0.0.62", + "@stremio/stremio-core-web": "0.52.0", + "@stremio/stremio-icons": "5.8.0", + "@stremio/stremio-video": "0.0.70", "a-color-picker": "1.2.1", "bowser": "2.11.0", "buffer": "6.0.3", "classnames": "2.5.1", "eventemitter3": "5.0.1", + "fast-equals": "^6.0.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.intersection": "4.4.0", - "lodash.isequal": "4.5.0", "lodash.throttle": "4.1.1", "magnet-uri": "6.2.0", "prop-types": "15.8.1", @@ -41,7 +41,7 @@ "react-i18next": "^15.1.3", "react-is": "18.3.1", "spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6", - "stremio-translations": "github:Stremio/stremio-translations#abe7684165a031755e9aee39da26daa806ba7824", + "stremio-translations": "github:Stremio/stremio-translations#7c0c337f32163aa13158bb90cd6133da43feafef", "url": "0.11.4", "use-long-press": "^3.2.0" }, @@ -53,12 +53,10 @@ "@stylistic/eslint-plugin": "^5.4.0", "@stylistic/eslint-plugin-jsx": "^4.4.1", "@types/hat": "^0.0.4", - "@types/lodash.isequal": "^4.5.8", "@types/lodash.throttle": "^4.1.9", "@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": "12.0.2", "css-loader": "6.11.0", "cssnano": "7.0.6", @@ -82,7 +80,6 @@ "webpack": "5.97.0", "webpack-cli": "5.1.4", "webpack-dev-server": "^5.1.0", - "webpack-pwa-manifest": "^4.3.0", "workbox-webpack-plugin": "^7.3.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 121d0dbea..435a809e8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,14 +18,14 @@ importers: specifier: 5.2.0 version: 5.2.0 '@stremio/stremio-core-web': - specifier: 0.49.4 - version: 0.49.4 + specifier: 0.52.0 + version: 0.52.0 '@stremio/stremio-icons': - specifier: 5.7.1 - version: 5.7.1 + specifier: 5.8.0 + version: 5.8.0 '@stremio/stremio-video': - specifier: 0.0.62 - version: 0.0.62 + specifier: 0.0.70 + version: 0.0.70 a-color-picker: specifier: 1.2.1 version: 1.2.1 @@ -41,6 +41,9 @@ importers: eventemitter3: specifier: 5.0.1 version: 5.0.1 + fast-equals: + specifier: ^6.0.0 + version: 6.0.0 filter-invalid-dom-props: specifier: 3.0.1 version: 3.0.1 @@ -59,9 +62,6 @@ importers: lodash.intersection: specifier: 4.4.0 version: 4.4.0 - lodash.isequal: - specifier: 4.5.0 - version: 4.5.0 lodash.throttle: specifier: 4.1.1 version: 4.1.1 @@ -90,8 +90,8 @@ importers: specifier: github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6 version: https://codeload.github.com/Stremio/spatial-navigation/tar.gz/64871b1422466f5f45d24ebc8bbd315b2ebab6a6 stremio-translations: - specifier: github:Stremio/stremio-translations#abe7684165a031755e9aee39da26daa806ba7824 - version: https://codeload.github.com/Stremio/stremio-translations/tar.gz/abe7684165a031755e9aee39da26daa806ba7824 + specifier: github:Stremio/stremio-translations#7c0c337f32163aa13158bb90cd6133da43feafef + version: https://codeload.github.com/Stremio/stremio-translations/tar.gz/7c0c337f32163aa13158bb90cd6133da43feafef url: specifier: 0.11.4 version: 0.11.4 @@ -120,9 +120,6 @@ importers: '@types/hat': specifier: ^0.0.4 version: 0.0.4 - '@types/lodash.isequal': - specifier: ^4.5.8 - version: 4.5.8 '@types/lodash.throttle': specifier: ^4.1.9 version: 4.1.9 @@ -135,9 +132,6 @@ importers: babel-loader: specifier: 9.2.1 version: 9.2.1(@babel/core@7.26.0)(webpack@5.97.0) - clean-webpack-plugin: - specifier: 4.0.0 - version: 4.0.0(webpack@5.97.0) copy-webpack-plugin: specifier: 12.0.2 version: 12.0.2(webpack@5.97.0) @@ -207,9 +201,6 @@ importers: webpack-dev-server: specifier: ^5.1.0 version: 5.2.2(webpack-cli@5.1.4)(webpack@5.97.0) - webpack-pwa-manifest: - specifier: ^4.3.0 - version: 4.3.0 workbox-webpack-plugin: specifier: ^7.3.0 version: 7.3.0(@types/babel__core@7.20.5)(webpack@5.97.0) @@ -899,14 +890,6 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@isaacs/balanced-match@4.0.1': - resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} - engines: {node: 20 || >=22} - - '@isaacs/brace-expansion@5.0.0': - resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} - engines: {node: 20 || >=22} - '@istanbuljs/load-nyc-config@1.1.0': resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} engines: {node: '>=8'} @@ -981,171 +964,6 @@ packages: resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - '@jimp/bmp@0.16.13': - resolution: {integrity: sha512-9edAxu7N2FX7vzkdl5Jo1BbACfycUtBQX+XBMcHA2bk62P8R0otgkHg798frgAk/WxQIzwxqOH6wMiCwrlAzdQ==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - - '@jimp/core@0.16.13': - resolution: {integrity: sha512-qXpA1tzTnlkTku9yqtuRtS/wVntvE6f3m3GNxdTdtmc+O+Wcg9Xo2ABPMh7Nc0AHbMKzwvwgB2JnjZmlmJEObg==} - - '@jimp/custom@0.16.13': - resolution: {integrity: sha512-LTATglVUPGkPf15zX1wTMlZ0+AU7cGEGF6ekVF1crA8eHUWsGjrYTB+Ht4E3HTrCok8weQG+K01rJndCp/l4XA==} - - '@jimp/gif@0.16.13': - resolution: {integrity: sha512-yFAMZGv3o+YcjXilMWWwS/bv1iSqykFahFMSO169uVMtfQVfa90kt4/kDwrXNR6Q9i6VHpFiGZMlF2UnHClBvg==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - - '@jimp/jpeg@0.16.13': - resolution: {integrity: sha512-BJHlDxzTlCqP2ThqP8J0eDrbBfod7npWCbJAcfkKqdQuFk0zBPaZ6KKaQKyKxmWJ87Z6ohANZoMKEbtvrwz1AA==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - - '@jimp/plugin-blit@0.16.13': - resolution: {integrity: sha512-8Z1k96ZFxlhK2bgrY1JNWNwvaBeI/bciLM0yDOni2+aZwfIIiC7Y6PeWHTAvjHNjphz+XCt01WQmOYWCn0ML6g==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - - '@jimp/plugin-blur@0.16.13': - resolution: {integrity: sha512-PvLrfa8vkej3qinlebyhLpksJgCF5aiysDMSVhOZqwH5nQLLtDE9WYbnsofGw4r0VVpyw3H/ANCIzYTyCtP9Cg==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - - '@jimp/plugin-circle@0.16.13': - resolution: {integrity: sha512-RNave7EFgZrb5V5EpdvJGAEHMnDAJuwv05hKscNfIYxf0kR3KhViBTDy+MoTnMlIvaKFULfwIgaZWzyhuINMzA==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - - '@jimp/plugin-color@0.16.13': - resolution: {integrity: sha512-xW+9BtEvoIkkH/Wde9ql4nAFbYLkVINhpgAE7VcBUsuuB34WUbcBl/taOuUYQrPEFQJ4jfXiAJZ2H/rvKjCVnQ==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - - '@jimp/plugin-contain@0.16.13': - resolution: {integrity: sha512-QayTXw4tXMwU6q6acNTQrTTFTXpNRBe+MgTGMDU0lk+23PjlFCO/9sacflelG8lsp7vNHhAxFeHptDMAksEYzg==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - '@jimp/plugin-blit': '>=0.3.5' - '@jimp/plugin-resize': '>=0.3.5' - '@jimp/plugin-scale': '>=0.3.5' - - '@jimp/plugin-cover@0.16.13': - resolution: {integrity: sha512-BSsP71GTNaqWRcvkbWuIVH+zK7b3TSNebbhDkFK0fVaUTzHuKMS/mgY4hDZIEVt7Rf5FjadAYtsujHN9w0iSYA==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - '@jimp/plugin-crop': '>=0.3.5' - '@jimp/plugin-resize': '>=0.3.5' - '@jimp/plugin-scale': '>=0.3.5' - - '@jimp/plugin-crop@0.16.13': - resolution: {integrity: sha512-WEl2tPVYwzYL8OKme6Go2xqiWgKsgxlMwyHabdAU4tXaRwOCnOI7v4021gCcBb9zn/oWwguHuKHmK30Fw2Z/PA==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - - '@jimp/plugin-displace@0.16.13': - resolution: {integrity: sha512-qt9WKq8vWrcjySa9DyQ0x/RBMHQeiVjdVSY1SJsMjssPUf0pS74qorcuAkGi89biN3YoGUgPkpqECnAWnYwgGA==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - - '@jimp/plugin-dither@0.16.13': - resolution: {integrity: sha512-5/N3yJggbWQTlGZHQYJPmQXEwR52qaXjEzkp1yRBbtdaekXE3BG/suo0fqeoV/csf8ooI78sJzYmIrxNoWVtgQ==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - - '@jimp/plugin-fisheye@0.16.13': - resolution: {integrity: sha512-2rZmTdFbT/cF9lEZIkXCYO0TsT114Q27AX5IAo0Sju6jVQbvIk1dFUTnwLDadTo8wkJlFzGqMQ24Cs8cHWOliA==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - - '@jimp/plugin-flip@0.16.13': - resolution: {integrity: sha512-EmcgAA74FTc5u7Z+hUO/sRjWwfPPLuOQP5O64x5g4j0T12Bd29IgsYZxoutZo/rb3579+JNa/3wsSEmyVv1EpA==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - '@jimp/plugin-rotate': '>=0.3.5' - - '@jimp/plugin-gaussian@0.16.13': - resolution: {integrity: sha512-A1XKfGQD0iDdIiKqFYi8nZMv4dDVYdxbrmgR7y/CzUHhSYdcmoljLIIsZZM3Iks/Wa353W3vtvkWLuDbQbch1w==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - - '@jimp/plugin-invert@0.16.13': - resolution: {integrity: sha512-xFMrIn7czEZbdbMzZWuaZFnlLGJDVJ82y5vlsKsXRTG2kcxRsMPXvZRWHV57nSs1YFsNqXSbrC8B98n0E32njQ==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - - '@jimp/plugin-mask@0.16.13': - resolution: {integrity: sha512-wLRYKVBXql2GAYgt6FkTnCfE+q5NomM7Dlh0oIPGAoMBWDyTx0eYutRK6PlUrRK2yMHuroAJCglICTbxqGzowQ==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - - '@jimp/plugin-normalize@0.16.13': - resolution: {integrity: sha512-3tfad0n9soRna4IfW9NzQdQ2Z3ijkmo21DREHbE6CGcMIxOSvfRdSvf1qQPApxjTSo8LTU4MCi/fidx/NZ0GqQ==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - - '@jimp/plugin-print@0.16.13': - resolution: {integrity: sha512-0m6i3p01PGRkGAK9r53hDYrkyMq+tlhLOIbsSTmZyh6HLshUKlTB7eXskF5OpVd5ZUHoltlNc6R+ggvKIzxRFw==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - '@jimp/plugin-blit': '>=0.3.5' - - '@jimp/plugin-resize@0.16.13': - resolution: {integrity: sha512-qoqtN8LDknm3fJm9nuPygJv30O3vGhSBD2TxrsCnhtOsxKAqVPJtFVdGd/qVuZ8nqQANQmTlfqTiK9mVWQ7MiQ==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - - '@jimp/plugin-rotate@0.16.13': - resolution: {integrity: sha512-Ev+Jjmj1nHYw897z9C3R9dYsPv7S2/nxdgfFb/h8hOwK0Ovd1k/+yYS46A0uj/JCKK0pQk8wOslYBkPwdnLorw==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - '@jimp/plugin-blit': '>=0.3.5' - '@jimp/plugin-crop': '>=0.3.5' - '@jimp/plugin-resize': '>=0.3.5' - - '@jimp/plugin-scale@0.16.13': - resolution: {integrity: sha512-05POQaEJVucjTiSGMoH68ZiELc7QqpIpuQlZ2JBbhCV+WCbPFUBcGSmE7w4Jd0E2GvCho/NoMODLwgcVGQA97A==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - '@jimp/plugin-resize': '>=0.3.5' - - '@jimp/plugin-shadow@0.16.13': - resolution: {integrity: sha512-nmu5VSZ9hsB1JchTKhnnCY+paRBnwzSyK5fhkhtQHHoFD5ArBQ/5wU8y6tCr7k/GQhhGq1OrixsECeMjPoc8Zw==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - '@jimp/plugin-blur': '>=0.3.5' - '@jimp/plugin-resize': '>=0.3.5' - - '@jimp/plugin-threshold@0.16.13': - resolution: {integrity: sha512-+3zArBH0OE3Rhjm4HyAokMsZlIq5gpQec33CncyoSwxtRBM2WAhUVmCUKuBo+Lr/2/4ISoY4BWpHKhMLDix6cA==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - '@jimp/plugin-color': '>=0.8.0' - '@jimp/plugin-resize': '>=0.8.0' - - '@jimp/plugins@0.16.13': - resolution: {integrity: sha512-CJLdqODEhEVs4MgWCxpWL5l95sCBlkuSLz65cxEm56X5akIsn4LOlwnKoSEZioYcZUBvHhCheH67AyPTudfnQQ==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - - '@jimp/png@0.16.13': - resolution: {integrity: sha512-8cGqINvbWJf1G0Her9zbq9I80roEX0A+U45xFby3tDWfzn+Zz8XKDF1Nv9VUwVx0N3zpcG1RPs9hfheG4Cq2kg==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - - '@jimp/tiff@0.16.13': - resolution: {integrity: sha512-oJY8d9u95SwW00VPHuCNxPap6Q1+E/xM5QThb9Hu+P6EGuu6lIeLaNBMmFZyblwFbwrH+WBOZlvIzDhi4Dm/6Q==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - - '@jimp/types@0.16.13': - resolution: {integrity: sha512-mC0yVNUobFDjoYLg4hoUwzMKgNlxynzwt3cDXzumGvRJ7Kb8qQGOWJQjQFo5OxmGExqzPphkirdbBF88RVLBCg==} - peerDependencies: - '@jimp/custom': '>=0.3.5' - - '@jimp/utils@0.16.13': - resolution: {integrity: sha512-VyCpkZzFTHXtKgVO35iKN0sYR10psGpV6SkcSeV4oF7eSYlR8Bl6aQLCzVeFjvESF7mxTmIiI3/XrMobVrtxDA==} - '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -1302,14 +1120,14 @@ packages: '@stremio/stremio-colors@5.2.0': resolution: {integrity: sha512-dYlPgu9W/H7c9s1zmW5tiDnRenaUa4Hg1QCyOg1lhOcgSfM/bVTi5nnqX+IfvGTTUNA0zgzh8hI3o3miwnZxTg==} - '@stremio/stremio-core-web@0.49.4': - resolution: {integrity: sha512-K9LJGKXs8juV3pZOHH6thWTwOShAhjFt9bLL6K1VlORAe6AiieZ2uRp9wdOwFmPX+UgzWLIOd0r2aFXJ4OsJCw==} + '@stremio/stremio-core-web@0.52.0': + resolution: {integrity: sha512-zT0P8JspGZ1oI9/11f3RIt7XG9b/1fOZE+xSnP+oAyhRmzzkqrnPUJkHdJdgoVD9XELDFAS2awNfl5/eRdh5kA==} - '@stremio/stremio-icons@5.7.1': - resolution: {integrity: sha512-Z96p36LLX3G+ewMnFKmNZVsO/AtcHA33WQ3wGOYFubxiYADPRAkcLVU5rHIfiGSC9IUaUVhxQWTPVB9ScY4Q5Q==} + '@stremio/stremio-icons@5.8.0': + resolution: {integrity: sha512-IVUvQbIWfA4YEHCTed7v/sdQJCJ+OOCf84LTWpkE2W6GLQ+15WHcMEJrVkE1X3ekYJnGg3GjT0KLO6tKSU0P4w==} - '@stremio/stremio-video@0.0.62': - resolution: {integrity: sha512-lzm1sWLVN9Z3qr8mZm3MRLw7S9v7QYIe6swWava3Ao4sjhVc8jVlwzF5un0LcHxY8hQe0OjK7TDCvPRbw+spBQ==} + '@stremio/stremio-video@0.0.70': + resolution: {integrity: sha512-a0flQYAUdrZNMm7mmts2vpZOqN1nus7Hs9Mjl4mrN5rtduD0ojUyhD5J4lPcCpZ7WB0YdEUOGLXR19qHpgoKmg==} '@stylistic/eslint-plugin-jsx@4.4.1': resolution: {integrity: sha512-83SInq4u7z71vWwGG+6ViOtlOmZ6tSrDkMPhrvdBBTGMLA0gs22WSdhQ4vZP3oJ5Xg4ythvqeUiFSedvVxzhyA==} @@ -1326,9 +1144,6 @@ packages: '@surma/rollup-plugin-off-main-thread@2.2.3': resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} - '@tokenizer/token@0.3.0': - resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} - '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -1371,9 +1186,6 @@ packages: '@types/express@4.17.23': resolution: {integrity: sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==} - '@types/glob@7.2.0': - resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} - '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} @@ -1401,9 +1213,6 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/lodash.isequal@4.5.8': - resolution: {integrity: sha512-uput6pg4E/tj2LGxCZo9+y27JNyB2OZuuI/T5F+ylVDYuqICLG2/ktjxx0v6GvVntAf8TvEzeQLcV0ffRirXuA==} - '@types/lodash.throttle@4.1.9': resolution: {integrity: sha512-PCPVfpfueguWZQB7pJQK890F2scYKoDUL3iM522AptHWn7d5NQmeS/LTEHIcLr5PaTzl3dK2Z0xSUHHTHwaL5g==} @@ -1413,16 +1222,9 @@ packages: '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} - '@types/minimatch@6.0.0': - resolution: {integrity: sha512-zmPitbQ8+6zNutpwgcQuLcsEpn/Cj54Kbn7L5pX0Os5kdWplB7xPgEh/g+SWOB/qmows2gpuCaPyduq8ZZRnxA==} - deprecated: This is a stub types definition. minimatch provides its own type definitions, so you do not need this installed. - '@types/node-forge@1.3.14': resolution: {integrity: sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==} - '@types/node@16.9.1': - resolution: {integrity: sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==} - '@types/node@24.5.2': resolution: {integrity: sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==} @@ -1614,10 +1416,6 @@ packages: a-color-picker@1.2.1: resolution: {integrity: sha512-aMCUKd2zTDWK2YWnjz0k3YhFc9XL0jZlPIywF6XkP6i3wdq2iHTEnl1BFPZkOVDV89M12t+zeZ8m23cfzn57/Q==} - abort-controller@3.0.0: - resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} - engines: {node: '>=6.5'} - accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -1677,9 +1475,6 @@ packages: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} - any-base@1.1.0: - resolution: {integrity: sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==} - anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -1701,14 +1496,6 @@ packages: resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} engines: {node: '>= 0.4'} - array-union@1.0.2: - resolution: {integrity: sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==} - engines: {node: '>=0.10.0'} - - array-uniq@1.0.3: - resolution: {integrity: sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==} - engines: {node: '>=0.10.0'} - array.prototype.findlast@1.2.5: resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} engines: {node: '>= 0.4'} @@ -1808,8 +1595,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.8.7: - resolution: {integrity: sha512-bxxN2M3a4d1CRoQC//IqsR5XrLh0IJ8TCv2x6Y9N0nckNz/rTjZB3//GGscZziZOxmjP55rzxg/ze7usFI9FqQ==} + baseline-browser-mapping@2.9.14: + resolution: {integrity: sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==} hasBin: true batch@0.6.1: @@ -1822,9 +1609,6 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - bmp-js@0.1.0: - resolution: {integrity: sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==} - body-parser@1.20.3: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -1856,16 +1640,9 @@ packages: bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} - buffer-equal@0.0.1: - resolution: {integrity: sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==} - engines: {node: '>=0.4.0'} - buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} @@ -1907,11 +1684,8 @@ packages: caniuse-api@3.0.0: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} - caniuse-lite@1.0.30001745: - resolution: {integrity: sha512-ywt6i8FzvdgrrrGbr1jZVObnVv6adj+0if2/omv9cmR2oiZs30zL4DIyaptKcbOrBdOIc74QTMoJvSE2QHh5UQ==} - - centra@2.7.0: - resolution: {integrity: sha512-PbFMgMSrmgx6uxCdm57RUos9Tc3fclMvhLSATYN39XsDV29B89zZ3KA89jmY0vwSGazyU+uerqwa6t+KaodPcg==} + caniuse-lite@1.0.30001763: + resolution: {integrity: sha512-mh/dGtq56uN98LlNX9qdbKnzINhX0QzhiWBFEkFfsFO4QyCvL8YegrJAazCwXIeqkIob8BlZPGM3xdnY+sgmvQ==} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -1943,12 +1717,6 @@ packages: resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==} engines: {node: '>= 10.0'} - clean-webpack-plugin@4.0.0: - resolution: {integrity: sha512-WuWE1nyTNAyW5T7oNyys2EN0cfP2fdRxhxnIQWiAp0bMabPdHhoGxM8A6YL2GhqwgrPnnaemVE7nv5XJ2Fhh2w==} - engines: {node: '>=10.0.0'} - peerDependencies: - webpack: '>=4.0.0 <6.0.0' - cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -2076,9 +1844,6 @@ packages: resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} engines: {node: '>=8'} - css-color-names@1.0.1: - resolution: {integrity: sha512-/loXYOch1qU1biStIFsHH8SxTmOseh1IJqFvy8IujXOm1h+QjUdDhkzOrR5HG8K8mlxREj0yfi8ewCHx0eMxzA==} - css-declaration-sorter@7.3.0: resolution: {integrity: sha512-LQF6N/3vkAMYF4xoHLJfG718HRJh34Z8BnNhd6bosOMIVjMlhuZK5++oZa3uYAgrI5+7x2o27gUqTR2U/KjUOQ==} engines: {node: ^14 || ^16 || >=18} @@ -2218,10 +1983,6 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} - del@4.1.1: - resolution: {integrity: sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==} - engines: {node: '>=6'} - depd@1.1.2: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} engines: {node: '>= 0.6'} @@ -2265,9 +2026,6 @@ packages: dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} - dom-walk@0.1.2: - resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==} - domelementtype@2.3.0: resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} @@ -2466,10 +2224,6 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} - event-target-shim@5.0.1: - resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} - engines: {node: '>=6'} - eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} @@ -2484,9 +2238,6 @@ packages: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} - exif-parser@0.1.12: - resolution: {integrity: sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==} - exit@0.1.2: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} engines: {node: '>= 0.8.0'} @@ -2502,6 +2253,10 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-equals@6.0.0: + resolution: {integrity: sha512-PFhhIGgdM79r5Uztdj9Zb6Tt1zKafqVfdMGwVca1z5z6fbX7DmsySSuJd8HiP6I1j505DCS83cLxo5rmSNeVEA==} + engines: {node: '>=6.0.0'} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -2533,10 +2288,6 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} - file-type@16.5.4: - resolution: {integrity: sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==} - engines: {node: '>=10'} - filelist@1.0.4: resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} @@ -2659,9 +2410,6 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} - gifwrap@0.9.4: - resolution: {integrity: sha512-MDMwbhASQuVeD4JKd1fKgNgCRL3fGqMM4WaqpNhWO0JiMOAjbQdumbs4BbBZEy9/M00EHEjKN3HieVhCUlwjeQ==} - glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -2683,9 +2431,6 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported - global@4.4.0: - resolution: {integrity: sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==} - globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -2702,10 +2447,6 @@ packages: resolution: {integrity: sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==} engines: {node: '>=18'} - globby@6.1.0: - resolution: {integrity: sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==} - engines: {node: '>=0.10.0'} - gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -2857,9 +2598,6 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} - image-q@4.0.0: - resolution: {integrity: sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==} - image-size@0.5.5: resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==} engines: {node: '>=0.10.0'} @@ -2963,9 +2701,6 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-function@1.0.2: - resolution: {integrity: sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==} - is-generator-fn@2.1.0: resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} engines: {node: '>=6'} @@ -3010,18 +2745,6 @@ packages: resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==} engines: {node: '>=0.10.0'} - is-path-cwd@2.2.0: - resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==} - engines: {node: '>=6'} - - is-path-in-cwd@2.1.0: - resolution: {integrity: sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==} - engines: {node: '>=6'} - - is-path-inside@2.1.0: - resolution: {integrity: sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==} - engines: {node: '>=6'} - is-plain-obj@3.0.0: resolution: {integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==} engines: {node: '>=10'} @@ -3260,16 +2983,10 @@ packages: node-notifier: optional: true - jimp@0.16.1: - resolution: {integrity: sha512-+EKVxbR36Td7Hfd23wKGIeEyHbxShZDX6L8uJkgVW3ESA9GiTEPK08tG1XI2r/0w5Ch0HyJF5kPqF9K7EmGjaw==} - jiti@1.21.7: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true - jpeg-js@0.4.4: - resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==} - js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -3374,9 +3091,6 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - load-bmfont@1.4.2: - resolution: {integrity: sha512-qElWkmjW9Oq1F9EI5Gt7aD9zcdHb9spJCW1L/dmPf7KzCCEJxq8nhHz5eCgI9aMf7vrG/wyaCqdsI+Iy9ZTlog==} - loader-runner@4.3.0: resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} engines: {node: '>=6.11.5'} @@ -3402,10 +3116,6 @@ packages: lodash.intersection@4.4.0: resolution: {integrity: sha512-N+L0cCfnqMv6mxXtSPeKt+IavbOBBSiAEkKyLasZ8BVcP9YXQgxLO12oPR8OyURwKV8l5vJKiE1M8aS70heuMg==} - lodash.isequal@4.5.0: - resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} - deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. - lodash.memoize@4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} @@ -3507,18 +3217,10 @@ packages: engines: {node: '>=4'} hasBin: true - mime@2.4.6: - resolution: {integrity: sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==} - engines: {node: '>=4.0.0'} - hasBin: true - mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} - min-document@2.19.0: - resolution: {integrity: sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==} - mini-css-extract-plugin@2.9.2: resolution: {integrity: sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==} engines: {node: '>= 12.13.0'} @@ -3528,10 +3230,6 @@ packages: minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - minimatch@10.0.3: - resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} - engines: {node: 20 || >=22} - minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -3543,13 +3241,6 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - mkdirp@0.5.6: - resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} - hasBin: true - ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -3643,9 +3334,6 @@ packages: obuf@1.1.2: resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} - omggif@1.0.10: - resolution: {integrity: sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==} - on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -3697,10 +3385,6 @@ packages: resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - p-map@2.1.0: - resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} - engines: {node: '>=6'} - p-retry@6.2.1: resolution: {integrity: sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==} engines: {node: '>=16.17'} @@ -3709,9 +3393,6 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - pako@1.0.11: - resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} - param-case@3.0.4: resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} @@ -3719,18 +3400,6 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} - parse-bmfont-ascii@1.0.6: - resolution: {integrity: sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==} - - parse-bmfont-binary@1.0.6: - resolution: {integrity: sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==} - - parse-bmfont-xml@1.1.6: - resolution: {integrity: sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==} - - parse-headers@2.0.6: - resolution: {integrity: sha512-Tz11t3uKztEW5FEVZnj1ox8GKblWn+PvHY9TmJV5Mll2uHEwRdR/5Li1OlXoECjLYkApdhWy44ocONwXLiKO5A==} - parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} @@ -3758,9 +3427,6 @@ packages: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} - path-is-inside@1.0.2: - resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==} - path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -3775,18 +3441,6 @@ packages: resolution: {integrity: sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==} engines: {node: '>=18'} - peek-readable@4.1.0: - resolution: {integrity: sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==} - engines: {node: '>=8'} - - phin@2.9.3: - resolution: {integrity: sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - - phin@3.7.1: - resolution: {integrity: sha512-GEazpTWwTZaEQ9RhL7Nyz0WwqilbqgLahDM3D0hxWwmVDI52nXEybHqiN6/elwpkJBhcuj+WbBu+QfT0uhPGfQ==} - engines: {node: '>= 8'} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -3798,30 +3452,14 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} - pify@2.3.0: - resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} - engines: {node: '>=0.10.0'} - pify@4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} - pinkie-promise@2.0.1: - resolution: {integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==} - engines: {node: '>=0.10.0'} - - pinkie@2.0.4: - resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==} - engines: {node: '>=0.10.0'} - pirates@4.0.7: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} - pixelmatch@4.0.2: - resolution: {integrity: sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==} - hasBin: true - pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} @@ -3830,10 +3468,6 @@ packages: resolution: {integrity: sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==} engines: {node: '>=14.16'} - pngjs@3.4.0: - resolution: {integrity: sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==} - engines: {node: '>=4.0.0'} - possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -4090,10 +3724,6 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - process@0.11.10: - resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} - engines: {node: '>= 0.6.0'} - prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -4108,9 +3738,6 @@ packages: prr@1.0.1: resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} - punycode@1.3.2: - resolution: {integrity: sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==} - punycode@1.4.1: resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} @@ -4129,11 +3756,6 @@ packages: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} - querystring@0.2.0: - resolution: {integrity: sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==} - engines: {node: '>=0.4.x'} - deprecated: The querystring API is considered Legacy. new code should use the URLSearchParams API instead. - queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -4200,14 +3822,6 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} - readable-stream@4.7.0: - resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - readable-web-to-node-stream@3.0.4: - resolution: {integrity: sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==} - engines: {node: '>=8'} - readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -4235,9 +3849,6 @@ packages: regenerate@1.4.2: resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} - regenerator-runtime@0.13.11: - resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} - regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} @@ -4307,11 +3918,6 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rimraf@2.7.1: - resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - rollup@2.79.2: resolution: {integrity: sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==} engines: {node: '>=10.0.0'} @@ -4527,9 +4133,9 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} - stremio-translations@https://codeload.github.com/Stremio/stremio-translations/tar.gz/abe7684165a031755e9aee39da26daa806ba7824: - resolution: {tarball: https://codeload.github.com/Stremio/stremio-translations/tar.gz/abe7684165a031755e9aee39da26daa806ba7824} - version: 1.44.12 + stremio-translations@https://codeload.github.com/Stremio/stremio-translations/tar.gz/7c0c337f32163aa13158bb90cd6133da43feafef: + resolution: {tarball: https://codeload.github.com/Stremio/stremio-translations/tar.gz/7c0c337f32163aa13158bb90cd6133da43feafef} + version: 1.45.0 string-length@4.0.2: resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} @@ -4588,10 +4194,6 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - strtok3@6.3.0: - resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==} - engines: {node: '>=10'} - stylehacks@7.0.6: resolution: {integrity: sha512-iitguKivmsueOmTO0wmxURXBP8uqOO+zikLGZ7Mm9e/94R4w5T999Js2taS/KBOnQ/wdC3jN3vNSrkGDrlnqQg==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} @@ -4671,15 +4273,9 @@ packages: thunky@1.1.0: resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==} - timm@1.7.1: - resolution: {integrity: sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==} - tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - tinycolor2@1.6.0: - resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} - tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} @@ -4691,10 +4287,6 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} - token-types@4.2.1: - resolution: {integrity: sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==} - engines: {node: '>=10'} - tr46@1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} @@ -4820,9 +4412,6 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - url@0.11.0: - resolution: {integrity: sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ==} - url@0.11.4: resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==} engines: {node: '>= 0.4'} @@ -4852,9 +4441,6 @@ packages: '@types/react': optional: true - utif@2.0.1: - resolution: {integrity: sha512-Z/S1fNKCicQTf375lIP9G8Sa1H/phcysstNrrSdZKj1f9g58J4NMgb5IgiEZN9/nLMPDwF0W7hdOe9Qq2IYoLg==} - util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -4944,10 +4530,6 @@ packages: resolution: {integrity: sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==} engines: {node: '>=10.0.0'} - webpack-pwa-manifest@4.3.0: - resolution: {integrity: sha512-3hK8Qg58SyLCUIz4PBYnfUPM6iJ5K88h8Uhc3MxmlJcVtDF/11aBBdUTdQkqc9bo6Cb8Q1v2xdsB2XO6pzTbiA==} - engines: {node: '>=6.0.0'} - webpack-sources@1.4.3: resolution: {integrity: sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==} @@ -5086,24 +4668,6 @@ packages: resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} engines: {node: '>=18'} - xhr@2.6.0: - resolution: {integrity: sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==} - - xml-parse-from-string@1.0.1: - resolution: {integrity: sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==} - - xml2js@0.5.0: - resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} - engines: {node: '>=4.0.0'} - - xmlbuilder@11.0.1: - resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} - engines: {node: '>=4.0'} - - xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} - y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -5974,12 +5538,6 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@isaacs/balanced-match@4.0.1': {} - - '@isaacs/brace-expansion@5.0.0': - dependencies: - '@isaacs/balanced-match': 4.0.1 - '@istanbuljs/load-nyc-config@1.1.0': dependencies: camelcase: 5.3.1 @@ -6152,255 +5710,6 @@ snapshots: '@types/yargs': 17.0.33 chalk: 4.1.2 - '@jimp/bmp@0.16.13(@jimp/custom@0.16.13)': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/utils': 0.16.13 - bmp-js: 0.1.0 - - '@jimp/core@0.16.13': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/utils': 0.16.13 - any-base: 1.1.0 - buffer: 5.7.1 - exif-parser: 0.1.12 - file-type: 16.5.4 - load-bmfont: 1.4.2 - mkdirp: 0.5.6 - phin: 2.9.3 - pixelmatch: 4.0.2 - tinycolor2: 1.6.0 - transitivePeerDependencies: - - debug - - '@jimp/custom@0.16.13': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/core': 0.16.13 - transitivePeerDependencies: - - debug - - '@jimp/gif@0.16.13(@jimp/custom@0.16.13)': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/utils': 0.16.13 - gifwrap: 0.9.4 - omggif: 1.0.10 - - '@jimp/jpeg@0.16.13(@jimp/custom@0.16.13)': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/utils': 0.16.13 - jpeg-js: 0.4.4 - - '@jimp/plugin-blit@0.16.13(@jimp/custom@0.16.13)': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/utils': 0.16.13 - - '@jimp/plugin-blur@0.16.13(@jimp/custom@0.16.13)': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/utils': 0.16.13 - - '@jimp/plugin-circle@0.16.13(@jimp/custom@0.16.13)': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/utils': 0.16.13 - - '@jimp/plugin-color@0.16.13(@jimp/custom@0.16.13)': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/utils': 0.16.13 - tinycolor2: 1.6.0 - - '@jimp/plugin-contain@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blit@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-scale@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13)))': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/plugin-blit': 0.16.13(@jimp/custom@0.16.13) - '@jimp/plugin-resize': 0.16.13(@jimp/custom@0.16.13) - '@jimp/plugin-scale': 0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13)) - '@jimp/utils': 0.16.13 - - '@jimp/plugin-cover@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-crop@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-scale@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13)))': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/plugin-crop': 0.16.13(@jimp/custom@0.16.13) - '@jimp/plugin-resize': 0.16.13(@jimp/custom@0.16.13) - '@jimp/plugin-scale': 0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13)) - '@jimp/utils': 0.16.13 - - '@jimp/plugin-crop@0.16.13(@jimp/custom@0.16.13)': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/utils': 0.16.13 - - '@jimp/plugin-displace@0.16.13(@jimp/custom@0.16.13)': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/utils': 0.16.13 - - '@jimp/plugin-dither@0.16.13(@jimp/custom@0.16.13)': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/utils': 0.16.13 - - '@jimp/plugin-fisheye@0.16.13(@jimp/custom@0.16.13)': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/utils': 0.16.13 - - '@jimp/plugin-flip@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-rotate@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blit@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-crop@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13)))': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/plugin-rotate': 0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blit@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-crop@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13)) - '@jimp/utils': 0.16.13 - - '@jimp/plugin-gaussian@0.16.13(@jimp/custom@0.16.13)': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/utils': 0.16.13 - - '@jimp/plugin-invert@0.16.13(@jimp/custom@0.16.13)': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/utils': 0.16.13 - - '@jimp/plugin-mask@0.16.13(@jimp/custom@0.16.13)': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/utils': 0.16.13 - - '@jimp/plugin-normalize@0.16.13(@jimp/custom@0.16.13)': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/utils': 0.16.13 - - '@jimp/plugin-print@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blit@0.16.13(@jimp/custom@0.16.13))': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/plugin-blit': 0.16.13(@jimp/custom@0.16.13) - '@jimp/utils': 0.16.13 - load-bmfont: 1.4.2 - transitivePeerDependencies: - - debug - - '@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13)': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/utils': 0.16.13 - - '@jimp/plugin-rotate@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blit@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-crop@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13))': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/plugin-blit': 0.16.13(@jimp/custom@0.16.13) - '@jimp/plugin-crop': 0.16.13(@jimp/custom@0.16.13) - '@jimp/plugin-resize': 0.16.13(@jimp/custom@0.16.13) - '@jimp/utils': 0.16.13 - - '@jimp/plugin-scale@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13))': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/plugin-resize': 0.16.13(@jimp/custom@0.16.13) - '@jimp/utils': 0.16.13 - - '@jimp/plugin-shadow@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blur@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13))': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/plugin-blur': 0.16.13(@jimp/custom@0.16.13) - '@jimp/plugin-resize': 0.16.13(@jimp/custom@0.16.13) - '@jimp/utils': 0.16.13 - - '@jimp/plugin-threshold@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-color@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13))': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/plugin-color': 0.16.13(@jimp/custom@0.16.13) - '@jimp/plugin-resize': 0.16.13(@jimp/custom@0.16.13) - '@jimp/utils': 0.16.13 - - '@jimp/plugins@0.16.13(@jimp/custom@0.16.13)': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/plugin-blit': 0.16.13(@jimp/custom@0.16.13) - '@jimp/plugin-blur': 0.16.13(@jimp/custom@0.16.13) - '@jimp/plugin-circle': 0.16.13(@jimp/custom@0.16.13) - '@jimp/plugin-color': 0.16.13(@jimp/custom@0.16.13) - '@jimp/plugin-contain': 0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blit@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-scale@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13))) - '@jimp/plugin-cover': 0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-crop@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-scale@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13))) - '@jimp/plugin-crop': 0.16.13(@jimp/custom@0.16.13) - '@jimp/plugin-displace': 0.16.13(@jimp/custom@0.16.13) - '@jimp/plugin-dither': 0.16.13(@jimp/custom@0.16.13) - '@jimp/plugin-fisheye': 0.16.13(@jimp/custom@0.16.13) - '@jimp/plugin-flip': 0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-rotate@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blit@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-crop@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13))) - '@jimp/plugin-gaussian': 0.16.13(@jimp/custom@0.16.13) - '@jimp/plugin-invert': 0.16.13(@jimp/custom@0.16.13) - '@jimp/plugin-mask': 0.16.13(@jimp/custom@0.16.13) - '@jimp/plugin-normalize': 0.16.13(@jimp/custom@0.16.13) - '@jimp/plugin-print': 0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blit@0.16.13(@jimp/custom@0.16.13)) - '@jimp/plugin-resize': 0.16.13(@jimp/custom@0.16.13) - '@jimp/plugin-rotate': 0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blit@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-crop@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13)) - '@jimp/plugin-scale': 0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13)) - '@jimp/plugin-shadow': 0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blur@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13)) - '@jimp/plugin-threshold': 0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-color@0.16.13(@jimp/custom@0.16.13))(@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13)) - timm: 1.7.1 - transitivePeerDependencies: - - debug - - '@jimp/png@0.16.13(@jimp/custom@0.16.13)': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/utils': 0.16.13 - pngjs: 3.4.0 - - '@jimp/tiff@0.16.13(@jimp/custom@0.16.13)': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - utif: 2.0.1 - - '@jimp/types@0.16.13(@jimp/custom@0.16.13)': - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/bmp': 0.16.13(@jimp/custom@0.16.13) - '@jimp/custom': 0.16.13 - '@jimp/gif': 0.16.13(@jimp/custom@0.16.13) - '@jimp/jpeg': 0.16.13(@jimp/custom@0.16.13) - '@jimp/png': 0.16.13(@jimp/custom@0.16.13) - '@jimp/tiff': 0.16.13(@jimp/custom@0.16.13) - timm: 1.7.1 - - '@jimp/utils@0.16.13': - dependencies: - '@babel/runtime': 7.26.0 - regenerator-runtime: 0.13.11 - '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -6561,13 +5870,13 @@ snapshots: '@stremio/stremio-colors@5.2.0': {} - '@stremio/stremio-core-web@0.49.4': + '@stremio/stremio-core-web@0.52.0': dependencies: '@babel/runtime': 7.24.1 - '@stremio/stremio-icons@5.7.1': {} + '@stremio/stremio-icons@5.8.0': {} - '@stremio/stremio-video@0.0.62': + '@stremio/stremio-video@0.0.70': dependencies: buffer: 6.0.3 color: 4.2.3 @@ -6577,7 +5886,7 @@ snapshots: hls.js: https://github.com/Stremio/hls.js/releases/download/v1.5.4-patch2/hls.js-1.5.4-patch2.tgz lodash.clonedeep: 4.5.0 magnet-uri: 6.2.0 - url: 0.11.0 + url: 0.11.4 video-name-parser: 1.4.6 vtt.js: https://codeload.github.com/jaruba/vtt.js/tar.gz/84d33d157848407d790d78423dacc41a096294f0 @@ -6606,8 +5915,6 @@ snapshots: magic-string: 0.25.9 string.prototype.matchall: 4.0.12 - '@tokenizer/token@0.3.0': {} - '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.28.4 @@ -6675,11 +5982,6 @@ snapshots: '@types/qs': 6.14.0 '@types/serve-static': 1.15.8 - '@types/glob@7.2.0': - dependencies: - '@types/minimatch': 6.0.0 - '@types/node': 24.5.2 - '@types/graceful-fs@4.1.9': dependencies: '@types/node': 24.5.2 @@ -6706,10 +6008,6 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/lodash.isequal@4.5.8': - dependencies: - '@types/lodash': 4.17.20 - '@types/lodash.throttle@4.1.9': dependencies: '@types/lodash': 4.17.20 @@ -6718,16 +6016,10 @@ snapshots: '@types/mime@1.3.5': {} - '@types/minimatch@6.0.0': - dependencies: - minimatch: 10.0.3 - '@types/node-forge@1.3.14': dependencies: '@types/node': 24.5.2 - '@types/node@16.9.1': {} - '@types/node@24.5.2': dependencies: undici-types: 7.12.0 @@ -6978,10 +6270,6 @@ snapshots: dependencies: is-plain-object: 2.0.4 - abort-controller@3.0.0: - dependencies: - event-target-shim: 5.0.1 - accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -7034,8 +6322,6 @@ snapshots: ansi-styles@5.2.0: {} - any-base@1.1.0: {} - anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -7065,12 +6351,6 @@ snapshots: is-string: 1.1.1 math-intrinsics: 1.1.0 - array-union@1.0.2: - dependencies: - array-uniq: 1.0.3 - - array-uniq@1.0.3: {} - array.prototype.findlast@1.2.5: dependencies: call-bind: 1.0.8 @@ -7125,7 +6405,7 @@ snapshots: autoprefixer@10.4.21(postcss@8.5.6): dependencies: browserslist: 4.26.2 - caniuse-lite: 1.0.30001745 + caniuse-lite: 1.0.30001763 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -7226,7 +6506,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.8.7: {} + baseline-browser-mapping@2.9.14: {} batch@0.6.1: {} @@ -7234,8 +6514,6 @@ snapshots: binary-extensions@2.3.0: {} - bmp-js@0.1.0: {} - body-parser@1.20.3: dependencies: bytes: 3.1.2 @@ -7277,8 +6555,8 @@ snapshots: browserslist@4.26.2: dependencies: - baseline-browser-mapping: 2.8.7 - caniuse-lite: 1.0.30001745 + baseline-browser-mapping: 2.9.14 + caniuse-lite: 1.0.30001763 electron-to-chromium: 1.5.224 node-releases: 2.0.21 update-browserslist-db: 1.1.3(browserslist@4.26.2) @@ -7287,15 +6565,8 @@ snapshots: dependencies: node-int64: 0.4.0 - buffer-equal@0.0.1: {} - buffer-from@1.1.2: {} - buffer@5.7.1: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - buffer@6.0.3: dependencies: base64-js: 1.5.1 @@ -7338,17 +6609,11 @@ snapshots: caniuse-api@3.0.0: dependencies: browserslist: 4.26.2 - caniuse-lite: 1.0.30001745 + caniuse-lite: 1.0.30001763 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 - caniuse-lite@1.0.30001745: {} - - centra@2.7.0: - dependencies: - follow-redirects: 1.15.11 - transitivePeerDependencies: - - debug + caniuse-lite@1.0.30001763: {} chalk@4.1.2: dependencies: @@ -7381,11 +6646,6 @@ snapshots: dependencies: source-map: 0.6.1 - clean-webpack-plugin@4.0.0(webpack@5.97.0): - dependencies: - del: 4.1.1 - webpack: 5.97.0(webpack-cli@5.1.4) - cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -7518,8 +6778,6 @@ snapshots: crypto-random-string@2.0.0: {} - css-color-names@1.0.1: {} - css-declaration-sorter@7.3.0(postcss@8.5.6): dependencies: postcss: 8.5.6 @@ -7683,16 +6941,6 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - del@4.1.1: - dependencies: - '@types/glob': 7.2.0 - globby: 6.1.0 - is-path-cwd: 2.2.0 - is-path-in-cwd: 2.1.0 - p-map: 2.1.0 - pify: 4.0.1 - rimraf: 2.7.1 - depd@1.1.2: {} depd@2.0.0: {} @@ -7731,8 +6979,6 @@ snapshots: domhandler: 5.0.3 entities: 4.5.0 - dom-walk@0.1.2: {} - domelementtype@2.3.0: {} domhandler@4.3.1: @@ -8021,8 +7267,6 @@ snapshots: etag@1.8.1: {} - event-target-shim@5.0.1: {} - eventemitter3@4.0.7: {} eventemitter3@5.0.1: {} @@ -8041,8 +7285,6 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 - exif-parser@0.1.12: {} - exit@0.1.2: {} expect@29.7.0: @@ -8091,6 +7333,8 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-equals@6.0.0: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -8123,12 +7367,6 @@ snapshots: dependencies: flat-cache: 4.0.1 - file-type@16.5.4: - dependencies: - readable-web-to-node-stream: 3.0.4 - strtok3: 6.3.0 - token-types: 4.2.1 - filelist@1.0.4: dependencies: minimatch: 5.1.6 @@ -8255,11 +7493,6 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 - gifwrap@0.9.4: - dependencies: - image-q: 4.0.0 - omggif: 1.0.10 - glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -8283,11 +7516,6 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 - global@4.4.0: - dependencies: - min-document: 2.19.0 - process: 0.11.10 - globals@14.0.0: {} globals@15.15.0: {} @@ -8306,14 +7534,6 @@ snapshots: slash: 5.1.0 unicorn-magic: 0.3.0 - globby@6.1.0: - dependencies: - array-union: 1.0.2 - glob: 7.2.3 - object-assign: 4.1.1 - pify: 2.3.0 - pinkie-promise: 2.0.1 - gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -8460,10 +7680,6 @@ snapshots: ignore@7.0.5: {} - image-q@4.0.0: - dependencies: - '@types/node': 16.9.1 - image-size@0.5.5: optional: true @@ -8558,8 +7774,6 @@ snapshots: is-fullwidth-code-point@3.0.0: {} - is-function@1.0.2: {} - is-generator-fn@2.1.0: {} is-generator-function@1.1.0: @@ -8594,16 +7808,6 @@ snapshots: is-obj@1.0.1: {} - is-path-cwd@2.2.0: {} - - is-path-in-cwd@2.1.0: - dependencies: - is-path-inside: 2.1.0 - - is-path-inside@2.1.0: - dependencies: - path-is-inside: 1.0.2 - is-plain-obj@3.0.0: {} is-plain-object@2.0.4: @@ -9037,20 +8241,8 @@ snapshots: - supports-color - ts-node - jimp@0.16.1: - dependencies: - '@babel/runtime': 7.26.0 - '@jimp/custom': 0.16.13 - '@jimp/plugins': 0.16.13(@jimp/custom@0.16.13) - '@jimp/types': 0.16.13(@jimp/custom@0.16.13) - regenerator-runtime: 0.13.11 - transitivePeerDependencies: - - debug - jiti@1.21.7: {} - jpeg-js@0.4.4: {} - js-tokens@4.0.0: {} js-yaml@3.14.1: @@ -9141,19 +8333,6 @@ snapshots: lines-and-columns@1.2.4: {} - load-bmfont@1.4.2: - dependencies: - buffer-equal: 0.0.1 - mime: 1.6.0 - parse-bmfont-ascii: 1.0.6 - parse-bmfont-binary: 1.0.6 - parse-bmfont-xml: 1.1.6 - phin: 3.7.1 - xhr: 2.6.0 - xtend: 4.0.2 - transitivePeerDependencies: - - debug - loader-runner@4.3.0: {} locate-path@5.0.0: @@ -9174,8 +8353,6 @@ snapshots: lodash.intersection@4.4.0: {} - lodash.isequal@4.5.0: {} - lodash.memoize@4.1.2: {} lodash.merge@4.6.2: {} @@ -9267,14 +8444,8 @@ snapshots: mime@1.6.0: {} - mime@2.4.6: {} - mimic-fn@2.1.0: {} - min-document@2.19.0: - dependencies: - dom-walk: 0.1.2 - mini-css-extract-plugin@2.9.2(webpack@5.97.0): dependencies: schema-utils: 4.3.2 @@ -9283,10 +8454,6 @@ snapshots: minimalistic-assert@1.0.1: {} - minimatch@10.0.3: - dependencies: - '@isaacs/brace-expansion': 5.0.0 - minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -9299,12 +8466,6 @@ snapshots: dependencies: brace-expansion: 2.0.2 - minimist@1.2.8: {} - - mkdirp@0.5.6: - dependencies: - minimist: 1.2.8 - ms@2.0.0: {} ms@2.1.3: {} @@ -9391,8 +8552,6 @@ snapshots: obuf@1.1.2: {} - omggif@1.0.10: {} - on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -9453,8 +8612,6 @@ snapshots: dependencies: p-limit: 4.0.0 - p-map@2.1.0: {} - p-retry@6.2.1: dependencies: '@types/retry': 0.12.2 @@ -9463,8 +8620,6 @@ snapshots: p-try@2.2.0: {} - pako@1.0.11: {} - param-case@3.0.4: dependencies: dot-case: 3.0.4 @@ -9474,17 +8629,6 @@ snapshots: dependencies: callsites: 3.1.0 - parse-bmfont-ascii@1.0.6: {} - - parse-bmfont-binary@1.0.6: {} - - parse-bmfont-xml@1.1.6: - dependencies: - xml-parse-from-string: 1.0.1 - xml2js: 0.5.0 - - parse-headers@2.0.6: {} - parse-json@5.2.0: dependencies: '@babel/code-frame': 7.27.1 @@ -9507,8 +8651,6 @@ snapshots: path-is-absolute@1.0.1: {} - path-is-inside@1.0.2: {} - path-key@3.1.1: {} path-parse@1.0.7: {} @@ -9517,38 +8659,17 @@ snapshots: path-type@6.0.0: {} - peek-readable@4.1.0: {} - - phin@2.9.3: {} - - phin@3.7.1: - dependencies: - centra: 2.7.0 - transitivePeerDependencies: - - debug - picocolors@1.1.1: {} picomatch@2.3.1: {} picomatch@4.0.3: {} - pify@2.3.0: {} - - pify@4.0.1: {} - - pinkie-promise@2.0.1: - dependencies: - pinkie: 2.0.4 - - pinkie@2.0.4: {} + pify@4.0.1: + optional: true pirates@4.0.7: {} - pixelmatch@4.0.2: - dependencies: - pngjs: 3.4.0 - pkg-dir@4.2.0: dependencies: find-up: 4.1.0 @@ -9557,8 +8678,6 @@ snapshots: dependencies: find-up: 6.3.0 - pngjs@3.4.0: {} - possible-typed-array-names@1.1.0: {} postcss-calc@10.1.1(postcss@8.5.6): @@ -9792,8 +8911,6 @@ snapshots: process-nextick-args@2.0.1: {} - process@0.11.10: {} - prompts@2.4.2: dependencies: kleur: 3.0.3 @@ -9813,8 +8930,6 @@ snapshots: prr@1.0.1: optional: true - punycode@1.3.2: {} - punycode@1.4.1: {} punycode@2.3.1: {} @@ -9829,8 +8944,6 @@ snapshots: dependencies: side-channel: 1.1.0 - querystring@0.2.0: {} - queue-microtask@1.2.3: {} randombytes@2.1.0: @@ -9903,18 +9016,6 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 - readable-stream@4.7.0: - dependencies: - abort-controller: 3.0.0 - buffer: 6.0.3 - events: 3.3.0 - process: 0.11.10 - string_decoder: 1.3.0 - - readable-web-to-node-stream@3.0.4: - dependencies: - readable-stream: 4.7.0 - readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -9950,8 +9051,6 @@ snapshots: regenerate@1.4.2: {} - regenerator-runtime@0.13.11: {} - regenerator-runtime@0.14.1: {} regexp.prototype.flags@1.5.4: @@ -10020,10 +9119,6 @@ snapshots: reusify@1.1.0: {} - rimraf@2.7.1: - dependencies: - glob: 7.2.3 - rollup@2.79.2: optionalDependencies: fsevents: 2.3.3 @@ -10283,7 +9378,7 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 - stremio-translations@https://codeload.github.com/Stremio/stremio-translations/tar.gz/abe7684165a031755e9aee39da26daa806ba7824: {} + stremio-translations@https://codeload.github.com/Stremio/stremio-translations/tar.gz/7c0c337f32163aa13158bb90cd6133da43feafef: {} string-length@4.0.2: dependencies: @@ -10366,11 +9461,6 @@ snapshots: strip-json-comments@3.1.1: {} - strtok3@6.3.0: - dependencies: - '@tokenizer/token': 0.3.0 - peek-readable: 4.1.0 - stylehacks@7.0.6(postcss@8.5.6): dependencies: browserslist: 4.26.2 @@ -10446,12 +9536,8 @@ snapshots: thunky@1.1.0: {} - timm@1.7.1: {} - tiny-invariant@1.3.3: {} - tinycolor2@1.6.0: {} - tmpl@1.0.5: {} to-regex-range@5.0.1: @@ -10460,11 +9546,6 @@ snapshots: toidentifier@1.0.1: {} - token-types@4.2.1: - dependencies: - '@tokenizer/token': 0.3.0 - ieee754: 1.2.1 - tr46@1.0.1: dependencies: punycode: 2.3.1 @@ -10592,11 +9673,6 @@ snapshots: dependencies: punycode: 2.3.1 - url@0.11.0: - dependencies: - punycode: 1.3.2 - querystring: 0.2.0 - url@0.11.4: dependencies: punycode: 1.4.1 @@ -10621,10 +9697,6 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 - utif@2.0.1: - dependencies: - pako: 1.0.11 - util-deprecate@1.0.2: {} utila@0.4.0: {} @@ -10737,14 +9809,6 @@ snapshots: flat: 5.0.2 wildcard: 2.0.1 - webpack-pwa-manifest@4.3.0: - dependencies: - css-color-names: 1.0.1 - jimp: 0.16.1 - mime: 2.4.6 - transitivePeerDependencies: - - debug - webpack-sources@1.4.3: dependencies: source-list-map: 2.0.1 @@ -10991,24 +10055,6 @@ snapshots: dependencies: is-wsl: 3.1.0 - xhr@2.6.0: - dependencies: - global: 4.4.0 - is-function: 1.0.2 - parse-headers: 2.0.6 - xtend: 4.0.2 - - xml-parse-from-string@1.0.1: {} - - xml2js@0.5.0: - dependencies: - sax: 1.4.1 - xmlbuilder: 11.0.1 - - xmlbuilder@11.0.1: {} - - xtend@4.0.2: {} - y18n@5.0.8: {} yallist@3.1.1: {} diff --git a/src/App/App.js b/src/App/App.js index 3e816be9f..7a1383dc4 100644 --- a/src/App/App.js +++ b/src/App/App.js @@ -6,11 +6,12 @@ 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 { FileDropProvider, PlatformProvider, ToastProvider, TooltipProvider, CONSTANTS, withCoreSuspender, useShell } = require('stremio/common'); +const { FileDropProvider, PlatformProvider, ToastProvider, TooltipProvider, ShortcutsProvider, CONSTANTS, withCoreSuspender, useShell, useBinaryState } = require('stremio/common'); const ServicesToaster = require('./ServicesToaster'); const DeepLinkHandler = require('./DeepLinkHandler'); const SearchParamsHandler = require('./SearchParamsHandler'); const { default: UpdaterBanner } = require('./UpdaterBanner'); +const { default: ShortcutsModal } = require('./ShortcutsModal'); const ErrorDialog = require('./ErrorDialog'); const withProtectedRoutes = require('./withProtectedRoutes'); const routerViewsConfig = require('./routerViewsConfig'); @@ -38,6 +39,14 @@ const App = () => { }; }, []); const [initialized, setInitialized] = React.useState(false); + const [shortcutModalOpen,, closeShortcutsModal, toggleShortcutModal] = useBinaryState(false); + + const onShortcut = React.useCallback((name) => { + if (name === 'shortcuts') { + toggleShortcutModal(); + } + }, [toggleShortcutModal]); + React.useEffect(() => { let prevPath = window.location.hash.slice(1); const onLocationHashChange = () => { @@ -159,7 +168,8 @@ const App = () => { services.core.transport.dispatch({ action: 'Ctx', args: { - action: 'PullUserFromAPI' + action: 'PullUserFromAPI', + args: {} } }); services.core.transport.dispatch({ @@ -203,15 +213,20 @@ const App = () => { - - - - - + + { + shortcutModalOpen && + } + + + + + + diff --git a/src/App/ErrorDialog/ErrorDialog.js b/src/App/ErrorDialog/ErrorDialog.js index a2b31ba41..eac9b34ac 100644 --- a/src/App/ErrorDialog/ErrorDialog.js +++ b/src/App/ErrorDialog/ErrorDialog.js @@ -22,7 +22,7 @@ const ErrorDialog = ({ className }) => {
{'
diff --git a/src/App/SearchParamsHandler.js b/src/App/SearchParamsHandler.js index 8834e6e0c..86f10d0c4 100644 --- a/src/App/SearchParamsHandler.js +++ b/src/App/SearchParamsHandler.js @@ -1,7 +1,7 @@ // Copyright (C) 2017-2023 Smart code 203358507 const React = require('react'); -const isEqual = require('lodash.isequal'); +const { deepEqual } = require('fast-equals'); const { withCoreSuspender, useProfile, useToast } = require('stremio/common'); const { useServices } = require('stremio/services'); @@ -18,7 +18,7 @@ const SearchParamsHandler = () => { setSearchParams((previousSearchParams) => { const currentSearchParams = Object.fromEntries(searchParams.entries()); - return isEqual(previousSearchParams, currentSearchParams) ? previousSearchParams : currentSearchParams; + return deepEqual(previousSearchParams, currentSearchParams) ? previousSearchParams : currentSearchParams; }); }; diff --git a/src/App/ShortcutsModal/ShortcutsModal.tsx b/src/App/ShortcutsModal/ShortcutsModal.tsx new file mode 100644 index 000000000..5fec24837 --- /dev/null +++ b/src/App/ShortcutsModal/ShortcutsModal.tsx @@ -0,0 +1,59 @@ +// 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(( +
+
+ +
+
+
+ {t('SETTINGS_NAV_SHORTCUTS')} +
+ + +
+ +
+ { + grouped.map(({ name, label, shortcuts }) => ( + + )) + } +
+
+
+ ), document.body); +}; + +export default ShortcutsModal; diff --git a/src/App/ShortcutsModal/index.ts b/src/App/ShortcutsModal/index.ts new file mode 100644 index 000000000..5a7549fac --- /dev/null +++ b/src/App/ShortcutsModal/index.ts @@ -0,0 +1,2 @@ +import ShortcutsModal from './ShortcutsModal'; +export default ShortcutsModal; diff --git a/src/App/ShortcutsModal/styles.less b/src/App/ShortcutsModal/styles.less new file mode 100644 index 000000000..ebbc19c62 --- /dev/null +++ b/src/App/ShortcutsModal/styles.less @@ -0,0 +1,91 @@ +@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; + } + } +} \ No newline at end of file diff --git a/src/App/styles.less b/src/App/styles.less index 373b46900..e6fd7d747 100644 --- a/src/App/styles.less +++ b/src/App/styles.less @@ -5,7 +5,7 @@ @font-face { font-family: 'PlusJakartaSans'; - src: url('/fonts/PlusJakartaSans.ttf') format('truetype'); + src: url('/assets/fonts/PlusJakartaSans.ttf') format('truetype'); } :global { @@ -35,7 +35,7 @@ @top-overlay-size: 5.25rem; @bottom-overlay-size: 0rem; @overlap-size: 3rem; -@transparency-grandient-pad: 6rem; +@transparency-gradient-pad: 6rem; :root { --landscape-shape-ratio: 0.5625; @@ -48,7 +48,7 @@ --color-x: #000000; --color-reddit: #FF4500; --color-imdb: #f5c518; - --color-trakt: #ED2224; + --color-trakt: rgb(255, 255, 255); --color-placeholder: #60606080; --color-placeholder-text: @color-surface-50; --color-placeholder-background: @color-surface-dark5-20; @@ -69,7 +69,7 @@ --top-overlay-size: @top-overlay-size; --bottom-overlay-size: @bottom-overlay-size; --overlap-size: @overlap-size; - --transparency-grandient-pad: @transparency-grandient-pad; + --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; diff --git a/src/common/FileDrop/FileDrop.tsx b/src/common/FileDrop/FileDrop.tsx index aae4e146b..2993991e7 100644 --- a/src/common/FileDrop/FileDrop.tsx +++ b/src/common/FileDrop/FileDrop.tsx @@ -42,7 +42,7 @@ const FileDropProvider = ({ className, children }: Props) => { .then((buffer) => { listeners .filter(([type]) => file.type ? type === file.type : isFileType(buffer, type)) - .forEach(([, listerner]) => listerner(file.name, buffer)); + .forEach(([, listener]) => listener(file.name, buffer)); }); } diff --git a/src/common/Shortcuts/Shortcuts.tsx b/src/common/Shortcuts/Shortcuts.tsx new file mode 100644 index 000000000..c9198a857 --- /dev/null +++ b/src/common/Shortcuts/Shortcuts.tsx @@ -0,0 +1,67 @@ +import React, { createContext, useCallback, useContext, useEffect, useRef } from 'react'; +import shortcuts from './shortcuts.json'; + +const SHORTCUTS = shortcuts.map(({ shortcuts }) => shortcuts).flat(); + +export type ShortcutName = string; +export type ShortcutListener = (combo: number) => void; + +interface ShortcutsContext { + grouped: ShortcutGroup[], + on: (name: ShortcutName, listener: ShortcutListener) => void, + off: (name: ShortcutName, listener: ShortcutListener) => void, +} + +const ShortcutsContext = createContext({} as ShortcutsContext); + +type Props = { + children: JSX.Element, + onShortcut: (name: ShortcutName) => void, +}; + +const ShortcutsProvider = ({ children, onShortcut }: Props) => { + const listeners = useRef>>(new Map()); + + const onKeyDown = useCallback(({ ctrlKey, shiftKey, code, key }: KeyboardEvent) => { + SHORTCUTS.forEach(({ name, combos }) => combos.forEach((keys) => { + const modifers = (keys.includes('Ctrl') ? ctrlKey : true) + && (keys.includes('Shift') ? shiftKey : true); + + if (modifers && (keys.includes(code) || keys.includes(key.toUpperCase()))) { + const combo = combos.indexOf(keys); + listeners.current.get(name)?.forEach((listener) => listener(combo)); + + onShortcut(name as ShortcutName); + } + })); + }, [onShortcut]); + + const on = (name: ShortcutName, listener: ShortcutListener) => { + !listeners.current.has(name) && listeners.current.set(name, new Set()); + listeners.current.get(name)!.add(listener); + }; + + const off = (name: ShortcutName, listener: ShortcutListener) => { + listeners.current.get(name)?.delete(listener); + }; + + useEffect(() => { + document.addEventListener('keydown', onKeyDown); + return () => document.removeEventListener('keydown', onKeyDown); + }, [onKeyDown]); + + return ( + + {children} + + ); +}; + +const useShortcuts = () => { + return useContext(ShortcutsContext); +}; + +export { + ShortcutsProvider, + useShortcuts, +}; diff --git a/src/common/Shortcuts/index.ts b/src/common/Shortcuts/index.ts new file mode 100644 index 000000000..971003a38 --- /dev/null +++ b/src/common/Shortcuts/index.ts @@ -0,0 +1,8 @@ +import { ShortcutsProvider, useShortcuts } from './Shortcuts'; +import onShortcut from './onShortcut'; + +export { + ShortcutsProvider, + useShortcuts, + onShortcut, +}; diff --git a/src/common/Shortcuts/onShortcut.ts b/src/common/Shortcuts/onShortcut.ts new file mode 100644 index 000000000..f13c970cc --- /dev/null +++ b/src/common/Shortcuts/onShortcut.ts @@ -0,0 +1,15 @@ +import { DependencyList, useCallback, useEffect } from 'react'; +import { ShortcutListener, ShortcutName, useShortcuts } from './Shortcuts'; + +const onShortcut = (name: ShortcutName, listener: ShortcutListener, deps: DependencyList) => { + const shortcuts = useShortcuts(); + + const listenerCallback = useCallback(listener, deps); + + useEffect(() => { + shortcuts.on(name, listenerCallback); + return () => shortcuts.off(name, listenerCallback); + }, [listenerCallback]); +}; + +export default onShortcut; diff --git a/src/common/Shortcuts/shortcuts.json b/src/common/Shortcuts/shortcuts.json new file mode 100644 index 000000000..a3ac0f8fe --- /dev/null +++ b/src/common/Shortcuts/shortcuts.json @@ -0,0 +1,104 @@ +[ + { + "name": "general", + "label": "SETTINGS_NAV_GENERAL", + "shortcuts": [ + { + "name": "navigateTabs", + "label": "SETTINGS_SHORTCUT_NAVIGATE_MENUS", + "combos": [["1", "2", "3", "4", "5", "6"]] + }, + { + "name": "navigateSearch", + "label": "SETTINGS_SHORTCUT_GO_TO_SEARCH", + "combos": [["0"]] + }, + { + "name": "fullscreen", + "label": "SETTINGS_SHORTCUT_FULLSCREEN", + "combos": [["F"]] + }, + { + "name": "exit", + "label": "SETTINGS_SHORTCUT_EXIT_BACK", + "combos": [["Escape"]] + }, + { + "name": "shortcuts", + "label": "SETTINGS_SHORTCUT_SHORTCUTS", + "combos": [["Ctrl", "/"]] + } + ] + }, + { + "name": "player", + "label": "SETTINGS_NAV_PLAYER", + "shortcuts": [ + { + "name": "playPause", + "label": "SETTINGS_SHORTCUT_PLAY_PAUSE", + "combos": [["Space"]] + }, + { + "name": "seekForward", + "label": "SETTINGS_SHORTCUT_SEEK_FORWARD", + "combos": [["ArrowRight"], ["Shift", "ArrowRight"]] + }, + { + "name": "seekBackward", + "label": "SETTINGS_SHORTCUT_SEEK_BACKWARD", + "combos": [["ArrowLeft"], ["Shift", "ArrowLeft"]] + }, + { + "name": "volumeUp", + "label": "SETTINGS_SHORTCUT_VOLUME_UP", + "combos": [["ArrowUp"]] + }, + { + "name": "volumeDown", + "label": "SETTINGS_SHORTCUT_VOLUME_DOWN", + "combos": [["ArrowDown"]] + }, + { + "name": "mute", + "label": "SETTINGS_SHORTCUT_MUTE", + "combos": [["M"]] + }, + { + "name": "subtitlesSize", + "label": "SETTINGS_SHORTCUT_SUBTITLES_SIZE", + "combos": [["-"], ["="]] + }, + { + "name": "subtitlesDelay", + "label": "SETTINGS_SHORTCUT_SUBTITLES_DELAY", + "combos": [["G"], ["H"]] + }, + { + "name": "subtitlesMenu", + "label": "SETTINGS_SHORTCUT_MENU_SUBTITLES", + "combos": [["S"]] + }, + { + "name": "audioMenu", + "label": "SETTINGS_SHORTCUT_MENU_AUDIO", + "combos": [["A"]] + }, + { + "name": "infoMenu", + "label": "SETTINGS_SHORTCUT_MENU_INFO", + "combos": [["I"]] + }, + { + "name": "speedMenu", + "label": "SETTINGS_SHORTCUT_MENU_PLAYBACK_SPEED", + "combos": [["R"]] + }, + { + "name": "statisticsMenu", + "label": "SETTINGS_SHORTCUT_MENU_STATISTICS", + "combos": [["D"]] + } + ] + } +] \ No newline at end of file diff --git a/src/common/Shortcuts/types.d.ts b/src/common/Shortcuts/types.d.ts new file mode 100644 index 000000000..e4180616d --- /dev/null +++ b/src/common/Shortcuts/types.d.ts @@ -0,0 +1,11 @@ +type Shortcut = { + name: string, + label: string, + combos: string[][], +}; + +type ShortcutGroup = { + name: string, + label: string, + shortcuts: Shortcut[], +}; diff --git a/src/common/Toast/ToastItem/styles.less b/src/common/Toast/ToastItem/styles.less index 56f535695..075edeea5 100644 --- a/src/common/Toast/ToastItem/styles.less +++ b/src/common/Toast/ToastItem/styles.less @@ -27,7 +27,7 @@ &.error { .icon-container { .icon { - color: var(--color-trakt); + color: var(--danger-accent-color); } } } diff --git a/src/common/index.js b/src/common/index.js index 25df5c158..1b248c1ff 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -4,6 +4,7 @@ const { FileDropProvider, onFileDrop } = require('./FileDrop'); const { PlatformProvider, usePlatform } = require('./Platform'); const { ToastProvider, useToast } = require('./Toast'); const { TooltipProvider, Tooltip } = require('./Tooltips'); +const { ShortcutsProvider, useShortcuts, onShortcut } = require('./Shortcuts'); const comparatorWithPriorities = require('./comparatorWithPriorities'); const CONSTANTS = require('./CONSTANTS'); const { withCoreSuspender, useCoreSuspender } = require('./CoreSuspender'); @@ -35,6 +36,9 @@ module.exports = { onFileDrop, PlatformProvider, usePlatform, + ShortcutsProvider, + useShortcuts, + onShortcut, ToastProvider, useToast, TooltipProvider, diff --git a/src/common/interfaceLanguages.json b/src/common/interfaceLanguages.json index d4eaa7f60..91b87d5af 100644 --- a/src/common/interfaceLanguages.json +++ b/src/common/interfaceLanguages.json @@ -3,6 +3,10 @@ "name": "العربية", "codes": ["ar-AR", "ara"] }, + { + "name": "Беларуская", + "codes": ["be-BY", "bel"] + }, { "name": "български език", "codes": ["bg-BG", "bul"] @@ -13,7 +17,7 @@ }, { "name": "català", - "codes": ["ca-CA", "cat"] + "codes": ["ca-ES", "cat"] }, { "name": "čeština", @@ -43,6 +47,10 @@ "name": "español", "codes": ["es-ES", "spa"] }, + { + "name": "Eesti", + "codes": ["et-EE", "est"] + }, { "name": "euskara", "codes": ["eu-ES", "eus"] @@ -51,6 +59,10 @@ "name": "فارسی", "codes": ["fa-IR", "fas"] }, + { + "name": "Suomi", + "codes": ["fi-FI", "fin"] + }, { "name": "Français", "codes": ["fr-FR", "fre"] @@ -107,6 +119,10 @@ "name": "Norsk nynorsk", "codes": ["nn-NO", "nno"] }, + { + "name": "ਪੰਜਾਬੀ", + "codes": ["pa-IN", "pan"] + }, { "name": "język polski", "codes": ["pl-PL", "pol"] @@ -119,13 +135,17 @@ "name": "português", "codes": ["pt-PT", "por"] }, + { + "name": "Română", + "codes": ["ro-RO", "ron"] + }, { "name": "русский язык", "codes": ["ru-RU", "rus"] }, { - "name": "Svenska", - "codes": ["sv-SE", "swe"] + "name": "Slovenčina", + "codes": ["sk-SK", "slk"] }, { "name": "slovenski jezik", @@ -135,10 +155,18 @@ "name": "српски језик", "codes": ["sr-RS", "srp"] }, + { + "name": "Svenska", + "codes": ["sv-SE", "swe"] + }, { "name": "తెలుగు", "codes": ["te-IN", "tel"] }, + { + "name": "தமிழ்", + "codes": ["tl-TM", "tam"] + }, { "name": "Türkçe", "codes": ["tr-TR", "tur"] @@ -147,6 +175,10 @@ "name": "українська мова", "codes": ["uk-UA", "ukr"] }, + { + "name": "اُرْدُو", + "codes": ["ur-PK", "urd"] + }, { "name": "Tiếng Việt", "codes": ["vi-VN", "vie"] diff --git a/src/common/useModelState.js b/src/common/useModelState.js index 42672dc22..da637b8ff 100644 --- a/src/common/useModelState.js +++ b/src/common/useModelState.js @@ -2,7 +2,7 @@ const React = require('react'); const throttle = require('lodash.throttle'); -const isEqual = require('lodash.isequal'); +const { deepEqual } = require('fast-equals'); const intersection = require('lodash.intersection'); const { useCoreSuspender } = require('stremio/common/CoreSuspender'); const { useRouteFocused } = require('stremio-router'); @@ -19,7 +19,7 @@ const useModelState = ({ action, ...args }) => { const [state, setState] = React.useReducer( (prevState, nextState) => { return Object.keys(prevState).reduce((result, key) => { - result[key] = isEqual(prevState[key], nextState[key]) ? prevState[key] : nextState[key]; + result[key] = deepEqual(prevState[key], nextState[key]) ? prevState[key] : nextState[key]; return result; }, {}); }, diff --git a/src/common/useNotifications.d.ts b/src/common/useNotifications.d.ts index e3cae5b81..bfe63d67b 100644 --- a/src/common/useNotifications.d.ts +++ b/src/common/useNotifications.d.ts @@ -1,2 +1,2 @@ -declare const useNotifcations: () => Notifications; -export = useNotifcations; +declare const useNotifications: () => Notifications; +export = useNotifications; diff --git a/src/components/BottomSheet/BottomSheet.less b/src/components/BottomSheet/BottomSheet.less index f7e3315e1..7fcc6e508 100644 --- a/src/components/BottomSheet/BottomSheet.less +++ b/src/components/BottomSheet/BottomSheet.less @@ -86,7 +86,7 @@ } } -@media only screen and (min-width: @small) and (orientation: portait) { +@media only screen and (min-width: @small) and (orientation: portrait) { .bottom-sheet { display: none; } diff --git a/src/components/Checkbox/Checkbox.less b/src/components/Checkbox/Checkbox.less index 9276990b3..a9df8ea9e 100644 --- a/src/components/Checkbox/Checkbox.less +++ b/src/components/Checkbox/Checkbox.less @@ -70,7 +70,7 @@ } &.error { - border-color: var(--color-trakt); + border-color: var(--danger-accent-color); } &.checked { diff --git a/src/components/ColorInput/ColorPicker/ColorPicker.js b/src/components/ColorInput/ColorPicker/ColorPicker.js index 823ea55c8..3b66fcdf4 100644 --- a/src/components/ColorInput/ColorPicker/ColorPicker.js +++ b/src/components/ColorInput/ColorPicker/ColorPicker.js @@ -21,7 +21,7 @@ const ColorPicker = ({ className, value, onInput }) => { showRGB: false, showAlpha: true }); - const pickerClipboard = pickerElementRef.current.querySelector('.a-color-picker-clipbaord'); + const pickerClipboard = pickerElementRef.current.querySelector('.a-color-picker-clipboard'); if (pickerClipboard instanceof HTMLElement) { pickerClipboard.tabIndex = -1; } diff --git a/src/components/ColorInput/ColorPicker/styles.less b/src/components/ColorInput/ColorPicker/styles.less index 7156d440d..536228ed1 100644 --- a/src/components/ColorInput/ColorPicker/styles.less +++ b/src/components/ColorInput/ColorPicker/styles.less @@ -16,7 +16,7 @@ box-shadow: 0 0 .2rem var(--color-surfacedark); } - :global(.a-color-picker-clipbaord) { + :global(.a-color-picker-clipboard) { pointer-events: none; } } \ No newline at end of file diff --git a/src/components/ContinueWatchingItem/index.js b/src/components/ContinueWatchingItem/index.js index 5d3b2dd76..2c50f3c22 100644 --- a/src/components/ContinueWatchingItem/index.js +++ b/src/components/ContinueWatchingItem/index.js @@ -1,5 +1,5 @@ // Copyright (C) 2017-2023 Smart code 203358507 -const ContineWatchingItem = require('./ContinueWatchingItem'); +const ContinueWatchingItem = require('./ContinueWatchingItem'); -module.exports = ContineWatchingItem; +module.exports = ContinueWatchingItem; diff --git a/src/components/MetaPreview/MetaLinks/MetaLinks.js b/src/components/MetaPreview/MetaLinks/MetaLinks.js index ce2cf341a..3be090911 100644 --- a/src/components/MetaPreview/MetaLinks/MetaLinks.js +++ b/src/components/MetaPreview/MetaLinks/MetaLinks.js @@ -3,18 +3,18 @@ const React = require('react'); const PropTypes = require('prop-types'); const classnames = require('classnames'); -const { useTranslation } = require('react-i18next'); const { Button } = require('stremio/components'); +const useTranslate = require('stremio/common/useTranslate'); const styles = require('./styles'); const MetaLinks = ({ className, label, links }) => { - const { t } = useTranslation(); + const { string, stringWithPrefix } = useTranslate(); return (
{ typeof label === 'string' && label.length > 0 ?
- {t(`LINKS_${label.toUpperCase()}`)} + { stringWithPrefix(label.toUpperCase(), 'LINKS') }
: null @@ -24,7 +24,7 @@ const MetaLinks = ({ className, label, links }) => {
{links.map(({ label, href }, index) => ( ))}
diff --git a/src/components/MetaPreview/Ratings/Ratings.less b/src/components/MetaPreview/Ratings/Ratings.less index afe7b3637..ffba0415b 100644 --- a/src/components/MetaPreview/Ratings/Ratings.less +++ b/src/components/MetaPreview/Ratings/Ratings.less @@ -17,6 +17,7 @@ border-radius: 2rem; height: @height; width: fit-content; + backdrop-filter: blur(5px); .icon-container { display: flex; diff --git a/src/components/NavBar/HorizontalNavBar/HorizontalNavBar.js b/src/components/NavBar/HorizontalNavBar/HorizontalNavBar.js index 6be35cd5d..65bf30c94 100644 --- a/src/components/NavBar/HorizontalNavBar/HorizontalNavBar.js +++ b/src/components/NavBar/HorizontalNavBar/HorizontalNavBar.js @@ -35,7 +35,7 @@ const HorizontalNavBar = React.memo(({ className, route, query, title, backButto
{'
diff --git a/src/components/NavBar/HorizontalNavBar/NavMenu/NavMenuContent.js b/src/components/NavBar/HorizontalNavBar/NavMenu/NavMenuContent.js index de0a02212..4b2fdc87e 100644 --- a/src/components/NavBar/HorizontalNavBar/NavMenu/NavMenuContent.js +++ b/src/components/NavBar/HorizontalNavBar/NavMenu/NavMenuContent.js @@ -52,12 +52,12 @@ const NavMenuContent = ({ onClick }) => { className={styles['avatar-container']} style={{ backgroundImage: profile.auth === null ? - `url('${require('/images/anonymous.png')}')` + `url('${require('/assets/images/anonymous.png')}')` : profile.auth.user.avatar ? `url('${profile.auth.user.avatar}')` : - `url('${require('/images/default_avatar.png')}')` + `url('${require('/assets/images/default_avatar.png')}')` }} />
diff --git a/src/components/RadioButton/RadioButton.less b/src/components/RadioButton/RadioButton.less index 20d90bdef..69d179362 100644 --- a/src/components/RadioButton/RadioButton.less +++ b/src/components/RadioButton/RadioButton.less @@ -52,7 +52,7 @@ } &.error { - border-color: var(--color-trakt); + border-color: var(--danger-accent-color); } &.selected { diff --git a/src/components/ShortcutsGroup/Combos/Combos.less b/src/components/ShortcutsGroup/Combos/Combos.less new file mode 100644 index 000000000..a862d54ca --- /dev/null +++ b/src/components/ShortcutsGroup/Combos/Combos.less @@ -0,0 +1,22 @@ +.combos { + position: relative; + display: flex; + overflow: visible; + + .combo { + position: relative; + display: flex; + overflow: visible; + + .separator { + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: 3.5rem; + font-size: 1rem; + color: var(--primary-foreground-color); + opacity: 0.6; + } + } +} \ No newline at end of file diff --git a/src/components/ShortcutsGroup/Combos/Combos.tsx b/src/components/ShortcutsGroup/Combos/Combos.tsx new file mode 100644 index 000000000..0168441bc --- /dev/null +++ b/src/components/ShortcutsGroup/Combos/Combos.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import Keys from './Keys'; +import styles from './Combos.less'; + +type Props = { + combos: string[][], +}; + +const Combos = ({ combos }: Props) => { + const { t } = useTranslation(); + + return ( +
+ { + combos.map((keys, index) => ( +
+ + { + index < (combos.length - 1) && ( +
+ { t('SETTINGS_SHORTCUT_OR') } +
+ ) + } +
+ )) + } +
+ ); +}; + +export default Combos; diff --git a/src/components/ShortcutsGroup/Combos/Keys/Keys.less b/src/components/ShortcutsGroup/Combos/Keys/Keys.less new file mode 100644 index 000000000..7bb8c76e7 --- /dev/null +++ b/src/components/ShortcutsGroup/Combos/Keys/Keys.less @@ -0,0 +1,26 @@ +kbd { + flex: none; + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + height: 2.5rem; + min-width: 2.5rem; + padding: 0 1rem; + font-size: 1rem; + font-weight: 500; + color: var(--primary-foreground-color); + border-radius: 0.25em; + box-shadow: 0 4px 0 1px rgba(255, 255, 255, 0.1); + background-color: var(--overlay-color); +} + +.separator { + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: 2.5rem; + font-size: 1rem; + color: var(--primary-foreground-color); +} \ No newline at end of file diff --git a/src/components/ShortcutsGroup/Combos/Keys/Keys.tsx b/src/components/ShortcutsGroup/Combos/Keys/Keys.tsx new file mode 100644 index 000000000..71ec610da --- /dev/null +++ b/src/components/ShortcutsGroup/Combos/Keys/Keys.tsx @@ -0,0 +1,51 @@ +import React, { Fragment, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import styles from './Keys.less'; + +type Props = { + keys: string[], +}; + +const Keys = ({ keys }: Props) => { + const { t } = useTranslation(); + + const keyLabelMap: Record = useMemo(() => ({ + 'Shift': `⇧ ${t('SETTINGS_SHORTCUT_SHIFT')}`, + 'Space': t('SETTINGS_SHORTCUT_SPACE'), + 'Ctrl': t('SETTINGS_SHORTCUT_CTRL'), + 'Escape': t('SETTINGS_SHORTCUT_ESC'), + 'ArrowUp': '↑', + 'ArrowDown': '↓', + 'ArrowLeft': '←', + 'ArrowRight': '→', + }), [t]); + + const isRange = useMemo(() => { + return keys.length > 1 && keys.every((key) => !Number.isNaN(parseInt(key))); + }, [keys]); + + const filteredKeys = useMemo(() => { + return isRange ? [keys[0], keys[keys.length - 1]] : keys; + }, [keys, isRange]); + + return ( + filteredKeys.map((key, index) => ( + + + {keyLabelMap[key] ?? key.toUpperCase()} + + { + index < (filteredKeys.length - 1) && ( +
+ { + isRange ? t('SETTINGS_SHORTCUT_TO') : '+' + } +
+ ) + } +
+ )) + ); +}; + +export default Keys; diff --git a/src/components/ShortcutsGroup/Combos/Keys/index.ts b/src/components/ShortcutsGroup/Combos/Keys/index.ts new file mode 100644 index 000000000..ba8d58731 --- /dev/null +++ b/src/components/ShortcutsGroup/Combos/Keys/index.ts @@ -0,0 +1,2 @@ +import Keys from './Keys'; +export default Keys; diff --git a/src/components/ShortcutsGroup/Combos/index.ts b/src/components/ShortcutsGroup/Combos/index.ts new file mode 100644 index 000000000..c66667f91 --- /dev/null +++ b/src/components/ShortcutsGroup/Combos/index.ts @@ -0,0 +1,2 @@ +import Combos from './Combos'; +export default Combos; diff --git a/src/components/ShortcutsGroup/ShortcutsGroup.less b/src/components/ShortcutsGroup/ShortcutsGroup.less new file mode 100644 index 000000000..cbc08ada0 --- /dev/null +++ b/src/components/ShortcutsGroup/ShortcutsGroup.less @@ -0,0 +1,44 @@ +.shortcuts-group { + flex: 1 1 0; + position: relative; + width: 30rem; + display: flex; + flex-direction: column; + gap: 2rem; + overflow: visible; + + .title { + flex: none; + display: flex; + font-size: 1rem; + font-weight: 400; + color: var(--primary-foreground-color); + opacity: 0.6; + } + + .shortcuts { + position: relative; + display: flex; + flex-direction: column; + gap: 2rem; + overflow: visible; + + .shortcut { + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + gap: 2rem; + overflow: visible; + + .label { + position: relative; + font-size: 1rem; + color: var(--primary-foreground-color); + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + } + } + } +} diff --git a/src/components/ShortcutsGroup/ShortcutsGroup.tsx b/src/components/ShortcutsGroup/ShortcutsGroup.tsx new file mode 100644 index 000000000..069d5d1e8 --- /dev/null +++ b/src/components/ShortcutsGroup/ShortcutsGroup.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import classNames from 'classnames'; +import { useTranslation } from 'react-i18next'; +import Combos from './Combos'; +import styles from './ShortcutsGroup.less'; + +type Props = { + className?: string, + label: string, + shortcuts: Shortcut[], +}; + +const ShortcutsGroup = ({ className, label, shortcuts }: Props) => { + const { t } = useTranslation(); + + return ( +
+
+ {t(label)} +
+ +
+ { + shortcuts.map(({ name, label, combos }) => ( +
+
+ {t(label)} +
+ +
+ )) + } +
+
+ ); +}; + +export default ShortcutsGroup; diff --git a/src/components/ShortcutsGroup/index.ts b/src/components/ShortcutsGroup/index.ts new file mode 100644 index 000000000..11f8d0678 --- /dev/null +++ b/src/components/ShortcutsGroup/index.ts @@ -0,0 +1,2 @@ +import ShortcutsGroup from './ShortcutsGroup'; +export default ShortcutsGroup; diff --git a/src/components/Slider/Slider.js b/src/components/Slider/Slider.js index d0d4721b8..5ad113798 100644 --- a/src/components/Slider/Slider.js +++ b/src/components/Slider/Slider.js @@ -142,11 +142,11 @@ const Slider = ({ className, value, buffered, minimumValue, maximumValue, disabl
-
+
); diff --git a/src/components/Video/Video.js b/src/components/Video/Video.js index 229fe758d..fe615e39f 100644 --- a/src/components/Video/Video.js +++ b/src/components/Video/Video.js @@ -12,11 +12,12 @@ const useProfile = require('stremio/common/useProfile'); const VideoPlaceholder = require('./VideoPlaceholder'); const styles = require('./styles'); -const Video = React.forwardRef(({ className, id, title, thumbnail, season, episode, released, upcoming, watched, progress, scheduled, seasonWatched, deepLinks, onMarkVideoAsWatched, onMarkSeasonAsWatched, ...props }, ref) => { +const Video = ({ className, id, title, thumbnail, season, episode, released, upcoming, watched, progress, scheduled, seasonWatched, selected, deepLinks, onMarkVideoAsWatched, onMarkSeasonAsWatched, ...props }) => { const routeFocused = useRouteFocused(); const profile = useProfile(); const { t } = useTranslation(); const [menuOpen, , closeMenu, toggleMenu] = useBinaryState(false); + const popupLabelOnMouseUp = React.useCallback((event) => { if (!event.nativeEvent.togglePopupPrevented) { if (event.nativeEvent.ctrlKey || event.nativeEvent.button === 2) { @@ -68,27 +69,23 @@ const Video = React.forwardRef(({ className, id, title, thumbnail, season, episo } } }, [deepLinks]); - const renderLabel = React.useMemo(() => function renderLabel({ className, id, title, thumbnail, episode, released, upcoming, watched, progress, scheduled, children, ref: popupRef, ...props }) { + const renderLabel = React.useMemo(() => function renderLabel({ className, id, title, thumbnail, episode, released, upcoming, watched, progress, scheduled, children, ref, ...props }) { const blurThumbnail = profile.settings.hideSpoilers && season && episode && !watched; - const handleRef = React.useCallback((node) => { - if (popupRef) { - if (typeof popupRef === 'function') { - popupRef(node); - } else { - popupRef.current = node; + + React.useEffect(() => { + if (selected && ref.current) { + if ((progress && watched) || !watched) { + ref.current.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + inline: 'start' + }); } } - if (ref) { - if (typeof ref === 'function') { - ref(node); - } else { - ref.current = node; - } - } - }, [popupRef]); + }, [selected]); return ( - ); - }, []); + }, [selected]); const renderMenu = React.useMemo(() => function renderMenu() { return (
@@ -203,7 +200,7 @@ const Video = React.forwardRef(({ className, id, title, thumbnail, season, episo renderMenu={renderMenu} /> ); -}); +}; Video.Placeholder = VideoPlaceholder; @@ -220,6 +217,7 @@ Video.propTypes = { progress: PropTypes.number, scheduled: PropTypes.bool, seasonWatched: PropTypes.bool, + selected: PropTypes.bool, deepLinks: PropTypes.shape({ metaDetailsStreams: PropTypes.string, player: PropTypes.string diff --git a/src/components/Video/styles.less b/src/components/Video/styles.less index 0473ab9cb..907c9a943 100644 --- a/src/components/Video/styles.less +++ b/src/components/Video/styles.less @@ -19,6 +19,11 @@ padding: 0.5rem; margin-bottom: 0.5rem; border-radius: var(--border-radius); + border: 0.15rem solid transparent; + + @supports (scroll-margin: 1.25rem) { + scroll-margin: 1.25rem; + } &:hover, &:focus, @@ -172,6 +177,20 @@ } } + &.selected { + animation: border 3s ease-in-out forwards; + } + + @keyframes border { + 0% { + border: 0.15rem solid var(--primary-accent-color); + } + 100% { + border: 0.15rem solid transparent; + } + } + + .context-menu-container { max-width: calc(90% - 1.5rem); z-index: 2; diff --git a/src/components/index.ts b/src/components/index.ts index a5638007e..a47c2c709 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -25,6 +25,7 @@ import RadioButton from './RadioButton'; import SearchBar from './SearchBar'; import SharePrompt from './SharePrompt'; import Slider from './Slider'; +import ShortcutsGroup from './ShortcutsGroup'; import TextInput from './TextInput'; import Toggle from './Toggle'; import Transition from './Transition'; @@ -59,6 +60,7 @@ export { SearchBar, SharePrompt, Slider, + ShortcutsGroup, TextInput, Toggle, Transition, diff --git a/src/index.html b/src/index.html index 033f9a267..ff8166004 100644 --- a/src/index.html +++ b/src/index.html @@ -7,6 +7,7 @@ + Stremio - Freedom to Stream <%= htmlWebpackPlugin.tags.headTags %> diff --git a/src/router/Router/Router.js b/src/router/Router/Router.js index db4c9e957..2c7830bc3 100644 --- a/src/router/Router/Router.js +++ b/src/router/Router/Router.js @@ -5,7 +5,7 @@ const ReactIs = require('react-is'); const PropTypes = require('prop-types'); const classnames = require('classnames'); const UrlUtils = require('url'); -const isEqual = require('lodash.isequal'); +const { deepEqual } = require('fast-equals'); const { RouteFocusedProvider } = require('../RouteFocusedContext'); const Route = require('../Route'); const routeConfigForPath = require('./routeConfigForPath'); @@ -54,11 +54,11 @@ const Router = ({ className, onPathNotMatch, onRouteChange, ...props }) => { return { key: `${routeViewIndex}${routeIndex}`, component: routeConfig.component, - urlParams: view !== null && isEqual(view.urlParams, urlParams) ? + urlParams: view !== null && deepEqual(view.urlParams, urlParams) ? view.urlParams : urlParams, - queryParams: view !== null && isEqual(Array.from(view.queryParams.entries()), Array.from(queryParams.entries())) ? + queryParams: view !== null && deepEqual(Array.from(view.queryParams.entries()), Array.from(queryParams.entries())) ? view.queryParams : queryParams diff --git a/src/routes/Addons/useSelectableInputs.js b/src/routes/Addons/useSelectableInputs.js index 003da745d..8659b99e6 100644 --- a/src/routes/Addons/useSelectableInputs.js +++ b/src/routes/Addons/useSelectableInputs.js @@ -50,7 +50,7 @@ const mapSelectableInputs = (installedAddons, remoteAddons, t) => { remoteAddons.selected !== null ? t.stringWithPrefix(remoteAddons.selected.request.path.type, 'TYPE_') : - typeSelect.title; + t.string('SELECT_TYPE'); }, onSelect: (value) => { window.location = value; diff --git a/src/routes/Board/Board.js b/src/routes/Board/Board.js index 13acb4a86..cb2888a52 100644 --- a/src/routes/Board/Board.js +++ b/src/routes/Board/Board.js @@ -22,11 +22,10 @@ const Board = () => { const profile = useProfile(); const boardCatalogsOffset = continueWatchingPreview.items.length > 0 ? 1 : 0; const scrollContainerRef = React.useRef(); - const streamingServerWarningDismissed = React.useMemo(() => { - return streamingServer.settings !== null && streamingServer.settings.type === 'Ready' || ( - !isNaN(profile.settings.streamingServerWarningDismissed.getTime()) && - profile.settings.streamingServerWarningDismissed.getTime() > Date.now() - ); + const showStreamingServerWarning = React.useMemo(() => { + return streamingServer.settings !== null && streamingServer.settings.type === 'Err' && ( + isNaN(profile.settings.streamingServerWarningDismissed.getTime()) || + profile.settings.streamingServerWarningDismissed.getTime() < Date.now()); }, [profile.settings, streamingServer.settings]); const onVisibleRangeChange = React.useCallback(() => { const range = getVisibleChildrenRange(scrollContainerRef.current); @@ -103,7 +102,7 @@ const Board = () => {
{ - !streamingServerWarningDismissed ? + showStreamingServerWarning ? : null diff --git a/src/routes/Calendar/Placeholder/Placeholder.tsx b/src/routes/Calendar/Placeholder/Placeholder.tsx index c84e7a1b8..1f87acb26 100644 --- a/src/routes/Calendar/Placeholder/Placeholder.tsx +++ b/src/routes/Calendar/Placeholder/Placeholder.tsx @@ -17,7 +17,7 @@ const Placeholder = () => {
{'
diff --git a/src/routes/Calendar/Table/Cell/Cell.less b/src/routes/Calendar/Table/Cell/Cell.less index 9e6228490..afd2a3889 100644 --- a/src/routes/Calendar/Table/Cell/Cell.less +++ b/src/routes/Calendar/Table/Cell/Cell.less @@ -2,6 +2,25 @@ @import (reference) '~stremio/common/screen-sizes.less'; +.disable-cell-items() { + .cell { + .items { + .item { + pointer-events: none; + } + } + } +} + +.compact-items() { + .cell { + .items { + padding: 1px; + gap: 0.15rem; + } + } +} + .cell { position: relative; display: flex; @@ -27,12 +46,9 @@ } .heading { - flex: none; position: relative; - height: 3rem; display: flex; - align-items: center; - padding: 0 1rem; + align-items: flex-start; .day { flex: none; @@ -50,12 +66,15 @@ } .items { - flex: 0 1 10rem; position: relative; display: flex; flex-direction: row; - gap: 1rem; - padding: 0 0.5rem 0.5rem 0.5rem; + gap: 0.2rem; + padding: 0.1rem; + flex: 1 1 60%; + overflow-x: auto; + overflow-y: hidden; + min-width: 0; .item { flex: none; @@ -64,7 +83,9 @@ justify-content: center; height: 100%; aspect-ratio: 2 / 3; - border-radius: var(--border-radius); + border-radius: calc(var(--border-radius) / 2); + max-height: 100%; + max-width: 100%; .icon { flex: none; @@ -80,13 +101,11 @@ } .poster { - flex: auto; - z-index: 0; - position: relative; - height: 100%; - width: 100%; + height: auto; + max-height: 100%; + aspect-ratio: 2 / 3; object-fit: cover; - opacity: 1; + border-radius: inherit } .icon, .poster { @@ -117,8 +136,11 @@ &.today { .heading { + padding: 0.3rem; .day { background-color: var(--primary-accent-color); + height: 1.5rem; + width: 1.5rem; } } } @@ -134,56 +156,55 @@ } } -@media only screen and (max-height: @minimum) and (orientation: portrait) { - .cell { - .heading { - justify-content: center; - } - - .items { - display: none; - } - - .more { - display: flex; - } - } +@media only screen and (max-width: @minimum) { + .disable-cell-items(); } -@media only screen and (max-height: @xxsmall) and (orientation: landscape) { +@media @phone-portrait { + .cell { + flex-direction: column; + display: grid; + } + .compact-items(); + .disable-cell-items(); +} + +@media @phone-landscape { .cell { flex-direction: row; - align-items: center; - - .items { - display: none; - } - - .more { - display: flex; - } } + .compact-items(); + .disable-cell-items(); } -@media only screen and (max-height: @xsmall) and (max-width: @xsmall) { +@media only screen and (max-height: @medium) and (max-width: @medium) and (orientation: landscape) { .cell { gap: 0; .heading { - height: 2rem; - .day { + padding: 0; font-size: 0.875rem; } } .items { - padding: 0.25rem; - - .item { - pointer-events: none; - border-radius: calc(var(--border-radius) / 2); - } + width: 100%; + padding-left: 0.5rem; } } -} \ No newline at end of file +} + +@media only screen and (max-width: @minimum) and (orientation: portrait) and (pointer: fine) { + .cell { + display: flex; + + .heading { + flex: 1 1 33%; + } + } +} + +@media screen and (max-width: @small) and (orientation: portrait) { + .disable-cell-items(); +} diff --git a/src/routes/Calendar/Table/Table.less b/src/routes/Calendar/Table/Table.less index 65a9b01e9..14fc89f7c 100644 --- a/src/routes/Calendar/Table/Table.less +++ b/src/routes/Calendar/Table/Table.less @@ -45,6 +45,7 @@ display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px; + grid-auto-rows: 1fr; } } diff --git a/src/routes/Discover/Discover.js b/src/routes/Discover/Discover.js index 6f32d1f9a..a28a86405 100644 --- a/src/routes/Discover/Discover.js +++ b/src/routes/Discover/Discover.js @@ -133,14 +133,14 @@ const Discover = ({ urlParams, queryParams }) => { discover.catalog === null ?
- {' + {'
{t('NO_CATALOG_SELECTED')}
: discover.catalog.content.type === 'Err' ?
- {' + {'
{discover.catalog.content.content}
: diff --git a/src/routes/Intro/Intro.js b/src/routes/Intro/Intro.js index 811c4859c..5a2f80aaa 100644 --- a/src/routes/Intro/Intro.js +++ b/src/routes/Intro/Intro.js @@ -296,7 +296,7 @@ const Intro = ({ queryParams }) => {
- {' + {'
{t('WEBSITE_SLOGAN_NEW_NEW')} @@ -387,7 +387,7 @@ const Intro = ({ queryParams }) => { { state.form === SIGNUP_FORM ? : null @@ -395,7 +395,7 @@ const Intro = ({ queryParams }) => { { state.form === LOGIN_FORM ? : null @@ -403,7 +403,7 @@ const Intro = ({ queryParams }) => { { state.form === SIGNUP_FORM ? : null diff --git a/src/routes/Intro/styles.less b/src/routes/Intro/styles.less index 32fc73d79..4a6f6ec8e 100644 --- a/src/routes/Intro/styles.less +++ b/src/routes/Intro/styles.less @@ -19,7 +19,7 @@ bottom: -1rem; left: -1rem; right: -1rem; - background: url('/images/background_1.svg'), url('/images/background_2.svg'); + background: url('/assets/images/background_1.svg'), url('/assets/images/background_2.svg'); background-color: var(--primary-background-color); background-position: bottom left, top right; background-size: 53%, 54%; @@ -101,10 +101,6 @@ color: var(--primary-foreground-color); text-align: center; } - - .uppercase { - text-transform: uppercase; - } } .submit-button, .guest-login-button, .signup-form-button, .login-form-button { diff --git a/src/routes/Library/Library.js b/src/routes/Library/Library.js index 16a4c79b5..8307b786a 100644 --- a/src/routes/Library/Library.js +++ b/src/routes/Library/Library.js @@ -85,7 +85,7 @@ const Library = ({ model, urlParams, queryParams }) => {
{'
{model === 'library' ? t('LIBRARY_NOT_LOADED') : t('BOARD_CONTINUE_WATCHING_NOT_LOADED')}
@@ -96,7 +96,7 @@ const Library = ({ model, urlParams, queryParams }) => {
{'
{model === 'library' ? t('LIBRARY_EMPTY') : t('BOARD_CONTINUE_WATCHING_EMPTY')}
diff --git a/src/routes/Library/Placeholder/Placeholder.tsx b/src/routes/Library/Placeholder/Placeholder.tsx index d854a2d54..a066324e0 100644 --- a/src/routes/Library/Placeholder/Placeholder.tsx +++ b/src/routes/Library/Placeholder/Placeholder.tsx @@ -17,7 +17,7 @@ const Placeholder = () => {
{'
diff --git a/src/routes/MetaDetails/MetaDetails.js b/src/routes/MetaDetails/MetaDetails.js index 7d6f21d69..9f2279bd9 100644 --- a/src/routes/MetaDetails/MetaDetails.js +++ b/src/routes/MetaDetails/MetaDetails.js @@ -134,20 +134,20 @@ const MetaDetails = ({ urlParams, queryParams }) => { metaPath === null ?
- {' + {'
{t('ERR_NO_META_SELECTED')}
: metaDetails.metaItem === null ?
- {' + {'
{t('ERR_NO_ADDONS_FOR_META')}
: metaDetails.metaItem.content.type === 'Err' ?
- {' + {'
{t('ERR_NO_META_FOUND')}
: @@ -194,6 +194,7 @@ const MetaDetails = ({ urlParams, queryParams }) => { metaItem={metaDetails.metaItem} libraryItem={metaDetails.libraryItem} season={season} + selectedVideoId={metaDetails.libraryItem?.state?.video_id} seasonOnSelect={seasonOnSelect} toggleNotifications={toggleNotifications} /> diff --git a/src/routes/MetaDetails/StreamsList/StreamsList.js b/src/routes/MetaDetails/StreamsList/StreamsList.js index eebc0c3cf..7021644ab 100644 --- a/src/routes/MetaDetails/StreamsList/StreamsList.js +++ b/src/routes/MetaDetails/StreamsList/StreamsList.js @@ -132,7 +132,7 @@ const StreamsList = ({ className, video, type, onEpisodeSearch, ...props }) => { : null } - {' + {'
{t('ERR_NO_ADDONS_FOR_STREAMS')}
: @@ -148,7 +148,7 @@ const StreamsList = ({ className, video, type, onEpisodeSearch, ...props }) => {
{t('UPCOMING')}...
: null } - {' + {'
{t('NO_STREAM')}
{ showInstallAddonsButton ? diff --git a/src/routes/MetaDetails/VideosList/VideosList.js b/src/routes/MetaDetails/VideosList/VideosList.js index 88d53d427..3cc3832d6 100644 --- a/src/routes/MetaDetails/VideosList/VideosList.js +++ b/src/routes/MetaDetails/VideosList/VideosList.js @@ -11,9 +11,10 @@ const SeasonsBar = require('./SeasonsBar'); const { default: EpisodePicker } = require('../EpisodePicker'); const styles = require('./styles'); -const VideosList = ({ className, metaItem, libraryItem, season, seasonOnSelect, toggleNotifications }) => { +const VideosList = ({ className, metaItem, libraryItem, season, seasonOnSelect, selectedVideoId, toggleNotifications }) => { const { core } = useServices(); const profile = useProfile(); + const showNotificationsToggle = React.useMemo(() => { return metaItem?.content?.content?.inLibrary && metaItem?.content?.content?.videos?.length; }, [metaItem]); @@ -123,7 +124,7 @@ const VideosList = ({ className, metaItem, libraryItem, season, seasonOnSelect, metaItem.content.type === 'Err' || videosForSeason.length === 0 ?
- {' + {'
{t('ERR_NO_VIDEOS_FOR_META')}
: @@ -178,6 +179,7 @@ const VideosList = ({ className, metaItem, libraryItem, season, seasonOnSelect, deepLinks={video.deepLinks} scheduled={video.scheduled} seasonWatched={seasonWatched} + selected={video.id === selectedVideoId} onMarkVideoAsWatched={onMarkVideoAsWatched} onMarkSeasonAsWatched={onMarkSeasonAsWatched} /> @@ -195,6 +197,7 @@ VideosList.propTypes = { metaItem: PropTypes.object, libraryItem: PropTypes.object, season: PropTypes.number, + selectedVideoId: PropTypes.string, seasonOnSelect: PropTypes.func, toggleNotifications: PropTypes.func, }; diff --git a/src/routes/NotFound/NotFound.js b/src/routes/NotFound/NotFound.js index d984496bc..a3c97d8c1 100644 --- a/src/routes/NotFound/NotFound.js +++ b/src/routes/NotFound/NotFound.js @@ -19,7 +19,7 @@ const NotFound = () => {
{'
{t('PAGE_NOT_FOUND')}
diff --git a/src/routes/Player/AudioMenu/AudioMenu.less b/src/routes/Player/AudioMenu/AudioMenu.less index 344ec13e7..ae3e8acb1 100644 --- a/src/routes/Player/AudioMenu/AudioMenu.less +++ b/src/routes/Player/AudioMenu/AudioMenu.less @@ -7,6 +7,7 @@ align-self: stretch; display: flex; flex-direction: column; + max-height: 25rem; width: 16rem; .header { diff --git a/src/routes/Player/BufferingLoader/BufferingLoader.js b/src/routes/Player/BufferingLoader/BufferingLoader.js index 3d5664ae4..a762506f4 100644 --- a/src/routes/Player/BufferingLoader/BufferingLoader.js +++ b/src/routes/Player/BufferingLoader/BufferingLoader.js @@ -13,7 +13,7 @@ const BufferingLoader = React.forwardRef(({ className, logo }, ref) => { className={styles['buffering-loader']} src={logo} alt={' '} - fallbackSrc={require('/images/stremio_symbol.png')} + fallbackSrc={require('/assets/images/stremio_symbol.png')} />
); diff --git a/src/routes/Player/OptionsMenu/OptionsMenu.js b/src/routes/Player/OptionsMenu/OptionsMenu.js index 968de5341..da826ba41 100644 --- a/src/routes/Player/OptionsMenu/OptionsMenu.js +++ b/src/routes/Player/OptionsMenu/OptionsMenu.js @@ -54,8 +54,8 @@ const OptionsMenu = ({ className, stream, playbackDevices, extraSubtitlesTracks, } }, [streamingUrl, downloadUrl]); const onDownloadVideoButtonClick = React.useCallback(() => { - if (streamingUrl || downloadUrl) { - platform.openExternal(streamingUrl || downloadUrl); + if (downloadUrl || streamingUrl ) { + platform.openExternal(downloadUrl || streamingUrl); } }, [streamingUrl, downloadUrl]); diff --git a/src/routes/Player/Player.js b/src/routes/Player/Player.js index eab38bd96..196bb813b 100644 --- a/src/routes/Player/Player.js +++ b/src/routes/Player/Player.js @@ -8,7 +8,7 @@ const langs = require('langs'); const { useTranslation } = require('react-i18next'); const { useRouteFocused } = require('stremio-router'); const { useServices } = require('stremio/services'); -const { onFileDrop, useSettings, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, CONSTANTS, useShell } = require('stremio/common'); +const { onFileDrop, useSettings, useProfile, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, CONSTANTS, useShell, usePlatform, onShortcut } = require('stremio/common'); const { HorizontalNavBar, Transition, ContextMenu } = require('stremio/components'); const BufferingLoader = require('./BufferingLoader'); const VolumeChangeIndicator = require('./VolumeChangeIndicator'); @@ -29,6 +29,9 @@ const styles = require('./styles'); const Video = require('./Video'); const { default: Indicator } = require('./Indicator/Indicator'); +const findTrackByLang = (tracks, lang) => tracks.find((track) => track.lang === lang || langs.where('1', track.lang)?.[2] === lang); +const findTrackById = (tracks, id) => tracks.find((track) => track.id === id); + const Player = ({ urlParams, queryParams }) => { const { t } = useTranslation(); const services = useServices(); @@ -36,13 +39,14 @@ const Player = ({ urlParams, queryParams }) => { const forceTranscoding = React.useMemo(() => { return queryParams.has('forceTranscoding'); }, [queryParams]); - - const [player, videoParamsChanged, timeChanged, seek, pausedChanged, ended, nextVideo] = usePlayer(urlParams); - const [settings, updateSettings] = useSettings(); + const profile = useProfile(); + const [player, videoParamsChanged, streamStateChanged, timeChanged, seek, pausedChanged, ended, nextVideo] = usePlayer(urlParams); + const [settings] = useSettings(); const streamingServer = useStreamingServer(); const statistics = useStatistics(player, streamingServer); const video = useVideo(); const routeFocused = useRouteFocused(); + const platform = usePlatform(); const toast = useToast(); const [seeking, setSeeking] = React.useState(false); @@ -92,25 +96,34 @@ const Player = ({ urlParams, queryParams }) => { const isNavigating = React.useRef(false); const onImplementationChanged = React.useCallback(() => { - video.setProp('subtitlesSize', settings.subtitlesSize); - video.setProp('subtitlesOffset', settings.subtitlesOffset); - video.setProp('subtitlesTextColor', settings.subtitlesTextColor); - video.setProp('subtitlesBackgroundColor', settings.subtitlesBackgroundColor); - video.setProp('subtitlesOutlineColor', settings.subtitlesOutlineColor); - video.setProp('extraSubtitlesSize', settings.subtitlesSize); - video.setProp('extraSubtitlesOffset', settings.subtitlesOffset); - video.setProp('extraSubtitlesTextColor', settings.subtitlesTextColor); - video.setProp('extraSubtitlesBackgroundColor', settings.subtitlesBackgroundColor); - video.setProp('extraSubtitlesOutlineColor', settings.subtitlesOutlineColor); - }, [settings.subtitlesSize, settings.subtitlesOffset, settings.subtitlesTextColor, settings.subtitlesBackgroundColor, settings.subtitlesOutlineColor]); + video.setSubtitlesSize(settings.subtitlesSize); + video.setSubtitlesOffset(settings.subtitlesOffset); + video.setSubtitlesTextColor(settings.subtitlesTextColor); + video.setSubtitlesBackgroundColor(settings.subtitlesBackgroundColor); + video.setSubtitlesOutlineColor(settings.subtitlesOutlineColor); + }, [settings]); - const handleNextVideoNavigation = React.useCallback((deepLinks) => { - if (deepLinks.player) { - isNavigating.current = true; - window.location.replace(deepLinks.player); - } else if (deepLinks.metaDetailsStreams) { - isNavigating.current = true; - window.location.replace(deepLinks.metaDetailsStreams); + const handleNextVideoNavigation = React.useCallback((deepLinks, bingeWatching, ended) => { + if (ended) { + if (bingeWatching) { + if (deepLinks.player) { + isNavigating.current = true; + window.location.replace(deepLinks.player); + } else if (deepLinks.metaDetailsStreams) { + isNavigating.current = true; + window.location.replace(deepLinks.metaDetailsStreams); + } + } else { + window.history.back(); + } + } else { + if (deepLinks.player) { + isNavigating.current = true; + window.location.replace(deepLinks.player); + } else if (deepLinks.metaDetailsStreams) { + isNavigating.current = true; + window.location.replace(deepLinks.metaDetailsStreams); + } } }, []); @@ -126,7 +139,8 @@ const Player = ({ urlParams, queryParams }) => { nextVideo(); const deepLinks = window.playerNextVideo.deepLinks; - handleNextVideoNavigation(deepLinks); + handleNextVideoNavigation(deepLinks, profile.settings.bingeWatching, true); + } else { window.history.back(); } @@ -174,53 +188,71 @@ const Player = ({ urlParams, queryParams }) => { }, []); const onPlayRequested = React.useCallback(() => { - video.setProp('paused', false); + video.setPaused(false); setSeeking(false); }, []); const onPlayRequestedDebounced = React.useCallback(debounce(onPlayRequested, 200), []); const onPauseRequested = React.useCallback(() => { - video.setProp('paused', true); + video.setPaused(true); }, []); const onPauseRequestedDebounced = React.useCallback(debounce(onPauseRequested, 200), []); const onMuteRequested = React.useCallback(() => { - video.setProp('muted', true); + video.setMuted(true); }, []); const onUnmuteRequested = React.useCallback(() => { - video.setProp('muted', false); + video.setMuted(false); }, []); const onVolumeChangeRequested = React.useCallback((volume) => { - video.setProp('volume', volume); + video.setVolume(volume); }, []); const onSeekRequested = React.useCallback((time) => { - video.setProp('time', time); + video.setTime(time); seek(time, video.state.duration, video.state.manifest?.name); }, [video.state.duration, video.state.manifest]); const onPlaybackSpeedChanged = React.useCallback((rate) => { - video.setProp('playbackSpeed', rate); + video.setPlaybackSpeed(rate); }, []); const onSubtitlesTrackSelected = React.useCallback((id) => { video.setSubtitlesTrack(id); - }, []); + streamStateChanged({ + subtitleTrack: { + id, + embedded: true, + }, + }); + }, [streamStateChanged]); const onExtraSubtitlesTrackSelected = React.useCallback((id) => { video.setExtraSubtitlesTrack(id); - }, []); + streamStateChanged({ + subtitleTrack: { + id, + embedded: false, + }, + }); + }, [streamStateChanged]); const onAudioTrackSelected = React.useCallback((id) => { - video.setProp('selectedAudioTrackId', id); - }, []); + video.setAudioTrack(id); + streamStateChanged({ + audioTrack: { + id, + }, + }); + }, [streamStateChanged]); const onExtraSubtitlesDelayChanged = React.useCallback((delay) => { - video.setProp('extraSubtitlesDelay', delay); - }, []); + video.setSubtitlesDelay(delay); + streamStateChanged({ subtitleDelay: delay }); + }, [streamStateChanged]); const onIncreaseSubtitlesDelay = React.useCallback(() => { const delay = video.state.extraSubtitlesDelay + 250; @@ -233,8 +265,9 @@ const Player = ({ urlParams, queryParams }) => { }, [video.state.extraSubtitlesDelay, onExtraSubtitlesDelayChanged]); const onSubtitlesSizeChanged = React.useCallback((size) => { - updateSettings({ subtitlesSize: size }); - }, [updateSettings]); + video.setSubtitlesSize(size); + streamStateChanged({ subtitleSize: size }); + }, [streamStateChanged]); const onUpdateSubtitlesSize = React.useCallback((delta) => { const sizeIndex = CONSTANTS.SUBTITLES_SIZES.indexOf(video.state.subtitlesSize); @@ -243,8 +276,9 @@ const Player = ({ urlParams, queryParams }) => { }, [video.state.subtitlesSize, onSubtitlesSizeChanged]); const onSubtitlesOffsetChanged = React.useCallback((offset) => { - updateSettings({ subtitlesOffset: offset }); - }, [updateSettings]); + video.setSubtitlesOffset(offset); + streamStateChanged({ subtitleOffset: offset }); + }, [streamStateChanged]); const onDismissNextVideoPopup = React.useCallback(() => { closeNextVideoPopup(); @@ -256,9 +290,9 @@ const Player = ({ urlParams, queryParams }) => { nextVideo(); const deepLinks = player.nextVideo.deepLinks; - handleNextVideoNavigation(deepLinks); + handleNextVideoNavigation(deepLinks, profile.settings.bingeWatching, false); } - }, [player.nextVideo, handleNextVideoNavigation]); + }, [player.nextVideo, handleNextVideoNavigation, profile.settings]); const onVideoClick = React.useCallback(() => { if (video.state.paused !== null) { @@ -322,10 +356,10 @@ const Player = ({ urlParams, queryParams }) => { setError(null); video.unload(); - if (player.selected && streamingServer.settings?.type !== 'Loading') { + if (player.selected && player.stream?.type === 'Ready' && streamingServer.settings?.type !== 'Loading') { video.load({ stream: { - ...player.selected.stream, + ...player.stream.content, subtitles: Array.isArray(player.selected.stream.subtitles) ? player.selected.stream.subtitles.map((subtitles) => ({ ...subtitles, @@ -345,6 +379,9 @@ const Player = ({ urlParams, queryParams }) => { forceTranscoding: forceTranscoding || casting, maxAudioChannels: settings.surroundSound ? 32 : 2, hardwareDecoding: settings.hardwareDecoding, + assSubtitlesStyling: settings.assSubtitlesStyling, + videoMode: settings.videoMode, + platform: platform.name, streamingServerURL: streamingServer.baseUrl ? casting ? streamingServer.baseUrl @@ -358,7 +395,7 @@ const Player = ({ urlParams, queryParams }) => { shellTransport: services.shell.active ? services.shell.transport : null, }); } - }, [streamingServer.baseUrl, player.selected, forceTranscoding, casting]); + }, [streamingServer.baseUrl, player.selected, player.stream, forceTranscoding, casting]); React.useEffect(() => { if (video.state.stream !== null) { const tracks = player.subtitles.map((subtitles) => ({ @@ -369,31 +406,6 @@ const Player = ({ urlParams, queryParams }) => { } }, [player.subtitles, video.state.stream]); - React.useEffect(() => { - video.setProp('subtitlesSize', settings.subtitlesSize); - video.setProp('extraSubtitlesSize', settings.subtitlesSize); - }, [settings.subtitlesSize]); - - React.useEffect(() => { - video.setProp('subtitlesOffset', settings.subtitlesOffset); - video.setProp('extraSubtitlesOffset', settings.subtitlesOffset); - }, [settings.subtitlesOffset]); - - React.useEffect(() => { - video.setProp('subtitlesTextColor', settings.subtitlesTextColor); - video.setProp('extraSubtitlesTextColor', settings.subtitlesTextColor); - }, [settings.subtitlesTextColor]); - - React.useEffect(() => { - video.setProp('subtitlesBackgroundColor', settings.subtitlesBackgroundColor); - video.setProp('extraSubtitlesBackgroundColor', settings.subtitlesBackgroundColor); - }, [settings.subtitlesBackgroundColor]); - - React.useEffect(() => { - video.setProp('subtitlesOutlineColor', settings.subtitlesOutlineColor); - video.setProp('extraSubtitlesOutlineColor', settings.subtitlesOutlineColor); - }, [settings.subtitlesOutlineColor]); - React.useEffect(() => { !seeking && timeChanged(video.state.time, video.state.duration, video.state.manifest?.name); }, [video.state.time, video.state.duration, video.state.manifest, seeking]); @@ -409,7 +421,7 @@ const Player = ({ urlParams, queryParams }) => { }, [video.state.videoParams]); React.useEffect(() => { - if (!!settings.bingeWatching && player.nextVideo !== null && !nextVideoPopupDismissed.current) { + if (player.nextVideo !== null && !nextVideoPopupDismissed.current) { if (video.state.time !== null && video.state.duration !== null && video.state.time < video.state.duration && (video.state.duration - video.state.time) <= settings.nextVideoNotificationDuration) { openNextVideoPopup(); } else { @@ -426,41 +438,69 @@ const Player = ({ urlParams, queryParams }) => { } }, [player.nextVideo, video.state.time, video.state.duration]); + // Auto subtitles track selection React.useEffect(() => { if (!defaultSubtitlesSelected.current) { - const findTrackByLang = (tracks, lang) => tracks.find((track) => track.lang === lang || langs.where('1', track.lang)?.[2] === lang); - if (settings.subtitlesLanguage === null) { - onSubtitlesTrackSelected(null); - onExtraSubtitlesTrackSelected(null); + video.setSubtitlesTrack(null); + video.setExtraSubtitlesTrack(null); defaultSubtitlesSelected.current = true; return; } - const subtitlesTrack = findTrackByLang(video.state.subtitlesTracks, settings.subtitlesLanguage); - const extraSubtitlesTrack = findTrackByLang(video.state.extraSubtitlesTracks, settings.subtitlesLanguage); + const savedTrackId = player.streamState?.subtitleTrack?.id; + const subtitlesTrack = savedTrackId ? + findTrackById(video.state.subtitlesTracks, savedTrackId) : + findTrackByLang(video.state.subtitlesTracks, settings.subtitlesLanguage); + + const extraSubtitlesTrack = savedTrackId ? + findTrackById(video.state.extraSubtitlesTracks, savedTrackId) : + findTrackByLang(video.state.extraSubtitlesTracks, settings.subtitlesLanguage); if (subtitlesTrack && subtitlesTrack.id) { - onSubtitlesTrackSelected(subtitlesTrack.id); + video.setSubtitlesTrack(subtitlesTrack.id); defaultSubtitlesSelected.current = true; } else if (extraSubtitlesTrack && extraSubtitlesTrack.id) { - onExtraSubtitlesTrackSelected(extraSubtitlesTrack.id); + video.setExtraSubtitlesTrack(extraSubtitlesTrack.id); defaultSubtitlesSelected.current = true; } } - }, [video.state.subtitlesTracks, video.state.extraSubtitlesTracks]); + }, [video.state.subtitlesTracks, video.state.extraSubtitlesTracks, player.streamState]); + // Auto audio track selection React.useEffect(() => { if (!defaultAudioTrackSelected.current) { - const findTrackByLang = (tracks, lang) => tracks.find((track) => track.lang === lang || langs.where('1', track.lang)?.[2] === lang); - const audioTrack = findTrackByLang(video.state.audioTracks, settings.audioLanguage); + const savedTrackId = player.streamState?.audioTrack?.id; + const audioTrack = savedTrackId ? + findTrackById(video.state.audioTracks, savedTrackId) : + findTrackByLang(video.state.audioTracks, settings.audioLanguage); if (audioTrack && audioTrack.id) { - onAudioTrackSelected(audioTrack.id); + video.setAudioTrack(audioTrack.id); defaultAudioTrackSelected.current = true; } } - }, [video.state.audioTracks]); + }, [video.state.audioTracks, player.streamState]); + + // Saved subtitles settings + React.useEffect(() => { + if (video.state.stream !== null) { + const delay = player.streamState?.subtitleDelay; + if (typeof delay === 'number') { + video.setSubtitlesDelay(delay); + } + + const size = player.streamState?.subtitleSize; + if (typeof size === 'number') { + video.setSubtitlesSize(size); + } + + const offset = player.streamState?.subtitleOffset; + if (typeof offset === 'number') { + video.setSubtitlesOffset(offset); + } + } + }, [video.state.stream, player.streamState]); React.useEffect(() => { defaultSubtitlesSelected.current = false; @@ -546,11 +586,11 @@ const Player = ({ urlParams, queryParams }) => { React.useEffect(() => { if (!navigator.mediaSession) return; - const metaItem = player.metaItem && player.metaItem.type === 'Ready' ? player.metaItem.content : null; - const videoId = player.selected ? player.selected.streamRequest.path.id : null; + const metaItem = player.metaItem && player.metaItem?.type === 'Ready' ? player.metaItem.content : null; + const videoId = player.selected ? player.selected?.streamRequest?.path?.id : null; const video = metaItem ? metaItem.videos.find(({ id }) => id === videoId) : null; - const videoInfo = video && video.season && video.episode ? ` (${video.season}x${video.episode})`: null; + const videoInfo = video && video.season && video.episode ? ` (${video.season}x${video.episode})` : null; const videoTitle = video ? `${video.title}${videoInfo}` : null; const metaTitle = metaItem ? metaItem.name : null; const imageUrl = metaItem ? metaItem.logo : null; @@ -579,117 +619,99 @@ const Player = ({ urlParams, queryParams }) => { navigator.mediaSession.setActionHandler('nexttrack', nexVideoCallback); }, [player.nextVideo, onPlayRequested, onPauseRequested, onNextVideoRequested]); - React.useLayoutEffect(() => { - const onKeyDown = (event) => { - switch (event.code) { - case 'Space': { - if (!menusOpen && !nextVideoPopupOpen && video.state.paused !== null) { - if (video.state.paused) { - onPlayRequested(); - setSeeking(false); - } else { - onPauseRequested(); - } - } - - break; - } - case 'ArrowRight': { - if (!menusOpen && !nextVideoPopupOpen && video.state.time !== null) { - const seekDuration = event.shiftKey ? settings.seekShortTimeDuration : settings.seekTimeDuration; - setSeeking(true); - onSeekRequested(video.state.time + seekDuration); - } - - break; - } - case 'ArrowLeft': { - if (!menusOpen && !nextVideoPopupOpen && video.state.time !== null) { - const seekDuration = event.shiftKey ? settings.seekShortTimeDuration : settings.seekTimeDuration; - setSeeking(true); - onSeekRequested(video.state.time - seekDuration); - } - - break; - } - case 'ArrowUp': { - if (!menusOpen && !nextVideoPopupOpen && video.state.volume !== null) { - onVolumeChangeRequested(Math.min(video.state.volume + 5, 200)); - } - - break; - } - case 'ArrowDown': { - if (!menusOpen && !nextVideoPopupOpen && video.state.volume !== null) { - onVolumeChangeRequested(Math.max(video.state.volume - 5, 0)); - } - - break; - } - case 'KeyS': { - closeMenus(); - if ((Array.isArray(video.state.subtitlesTracks) && video.state.subtitlesTracks.length > 0) || - (Array.isArray(video.state.extraSubtitlesTracks) && video.state.extraSubtitlesTracks.length > 0)) { - toggleSubtitlesMenu(); - } - - break; - } - case 'KeyA': { - closeMenus(); - if (Array.isArray(video.state.audioTracks) && video.state.audioTracks.length > 0) { - toggleAudioMenu(); - } - - break; - } - case 'KeyI': { - closeMenus(); - if (player.metaItem !== null && player.metaItem.type === 'Ready') { - toggleSideDrawer(); - } - - break; - } - case 'KeyR': { - closeMenus(); - if (video.state.playbackSpeed !== null) { - toggleSpeedMenu(); - } - - break; - } - case 'KeyD': { - closeMenus(); - if (streamingServer.statistics !== null && streamingServer.statistics.type !== 'Err' && player.selected && typeof player.selected.stream.infoHash === 'string' && typeof player.selected.stream.fileIdx === 'number') { - toggleStatisticsMenu(); - } - - break; - } - case 'KeyG': { - onDecreaseSubtitlesDelay(); - break; - } - case 'KeyH': { - onIncreaseSubtitlesDelay(); - break; - } - case 'Minus': { - onUpdateSubtitlesSize(-1); - break; - } - case 'Equal': { - onUpdateSubtitlesSize(1); - break; - } - case 'Escape': { - closeMenus(); - !settings.escExitFullscreen && window.history.back(); - break; - } + onShortcut('playPause', () => { + if (!menusOpen && !nextVideoPopupOpen && video.state.paused !== null) { + if (video.state.paused) { + onPlayRequested(); + setSeeking(false); + } else { + onPauseRequested(); } - }; + } + }, [menusOpen, nextVideoPopupOpen, video.state.paused, onPlayRequested, onPauseRequested]); + + onShortcut('seekForward', (combo) => { + if (!menusOpen && !nextVideoPopupOpen && video.state.time !== null) { + const seekDuration = combo === 1 ? settings.seekShortTimeDuration : settings.seekTimeDuration; + setSeeking(true); + onSeekRequested(video.state.time + seekDuration); + } + }, [menusOpen, nextVideoPopupOpen, video.state.time, onSeekRequested]); + + onShortcut('seekBackward', (combo) => { + if (!menusOpen && !nextVideoPopupOpen && video.state.time !== null) { + const seekDuration = combo === 1 ? settings.seekShortTimeDuration : settings.seekTimeDuration; + setSeeking(true); + onSeekRequested(video.state.time - seekDuration); + } + }, [menusOpen, nextVideoPopupOpen, video.state.time, onSeekRequested]); + + onShortcut('mute', () => { + video.state.muted === true ? onUnmuteRequested() : onMuteRequested(); + }, [video.state.muted]); + + onShortcut('volumeUp', () => { + if (!menusOpen && !nextVideoPopupOpen && video.state.volume !== null) { + onVolumeChangeRequested(Math.min(video.state.volume + 5, 200)); + } + }, [menusOpen, nextVideoPopupOpen, video.state.volume]); + + onShortcut('volumeDown', () => { + if (!menusOpen && !nextVideoPopupOpen && video.state.volume !== null) { + onVolumeChangeRequested(Math.min(video.state.volume - 5, 200)); + } + }, [menusOpen, nextVideoPopupOpen, video.state.volume]); + + onShortcut('subtitlesDelay', (combo) => { + combo === 1 ? onIncreaseSubtitlesDelay() : onDecreaseSubtitlesDelay(); + }, [onIncreaseSubtitlesDelay, onDecreaseSubtitlesDelay]); + + onShortcut('subtitlesSize', (combo) => { + combo === 1 ? onUpdateSubtitlesSize(-1) : onUpdateSubtitlesSize(1); + }, [onUpdateSubtitlesSize, onUpdateSubtitlesSize]); + + onShortcut('subtitlesMenu', () => { + closeMenus(); + if (video.state?.subtitlesTracks?.length > 0 || video.state?.extraSubtitlesTracks?.length > 0) { + toggleSubtitlesMenu(); + } + }, [video.state.subtitlesTracks, video.state.extraSubtitlesTracks, toggleSubtitlesMenu]); + + onShortcut('audioMenu', () => { + closeMenus(); + if (video.state?.audioTracks?.length > 0) { + toggleAudioMenu(); + } + }, [video.state.audioTracks, toggleAudioMenu]); + + onShortcut('infoMenu', () => { + closeMenus(); + if (player.metaItem?.type === 'Ready') { + toggleSideDrawer(); + } + }, [player.metaItem, toggleSideDrawer]); + + onShortcut('speedMenu', () => { + closeMenus(); + if (video.state.playbackSpeed !== null) { + toggleSpeedMenu(); + } + }, [video.state.playbackSpeed, toggleSpeedMenu]); + + onShortcut('statisticsMenu', () => { + closeMenus(); + const stream = player.selected?.stream; + if (streamingServer?.statistics?.type !== 'Err' && typeof stream === 'string' && typeof stream === 'number') { + toggleStatisticsMenu(); + } + }, [player.selected, streamingServer.statistics, toggleStatisticsMenu]); + + onShortcut('exit', () => { + closeMenus(); + !settings.escExitFullscreen && window.history.back(); + }, [settings.escExitFullscreen]); + + React.useLayoutEffect(() => { const onKeyUp = (event) => { if (event.code === 'ArrowRight' || event.code === 'ArrowLeft') { setSeeking(false); @@ -707,39 +729,14 @@ const Player = ({ urlParams, queryParams }) => { } }; if (routeFocused) { - window.addEventListener('keydown', onKeyDown); window.addEventListener('keyup', onKeyUp); window.addEventListener('wheel', onWheel); } return () => { - window.removeEventListener('keydown', onKeyDown); window.removeEventListener('keyup', onKeyUp); window.removeEventListener('wheel', onWheel); }; - }, [ - player.metaItem, - player.selected, - streamingServer.statistics, - settings.seekTimeDuration, - settings.seekShortTimeDuration, - settings.escExitFullscreen, - routeFocused, - menusOpen, - nextVideoPopupOpen, - video.state.paused, - video.state.time, - video.state.volume, - video.state.audioTracks, - video.state.subtitlesTracks, - video.state.extraSubtitlesTracks, - video.state.playbackSpeed, - toggleSubtitlesMenu, - toggleStatisticsMenu, - toggleSideDrawer, - onDecreaseSubtitlesDelay, - onIncreaseSubtitlesDelay, - onUpdateSubtitlesSize, - ]); + }, [routeFocused, menusOpen, video.state.volume]); React.useEffect(() => { video.events.on('error', onError); diff --git a/src/routes/Player/SideDrawer/SideDrawer.tsx b/src/routes/Player/SideDrawer/SideDrawer.tsx index 299c37b23..d5b13e11f 100644 --- a/src/routes/Player/SideDrawer/SideDrawer.tsx +++ b/src/routes/Player/SideDrawer/SideDrawer.tsx @@ -1,6 +1,6 @@ // Copyright (C) 2017-2024 Smart code 203358507 -import React, { useMemo, useCallback, useState, forwardRef, memo, useRef } from 'react'; +import React, { useMemo, useCallback, useState, forwardRef, memo } from 'react'; import classNames from 'classnames'; import Icon from '@stremio/stremio-icons/react'; import { useServices } from 'stremio/services'; @@ -21,7 +21,8 @@ type Props = { const SideDrawer = memo(forwardRef(({ seriesInfo, className, closeSideDrawer, selected, ...props }: Props, ref) => { const { core } = useServices(); const [season, setSeason] = useState(seriesInfo?.season); - const selectedVideoRef = useRef(null); + const [selectedVideoId, setSelectedVideoId] = useState(null); + const metaItem = useMemo(() => { return seriesInfo ? { @@ -78,11 +79,9 @@ const SideDrawer = memo(forwardRef(({ seriesInfo, classNa event.stopPropagation(); }; - const onTransitionEnd = () => { - selectedVideoRef.current?.scrollIntoView({ - behavior: 'smooth', - }); - }; + const onTransitionEnd = useCallback(() => { + setSelectedVideoId(selected); + }, [selected]); return (
@@ -114,7 +113,6 @@ const SideDrawer = memo(forwardRef(({ seriesInfo, classNa {videos.map((video, index) => (
- { disabled ? '--' : `${value}${unit}` } + { valueLabel }
- -
- - { - shell.active && - - } - { - shell.active && - - } - -
; }); diff --git a/src/routes/Settings/General/User/User.tsx b/src/routes/Settings/General/User/User.tsx index 6b44e9903..7555e521e 100644 --- a/src/routes/Settings/General/User/User.tsx +++ b/src/routes/Settings/General/User/User.tsx @@ -14,12 +14,12 @@ const User = ({ profile }: Props) => { const avatar = useMemo(() => ( !profile.auth ? - `url('${require('/images/anonymous.png')}')` + `url('${require('/assets/images/anonymous.png')}')` : profile.auth.user.avatar ? `url('${profile.auth.user.avatar}')` : - `url('${require('/images/default_avatar.png')}')` + `url('${require('/assets/images/default_avatar.png')}')` ), [profile.auth]); const onLogout = useCallback(() => { diff --git a/src/routes/Settings/Interface/Interface.tsx b/src/routes/Settings/Interface/Interface.tsx new file mode 100644 index 000000000..a4b429a56 --- /dev/null +++ b/src/routes/Settings/Interface/Interface.tsx @@ -0,0 +1,57 @@ +import React, { forwardRef } from 'react'; +import { useServices } from 'stremio/services'; +import { MultiselectMenu, Toggle } from 'stremio/components'; +import { Section, Option } from '../components'; +import useInterfaceOptions from './useInterfaceOptions'; + +type Props = { + profile: Profile, +}; + +const Interface = forwardRef(({ profile }: Props, ref) => { + const { shell } = useServices(); + + const { + interfaceLanguageSelect, + quitOnCloseToggle, + escExitFullscreenToggle, + hideSpoilersToggle, + } = useInterfaceOptions(profile); + + return ( +
+ + { + shell.active && + + } + { + shell.active && + + } + +
+ ); +}); + +export default Interface; diff --git a/src/routes/Settings/Interface/index.ts b/src/routes/Settings/Interface/index.ts new file mode 100644 index 000000000..480fa5ff4 --- /dev/null +++ b/src/routes/Settings/Interface/index.ts @@ -0,0 +1,2 @@ +import Interface from './Interface'; +export default Interface; diff --git a/src/routes/Settings/General/useGeneralOptions.ts b/src/routes/Settings/Interface/useInterfaceOptions.ts similarity index 96% rename from src/routes/Settings/General/useGeneralOptions.ts rename to src/routes/Settings/Interface/useInterfaceOptions.ts index a4ab84c5e..67780b7e1 100644 --- a/src/routes/Settings/General/useGeneralOptions.ts +++ b/src/routes/Settings/Interface/useInterfaceOptions.ts @@ -2,7 +2,7 @@ import { useMemo } from 'react'; import { interfaceLanguages, useLanguageSorting } from 'stremio/common'; import { useServices } from 'stremio/services'; -const useGeneralOptions = (profile: Profile) => { +const useInterfaceOptions = (profile: Profile) => { const { core } = useServices(); const interfaceLanguageOptions = useMemo(() => @@ -89,4 +89,4 @@ const useGeneralOptions = (profile: Profile) => { }; }; -export default useGeneralOptions; +export default useInterfaceOptions; diff --git a/src/routes/Settings/Menu/Menu.tsx b/src/routes/Settings/Menu/Menu.tsx index ceafee94b..33cf41dc0 100644 --- a/src/routes/Settings/Menu/Menu.tsx +++ b/src/routes/Settings/Menu/Menu.tsx @@ -26,6 +26,9 @@ const Menu = ({ selected, streamingServer, onSelect }: Props) => { + diff --git a/src/routes/Settings/Player/Player.tsx b/src/routes/Settings/Player/Player.tsx index 72a941e81..213757cc3 100644 --- a/src/routes/Settings/Player/Player.tsx +++ b/src/routes/Settings/Player/Player.tsx @@ -3,6 +3,7 @@ import { ColorInput, MultiselectMenu, Toggle } from 'stremio/components'; import { useServices } from 'stremio/services'; import { Category, Option, Section } from '../components'; import usePlayerOptions from './usePlayerOptions'; +import { usePlatform } from 'stremio/common'; type Props = { profile: Profile, @@ -10,6 +11,7 @@ type Props = { const Player = forwardRef(({ profile }: Props, ref) => { const { shell } = useServices(); + const platform = usePlatform(); const { subtitlesLanguageSelect, @@ -17,6 +19,7 @@ const Player = forwardRef(({ profile }: Props, ref) => { subtitlesTextColorInput, subtitlesBackgroundColorInput, subtitlesOutlineColorInput, + assSubtitlesStylingToggle, audioLanguageSelect, surroundSoundToggle, seekTimeDurationSelect, @@ -26,6 +29,7 @@ const Player = forwardRef(({ profile }: Props, ref) => { bingeWatchingToggle, playInBackgroundToggle, hardwareDecodingToggle, + videoModeSelect, pauseOnMinimizeToggle, } = usePlayerOptions(profile); @@ -108,7 +112,6 @@ const Player = forwardRef(({ profile }: Props, ref) => { @@ -129,6 +132,15 @@ const Player = forwardRef(({ profile }: Props, ref) => { /> } + { + shell.active && platform.name === 'windows' && + + } { shell.active && } + { + shell.active && + + } ); diff --git a/src/routes/Settings/Player/usePlayerOptions.ts b/src/routes/Settings/Player/usePlayerOptions.ts index edbce3d24..27081817b 100644 --- a/src/routes/Settings/Player/usePlayerOptions.ts +++ b/src/routes/Settings/Player/usePlayerOptions.ts @@ -92,6 +92,22 @@ const usePlayerOptions = (profile: Profile) => { } }), [profile.settings]); + const assSubtitlesStylingToggle = useMemo(() => ({ + checked: profile.settings.assSubtitlesStyling, + onClick: () => { + core.transport.dispatch({ + action: 'Ctx', + args: { + action: 'UpdateSettings', + args: { + ...profile.settings, + assSubtitlesStyling: !profile.settings.assSubtitlesStyling + } + } + }); + } + }), [profile.settings]); + const subtitlesOutlineColorInput = useMemo(() => ({ value: profile.settings.subtitlesOutlineColor, onChange: (value: string) => { @@ -287,6 +303,38 @@ const usePlayerOptions = (profile: Profile) => { } }), [profile.settings]); + const videoModeSelect = useMemo(() => ({ + options: [ + { + value: null, + label: t('SETTINGS_VIDEO_MODE_DEFAULT'), + }, + { + value: 'legacy', + label: t('SETTINGS_VIDEO_MODE_LEGACY'), + } + ], + value: profile.settings.videoMode, + title: () => { + return profile.settings.videoMode === 'legacy' ? + t('SETTINGS_VIDEO_MODE_LEGACY') + : + t('SETTINGS_VIDEO_MODE_DEFAULT'); + }, + onSelect: (value: string | null) => { + core.transport.dispatch({ + action: 'Ctx', + args: { + action: 'UpdateSettings', + args: { + ...profile.settings, + videoMode: value, + } + } + }); + } + }), [profile.settings]); + const pauseOnMinimizeToggle = useMemo(() => ({ checked: profile.settings.pauseOnMinimize, onClick: () => { @@ -309,6 +357,7 @@ const usePlayerOptions = (profile: Profile) => { subtitlesTextColorInput, subtitlesBackgroundColorInput, subtitlesOutlineColorInput, + assSubtitlesStylingToggle, audioLanguageSelect, surroundSoundToggle, seekTimeDurationSelect, @@ -318,6 +367,7 @@ const usePlayerOptions = (profile: Profile) => { bingeWatchingToggle, playInBackgroundToggle, hardwareDecodingToggle, + videoModeSelect, pauseOnMinimizeToggle, }; }; diff --git a/src/routes/Settings/Settings.tsx b/src/routes/Settings/Settings.tsx index b37d1f0c6..727da9e82 100644 --- a/src/routes/Settings/Settings.tsx +++ b/src/routes/Settings/Settings.tsx @@ -4,11 +4,12 @@ import React, { useCallback, useLayoutEffect, useMemo, useRef, useState } from ' import classnames from 'classnames'; import throttle from 'lodash.throttle'; import { useRouteFocused } from 'stremio-router'; -import { useProfile, useStreamingServer, withCoreSuspender } from 'stremio/common'; +import { usePlatform, useProfile, useStreamingServer, withCoreSuspender } from 'stremio/common'; import { MainNavBars } from 'stremio/components'; import { SECTIONS } from './constants'; import Menu from './Menu'; import General from './General'; +import Interface from './Interface'; import Player from './Player'; import Streaming from './Streaming'; import Shortcuts from './Shortcuts'; @@ -18,16 +19,19 @@ import styles from './Settings.less'; const Settings = () => { const { routeFocused } = useRouteFocused(); const profile = useProfile(); + const platform = usePlatform(); const streamingServer = useStreamingServer(); const sectionsContainerRef = useRef(null); const generalSectionRef = useRef(null); + const interfaceSectionRef = useRef(null); const playerSectionRef = useRef(null); const streamingServerSectionRef = useRef(null); const shortcutsSectionRef = useRef(null); const sections = useMemo(() => ([ { ref: generalSectionRef, id: SECTIONS.GENERAL }, + { ref: interfaceSectionRef, id: SECTIONS.INTERFACE }, { ref: playerSectionRef, id: SECTIONS.PLAYER }, { ref: streamingServerSectionRef, id: SECTIONS.STREAMING }, { ref: shortcutsSectionRef, id: SECTIONS.SHORTCUTS }, @@ -37,14 +41,10 @@ const Settings = () => { const updateSelectedSectionId = useCallback(() => { const container = sectionsContainerRef.current; - if (container!.scrollTop + container!.clientHeight >= container!.scrollHeight - 50) { - setSelectedSectionId(sections[sections.length - 1].id); - } else { - for (let i = sections.length - 1; i >= 0; i--) { - if (sections[i].ref.current!.offsetTop - container!.offsetTop <= container!.scrollTop) { - setSelectedSectionId(sections[i].id); - break; - } + for (const section of sections) { + const sectionContainer = section.ref.current; + if (sectionContainer && (sectionContainer.offsetTop + container!.offsetTop) < container!.scrollTop + 50) { + setSelectedSectionId(section.id); } } }, []); @@ -85,6 +85,10 @@ const Settings = () => { ref={generalSectionRef} profile={profile} /> + { profile={profile} streamingServer={streamingServer} /> - + { + !platform.isMobile && + }
diff --git a/src/routes/Settings/Shortcuts/Shortcuts.less b/src/routes/Settings/Shortcuts/Shortcuts.less index 40d97987d..186cfa837 100644 --- a/src/routes/Settings/Shortcuts/Shortcuts.less +++ b/src/routes/Settings/Shortcuts/Shortcuts.less @@ -1,27 +1,4 @@ -.shortcut-container { - display: flex; - align-items: center; - justify-content: center; - padding: 0; - overflow: visible; - - kbd { - flex: 0 1 auto; - height: 2.5rem; - min-width: 2.5rem; - line-height: 2.5rem; - padding: 0 1rem; - font-weight: 500; - color: var(--primary-foreground-color); - border-radius: 0.25em; - box-shadow: 0 4px 0 1px var(--modal-background-color); - background-color: var(--overlay-color); - } - - .label { - flex: none; - margin: 0 1rem; - white-space: nowrap; - color: var(--primary-foreground-color); - } +.shortcuts-group { + width: 100%; + margin-bottom: 3rem; } \ No newline at end of file diff --git a/src/routes/Settings/Shortcuts/Shortcuts.tsx b/src/routes/Settings/Shortcuts/Shortcuts.tsx index d852280a6..a0599a503 100644 --- a/src/routes/Settings/Shortcuts/Shortcuts.tsx +++ b/src/routes/Settings/Shortcuts/Shortcuts.tsx @@ -1,97 +1,24 @@ import React, { forwardRef } from 'react'; -import { Section, Option } from '../components'; +import { Section } from '../components'; +import { ShortcutsGroup } from 'stremio/components'; +import { useShortcuts } from 'stremio/common'; import styles from './Shortcuts.less'; -import { useTranslation } from 'react-i18next'; const Shortcuts = forwardRef((_, ref) => { - const { t } = useTranslation(); + const { grouped } = useShortcuts(); return (
- - - - - - - - - - - - - - + { + grouped.map(({ name, label, shortcuts }) => ( + + )) + }
); }); diff --git a/src/routes/Settings/Streaming/URLsManager/AddItem/AddItem.less b/src/routes/Settings/Streaming/URLsManager/AddItem/AddItem.less index e8de1ecf6..1ced5f14d 100644 --- a/src/routes/Settings/Streaming/URLsManager/AddItem/AddItem.less +++ b/src/routes/Settings/Streaming/URLsManager/AddItem/AddItem.less @@ -69,7 +69,7 @@ .cancel { &:hover { .icon { - color: var(--color-trakt); + color: var(--danger-accent-color); } } } diff --git a/src/routes/Settings/Streaming/URLsManager/AddItem/AddItem.tsx b/src/routes/Settings/Streaming/URLsManager/AddItem/AddItem.tsx index a73cf95e0..d0a2e0c41 100644 --- a/src/routes/Settings/Streaming/URLsManager/AddItem/AddItem.tsx +++ b/src/routes/Settings/Streaming/URLsManager/AddItem/AddItem.tsx @@ -17,7 +17,7 @@ const AddItem = ({ onCancel, handleAddUrl }: Props) => { setInputValue(target.value); }, []); - const onSumbit = useCallback(() => { + const onSubmit = useCallback(() => { handleAddUrl(inputValue); }, [inputValue]); @@ -27,11 +27,11 @@ const AddItem = ({ onCancel, handleAddUrl }: Props) => { className={styles['input']} value={inputValue} onChange={handleValueChange} - onSubmit={onSumbit} + onSubmit={onSubmit} placeholder={'Enter URL'} />
-