forked from akai/readest
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:
@@ -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;
|
||||
|
||||
@@ -23,7 +23,7 @@ class EventDispatcher {
|
||||
if (listeners) {
|
||||
const customEvent = new CustomEvent(event, { detail });
|
||||
for (const listener of listeners) {
|
||||
listener(customEvent);
|
||||
await listener(customEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user