diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index d728715a2..6782e9d8e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -27,7 +27,7 @@ jobs:
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/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/package.json b/package.json
index c824ad411..8dd374b02 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,7 @@
"@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-core-web": "0.50.0",
"@stremio/stremio-icons": "5.7.1",
"@stremio/stremio-video": "0.0.62",
"a-color-picker": "1.2.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#01aaa201e419782b26b9f2cbe4430795021426e5",
"url": "0.11.4",
"use-long-press": "^3.2.0"
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 121d0dbea..e42f446f8 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -18,8 +18,8 @@ importers:
specifier: 5.2.0
version: 5.2.0
'@stremio/stremio-core-web':
- specifier: 0.49.4
- version: 0.49.4
+ specifier: 0.50.0
+ version: 0.50.0
'@stremio/stremio-icons':
specifier: 5.7.1
version: 5.7.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#01aaa201e419782b26b9f2cbe4430795021426e5
+ version: https://codeload.github.com/Stremio/stremio-translations/tar.gz/01aaa201e419782b26b9f2cbe4430795021426e5
url:
specifier: 0.11.4
version: 0.11.4
@@ -1302,8 +1302,8 @@ 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.50.0':
+ resolution: {integrity: sha512-SRE9nStgYNbhjJAw7mXfmM0wdnSLS4GMSJsSMTXvoGxnUgd+yisJUkN/9Sughe4t2IU7Uct8QWpdx9zFdlil+g==}
'@stremio/stremio-icons@5.7.1':
resolution: {integrity: sha512-Z96p36LLX3G+ewMnFKmNZVsO/AtcHA33WQ3wGOYFubxiYADPRAkcLVU5rHIfiGSC9IUaUVhxQWTPVB9ScY4Q5Q==}
@@ -4527,9 +4527,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/01aaa201e419782b26b9f2cbe4430795021426e5:
+ resolution: {tarball: https://codeload.github.com/Stremio/stremio-translations/tar.gz/01aaa201e419782b26b9f2cbe4430795021426e5}
+ version: 1.44.13
string-length@4.0.2:
resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==}
@@ -6561,7 +6561,7 @@ snapshots:
'@stremio/stremio-colors@5.2.0': {}
- '@stremio/stremio-core-web@0.49.4':
+ '@stremio/stremio-core-web@0.50.0':
dependencies:
'@babel/runtime': 7.24.1
@@ -10283,7 +10283,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/01aaa201e419782b26b9f2cbe4430795021426e5: {}
string-length@4.0.2:
dependencies:
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/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..adce0ae5c 100644
--- a/src/App/styles.less
+++ b/src/App/styles.less
@@ -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;
@@ -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..532e9a409
--- /dev/null
+++ b/src/common/Shortcuts/Shortcuts.tsx
@@ -0,0 +1,54 @@
+import React, { createContext, useCallback, useContext, useEffect } from 'react';
+import shortcuts from './shortcuts.json';
+
+const SHORTCUTS = shortcuts.map(({ shortcuts }) => shortcuts).flat();
+
+export type ShortcutName = string;
+export type ShortcutListener = () => void;
+
+interface ShortcutsContext {
+ grouped: ShortcutGroup[],
+}
+
+const ShortcutsContext = createContext({} as ShortcutsContext);
+
+type Props = {
+ children: JSX.Element,
+ onShortcut: (name: ShortcutName) => void,
+};
+
+const ShortcutsProvider = ({ children, onShortcut }: Props) => {
+ const onKeyDown = useCallback(({ ctrlKey, shiftKey, 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(key.toUpperCase())) {
+ onShortcut(name as ShortcutName);
+ }
+ }));
+ }, [onShortcut]);
+
+ 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..f7fa38a18
--- /dev/null
+++ b/src/common/Shortcuts/index.ts
@@ -0,0 +1,5 @@
+import { ShortcutsProvider, useShortcuts } from './Shortcuts';
+export {
+ ShortcutsProvider,
+ useShortcuts,
+};
diff --git a/src/common/Shortcuts/shortcuts.json b/src/common/Shortcuts/shortcuts.json
new file mode 100644
index 000000000..766288fb0
--- /dev/null
+++ b/src/common/Shortcuts/shortcuts.json
@@ -0,0 +1,89 @@
+[
+ {
+ "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": "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"]]
+ }
+ ]
+ }
+]
\ 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/index.js b/src/common/index.js
index 25df5c158..0b9cb252f 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 } = require('./Shortcuts');
const comparatorWithPriorities = require('./comparatorWithPriorities');
const CONSTANTS = require('./CONSTANTS');
const { withCoreSuspender, useCoreSuspender } = require('./CoreSuspender');
@@ -35,6 +36,8 @@ module.exports = {
onFileDrop,
PlatformProvider,
usePlatform,
+ ShortcutsProvider,
+ useShortcuts,
ToastProvider,
useToast,
TooltipProvider,
diff --git a/src/common/useFullscreen.ts b/src/common/useFullscreen.ts
index 69cdcd494..8a1692254 100644
--- a/src/common/useFullscreen.ts
+++ b/src/common/useFullscreen.ts
@@ -10,11 +10,15 @@ const useFullscreen = () => {
const [fullscreen, setFullscreen] = useState(false);
- const requestFullscreen = useCallback(() => {
+ const requestFullscreen = useCallback(async () => {
if (shell.active) {
shell.send('win-set-visibility', { fullscreen: true });
} else {
- document.documentElement.requestFullscreen();
+ try {
+ await document.documentElement.requestFullscreen();
+ } catch (err) {
+ console.error('Error enabling fullscreen', err);
+ }
}
}, []);
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/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/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..f0fdd975c
--- /dev/null
+++ b/src/components/ShortcutsGroup/ShortcutsGroup.less
@@ -0,0 +1,44 @@
+.shortcuts-group {
+ flex: 1 1 0;
+ position: relative;
+ min-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 }) => (
+
+ ))
+ }
+
+
+ );
+};
+
+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/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/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/Intro/Intro.js b/src/routes/Intro/Intro.js
index 811c4859c..f04302fc7 100644
--- a/src/routes/Intro/Intro.js
+++ b/src/routes/Intro/Intro.js
@@ -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..31a09d54c 100644
--- a/src/routes/Intro/styles.less
+++ b/src/routes/Intro/styles.less
@@ -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/Player/SubtitlesMenu/SubtitlesMenu.js b/src/routes/Player/SubtitlesMenu/SubtitlesMenu.js
index d94c5f70b..fe690d0c4 100644
--- a/src/routes/Player/SubtitlesMenu/SubtitlesMenu.js
+++ b/src/routes/Player/SubtitlesMenu/SubtitlesMenu.js
@@ -228,7 +228,7 @@ const SubtitlesMenu = React.memo((props) => {
/>
((_, ref) => {
- const { t } = useTranslation();
+ const { grouped } = useShortcuts();
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ {
+ grouped.map(({ name, label, shortcuts }) => (
+
+ ))
+ }
);
});
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'}
/>
-