From f539afb4044110f55aa9c3a24fd29a56d3767851 Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Sat, 11 Oct 2025 00:39:44 +0200 Subject: [PATCH] Introduce "koration" duration api --- package.json | 1 + src/base/components/feedback/EmptyView.tsx | 2 +- .../feedback/EmptyViewAbsoluteCentered.tsx | 2 +- .../app-updates/hooks/useUpdateChecker.tsx | 7 ++--- src/features/backup/Backup.constants.ts | 8 +++++- .../global-search/screens/SearchAll.tsx | 5 ++-- src/features/manga/Manga.types.ts | 2 +- .../ReaderNavBarDesktopAutoScroll.tsx | 6 ++++- .../useReaderShowSettingPreviewOnChange.ts | 3 ++- .../reader/services/ReaderControls.ts | 6 ++++- .../hooks/useReaderHideCursorOnInactivity.ts | 3 ++- src/features/settings/Settings.constants.ts | 26 +++++++++---------- .../settings/screens/ServerSettings.tsx | 18 +++++-------- src/features/theme/services/ThemeCreator.ts | 2 +- src/index.tsx | 3 ++- src/lib/koration/Setup.ts | 12 +++++++++ src/lib/requests/client/GraphQLClient.ts | 7 ++--- tsconfig.json | 2 +- yarn.lock | 5 ++++ 19 files changed, 77 insertions(+), 43 deletions(-) create mode 100644 src/lib/koration/Setup.ts diff --git a/package.json b/package.json index 75cd1109..98a8fb07 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "i18next-http-backend": "3.0.2", "immer": "10.1.1", "jsonrepair": "3.13.0", + "koration": "0.1.0", "material-ui-popup-state": "5.3.6", "mui-nested-menu": "4.0.1", "node-vibrant": "4.0.3", diff --git a/src/base/components/feedback/EmptyView.tsx b/src/base/components/feedback/EmptyView.tsx index 634a0a8e..cdbfde9e 100644 --- a/src/base/components/feedback/EmptyView.tsx +++ b/src/base/components/feedback/EmptyView.tsx @@ -97,7 +97,7 @@ export function EmptyView({ message, messageExtra, retry, noFaces, sx }: EmptyVi textAlign: 'center', alignItems: 'center', justifyContent: 'center', - minWidth: '-webkit-fill-available', + minWidth: 'fill-available', maxWidth: '100%', minHeight: '100%', pointerEvents: 'none', diff --git a/src/base/components/feedback/EmptyViewAbsoluteCentered.tsx b/src/base/components/feedback/EmptyViewAbsoluteCentered.tsx index 8a635edc..0c8ce52f 100644 --- a/src/base/components/feedback/EmptyViewAbsoluteCentered.tsx +++ b/src/base/components/feedback/EmptyViewAbsoluteCentered.tsx @@ -14,7 +14,7 @@ export function EmptyViewAbsoluteCentered({ sx, ...emptyViewProps }: EmptyViewPr {...emptyViewProps} sx={{ position: 'absolute', - minHeight: '-webkit-fill-available', + minHeight: 'fill-available', ...sx, }} /> diff --git a/src/features/app-updates/hooks/useUpdateChecker.tsx b/src/features/app-updates/hooks/useUpdateChecker.tsx index fea7eda3..d8214f09 100644 --- a/src/features/app-updates/hooks/useUpdateChecker.tsx +++ b/src/features/app-updates/hooks/useUpdateChecker.tsx @@ -7,11 +7,12 @@ */ import { useCallback, useEffect, useMemo } from 'react'; -import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; +import { d } from 'koration'; import { useLocalStorage } from '@/base/hooks/useStorage.tsx'; +import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; -const UPDATE_CHECK_INTERVAL = 1000 * 60 * 60; // 1 hour -const UPDATE_REMINDER_THRESHOLD = 1000 * 60 * 60; // 1 hour +const UPDATE_CHECK_INTERVAL = d(1).hours.inWholeMilliseconds; +const UPDATE_REMINDER_THRESHOLD = d(1).hours.inWholeMilliseconds; export const useUpdateChecker = ( storageKey: string, diff --git a/src/features/backup/Backup.constants.ts b/src/features/backup/Backup.constants.ts index d841e50b..ef4469e9 100644 --- a/src/features/backup/Backup.constants.ts +++ b/src/features/backup/Backup.constants.ts @@ -27,6 +27,12 @@ export const BACKUP_FLAG_GROUP_TO_TRANSLATION: Record = { - [BackupFlagGroup.LIBRARY]: ['includeManga', 'includeChapters', 'includeTracking', 'includeHistory', 'includeCategories'], + [BackupFlagGroup.LIBRARY]: [ + 'includeManga', + 'includeChapters', + 'includeTracking', + 'includeHistory', + 'includeCategories', + ], [BackupFlagGroup.SETTINGS]: ['includeClientData', 'includeServerSettings'], }; diff --git a/src/features/global-search/screens/SearchAll.tsx b/src/features/global-search/screens/SearchAll.tsx index ef9b01ef..eb15f47f 100644 --- a/src/features/global-search/screens/SearchAll.tsx +++ b/src/features/global-search/screens/SearchAll.tsx @@ -20,8 +20,9 @@ import PushPinIcon from '@mui/icons-material/PushPin'; import DoneAllIcon from '@mui/icons-material/DoneAll'; import FilterListIcon from '@mui/icons-material/FilterList'; import { useElementSize } from '@mantine/hooks'; -import IconButton from '@mui/material/IconButton'; // ms +import IconButton from '@mui/material/IconButton'; import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; +import { d } from 'koration'; import { requestManager } from '@/lib/requests/RequestManager.ts'; import { AppbarSearch } from '@/base/components/AppbarSearch.tsx'; import { useDebounce } from '@/base/hooks/useDebounce.ts'; @@ -110,7 +111,7 @@ const compareSourcesBySearchResult = ( return 0; }; -const TRIGGER_SEARCH_THRESHOLD = 1000; // ms +const TRIGGER_SEARCH_THRESHOLD = d(1).seconds.inWholeMilliseconds; const SourceSearchPreview = React.memo( ({ diff --git a/src/features/manga/Manga.types.ts b/src/features/manga/Manga.types.ts index 58cf8d44..6097f7fa 100644 --- a/src/features/manga/Manga.types.ts +++ b/src/features/manga/Manga.types.ts @@ -6,7 +6,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { LongPressPointerHandlers, LongPressResult } from 'use-long-press/lib/use-long-press.types'; +import { LongPressPointerHandlers, LongPressResult } from 'node_modules/use-long-press/lib/use-long-press.types'; import { PopupState } from 'material-ui-popup-state/hooks'; import type { JSX } from 'react'; import { SelectableCollectionReturnType } from '@/base/collection/hooks/useSelectableCollection.ts'; diff --git a/src/features/reader/auto-scroll/settings/quick-setting/ReaderNavBarDesktopAutoScroll.tsx b/src/features/reader/auto-scroll/settings/quick-setting/ReaderNavBarDesktopAutoScroll.tsx index 8a02e034..a2b58cdb 100644 --- a/src/features/reader/auto-scroll/settings/quick-setting/ReaderNavBarDesktopAutoScroll.tsx +++ b/src/features/reader/auto-scroll/settings/quick-setting/ReaderNavBarDesktopAutoScroll.tsx @@ -14,6 +14,7 @@ import Stack from '@mui/material/Stack'; import TextField from '@mui/material/TextField'; import InputAdornment from '@mui/material/InputAdornment'; import { useRef } from 'react'; +import { d } from 'koration'; import { IReaderSettings } from '@/features/reader/Reader.types.ts'; import { AUTO_SCROLL_SPEED } from '@/features/reader/settings/ReaderSettings.constants.tsx'; import { coerceIn } from '@/lib/HelperFunctions.ts'; @@ -62,7 +63,10 @@ export const ReaderNavBarDesktopAutoScroll = ({ setAutoScroll({ ...autoScroll, value }, false); clearTimeout(updateTimeout.current); - updateTimeout.current = setTimeout(() => setAutoScroll({ ...autoScroll, value }, true), 1000); + updateTimeout.current = setTimeout( + () => setAutoScroll({ ...autoScroll, value }, true), + d(1).seconds.inWholeMilliseconds, + ); }} slotProps={{ input: { diff --git a/src/features/reader/hooks/useReaderShowSettingPreviewOnChange.ts b/src/features/reader/hooks/useReaderShowSettingPreviewOnChange.ts index 406c22ae..f3951964 100644 --- a/src/features/reader/hooks/useReaderShowSettingPreviewOnChange.ts +++ b/src/features/reader/hooks/useReaderShowSettingPreviewOnChange.ts @@ -8,13 +8,14 @@ import { useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; +import { d } from 'koration'; import { IReaderSettings, IReaderSettingsWithDefaultFlag } from '@/features/reader/Reader.types.ts'; import { makeToast } from '@/base/utils/Toast.ts'; import { READING_MODE_VALUE_TO_DISPLAY_DATA } from '@/features/reader/settings/ReaderSettings.constants.tsx'; import { TReaderTapZoneContext } from '@/features/reader/tap-zones/TapZoneLayout.types.ts'; import { TranslationKey } from '@/base/Base.types.ts'; -const HIDE_PREVIEW_TIMEOUT = 5000; +const HIDE_PREVIEW_TIMEOUT = d(5).seconds.inWholeMilliseconds; export const useReaderShowSettingPreviewOnChange = ( isLoading: boolean, diff --git a/src/features/reader/services/ReaderControls.ts b/src/features/reader/services/ReaderControls.ts index 2e7e5818..96abfe31 100644 --- a/src/features/reader/services/ReaderControls.ts +++ b/src/features/reader/services/ReaderControls.ts @@ -9,6 +9,7 @@ import { MutableRefObject, RefObject, useCallback, useEffect } from 'react'; import { Direction } from '@mui/material/styles'; import { t as translate } from 'i18next'; +import { d } from 'koration'; import { getNextIndexFromPage, getNextPageIndex, @@ -473,7 +474,10 @@ export class ReaderControls { clearTimeout(ReaderControls.updateCurrentPageTimeout); if (debounceChapterUpdate) { - ReaderControls.updateCurrentPageTimeout = setTimeout(handleCurrentPageIndexChange, 1000); + ReaderControls.updateCurrentPageTimeout = setTimeout( + handleCurrentPageIndexChange, + d(1).seconds.inWholeMilliseconds, + ); return; } diff --git a/src/features/reader/viewer/hooks/useReaderHideCursorOnInactivity.ts b/src/features/reader/viewer/hooks/useReaderHideCursorOnInactivity.ts index e7cd9b83..fc1e6249 100644 --- a/src/features/reader/viewer/hooks/useReaderHideCursorOnInactivity.ts +++ b/src/features/reader/viewer/hooks/useReaderHideCursorOnInactivity.ts @@ -7,6 +7,7 @@ */ import { MutableRefObject, useEffect, useRef } from 'react'; +import { d } from 'koration'; export const useReaderHideCursorOnInactivity = (scrollElementRef: MutableRefObject) => { const mouseInactiveTimeout = useRef(undefined); @@ -26,7 +27,7 @@ export const useReaderHideCursorOnInactivity = (scrollElementRef: MutableRefObje clearTimeout(mouseInactiveTimeout.current); mouseInactiveTimeout.current = setTimeout(() => { setCursorVisibility(false); - }, 5000); + }, d(5).seconds.inWholeMilliseconds); }; handleMouseMove(); diff --git a/src/features/settings/Settings.constants.ts b/src/features/settings/Settings.constants.ts index d48ea894..00ffc182 100644 --- a/src/features/settings/Settings.constants.ts +++ b/src/features/settings/Settings.constants.ts @@ -6,7 +6,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import dayjs from 'dayjs'; +import { d } from 'koration'; import { DEFAULT_DEVICE } from '@/features/device/services/Device.ts'; import { DEFAULT_SORT_SETTINGS } from '@/features/migration/Migration.constants.ts'; import { GlobalUpdateSkipEntriesSettings, MetadataServerSettings } from '@/features/settings/Settings.types.ts'; @@ -170,9 +170,9 @@ export const WEB_UI_INTERFACE_SELECT_VALUES: SelectSettingValue[ ); export const GLOBAL_UPDATE_INTERVAL = { - default: 12, - min: 6, - max: 24 * 7 * 4, // 1 month + default: d(12).hours.inWholeHours, + min: d(6).hours.inWholeHours, + max: d(1).months.inWholeHours, }; export const GLOBAL_UPDATE_SKIP_ENTRIES_TO_TRANSLATION: { @@ -184,21 +184,21 @@ export const GLOBAL_UPDATE_SKIP_ENTRIES_TO_TRANSLATION: { }; export const WEB_UI_UPDATE_INTERVAL = { - default: 23, - min: 1, - max: 23, + default: d(23).hours.inWholeHours, + min: d(1).hours.inWholeHours, + max: d(23).hours.inWholeHours, }; export const JWT_ACCESS_TOKEN_EXPIRY = { - default: dayjs.duration(5, 'minute').asMinutes(), - min: dayjs.duration(1, 'minute').asMinutes(), - max: dayjs.duration(4, 'hour').asMinutes(), + default: d(5).minutes.inWholeMinutes, + min: d(1).minutes.inWholeMinutes, + max: d(4).hours.inWholeMinutes, }; export const JWT_REFRESH_TOKEN_EXPIRY = { - default: dayjs.duration(60, 'day').asDays(), - min: dayjs.duration(1, 'day').asDays(), - max: dayjs.duration(1, 'year').asDays(), + default: d(60).days.inWholeDays, + min: d(1).days.inWholeDays, + max: d(1).years.inWholeDays, }; export const KOREADER_SYNC_PERCENTAGE_TOLERANCE = { diff --git a/src/features/settings/screens/ServerSettings.tsx b/src/features/settings/screens/ServerSettings.tsx index 59ec6804..3c42c98b 100644 --- a/src/features/settings/screens/ServerSettings.tsx +++ b/src/features/settings/screens/ServerSettings.tsx @@ -15,7 +15,7 @@ import ListItemText from '@mui/material/ListItemText'; import Switch from '@mui/material/Switch'; import ListSubheader from '@mui/material/ListSubheader'; import { t as translate } from 'i18next'; -import dayjs from 'dayjs'; +import { d } from 'koration'; import { requestManager } from '@/lib/requests/RequestManager.ts'; import { TextSetting } from '@/base/components/settings/text/TextSetting.tsx'; import { NumberSetting } from '@/base/components/settings/NumberSetting.tsx'; @@ -279,28 +279,24 @@ export const ServerSettings = () => { /> - updateSetting('jwtTokenExpiry', dayjs.duration(expiry, 'minute').toISOString()) - } + handleUpdate={(expiry) => updateSetting('jwtTokenExpiry', d(expiry).minutes.toISOString())} showSlider /> - updateSetting('jwtRefreshExpiry', dayjs.duration(expiry, 'day').toISOString()) - } + handleUpdate={(expiry) => updateSetting('jwtRefreshExpiry', d(expiry).days.toISOString())} showSlider /> diff --git a/src/features/theme/services/ThemeCreator.ts b/src/features/theme/services/ThemeCreator.ts index 66d8b6a5..04df313e 100644 --- a/src/features/theme/services/ThemeCreator.ts +++ b/src/features/theme/services/ThemeCreator.ts @@ -20,8 +20,8 @@ import { useCallback } from 'react'; // eslint-disable-next-line import/no-extraneous-dependencies,no-restricted-imports import { deepmerge } from '@mui/utils'; // eslint-disable-next-line no-restricted-imports -import { PaletteBackgroundChannel } from '@mui/material/styles/createThemeWithVars'; import { Palette } from '@vibrant/color'; +import { PaletteBackgroundChannel } from 'node_modules/@mui/material/esm/styles/createThemeWithVars'; import { MediaQuery } from '@/base/utils/MediaQuery.tsx'; import { AppTheme } from '@/features/theme/services/AppThemes.ts'; import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; diff --git a/src/index.tsx b/src/index.tsx index 0c8d55c0..4abf8ce3 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -9,11 +9,12 @@ import '@/polyfill.manual'; import '@/i18n'; import '@/lib/dayjs/Setup.ts'; +import '@/lib/koration/Setup.ts'; import '@/index.css'; import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; -import { App } from '@/App'; import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; +import { App } from '@/App'; if ('serviceWorker' in navigator) { navigator.serviceWorker.ready.then((registration) => { diff --git a/src/lib/koration/Setup.ts b/src/lib/koration/Setup.ts new file mode 100644 index 00000000..d3a1c958 --- /dev/null +++ b/src/lib/koration/Setup.ts @@ -0,0 +1,12 @@ +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +import { setDurationAdapter } from 'koration'; +import { DayjsDurationAdapter } from 'koration/adapters/DayjsDurationAdapter'; + +setDurationAdapter(new DayjsDurationAdapter()); diff --git a/src/lib/requests/client/GraphQLClient.ts b/src/lib/requests/client/GraphQLClient.ts index 9316a117..923efdd0 100644 --- a/src/lib/requests/client/GraphQLClient.ts +++ b/src/lib/requests/client/GraphQLClient.ts @@ -24,6 +24,7 @@ import { Client, createClient } from 'graphql-ws'; import { getMainDefinition } from '@apollo/client/utilities'; import { TypePolicies } from '@apollo/client/cache'; import { removeTypenameFromVariables } from '@apollo/client/link/remove-typename'; +import { d } from 'koration'; import { BaseClient } from '@/lib/requests/client/BaseClient.ts'; import { StrictTypedTypePolicies } from '@/lib/graphql/generated/apollo-helpers.ts'; import { AuthManager } from '@/features/authentication/AuthManager.ts'; @@ -271,7 +272,7 @@ export class GraphQLClient extends BaseClient< } private createWSClient(lazy: boolean = true): void { - const heartbeatInterval = 1000 * 20; + const heartbeatInterval = d(20).seconds.inWholeMilliseconds; this.wsClient = createClient({ lazy, @@ -280,7 +281,7 @@ export class GraphQLClient extends BaseClient< retryAttempts: Number.MAX_SAFE_INTEGER, shouldRetry: () => true, retryWait: async (retries) => { - const delay = Math.min(1000 * 2 ** retries, heartbeatInterval); + const delay = Math.min(d(1).seconds.inWholeMilliseconds * 2 ** retries, heartbeatInterval); return new Promise((resolve) => { setTimeout(resolve, delay); @@ -301,7 +302,7 @@ export class GraphQLClient extends BaseClient< lastHeartbeat = Date.now(); }); - const checkHeartbeatInterval = heartbeatInterval + 1000 * 30; + const checkHeartbeatInterval = heartbeatInterval + d(30).seconds.inWholeMilliseconds; clearInterval(this.wsClientAliveCheckInterval); this.wsClientAliveCheckInterval = setInterval(() => { const isHeartbeatMissing = Date.now() - lastHeartbeat > checkHeartbeatInterval * 1.1; diff --git a/tsconfig.json b/tsconfig.json index 2c2d0021..46fd7e08 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,7 +19,7 @@ /* Bundler mode */ "esModuleInterop": true, "allowSyntheticDefaultImports": true, - "moduleResolution": "node", + "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, diff --git a/yarn.lock b/yarn.lock index 1bc41ea9..2b222df6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6635,6 +6635,11 @@ keyv@^4.5.3: dependencies: json-buffer "3.0.1" +koration@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/koration/-/koration-0.1.0.tgz#149e6021d14f2e8e0034da35de11df4f2768da19" + integrity sha512-Ld+r9PRXb4wFCbvz3B4Qoundnu3VaJ9AnWaorK3sqWLWZWSW4YF0vxfujl4zZniUlISYao5L8UpZ4IHHwOVf1w== + language-subtag-registry@^0.3.20: version "0.3.22" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d"