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 }) => ( +
+
+ {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/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'} />
-