This commit is contained in:
Tim 2026-05-01 17:06:04 +00:00 committed by GitHub
commit 44979a5810
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 174 additions and 1 deletions

View file

@ -149,6 +149,9 @@ const App = () => {
i18n.changeLanguage(args.settings.interfaceLanguage);
}
if (args && args.settings && typeof args.settings.interfaceScale === 'number') {
shell.scaleInterface(args.settings.interfaceScale);
}
if (args?.settings?.gamepadSupport !== undefined) {
setGamepadSupportEnabled(args.settings.gamepadSupport);
}
@ -166,6 +169,9 @@ const App = () => {
i18n.changeLanguage(state.profile.settings.interfaceLanguage);
}
if (state && state.profile && state.profile.settings && typeof state.profile.settings.interfaceScale === 'number') {
shell.scaleInterface(state.profile.settings.interfaceScale);
}
if (typeof state.profile.settings.gamepadSupport === 'boolean') {
setGamepadSupportEnabled(state.profile.settings.gamepadSupport);
}

View file

@ -8,6 +8,7 @@ const SUBTITLES_SIZES = [75, 100, 125, 150, 175, 200, 250];
const SUBTITLES_FONTS = ['PlusJakartaSans', 'Arial', 'Halvetica', 'Times New Roman', 'Verdana', 'Courier', 'Lucida Console', 'sans-serif', 'serif', 'monospace'];
const SEEK_TIME_DURATIONS = [3000, 5000, 10000, 15000, 20000, 30000];
const NEXT_VIDEO_POPUP_DURATIONS = [0, 5000, 10000, 15000, 20000, 25000, 30000, 35000, 40000, 45000, 50000, 55000, 60000, 65000, 70000, 75000, 80000, 85000, 90000];
const INTERFACE_SCALES = { 75: 12, 100: 16, 125: 20, 150: 24, 175: 28 };
const CATALOG_PREVIEW_SIZE = 10;
const CATALOG_PAGE_SIZE = 100;
const NONE_EXTRA_VALUE = 'None';
@ -129,6 +130,7 @@ module.exports = {
SUBTITLES_FONTS,
SEEK_TIME_DURATIONS,
NEXT_VIDEO_POPUP_DURATIONS,
INTERFACE_SCALES,
CATALOG_PREVIEW_SIZE,
CATALOG_PAGE_SIZE,
NONE_EXTRA_VALUE,

View file

@ -1,6 +1,8 @@
import { useEffect, useState } from 'react';
import EventEmitter from 'eventemitter3';
import { INTERFACE_SCALES } from './CONSTANTS';
const UI_SCALES = INTERFACE_SCALES as Record<number, number>;
const SHELL_EVENT_OBJECT = 'transport';
const transport = globalThis?.chrome?.webview;
const events = new EventEmitter();
@ -55,6 +57,12 @@ const useShell = () => {
}
};
const scaleInterface = (value: number) => {
const root = document.documentElement;
const size = UI_SCALES[value];
root.style.setProperty('font-size', `${size}px`);
};
useEffect(() => {
const onWindowVisibilityChanged = (data: WindowVisibility) => {
setWindowClosed(data.visible === false && data.visibility === 0);
@ -97,6 +105,7 @@ const useShell = () => {
send,
on,
off,
scaleInterface,
windowClosed,
windowHidden,
};

View file

@ -0,0 +1,65 @@
.scale {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.25rem;
width: 100%;
.ticks {
display: grid;
width: 100%;
.label {
display: flex;
justify-content: center;
font-size: 0.8rem;
color: var(--primary-foreground-color);
opacity: 0.6;
cursor: pointer;
&.active {
font-weight: 700;
opacity: 1;
}
}
}
input {
appearance: none;
height: 1.5rem;
width: 100%;
outline: none;
padding: 0 1.5rem;
cursor: pointer;
&::-webkit-slider-runnable-track {
appearance: none;
height: 0.25rem;
border-radius: 1rem;
background: var(--overlay-color);
}
&::-webkit-slider-thumb {
appearance: none;
width: 0.5rem;
height: 1rem;
margin-top: -0.375rem;
border-radius: 1rem;
background: var(--primary-accent-color);
}
&::-moz-range-track {
border-radius: 1rem;
background: var(--overlay-color);
}
&::-moz-range-thumb {
width: 0.5rem;
height: 1rem;
border-radius: 1rem;
border: none;
background: var(--primary-accent-color);
}
}
}

View file

@ -0,0 +1,57 @@
import React, { MouseEvent, TouchEvent } from 'react';
import classNames from 'classnames';
import styles from './Scale.less';
type Props = {
min: number,
max: number,
step: number,
value: number,
options: number[],
tabIndex?: number,
onChange: (value: number) => void,
};
const Scale = ({ min, max, step, options, value, tabIndex, onChange }: Props) => {
const onInputChange = ({ target }: MouseEvent<HTMLInputElement> | TouchEvent<HTMLInputElement>) => {
const { value } = target as HTMLInputElement;
onChange(parseInt(value));
};
const onClick = ({ target }: MouseEvent<HTMLDivElement>) => {
const { dataset } = target as HTMLInputElement;
onChange(parseInt(dataset.value!));
};
return (
<div className={styles['scale']}>
<div className={styles['ticks']} style={{ gridTemplateColumns: `repeat(${options.length}, 1fr)` }}>
{
options.map((tick) => (
<div
className={classNames(styles['label'], { [styles['active']]: value === tick })}
key={tick}
data-value={tick}
onClick={onClick}
>
{tick}%
</div>
))
}
</div>
<input
type={'range'}
key={value}
tabIndex={tabIndex}
defaultValue={value}
min={min}
max={max}
step={step}
onMouseUp={onInputChange}
onTouchEnd={onInputChange}
/>
</div>
);
};
export default Scale;

View file

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

View file

@ -6,6 +6,7 @@ import Chips from './Chips';
import ColorInput from './ColorInput';
import ContextMenu from './ContextMenu';
import ContinueWatchingItem from './ContinueWatchingItem';
import Scale from './Scale';
import DelayedRenderer from './DelayedRenderer';
import EventModal from './EventModal';
import HorizontalScroll from './HorizontalScroll';
@ -41,6 +42,7 @@ export {
ColorInput,
ContextMenu,
ContinueWatchingItem,
Scale,
DelayedRenderer,
EventModal,
HorizontalScroll,

View file

@ -1,6 +1,6 @@
import React, { forwardRef } from 'react';
import { useServices } from 'stremio/services';
import { MultiselectMenu, Toggle } from 'stremio/components';
import { Scale, MultiselectMenu, Toggle } from 'stremio/components';
import { Section, Option } from '../components';
import useInterfaceOptions from './useInterfaceOptions';
@ -13,6 +13,7 @@ const Interface = forwardRef<HTMLDivElement, Props>(({ profile }: Props, ref) =>
const {
interfaceLanguageSelect,
interfaceSize,
quitOnCloseToggle,
escExitFullscreenToggle,
hideSpoilersToggle,
@ -27,6 +28,12 @@ const Interface = forwardRef<HTMLDivElement, Props>(({ profile }: Props, ref) =>
{...interfaceLanguageSelect}
/>
</Option>
{
shell.active &&
<Option label={'SETTINGS_UI_SIZE'}>
<Scale tabIndex={-1} {...interfaceSize} />
</Option>
}
{
shell.active &&
<Option label={'SETTINGS_QUIT_ON_CLOSE'}>

View file

@ -1,5 +1,6 @@
import { useMemo } from 'react';
import { interfaceLanguages, useLanguageSorting } from 'stremio/common';
import { INTERFACE_SCALES } from 'stremio/common/CONSTANTS';
import { useServices } from 'stremio/services';
const useInterfaceOptions = (profile: Profile) => {
@ -33,6 +34,26 @@ const useInterfaceOptions = (profile: Profile) => {
}
}), [profile.settings, sortedOptions]);
const interfaceSize = useMemo(() => ({
min: 75,
max: 175,
step: 25,
value: profile.settings.interfaceScale,
options: Object.keys(INTERFACE_SCALES).map((value) => parseInt(value)),
onChange: (value: number) => {
core.transport.dispatch({
action: 'Ctx',
args: {
action: 'UpdateSettings',
args: {
...profile.settings,
interfaceScale: value
}
}
});
}
}), [profile.settings]);
const escExitFullscreenToggle = useMemo(() => ({
checked: profile.settings.escExitFullscreen,
onClick: () => {
@ -99,6 +120,7 @@ const useInterfaceOptions = (profile: Profile) => {
return {
interfaceLanguageSelect,
interfaceSize,
escExitFullscreenToggle,
quitOnCloseToggle,
hideSpoilersToggle,

View file

@ -22,6 +22,7 @@ type Settings = {
videoMode: string | null,
escExitFullscreen: boolean,
interfaceLanguage: string,
interfaceScale: number,
quitOnClose: boolean,
hideSpoilers: boolean,
gamepadSupport: boolean,