From ee93062d2a4337462fecaf40ac37d945797a3fc6 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 15 Apr 2026 01:53:29 +0200 Subject: [PATCH 1/3] feat: add interface size setting for shell --- src/App/App.js | 8 +++ src/common/CONSTANTS.js | 2 + src/common/useShell.ts | 9 +++ src/components/Scale/Scale.less | 67 +++++++++++++++++++ src/components/Scale/Scale.tsx | 57 ++++++++++++++++ src/components/Scale/index.ts | 2 + src/components/index.ts | 2 + src/routes/Settings/Interface/Interface.tsx | 9 ++- .../Settings/Interface/useInterfaceOptions.ts | 22 ++++++ src/types/models/Ctx.d.ts | 1 + 10 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 src/components/Scale/Scale.less create mode 100644 src/components/Scale/Scale.tsx create mode 100644 src/components/Scale/index.ts diff --git a/src/App/App.js b/src/App/App.js index 7a1383dc4..e41239b7c 100644 --- a/src/App/App.js +++ b/src/App/App.js @@ -141,6 +141,10 @@ const App = () => { i18n.changeLanguage(args.settings.interfaceLanguage); } + if (args && args.settings && typeof args.settings.interfaceScale === 'number') { + shell.scaleInterface(args.settings.interfaceScale); + } + if (args?.settings?.quitOnClose && shell.windowClosed) { shell.send('quit'); } @@ -154,6 +158,10 @@ 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 (state?.profile?.settings?.quitOnClose && shell.windowClosed) { shell.send('quit'); } diff --git a/src/common/CONSTANTS.js b/src/common/CONSTANTS.js index 104d24a44..188c63297 100644 --- a/src/common/CONSTANTS.js +++ b/src/common/CONSTANTS.js @@ -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 = { 25: 13, 50: 14, 75: 15, 100: 16, 125: 17, 150: 18, 175: 19 }; 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, diff --git a/src/common/useShell.ts b/src/common/useShell.ts index 0471e38ab..da4b1a81a 100644 --- a/src/common/useShell.ts +++ b/src/common/useShell.ts @@ -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; 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, }; diff --git a/src/components/Scale/Scale.less b/src/components/Scale/Scale.less new file mode 100644 index 000000000..6873ef27f --- /dev/null +++ b/src/components/Scale/Scale.less @@ -0,0 +1,67 @@ +.scale { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.25rem; + width: 100%; + + .ticks { + display: flex; + justify-content: space-between; + width: 100%; + + .label { + display: flex; + justify-content: center; + font-size: 0.8rem; + width: 1.75rem; + 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 0.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); + } + } +} \ No newline at end of file diff --git a/src/components/Scale/Scale.tsx b/src/components/Scale/Scale.tsx new file mode 100644 index 000000000..fcd7d54df --- /dev/null +++ b/src/components/Scale/Scale.tsx @@ -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 | TouchEvent) => { + const { value } = target as HTMLInputElement; + onChange(parseInt(value)); + }; + + const onClick = ({ target }: MouseEvent) => { + const { dataset } = target as HTMLInputElement; + onChange(parseInt(dataset.value!)); + }; + + return ( +
+
+ { + options.map((tick) => ( +
+ {tick} +
+ )) + } +
+ +
+ ); +}; + +export default Scale; diff --git a/src/components/Scale/index.ts b/src/components/Scale/index.ts new file mode 100644 index 000000000..348214973 --- /dev/null +++ b/src/components/Scale/index.ts @@ -0,0 +1,2 @@ +import Scale from './Scale'; +export default Scale; diff --git a/src/components/index.ts b/src/components/index.ts index 75400b0dd..8a67b7b5c 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -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, diff --git a/src/routes/Settings/Interface/Interface.tsx b/src/routes/Settings/Interface/Interface.tsx index a4b429a56..038ec4304 100644 --- a/src/routes/Settings/Interface/Interface.tsx +++ b/src/routes/Settings/Interface/Interface.tsx @@ -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(({ profile }: Props, ref) => const { interfaceLanguageSelect, + interfaceSize, quitOnCloseToggle, escExitFullscreenToggle, hideSpoilersToggle, @@ -26,6 +27,12 @@ const Interface = forwardRef(({ profile }: Props, ref) => {...interfaceLanguageSelect} /> + { + shell.active && + + } { shell.active &&