Use "mantine/hooks"

This commit is contained in:
schroda
2025-05-02 17:56:56 +02:00
parent e982b7c417
commit 24a0cfb3b0
9 changed files with 73 additions and 106 deletions

View File

@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { useEffect, useLayoutEffect, useState } from 'react';
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
import List from '@mui/material/List';
import ListItemText from '@mui/material/ListItemText';
import { fromEvent } from 'file-selector';
@@ -23,6 +23,7 @@ import Button from '@mui/material/Button';
import ListItem from '@mui/material/ListItem';
import { Link } from 'react-router-dom';
import Stack from '@mui/material/Stack';
import { useEventListener, useMergedRef, useWindowEvent } from '@mantine/hooks';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { makeToast } from '@/modules/core/utils/Toast.ts';
import { BackupRestoreState, ValidateBackupQuery } from '@/lib/graphql/generated/graphql.ts';
@@ -89,6 +90,8 @@ export function Backup() {
const [, setTriggerReRender] = useState(0);
const inputRef = useRef<HTMLInputElement>(null);
const restoreProgress = (() => {
if (!data?.restoreStatus) {
return 0;
@@ -193,40 +196,25 @@ export function Backup() {
}
};
const dropHandler = async (e: Event) => {
e.preventDefault();
const files = await fromEvent(e);
submitBackup(files[0] as File);
};
const dragOverHandler = (e: Event) => {
e.preventDefault();
};
const closeInvalidBackupDialog = () => {
setIsInvalidBackupDialogOpen(false);
resetBackupState();
};
useEffect(() => {
document.addEventListener('drop', dropHandler);
document.addEventListener('dragover', dragOverHandler);
useWindowEvent('drop', async (e) => {
e.preventDefault();
const files = await fromEvent(e);
const handleFileSelection = async (event: Event) => {
const files = await fromEvent(event);
submitBackup(files[0] as File);
};
const input = document.getElementById('backup-file');
input?.addEventListener('change', handleFileSelection);
return () => {
document.removeEventListener('drop', dropHandler);
document.removeEventListener('dragover', dragOverHandler);
input?.removeEventListener('change', handleFileSelection);
};
}, []);
submitBackup(files[0] as File);
});
useWindowEvent('dragover', (e) => {
e.preventDefault();
});
const inputEventListenerRef = useEventListener('change', async (event) => {
const files = await fromEvent(event);
submitBackup(files[0] as File);
});
const mergedInputRef = useMergedRef(inputRef, inputEventListenerRef);
if (loading) {
return <LoadingPlaceholder />;
@@ -253,10 +241,7 @@ export function Backup() {
secondary={t('settings.backup.action.create.label.description')}
/>
</ListItemButton>
<ListItemButton
onClick={() => document.getElementById('backup-file')?.click()}
disabled={!!backupRestoreId}
>
<ListItemButton onClick={() => inputRef.current?.click()} disabled={!!backupRestoreId}>
<ListItemText
primary={t('settings.backup.action.restore.label.title')}
secondary={t('settings.backup.action.restore.label.description')}
@@ -318,7 +303,7 @@ export function Backup() {
/>
</List>
</List>
<input type="file" id="backup-file" style={{ display: 'none' }} />
<input ref={mergedInputRef} type="file" style={{ display: 'none' }} />
<Dialog open={isInvalidBackupDialogOpen}>
<DialogTitle>{t('settings.backup.action.validate.dialog.title')}</DialogTitle>
<DialogContent dividers>

View File

@@ -6,13 +6,14 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import React, { useState, useEffect } from 'react';
import React, { useState } from 'react';
import SearchIcon from '@mui/icons-material/Search';
import IconButton from '@mui/material/IconButton';
import { useQueryParam, StringParam } from 'use-query-params';
import { useTranslation } from 'react-i18next';
import { useLocation } from 'react-router-dom';
import { useTheme } from '@mui/material/styles';
import { useHotkeys } from 'react-hotkeys-hook';
import { CustomTooltip } from '@/modules/core/components/CustomTooltip.tsx';
import { SearchTextField } from '@/modules/core/components/inputs/SearchTextField.tsx';
@@ -75,20 +76,13 @@ export const AppbarSearch: React.FunctionComponent<IProps> = (props) => {
if (!searchString) updateSearchOpenState(false);
};
const handleKeyboardEvent = (e: KeyboardEvent) => {
if (e.key === 'F3' || (e.ctrlKey && e.key === 'f')) {
e.preventDefault();
useHotkeys(
'ctrl+f, F3',
() => {
updateSearchOpenState(true);
}
};
useEffect(() => {
window.addEventListener('keydown', handleKeyboardEvent);
return () => {
window.removeEventListener('keydown', handleKeyboardEvent);
};
}, [handleKeyboardEvent]);
},
{ preventDefault: true },
);
if (isOpen) {
return (

View File

@@ -23,10 +23,8 @@ import {
MouseEvent,
ReactNode,
RefAttributes,
useImperativeHandle,
useRef,
useState,
Ref,
} from 'react';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
@@ -34,6 +32,7 @@ import Box from '@mui/material/Box';
import { OverridableComponent } from '@mui/material/OverridableComponent';
import { SvgIconTypeMap } from '@mui/material/SvgIcon';
import { useMergedRef } from '@mantine/hooks';
import { IconMenuItem } from '@/modules/core/components/menu/IconMenuItem.tsx';
import { getOptionForDirection } from '@/modules/theme/services/ThemeCreator.ts';
import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx';
@@ -74,10 +73,10 @@ const NestedMenuItem = forwardRef<HTMLLIElement | null, NestedMenuItemProps>((pr
const { ref: containerRefProp, ...ContainerProps } = ContainerPropsProp;
const menuItemRef = useRef<HTMLLIElement | null>(null);
useImperativeHandle(ref, () => menuItemRef.current!);
const mergedMenuItemRef = useMergedRef(ref, menuItemRef);
const containerRef = useRef<HTMLElement>(null);
useImperativeHandle(containerRefProp as Ref<HTMLElement | null>, () => containerRef.current as HTMLElement);
const mergedContainerRef = useMergedRef(containerRefProp, containerRef);
const menuContainerRef = useRef<HTMLDivElement | null>(null);
@@ -184,7 +183,7 @@ const NestedMenuItem = forwardRef<HTMLLIElement | null, NestedMenuItemProps>((pr
return (
<Box
{...ContainerProps}
ref={containerRef}
ref={mergedContainerRef}
onFocus={handleFocus}
onClick={handleClick}
tabIndex={tabIndex}
@@ -195,7 +194,7 @@ const NestedMenuItem = forwardRef<HTMLLIElement | null, NestedMenuItemProps>((pr
<IconMenuItem
MenuItemProps={MenuItemProps}
className={className}
ref={menuItemRef}
ref={mergedMenuItemRef}
LeftIcon={LeftIcon}
RightIcon={RightIcon}
label={label}

View File

@@ -10,12 +10,13 @@ import PauseIcon from '@mui/icons-material/Pause';
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import Box from '@mui/material/Box';
import IconButton from '@mui/material/IconButton';
import React, { useEffect, useLayoutEffect, useMemo, useState } from 'react';
import React, { useLayoutEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import DeleteSweepIcon from '@mui/icons-material/DeleteSweep';
import { Virtuoso } from 'react-virtuoso';
import { closestCenter, DndContext, DragEndEvent } from '@dnd-kit/core';
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
import { useWindowEvent } from '@mantine/hooks';
import { CustomTooltip } from '@/modules/core/components/CustomTooltip.tsx';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { makeToast } from '@/modules/core/utils/Toast.ts';
@@ -122,22 +123,16 @@ export const DownloadQueue: React.FC = () => {
};
}, [t, status, isQueueEmpty]);
useEffect(() => {
const ignoreError = (e: WindowEventMap['error']) => {
if (
e.message === 'ResizeObserver loop completed with undelivered notifications.' ||
e.message === 'ResizeObserver loop limit exceeded'
) {
e.stopImmediatePropagation();
}
};
// Virtuoso's resize observer can throw this error,
// which is caught by DnD and aborts dragging.
window.addEventListener('error', ignoreError);
return () => window.removeEventListener('error', ignoreError);
}, []);
// Virtuoso's resize observer can throw this error,
// which is caught by DnD and aborts dragging.
useWindowEvent('error', (e) => {
if (
e.message === 'ResizeObserver loop completed with undelivered notifications.' ||
e.message === 'ResizeObserver loop limit exceeded'
) {
e.stopImmediatePropagation();
}
});
if (isLoading) {
return <LoadingPlaceholder />;

View File

@@ -16,6 +16,7 @@ import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { useTranslation } from 'react-i18next';
import { Link, useLocation, useNavigate } from 'react-router-dom';
import { useWindowEvent } from '@mantine/hooks';
import { CustomTooltip } from '@/modules/core/components/CustomTooltip.tsx';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { AppbarSearch } from '@/modules/core/components/AppbarSearch.tsx';
@@ -242,25 +243,14 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
};
}, [t, shownLangs, allLangs]);
useEffect(() => {
const dropHandler = async (e: Event) => {
e.preventDefault();
const files = await fromEvent(e);
submitExternalExtension(files[0] as File);
};
const dragOverHandler = (e: Event) => {
e.preventDefault();
};
document.addEventListener('drop', dropHandler);
document.addEventListener('dragover', dragOverHandler);
return () => {
document.removeEventListener('drop', dropHandler);
document.removeEventListener('dragover', dragOverHandler);
};
}, []);
useWindowEvent('drop', async (e) => {
e.preventDefault();
const files = await fromEvent(e);
submitExternalExtension(files[0] as File);
});
useWindowEvent('dragover', (e) => {
e.preventDefault();
});
if (isLoading) {
return <LoadingPlaceholder />;

View File

@@ -12,7 +12,6 @@ import {
memo,
useCallback,
useEffect,
useImperativeHandle,
useLayoutEffect,
useMemo,
useRef,
@@ -21,6 +20,7 @@ import {
import Stack from '@mui/material/Stack';
import { useTheme } from '@mui/material/styles';
import { useLocation } from 'react-router-dom';
import { useMergedRef } from '@mantine/hooks';
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
import {
IReaderSettings,
@@ -168,7 +168,7 @@ const BaseReaderViewer = forwardRef(
};
const scrollElementRef = useRef<HTMLDivElement | null>(null);
useImperativeHandle(ref, () => scrollElementRef.current!);
const mergedRef = useMergedRef(ref, scrollElementRef);
const isContinuousVerticalReadingModeActive = isContinuousVerticalReadingMode(readingMode);
const isContinuousReadingModeActive = isContinuousReadingMode(readingMode);
@@ -310,7 +310,7 @@ const BaseReaderViewer = forwardRef(
return (
<Stack
ref={scrollElementRef}
ref={mergedRef}
sx={{
width: '100%',
height: '100%',

View File

@@ -6,7 +6,8 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { RefObject, useEffect, useLayoutEffect, useRef } from 'react';
import { RefObject, useCallback, useEffect, useLayoutEffect, useRef } from 'react';
import { useWindowEvent } from '@mantine/hooks';
import {
ReaderPageScaleMode,
ReaderStateChapters,
@@ -64,21 +65,18 @@ const usePreserveOnWindowResize = (
) => {
const previousDimensionsRef = useRef({ width: window.innerWidth, height: window.innerHeight });
useEffect(() => {
const handleResize = () => {
const { width, height } = previousDimensionsRef.current;
previousDimensionsRef.current = { width: window.innerWidth, height: window.innerHeight };
const handleResize = useCallback(() => {
const { width, height } = previousDimensionsRef.current;
previousDimensionsRef.current = { width: window.innerWidth, height: window.innerHeight };
if (!shouldPreserveOnResizeChange(readingMode, pageScaleMode, width, height)) {
return;
}
if (!shouldPreserveOnResizeChange(readingMode, pageScaleMode, width, height)) {
return;
}
setPageToScrollToIndex(pageIndex);
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
setPageToScrollToIndex(pageIndex);
}, [readingMode, pageScaleMode, pageIndex]);
useWindowEvent('resize', handleResize);
};
interface ScrollPreservationInfo {