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/.
|
|
|
|
|
*/
|
|
|
|
|
|
2026-05-13 23:31:48 +02:00
|
|
|
import { useHotkeys as useHotKeysHook, useHotkeysContext } from 'react-hotkeys-hook';
|
2024-11-03 03:09:00 +01:00
|
|
|
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';
|
2026-03-11 00:11:29 +01:00
|
|
|
import type { IReaderSettings } from '@/features/reader/Reader.types.ts';
|
|
|
|
|
import { ReaderHotkey } from '@/features/reader/Reader.types.ts';
|
2025-08-16 02:48:46 +02:00
|
|
|
import { getNextRotationValue } from '@/base/utils/ValueRotationButton.utils.ts';
|
2024-11-03 03:09:00 +01:00
|
|
|
import {
|
2024-12-29 22:24:59 +01:00
|
|
|
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';
|
2025-08-16 02:48:46 +02:00
|
|
|
import { ScrollOffset } from '@/base/Base.types.ts';
|
2025-08-15 22:02:58 +02:00
|
|
|
import { getOptionForDirection } from '@/features/theme/services/ThemeCreator.ts';
|
2025-09-21 00:50:19 +02:00
|
|
|
import {
|
|
|
|
|
getReaderAutoScrollStore,
|
|
|
|
|
getReaderOverlayStore,
|
|
|
|
|
getReaderSettingsStore,
|
|
|
|
|
useReaderSettingsStore,
|
|
|
|
|
} from '@/features/reader/stores/ReaderStore.ts';
|
2024-11-03 03:09:00 +01:00
|
|
|
|
2026-05-13 23:31:48 +02:00
|
|
|
const useHotkeys = (...args: Parameters<typeof useHotKeysHook>): ReturnType<typeof useHotKeysHook> => {
|
2024-11-03 03:09:00 +01:00
|
|
|
const [keys, callback, options, dependencies] = args;
|
2026-05-13 23:31:48 +02:00
|
|
|
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) {
|
2025-08-31 12:57:28 +02:00
|
|
|
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) {
|
2025-08-31 12:57:28 +02:00
|
|
|
ReaderService.deleteSetting(setting);
|
2024-12-11 20:48:05 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-31 12:57:28 +02:00
|
|
|
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>;
|
|
|
|
|
}) => {
|
2024-12-09 13:58:17 +01:00
|
|
|
const readerThemeDirection = ReaderService.useGetThemeDirection();
|
2024-11-03 03:09:00 +01:00
|
|
|
const { enableScope, disableScope } = useHotkeysContext();
|
2026-03-11 22:10:34 +01:00
|
|
|
const hotkeys = useReaderSettingsStore('hotkeys');
|
2025-03-27 01:50:36 +01:00
|
|
|
const exitReader = ReaderService.useExit();
|
2024-11-03 03:09:00 +01:00
|
|
|
|
2026-05-13 23:31:48 +02:00
|
|
|
useHotkeys(hotkeys[ReaderHotkey.PREVIOUS_PAGE], () => ReaderControls.openPage('previous'));
|
|
|
|
|
useHotkeys(hotkeys[ReaderHotkey.NEXT_PAGE], () => ReaderControls.openPage('next'));
|
|
|
|
|
useHotkeys(hotkeys[ReaderHotkey.SCROLL_BACKWARD], () => {
|
2025-11-16 22:32:18 +01:00
|
|
|
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,
|
|
|
|
|
);
|
|
|
|
|
});
|
2026-05-13 23:31:48 +02:00
|
|
|
useHotkeys(hotkeys[ReaderHotkey.SCROLL_FORWARD], () => {
|
2025-11-16 22:32:18 +01:00
|
|
|
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,
|
|
|
|
|
);
|
|
|
|
|
});
|
2026-05-13 23:31:48 +02:00
|
|
|
useHotkeys(
|
2024-12-09 13:58:17 +01:00
|
|
|
hotkeys[ReaderHotkey.PREVIOUS_CHAPTER],
|
2025-09-20 19:51:26 +02:00
|
|
|
() => ReaderControls.openChapter(getOptionForDirection('previous', 'next', readerThemeDirection)),
|
|
|
|
|
[readerThemeDirection],
|
2024-12-09 13:58:17 +01:00
|
|
|
);
|
2026-05-13 23:31:48 +02:00
|
|
|
useHotkeys(
|
2024-12-09 13:58:17 +01:00
|
|
|
hotkeys[ReaderHotkey.NEXT_CHAPTER],
|
2025-09-20 19:51:26 +02:00
|
|
|
() => ReaderControls.openChapter(getOptionForDirection('next', 'previous', readerThemeDirection)),
|
|
|
|
|
[readerThemeDirection],
|
2024-12-09 13:58:17 +01:00
|
|
|
);
|
2026-05-13 23:31:48 +02:00
|
|
|
useHotkeys(hotkeys[ReaderHotkey.TOGGLE_MENU], () =>
|
2025-09-21 00:50:19 +02:00
|
|
|
getReaderOverlayStore().setIsVisible(!getReaderOverlayStore().isVisible),
|
2025-08-31 20:33:12 +02:00
|
|
|
);
|
2026-05-13 23:31:48 +02:00
|
|
|
useHotkeys(
|
2024-11-03 03:09:00 +01:00
|
|
|
hotkeys[ReaderHotkey.CYCLE_SCALE_TYPE],
|
|
|
|
|
() => {
|
2024-12-11 20:48:05 +01:00
|
|
|
updateSettingCycleThrough(
|
|
|
|
|
'pageScaleMode',
|
2025-09-21 00:50:19 +02:00
|
|
|
getReaderSettingsStore().pageScaleMode.value,
|
2024-11-03 03:09:00 +01:00
|
|
|
READER_PAGE_SCALE_MODE_VALUES,
|
2025-09-21 00:50:19 +02:00
|
|
|
getReaderSettingsStore().pageScaleMode.isDefault,
|
2024-11-03 03:09:00 +01:00
|
|
|
true,
|
|
|
|
|
);
|
|
|
|
|
},
|
2025-09-20 14:15:40 +02:00
|
|
|
[],
|
2024-11-03 03:09:00 +01:00
|
|
|
);
|
2026-05-13 23:31:48 +02:00
|
|
|
useHotkeys(hotkeys[ReaderHotkey.STRETCH_IMAGE], () =>
|
2025-09-21 00:50:19 +02:00
|
|
|
ReaderService.updateSetting('shouldStretchPage', !getReaderSettingsStore().shouldStretchPage.value),
|
2024-12-17 16:23:09 +01:00
|
|
|
);
|
2026-05-13 23:31:48 +02:00
|
|
|
useHotkeys(hotkeys[ReaderHotkey.OFFSET_SPREAD_PAGES], () =>
|
2026-02-21 14:57:00 +01:00
|
|
|
ReaderService.setOffsetDoubleSpreads(!getReaderSettingsStore().shouldOffsetDoubleSpreads.value),
|
2024-12-17 16:27:20 +01:00
|
|
|
);
|
2026-05-13 23:31:48 +02:00
|
|
|
useHotkeys(hotkeys[ReaderHotkey.CYCLE_READING_MODE], () => {
|
2025-09-20 14:15:40 +02:00
|
|
|
updateSettingCycleThrough(
|
|
|
|
|
'readingMode',
|
2025-09-21 00:50:19 +02:00
|
|
|
getReaderSettingsStore().readingMode.value,
|
2025-09-20 14:15:40 +02:00
|
|
|
READING_MODE_VALUES,
|
2025-09-21 00:50:19 +02:00
|
|
|
getReaderSettingsStore().readingMode.isDefault,
|
2025-09-20 14:15:40 +02:00
|
|
|
true,
|
|
|
|
|
);
|
|
|
|
|
});
|
2026-05-13 23:31:48 +02:00
|
|
|
useHotkeys(hotkeys[ReaderHotkey.CYCLE_READING_DIRECTION], () => {
|
2025-09-20 14:15:40 +02:00
|
|
|
updateSettingCycleThrough(
|
|
|
|
|
'readingDirection',
|
2025-09-21 00:50:19 +02:00
|
|
|
getReaderSettingsStore().readingDirection.value,
|
2025-09-20 14:15:40 +02:00
|
|
|
READING_DIRECTION_VALUES,
|
2025-09-21 00:50:19 +02:00
|
|
|
getReaderSettingsStore().readingDirection.isDefault,
|
2025-09-20 14:15:40 +02:00
|
|
|
true,
|
|
|
|
|
);
|
|
|
|
|
});
|
2026-05-13 23:31:48 +02:00
|
|
|
useHotkeys(hotkeys[ReaderHotkey.TOGGLE_AUTO_SCROLL], () => getReaderAutoScrollStore().toggleActive(), {});
|
|
|
|
|
useHotkeys(hotkeys[ReaderHotkey.AUTO_SCROLL_SPEED_DECREASE], () =>
|
2025-09-20 14:15:40 +02:00
|
|
|
ReaderService.updateSetting('autoScroll', {
|
2025-09-21 00:50:19 +02:00
|
|
|
...getReaderSettingsStore().autoScroll,
|
|
|
|
|
value: Math.min(AUTO_SCROLL_SPEED.max, getReaderSettingsStore().autoScroll.value + AUTO_SCROLL_SPEED.step),
|
2025-09-20 14:15:40 +02:00
|
|
|
}),
|
2024-12-29 22:24:59 +01:00
|
|
|
);
|
2026-05-13 23:31:48 +02:00
|
|
|
useHotkeys(hotkeys[ReaderHotkey.AUTO_SCROLL_SPEED_INCREASE], () =>
|
2025-09-20 14:15:40 +02:00
|
|
|
ReaderService.updateSetting('autoScroll', {
|
2025-09-21 00:50:19 +02:00
|
|
|
...getReaderSettingsStore().autoScroll,
|
|
|
|
|
value: Math.max(AUTO_SCROLL_SPEED.min, getReaderSettingsStore().autoScroll.value - AUTO_SCROLL_SPEED.step),
|
2025-09-20 14:15:40 +02:00
|
|
|
}),
|
2024-12-29 22:24:59 +01:00
|
|
|
);
|
2026-05-13 23:31:48 +02: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;
|
|
|
|
|
};
|