Add more keyboard shortcuts (#59)

1. use ctrl/cmd+=, ctrl/cmd+- and ctrl/cmd+0 to zoom in/out and reset zoom;
2. use ctrl/cmd+q to quit app;
This commit is contained in:
Huang Xin
2024-12-27 22:35:27 +01:00
committed by GitHub
parent 71ffe76e29
commit a5f94cced9
7 changed files with 78 additions and 21 deletions
@@ -23,6 +23,7 @@ import Spinner from '@/components/Spinner';
import SideBar from './sidebar/SideBar';
import Notebook from './notebook/Notebook';
import BooksGrid from './BooksGrid';
import { eventDispatcher } from '@/utils/event';
const ReaderContent: React.FC<{ ids?: string; settings: SystemSettings }> = ({ ids, settings }) => {
const router = useRouter();
@@ -64,8 +65,10 @@ const ReaderContent: React.FC<{ ids?: string; settings: SystemSettings }> = ({ i
useEffect(() => {
if (isTauriAppPlatform()) tauriHandleOnCloseWindow(handleCloseBooks);
window.addEventListener('beforeunload', handleCloseBooks);
eventDispatcher.on('quit-app', handleCloseBooks);
return () => {
window.removeEventListener('beforeunload', handleCloseBooks);
eventDispatcher.off('quit-app', handleCloseBooks);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [bookKeys]);
@@ -5,7 +5,7 @@ import { BiMoon, BiSun } from 'react-icons/bi';
import { TbSunMoon } from 'react-icons/tb';
import { MdZoomOut, MdZoomIn, MdCheck } from 'react-icons/md';
import { ONE_COLUMN_MAX_INLINE_SIZE } from '@/services/constants';
import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL, ONE_COLUMN_MAX_INLINE_SIZE } from '@/services/constants';
import MenuItem from '@/components/MenuItem';
import { useReaderStore } from '@/store/readerStore';
import { useTranslation } from '@/hooks/useTranslation';
@@ -32,8 +32,8 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
const [isInvertedColors, setInvertedColors] = useState(viewSettings!.invert);
const [zoomLevel, setZoomLevel] = useState(viewSettings!.zoomLevel!);
const zoomIn = () => setZoomLevel((prev) => Math.min(prev + 10, 200));
const zoomOut = () => setZoomLevel((prev) => Math.max(prev - 10, 50));
const zoomIn = () => setZoomLevel((prev) => Math.min(prev + 1, MAX_ZOOM_LEVEL));
const zoomOut = () => setZoomLevel((prev) => Math.max(prev - 1, MIN_ZOOM_LEVEL));
const resetZoom = () => setZoomLevel(100);
const toggleScrolledMode = () => setScrolledMode(!isScrolledMode);
const toggleInvertedColors = () => setInvertedColors(!isInvertedColors);
@@ -77,12 +77,9 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
useEffect(() => {
const view = getView(bookKey);
if (!view) return;
// FIXME: zoom level is not working in paginated mode
if (viewSettings?.scrolled) {
view.renderer.setStyles?.(getStyles(viewSettings!, themeCode));
}
viewSettings!.zoomLevel = zoomLevel;
setViewSettings(bookKey, viewSettings!);
view.renderer.setStyles?.(getStyles(viewSettings!, themeCode));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [zoomLevel]);
@@ -91,17 +88,12 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
tabIndex={0}
className='view-menu dropdown-content dropdown-right no-triangle border-base-100 z-20 mt-1 w-72 border shadow-2xl'
>
<div
className={clsx(
'flex items-center justify-between rounded-md',
!isScrolledMode && 'text-gray-400',
)}
>
<div className={clsx('flex items-center justify-between rounded-md')}>
<button
onClick={zoomOut}
className={clsx(
'hover:bg-base-200 text-base-content rounded-full p-2',
!isScrolledMode && 'btn-disabled text-gray-400',
zoomLevel <= MIN_ZOOM_LEVEL && 'btn-disabled text-gray-400',
)}
>
<MdZoomOut size={20} />
@@ -109,17 +101,16 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
<button
className={clsx(
'hover:bg-base-200 text-base-content h-8 min-h-8 w-[50%] rounded-md p-1 text-center',
!isScrolledMode && 'btn-disabled text-gray-400',
)}
onClick={resetZoom}
>
{zoomLevel}%
{100 - (100 - zoomLevel) * 10}%
</button>
<button
onClick={zoomIn}
className={clsx(
'hover:bg-base-200 text-base-content rounded-full p-2',
!isScrolledMode && 'btn-disabled text-gray-400',
zoomLevel >= MAX_ZOOM_LEVEL && 'btn-disabled text-gray-400',
)}
>
<MdZoomIn size={20} />
@@ -1,9 +1,14 @@
import { useReaderStore } from '@/store/readerStore';
import { useNotebookStore } from '@/store/notebookStore';
import { isTauriAppPlatform } from '@/services/environment';
import useShortcuts from '@/hooks/useShortcuts';
import useBooksManager from './useBooksManager';
import { useSidebarStore } from '@/store/sidebarStore';
import { useSettingsStore } from '@/store/settingsStore';
import { useTheme } from '@/hooks/useTheme';
import { getStyles } from '@/utils/style';
import { eventDispatcher } from '@/utils/event';
import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from '@/services/constants';
interface UseBookShortcutsProps {
sideBarBookKey: string | null;
@@ -16,6 +21,7 @@ const useBookShortcuts = ({ sideBarBookKey, bookKeys }: UseBookShortcutsProps) =
const { setFontLayoutSettingsDialogOpen } = useSettingsStore();
const { toggleNotebook } = useNotebookStore();
const { getNextBookKey } = useBooksManager();
const { themeCode } = useTheme();
const viewSettings = getViewSettings(sideBarBookKey ?? '');
const fontSize = viewSettings?.defaultFontSize ?? 16;
const lineHeight = viewSettings?.lineHeight ?? 1.6;
@@ -63,6 +69,47 @@ const useBookShortcuts = ({ sideBarBookKey, bookKeys }: UseBookShortcutsProps) =
window.location.reload();
};
const quitApp = async () => {
// on web platform use browser's default shortcut to close the tab
if (isTauriAppPlatform()) {
await eventDispatcher.dispatch('quit-app');
const { exit } = await import('@tauri-apps/plugin-process');
await exit(0);
}
};
const zoomIn = () => {
if (!sideBarBookKey) return;
const view = getView(sideBarBookKey);
if (!view?.renderer?.setStyles) return;
const viewSettings = getViewSettings(sideBarBookKey)!;
const zoomLevel = viewSettings!.zoomLevel + 1;
viewSettings!.zoomLevel = Math.min(zoomLevel, MAX_ZOOM_LEVEL);
setViewSettings(sideBarBookKey, viewSettings!);
view?.renderer.setStyles?.(getStyles(viewSettings!, themeCode));
};
const zoomOut = () => {
if (!sideBarBookKey) return;
const view = getView(sideBarBookKey);
if (!view?.renderer?.setStyles) return;
const viewSettings = getViewSettings(sideBarBookKey)!;
const zoomLevel = viewSettings!.zoomLevel - 1;
viewSettings!.zoomLevel = Math.max(zoomLevel, MIN_ZOOM_LEVEL);
setViewSettings(sideBarBookKey, viewSettings!);
view?.renderer.setStyles?.(getStyles(viewSettings!, themeCode));
};
const resetZoom = () => {
if (!sideBarBookKey) return;
const view = getView(sideBarBookKey);
if (!view?.renderer?.setStyles) return;
const viewSettings = getViewSettings(sideBarBookKey)!;
viewSettings!.zoomLevel = 100;
setViewSettings(sideBarBookKey, viewSettings!);
view?.renderer.setStyles?.(getStyles(viewSettings!, themeCode));
};
useShortcuts(
{
onSwitchSideBar: switchSideBar,
@@ -71,12 +118,16 @@ const useBookShortcuts = ({ sideBarBookKey, bookKeys }: UseBookShortcutsProps) =
onToggleScrollMode: toggleScrollMode,
onOpenFontLayoutSettings: () => setFontLayoutSettingsDialogOpen(true),
onReloadPage: reloadPage,
onQuitApp: quitApp,
onGoLeft: goLeft,
onGoRight: goRight,
onGoPrev: goPrev,
onGoNext: goNext,
onGoBack: goBack,
onGoForward: goForward,
onZoomIn: zoomIn,
onZoomOut: zoomOut,
onResetZoom: resetZoom,
},
[sideBarBookKey, bookKeys],
);
@@ -6,12 +6,16 @@ export interface ShortcutConfig {
onToggleScrollMode: string[];
onOpenFontLayoutSettings: string[];
onReloadPage: string[];
onQuitApp: string[];
onGoLeft: string[];
onGoRight: string[];
onGoNext: string[];
onGoPrev: string[];
onGoBack: string[];
onGoForward: string[];
onZoomIn: string[];
onZoomOut: string[];
onResetZoom: string[];
}
const DEFAULT_SHORTCUTS: ShortcutConfig = {
@@ -22,12 +26,16 @@ const DEFAULT_SHORTCUTS: ShortcutConfig = {
onToggleScrollMode: ['shift+j'],
onOpenFontLayoutSettings: ['shift+f'],
onReloadPage: ['shift+r'],
onQuitApp: ['ctrl+q', 'cmd+q'],
onGoLeft: ['ArrowLeft', 'PageUp', 'h'],
onGoRight: ['ArrowRight', 'PageDown', 'l'],
onGoNext: ['ArrowDown', 'j'],
onGoPrev: ['ArrowUp', 'k'],
onGoBack: ['shift+ArrowLeft', 'shift+h'],
onGoForward: ['shift+ArrowRight', 'shift+l'],
onZoomIn: ['ctrl+=', 'cmd+=', 'shift+='],
onZoomOut: ['ctrl+-', 'cmd+-', 'shift+-'],
onResetZoom: ['ctrl+0', 'cmd+0'],
};
// Load shortcuts from localStorage or fallback to defaults
@@ -93,3 +93,6 @@ export const READEST_WEB_BASE_URL = 'https://web.readest.com';
export const SYNC_PROGRESS_INTERVAL_SEC = 60;
export const SYNC_NOTES_INTERVAL_SEC = 60;
export const CHECK_UPDATE_INTERVAL_SEC = 24 * 60 * 60;
export const MAX_ZOOM_LEVEL = 140;
export const MIN_ZOOM_LEVEL = 95;
+1 -1
View File
@@ -23,7 +23,7 @@ class EventDispatcher {
if (listeners) {
const customEvent = new CustomEvent(event, { detail });
for (const listener of listeners) {
listener(customEvent);
await listener(customEvent);
}
}
}
+4 -3
View File
@@ -106,13 +106,15 @@ const getLayoutStyles = (
}
html, body {
color: ${fg};
zoom: ${zoomLevel}%;
}
body *:not(a):not(#b1):not(#b1 *):not(#b2):not(#b2 *):not(.bg):not(.bg *):not(.vol):not(.vol *):not(.background):not(.background *) {
border-color: currentColor !important;
${bg === '#ffffff' ? '' : `color: inherit;`}
${bg === '#ffffff' ? '' : `background-color: ${bg} !important;`}
}
body * {
zoom: ${zoomLevel};
}
svg, img {
background-color: transparent !important;
}
@@ -159,8 +161,7 @@ export const getStyles = (viewSettings: ViewSettings, themeCode: ThemeCode) => {
viewSettings.lineHeight!,
viewSettings.fullJustification!,
viewSettings.hyphenation!,
// FIXME: zoom level is not working in paginated mode
viewSettings.scrolled ? viewSettings.zoomLevel! : 100,
viewSettings.zoomLevel! / 100.0,
themeCode.bg,
themeCode.fg,
themeCode.primary,