mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-03-11 17:15:48 +00:00
Merge branch 'development' of https://github.com/Stremio/stremio-web into feat/video-mode-setting
This commit is contained in:
commit
e3c4bc14bb
42 changed files with 691 additions and 214 deletions
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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,6 +213,10 @@ const App = () => {
|
|||
<ToastProvider className={styles['toasts-container']}>
|
||||
<TooltipProvider className={styles['tooltip-container']}>
|
||||
<FileDropProvider className={styles['file-drop-container']}>
|
||||
<ShortcutsProvider onShortcut={onShortcut}>
|
||||
{
|
||||
shortcutModalOpen && <ShortcutsModal onClose={closeShortcutsModal}/>
|
||||
}
|
||||
<ServicesToaster />
|
||||
<DeepLinkHandler />
|
||||
<SearchParamsHandler />
|
||||
|
|
@ -212,6 +226,7 @@ const App = () => {
|
|||
viewsConfig={routerViewsConfig}
|
||||
onPathNotMatch={onPathNotMatch}
|
||||
/>
|
||||
</ShortcutsProvider>
|
||||
</FileDropProvider>
|
||||
</TooltipProvider>
|
||||
</ToastProvider>
|
||||
|
|
|
|||
59
src/App/ShortcutsModal/ShortcutsModal.tsx
Normal file
59
src/App/ShortcutsModal/ShortcutsModal.tsx
Normal file
|
|
@ -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((
|
||||
<div className={styles['shortcuts-modal']}>
|
||||
<div className={styles['backdrop']} onClick={onClose} />
|
||||
|
||||
<div className={styles['container']}>
|
||||
<div className={styles['header']}>
|
||||
<div className={styles['title']}>
|
||||
{t('SETTINGS_NAV_SHORTCUTS')}
|
||||
</div>
|
||||
|
||||
<Button className={styles['close-button']} title={t('BUTTON_CLOSE')} onClick={onClose}>
|
||||
<Icon className={styles['icon']} name={'close'} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className={styles['content']}>
|
||||
{
|
||||
grouped.map(({ name, label, shortcuts }) => (
|
||||
<ShortcutsGroup
|
||||
key={name}
|
||||
label={label}
|
||||
shortcuts={shortcuts}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
), document.body);
|
||||
};
|
||||
|
||||
export default ShortcutsModal;
|
||||
2
src/App/ShortcutsModal/index.ts
Normal file
2
src/App/ShortcutsModal/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import ShortcutsModal from './ShortcutsModal';
|
||||
export default ShortcutsModal;
|
||||
91
src/App/ShortcutsModal/styles.less
Normal file
91
src/App/ShortcutsModal/styles.less
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
54
src/common/Shortcuts/Shortcuts.tsx
Normal file
54
src/common/Shortcuts/Shortcuts.tsx
Normal file
|
|
@ -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<ShortcutsContext>({} 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 (
|
||||
<ShortcutsContext.Provider value={{ grouped: shortcuts }}>
|
||||
{children}
|
||||
</ShortcutsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const useShortcuts = () => {
|
||||
return useContext(ShortcutsContext);
|
||||
};
|
||||
|
||||
export {
|
||||
ShortcutsProvider,
|
||||
useShortcuts
|
||||
};
|
||||
5
src/common/Shortcuts/index.ts
Normal file
5
src/common/Shortcuts/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { ShortcutsProvider, useShortcuts } from './Shortcuts';
|
||||
export {
|
||||
ShortcutsProvider,
|
||||
useShortcuts,
|
||||
};
|
||||
89
src/common/Shortcuts/shortcuts.json
Normal file
89
src/common/Shortcuts/shortcuts.json
Normal file
|
|
@ -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"]]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
11
src/common/Shortcuts/types.d.ts
vendored
Normal file
11
src/common/Shortcuts/types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
type Shortcut = {
|
||||
name: string,
|
||||
label: string,
|
||||
combos: string[][],
|
||||
};
|
||||
|
||||
type ShortcutGroup = {
|
||||
name: string,
|
||||
label: string,
|
||||
shortcuts: Shortcut[],
|
||||
};
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
|
|
|||
4
src/common/useNotifications.d.ts
vendored
4
src/common/useNotifications.d.ts
vendored
|
|
@ -1,2 +1,2 @@
|
|||
declare const useNotifcations: () => Notifications;
|
||||
export = useNotifcations;
|
||||
declare const useNotifications: () => Notifications;
|
||||
export = useNotifications;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
22
src/components/ShortcutsGroup/Combos/Combos.less
Normal file
22
src/components/ShortcutsGroup/Combos/Combos.less
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
src/components/ShortcutsGroup/Combos/Combos.tsx
Normal file
33
src/components/ShortcutsGroup/Combos/Combos.tsx
Normal file
|
|
@ -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 (
|
||||
<div className={styles['combos']}>
|
||||
{
|
||||
combos.map((keys, index) => (
|
||||
<div className={styles['combo']} key={index}>
|
||||
<Keys keys={keys} />
|
||||
{
|
||||
index < (combos.length - 1) && (
|
||||
<div className={styles['separator']}>
|
||||
{ t('SETTINGS_SHORTCUT_OR') }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Combos;
|
||||
26
src/components/ShortcutsGroup/Combos/Keys/Keys.less
Normal file
26
src/components/ShortcutsGroup/Combos/Keys/Keys.less
Normal file
|
|
@ -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);
|
||||
}
|
||||
51
src/components/ShortcutsGroup/Combos/Keys/Keys.tsx
Normal file
51
src/components/ShortcutsGroup/Combos/Keys/Keys.tsx
Normal file
|
|
@ -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<string, string> = 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) => (
|
||||
<Fragment key={key}>
|
||||
<kbd>
|
||||
{keyLabelMap[key] ?? key.toUpperCase()}
|
||||
</kbd>
|
||||
{
|
||||
index < (filteredKeys.length - 1) && (
|
||||
<div className={styles['separator']}>
|
||||
{
|
||||
isRange ? t('SETTINGS_SHORTCUT_TO') : '+'
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</Fragment>
|
||||
))
|
||||
);
|
||||
};
|
||||
|
||||
export default Keys;
|
||||
2
src/components/ShortcutsGroup/Combos/Keys/index.ts
Normal file
2
src/components/ShortcutsGroup/Combos/Keys/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import Keys from './Keys';
|
||||
export default Keys;
|
||||
2
src/components/ShortcutsGroup/Combos/index.ts
Normal file
2
src/components/ShortcutsGroup/Combos/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import Combos from './Combos';
|
||||
export default Combos;
|
||||
44
src/components/ShortcutsGroup/ShortcutsGroup.less
Normal file
44
src/components/ShortcutsGroup/ShortcutsGroup.less
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/components/ShortcutsGroup/ShortcutsGroup.tsx
Normal file
38
src/components/ShortcutsGroup/ShortcutsGroup.tsx
Normal file
|
|
@ -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 (
|
||||
<div className={classNames(className, styles['shortcuts-group'])}>
|
||||
<div className={styles['title']}>
|
||||
{t(label)}
|
||||
</div>
|
||||
|
||||
<div className={styles['shortcuts']}>
|
||||
{
|
||||
shortcuts.map(({ name, label, combos }) => (
|
||||
<div className={styles['shortcut']} key={name}>
|
||||
<div className={styles['label']}>
|
||||
{t(label)}
|
||||
</div>
|
||||
<Combos combos={combos} />
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShortcutsGroup;
|
||||
2
src/components/ShortcutsGroup/index.ts
Normal file
2
src/components/ShortcutsGroup/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import ShortcutsGroup from './ShortcutsGroup';
|
||||
export default ShortcutsGroup;
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
@media only screen and (max-width: @minimum) {
|
||||
.disable-cell-items();
|
||||
}
|
||||
|
||||
@media @phone-portrait {
|
||||
.cell {
|
||||
.heading {
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
display: grid;
|
||||
}
|
||||
.compact-items();
|
||||
.disable-cell-items();
|
||||
}
|
||||
|
||||
.items {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.more {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-height: @xxsmall) and (orientation: landscape) {
|
||||
@media @phone-landscape {
|
||||
.cell {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.items {
|
||||
display: none;
|
||||
}
|
||||
.compact-items();
|
||||
.disable-cell-items();
|
||||
}
|
||||
|
||||
.more {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
width: 100%;
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
pointer-events: none;
|
||||
border-radius: calc(var(--border-radius) / 2);
|
||||
@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();
|
||||
}
|
||||
|
|
@ -45,6 +45,7 @@
|
|||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: 1px;
|
||||
grid-auto-rows: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -387,7 +387,7 @@ const Intro = ({ queryParams }) => {
|
|||
{
|
||||
state.form === SIGNUP_FORM ?
|
||||
<Button className={classnames(styles['form-button'], styles['login-form-button'])} onClick={switchFormOnClick}>
|
||||
<div className={classnames(styles['label'], styles['uppercase'])}>{t('LOG_IN')}</div>
|
||||
<div className={styles['label']}>{t('LOG_IN')}</div>
|
||||
</Button>
|
||||
:
|
||||
null
|
||||
|
|
@ -395,7 +395,7 @@ const Intro = ({ queryParams }) => {
|
|||
{
|
||||
state.form === LOGIN_FORM ?
|
||||
<Button className={classnames(styles['form-button'], styles['signup-form-button'])} onClick={switchFormOnClick}>
|
||||
<div className={classnames(styles['label'], styles['uppercase'])}>{t('SIGN_UP_EMAIL')}</div>
|
||||
<div className={styles['label']}>{t('SIGN_UP_EMAIL')}</div>
|
||||
</Button>
|
||||
:
|
||||
null
|
||||
|
|
@ -403,7 +403,7 @@ const Intro = ({ queryParams }) => {
|
|||
{
|
||||
state.form === SIGNUP_FORM ?
|
||||
<Button className={classnames(styles['form-button'], styles['guest-login-button'])} onClick={loginAsGuest}>
|
||||
<div className={classnames(styles['label'], styles['uppercase'])}>{t('GUEST_LOGIN')}</div>
|
||||
<div className={styles['label']}>{t('GUEST_LOGIN')}</div>
|
||||
</Button>
|
||||
:
|
||||
null
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ const SubtitlesMenu = React.memo((props) => {
|
|||
/>
|
||||
<Stepper
|
||||
className={styles['stepper']}
|
||||
label={'PLAYER_SUBTITLES_VERTICAL_POSIITON'}
|
||||
label={'PLAYER_SUBTITLES_VERTICAL_POSITION'}
|
||||
value={props.selectedSubtitlesTrackId ? props.subtitlesOffset : props.selectedExtraSubtitlesTrackId ? props.extraSubtitlesOffset : null}
|
||||
unit={'%'}
|
||||
step={1}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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<HTMLDivElement>((_, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const { grouped } = useShortcuts();
|
||||
|
||||
return (
|
||||
<Section ref={ref} label={'SETTINGS_NAV_SHORTCUTS'}>
|
||||
<Option label={'SETTINGS_SHORTCUT_PLAY_PAUSE'}>
|
||||
<div className={styles['shortcut-container']}>
|
||||
<kbd>{t('SETTINGS_SHORTCUT_SPACE')}</kbd>
|
||||
</div>
|
||||
</Option>
|
||||
<Option label={'SETTINGS_SHORTCUT_SEEK_FORWARD'}>
|
||||
<div className={styles['shortcut-container']}>
|
||||
<kbd>→</kbd>
|
||||
<div className={styles['label']}>{t('SETTINGS_SHORTCUT_OR')}</div>
|
||||
<kbd>⇧ {t('SETTINGS_SHORTCUT_SHIFT')}</kbd>
|
||||
<div className={styles['label']}>+</div>
|
||||
<kbd>→</kbd>
|
||||
</div>
|
||||
</Option>
|
||||
<Option label={'SETTINGS_SHORTCUT_SEEK_BACKWARD'}>
|
||||
<div className={styles['shortcut-container']}>
|
||||
<kbd>←</kbd>
|
||||
<div className={styles['label']}>{t('SETTINGS_SHORTCUT_OR')}</div>
|
||||
<kbd>⇧ {t('SETTINGS_SHORTCUT_SHIFT')}</kbd>
|
||||
<div className={styles['label']}>+</div>
|
||||
<kbd>←</kbd>
|
||||
</div>
|
||||
</Option>
|
||||
<Option label={'SETTINGS_SHORTCUT_VOLUME_UP'}>
|
||||
<div className={styles['shortcut-container']}>
|
||||
<kbd>↑</kbd>
|
||||
</div>
|
||||
</Option>
|
||||
<Option label={'SETTINGS_SHORTCUT_VOLUME_DOWN'}>
|
||||
<div className={styles['shortcut-container']}>
|
||||
<kbd>↓</kbd>
|
||||
</div>
|
||||
</Option>
|
||||
<Option label={'SETTINGS_SHORTCUT_MENU_SUBTITLES'}>
|
||||
<div className={styles['shortcut-container']}>
|
||||
<kbd>S</kbd>
|
||||
</div>
|
||||
</Option>
|
||||
<Option label={'SETTINGS_SHORTCUT_MENU_AUDIO'}>
|
||||
<div className={styles['shortcut-container']}>
|
||||
<kbd>A</kbd>
|
||||
</div>
|
||||
</Option>
|
||||
<Option label={'SETTINGS_SHORTCUT_MENU_INFO'}>
|
||||
<div className={styles['shortcut-container']}>
|
||||
<kbd>I</kbd>
|
||||
</div>
|
||||
</Option>
|
||||
<Option label={'SETTINGS_SHORTCUT_FULLSCREEN'}>
|
||||
<div className={styles['shortcut-container']}>
|
||||
<kbd>F</kbd>
|
||||
</div>
|
||||
</Option>
|
||||
<Option label={'SETTINGS_SHORTCUT_SUBTITLES_SIZE'}>
|
||||
<div className={styles['shortcut-container']}>
|
||||
<kbd>-</kbd>
|
||||
<div className={styles['label']}>{ t('SETTINGS_SHORTCUT_AND') }</div>
|
||||
<kbd>=</kbd>
|
||||
</div>
|
||||
</Option>
|
||||
<Option label={'SETTINGS_SHORTCUT_SUBTITLES_DELAY'}>
|
||||
<div className={styles['shortcut-container']}>
|
||||
<kbd>G</kbd>
|
||||
<div className={styles['label']}>{ t('SETTINGS_SHORTCUT_AND') }</div>
|
||||
<kbd>H</kbd>
|
||||
</div>
|
||||
</Option>
|
||||
<Option label={'SETTINGS_SHORTCUT_NAVIGATE_MENUS'}>
|
||||
<div className={styles['shortcut-container']}>
|
||||
<kbd>1</kbd>
|
||||
<div className={styles['label']}>{t('SETTINGS_SHORTCUT_TO')}</div>
|
||||
<kbd>6</kbd>
|
||||
</div>
|
||||
</Option>
|
||||
<Option label={'SETTINGS_SHORTCUT_GO_TO_SEARCH'}>
|
||||
<div className={styles['shortcut-container']}>
|
||||
<kbd>0</kbd>
|
||||
</div>
|
||||
</Option>
|
||||
<Option label={'SETTINGS_SHORTCUT_EXIT_BACK'}>
|
||||
<div className={styles['shortcut-container']}>
|
||||
<kbd>{t('SETTINGS_SHORTCUT_ESC')}</kbd>
|
||||
</div>
|
||||
</Option>
|
||||
{
|
||||
grouped.map(({ name, label, shortcuts }) => (
|
||||
<ShortcutsGroup
|
||||
key={name}
|
||||
className={styles['shortcuts-group']}
|
||||
label={label}
|
||||
shortcuts={shortcuts}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</Section>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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'}
|
||||
/>
|
||||
<div className={styles['actions']}>
|
||||
<Button className={styles['add']} onClick={onSumbit}>
|
||||
<Button className={styles['add']} onClick={onSubmit}>
|
||||
<Icon name={'checkmark'} className={styles['icon']} />
|
||||
</Button>
|
||||
<Button className={styles['cancel']} onClick={onCancel}>
|
||||
|
|
|
|||
2
src/types/models/Player.d.ts
vendored
2
src/types/models/Player.d.ts
vendored
|
|
@ -3,7 +3,7 @@ type LibraryItemPlayer = Pick<LibraryItem, '_id'> & {
|
|||
};
|
||||
|
||||
type VideoPlayer = Video & {
|
||||
upcomming: boolean,
|
||||
upcoming: boolean,
|
||||
watched: boolean,
|
||||
progress: boolean | null,
|
||||
scheduled: boolean,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": [ "ES2016", "DOM", "DOM.Iterable"],
|
||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||
"jsx": "react",
|
||||
"baseUrl": "./src",
|
||||
"outDir": "./dist",
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ const WorkboxPlugin = require('workbox-webpack-plugin');
|
|||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const WebpackPwaManifest = require('webpack-pwa-manifest');
|
||||
const pachageJson = require('./package.json');
|
||||
const packageJson = require('./package.json');
|
||||
|
||||
const COMMIT_HASH = execSync('git rev-parse HEAD').toString().trim();
|
||||
|
||||
|
|
@ -215,7 +215,7 @@ module.exports = (env, argv) => ({
|
|||
...env,
|
||||
SERVICE_WORKER_DISABLED: false,
|
||||
DEBUG: argv.mode !== 'production',
|
||||
VERSION: pachageJson.version,
|
||||
VERSION: packageJson.version,
|
||||
COMMIT_HASH
|
||||
}),
|
||||
new webpack.ProvidePlugin({
|
||||
|
|
|
|||
Loading…
Reference in a new issue