Use "mantine/hooks"
This commit is contained in:
@@ -42,6 +42,7 @@
|
||||
"@fontsource/roboto": "5.2.5",
|
||||
"@juggle/resize-observer": "3.4.0",
|
||||
"@loadable/component": "5.16.4",
|
||||
"@mantine/hooks": "^7.17.5",
|
||||
"@mui/icons-material": "7.0.2",
|
||||
"@mui/material": "7.0.2",
|
||||
"@mui/system": "7.0.2",
|
||||
|
||||
@@ -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) => {
|
||||
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 input = document.getElementById('backup-file');
|
||||
input?.addEventListener('change', handleFileSelection);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('drop', dropHandler);
|
||||
document.removeEventListener('dragover', dragOverHandler);
|
||||
input?.removeEventListener('change', handleFileSelection);
|
||||
};
|
||||
}, []);
|
||||
});
|
||||
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>
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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']) => {
|
||||
// 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();
|
||||
}
|
||||
};
|
||||
|
||||
// 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);
|
||||
}, []);
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingPlaceholder />;
|
||||
|
||||
@@ -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) => {
|
||||
useWindowEvent('drop', async (e) => {
|
||||
e.preventDefault();
|
||||
const files = await fromEvent(e);
|
||||
submitExternalExtension(files[0] as File);
|
||||
};
|
||||
|
||||
const dragOverHandler = (e: Event) => {
|
||||
});
|
||||
useWindowEvent('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
document.addEventListener('drop', dropHandler);
|
||||
document.addEventListener('dragover', dragOverHandler);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('drop', dropHandler);
|
||||
document.removeEventListener('dragover', dragOverHandler);
|
||||
};
|
||||
}, []);
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingPlaceholder />;
|
||||
|
||||
@@ -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%',
|
||||
|
||||
@@ -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,8 +65,7 @@ const usePreserveOnWindowResize = (
|
||||
) => {
|
||||
const previousDimensionsRef = useRef({ width: window.innerWidth, height: window.innerHeight });
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
const handleResize = useCallback(() => {
|
||||
const { width, height } = previousDimensionsRef.current;
|
||||
previousDimensionsRef.current = { width: window.innerWidth, height: window.innerHeight };
|
||||
|
||||
@@ -74,11 +74,9 @@ const usePreserveOnWindowResize = (
|
||||
}
|
||||
|
||||
setPageToScrollToIndex(pageIndex);
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, [readingMode, pageScaleMode, pageIndex]);
|
||||
|
||||
useWindowEvent('resize', handleResize);
|
||||
};
|
||||
|
||||
interface ScrollPreservationInfo {
|
||||
|
||||
@@ -2564,6 +2564,11 @@
|
||||
hoist-non-react-statics "^3.3.1"
|
||||
react-is "^16.12.0"
|
||||
|
||||
"@mantine/hooks@^7.17.5":
|
||||
version "7.17.5"
|
||||
resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-7.17.5.tgz#ae3c270161c4a156e3ca8dff1c7a73c4a3710c48"
|
||||
integrity sha512-Q/3AHI1fjl+W7xQ3jEoMmSoTxLqxMI2gPfxIjd73OPmRpPenYWR1zk/diirXXm2t7JOrAbmpA3/O1gzmgqzc/Q==
|
||||
|
||||
"@mui/core-downloads-tracker@^7.0.2":
|
||||
version "7.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-7.0.2.tgz#2e6dcaf5027a3957d37797b8dce5c15c78fd4b82"
|
||||
|
||||
Reference in New Issue
Block a user