Files
suwayomi-material-you-webui/src/features/reader/hotkeys/ReaderHotkeys.tsx

192 lines
7.1 KiB
TypeScript
Raw Normal View History

2024-11-03 03:09:00 +01:00
/*
* 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 { useHotkeys as useHotKeysHook, useHotkeysContext } from 'react-hotkeys-hook';
import { useEffect } from 'react';
2025-08-15 22:02:58 +02:00
import { HOTKEY_SCOPES } from '@/features/hotkeys/Hotkeys.constants.ts';
import { ReaderService } from '@/features/reader/services/ReaderService.ts';
2025-08-16 02:02:13 +02:00
import { IReaderSettings, ReaderHotkey } from '@/features/reader/Reader.types.ts';
import { getNextRotationValue } from '@/base/utils/ValueRotationButton.utils.ts';
2024-11-03 03:09:00 +01:00
import {
AUTO_SCROLL_SPEED,
2024-12-27 00:30:19 +01:00
CONTINUOUS_READING_MODE_TO_SCROLL_DIRECTION,
2024-11-03 03:09:00 +01:00
READER_PAGE_SCALE_MODE_VALUES,
2024-12-17 16:27:20 +01:00
READING_DIRECTION_VALUES,
2024-12-17 16:23:09 +01:00
READING_MODE_VALUES,
2025-08-16 02:02:13 +02:00
} from '@/features/reader/settings/ReaderSettings.constants.tsx';
2025-08-15 22:02:58 +02:00
import { HotkeyScope } from '@/features/hotkeys/Hotkeys.types.ts';
import { ReaderControls } from '@/features/reader/services/ReaderControls.ts';
import { ScrollOffset } from '@/base/Base.types.ts';
2025-08-15 22:02:58 +02:00
import { getOptionForDirection } from '@/features/theme/services/ThemeCreator.ts';
import {
getReaderAutoScrollStore,
getReaderOverlayStore,
getReaderSettingsStore,
useReaderSettingsStore,
} from '@/features/reader/stores/ReaderStore.ts';
2024-11-03 03:09:00 +01:00
const useHotkeys = (...args: Parameters<typeof useHotKeysHook>): ReturnType<typeof useHotKeysHook> => {
const [keys, callback, options, dependencies] = args;
return useHotKeysHook(keys, callback, { preventDefault: true, ...options, ...HOTKEY_SCOPES.reader }, dependencies);
2024-11-03 03:09:00 +01:00
};
2024-12-11 20:48:05 +01:00
const updateSettingCycleThrough = <Setting extends keyof IReaderSettings>(
setting: Setting,
value: IReaderSettings[Setting],
values: IReaderSettings[Setting][],
isDefault: boolean,
isDefaultable: boolean,
) => {
if (isDefault) {
ReaderService.updateSetting(setting, values[0]);
2024-12-11 20:48:05 +01:00
return;
}
const nextValue = getNextRotationValue(values.indexOf(value), values, isDefaultable);
const isDefaultNextValue = nextValue === undefined;
if (isDefaultNextValue) {
ReaderService.deleteSetting(setting);
2024-12-11 20:48:05 +01:00
return;
}
ReaderService.updateSetting(setting, nextValue);
2024-12-11 20:48:05 +01:00
};
2024-11-03 03:09:00 +01:00
export const ReaderHotkeys = ({
scrollElementRef,
}: {
scrollElementRef: React.MutableRefObject<HTMLElement | null>;
}) => {
const readerThemeDirection = ReaderService.useGetThemeDirection();
2024-11-03 03:09:00 +01:00
const { enableScope, disableScope } = useHotkeysContext();
const { hotkeys } = useReaderSettingsStore((state) => ({
2026-02-21 17:33:13 +01:00
hotkeys: state.hotkeys,
}));
2025-03-27 01:50:36 +01:00
const exitReader = ReaderService.useExit();
2024-11-03 03:09:00 +01:00
2025-09-20 19:56:31 +02:00
useHotkeys(hotkeys[ReaderHotkey.PREVIOUS_PAGE], () => ReaderControls.openPage('previous'));
useHotkeys(hotkeys[ReaderHotkey.NEXT_PAGE], () => ReaderControls.openPage('next'));
useHotkeys(hotkeys[ReaderHotkey.SCROLL_BACKWARD], () => {
const autoScroll = getReaderAutoScrollStore();
const { readingMode, readingDirection, scrollAmount } = getReaderSettingsStore();
if (autoScroll.isActive) {
autoScroll.setDirection(ScrollOffset.BACKWARD);
return;
}
if (!scrollElementRef.current) {
return;
}
ReaderControls.scroll(
ScrollOffset.BACKWARD,
CONTINUOUS_READING_MODE_TO_SCROLL_DIRECTION[readingMode.value],
readingMode.value,
readingDirection.value,
scrollElementRef.current,
scrollAmount,
);
});
useHotkeys(hotkeys[ReaderHotkey.SCROLL_FORWARD], () => {
const autoScroll = getReaderAutoScrollStore();
const { readingMode, readingDirection, scrollAmount } = getReaderSettingsStore();
if (autoScroll.isActive) {
autoScroll.setDirection(ScrollOffset.FORWARD);
return;
}
if (!scrollElementRef.current) {
return;
}
ReaderControls.scroll(
ScrollOffset.FORWARD,
CONTINUOUS_READING_MODE_TO_SCROLL_DIRECTION[readingMode.value],
readingMode.value,
readingDirection.value,
scrollElementRef.current,
scrollAmount,
);
});
useHotkeys(
hotkeys[ReaderHotkey.PREVIOUS_CHAPTER],
2025-09-20 19:51:26 +02:00
() => ReaderControls.openChapter(getOptionForDirection('previous', 'next', readerThemeDirection)),
[readerThemeDirection],
);
useHotkeys(
hotkeys[ReaderHotkey.NEXT_CHAPTER],
2025-09-20 19:51:26 +02:00
() => ReaderControls.openChapter(getOptionForDirection('next', 'previous', readerThemeDirection)),
[readerThemeDirection],
);
useHotkeys(hotkeys[ReaderHotkey.TOGGLE_MENU], () =>
getReaderOverlayStore().setIsVisible(!getReaderOverlayStore().isVisible),
);
2024-11-03 03:09:00 +01:00
useHotkeys(
hotkeys[ReaderHotkey.CYCLE_SCALE_TYPE],
() => {
2024-12-11 20:48:05 +01:00
updateSettingCycleThrough(
'pageScaleMode',
getReaderSettingsStore().pageScaleMode.value,
2024-11-03 03:09:00 +01:00
READER_PAGE_SCALE_MODE_VALUES,
getReaderSettingsStore().pageScaleMode.isDefault,
2024-11-03 03:09:00 +01:00
true,
);
},
[],
2024-11-03 03:09:00 +01:00
);
useHotkeys(hotkeys[ReaderHotkey.STRETCH_IMAGE], () =>
ReaderService.updateSetting('shouldStretchPage', !getReaderSettingsStore().shouldStretchPage.value),
2024-12-17 16:23:09 +01:00
);
useHotkeys(hotkeys[ReaderHotkey.OFFSET_SPREAD_PAGES], () =>
ReaderService.setOffsetDoubleSpreads(!getReaderSettingsStore().shouldOffsetDoubleSpreads.value),
2024-12-17 16:27:20 +01:00
);
useHotkeys(hotkeys[ReaderHotkey.CYCLE_READING_MODE], () => {
updateSettingCycleThrough(
'readingMode',
getReaderSettingsStore().readingMode.value,
READING_MODE_VALUES,
getReaderSettingsStore().readingMode.isDefault,
true,
);
});
useHotkeys(hotkeys[ReaderHotkey.CYCLE_READING_DIRECTION], () => {
updateSettingCycleThrough(
'readingDirection',
getReaderSettingsStore().readingDirection.value,
READING_DIRECTION_VALUES,
getReaderSettingsStore().readingDirection.isDefault,
true,
);
});
useHotkeys(hotkeys[ReaderHotkey.TOGGLE_AUTO_SCROLL], () => getReaderAutoScrollStore().toggleActive(), {});
useHotkeys(hotkeys[ReaderHotkey.AUTO_SCROLL_SPEED_DECREASE], () =>
ReaderService.updateSetting('autoScroll', {
...getReaderSettingsStore().autoScroll,
value: Math.min(AUTO_SCROLL_SPEED.max, getReaderSettingsStore().autoScroll.value + AUTO_SCROLL_SPEED.step),
}),
);
useHotkeys(hotkeys[ReaderHotkey.AUTO_SCROLL_SPEED_INCREASE], () =>
ReaderService.updateSetting('autoScroll', {
...getReaderSettingsStore().autoScroll,
value: Math.max(AUTO_SCROLL_SPEED.min, getReaderSettingsStore().autoScroll.value - AUTO_SCROLL_SPEED.step),
}),
);
2025-03-27 01:50:36 +01:00
useHotkeys(hotkeys[ReaderHotkey.EXIT_READER], exitReader, [exitReader]);
2024-11-03 03:09:00 +01:00
useEffect(() => {
enableScope(HotkeyScope.READER);
return () => disableScope(HotkeyScope.READER);
}, []);
return null;
};