mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): use windowing for gallery
vastly improves the gallery performance when many images are loaded. - `react-virtuoso` to do the virtualized list - `overlayscrollbars` for a scrollbar
This commit is contained in:
parent
d39de0ad38
commit
475b6bef53
@ -76,6 +76,8 @@
|
|||||||
"i18next-http-backend": "^2.2.0",
|
"i18next-http-backend": "^2.2.0",
|
||||||
"konva": "^9.0.1",
|
"konva": "^9.0.1",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
|
"overlayscrollbars": "^2.1.1",
|
||||||
|
"overlayscrollbars-react": "^0.5.0",
|
||||||
"patch-package": "^7.0.0",
|
"patch-package": "^7.0.0",
|
||||||
"re-resizable": "^6.9.9",
|
"re-resizable": "^6.9.9",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
@ -91,6 +93,7 @@
|
|||||||
"react-rnd": "^10.4.1",
|
"react-rnd": "^10.4.1",
|
||||||
"react-transition-group": "^4.4.5",
|
"react-transition-group": "^4.4.5",
|
||||||
"react-use": "^17.4.0",
|
"react-use": "^17.4.0",
|
||||||
|
"react-virtuoso": "^4.3.3",
|
||||||
"react-zoom-pan-pinch": "^3.0.7",
|
"react-zoom-pan-pinch": "^3.0.7",
|
||||||
"reactflow": "^11.7.0",
|
"reactflow": "^11.7.0",
|
||||||
"redux-deep-persist": "^1.0.7",
|
"redux-deep-persist": "^1.0.7",
|
||||||
|
@ -18,6 +18,8 @@ import '@fontsource/inter/600.css';
|
|||||||
import '@fontsource/inter/700.css';
|
import '@fontsource/inter/700.css';
|
||||||
import '@fontsource/inter/800.css';
|
import '@fontsource/inter/800.css';
|
||||||
import '@fontsource/inter/900.css';
|
import '@fontsource/inter/900.css';
|
||||||
|
import 'overlayscrollbars/overlayscrollbars.css';
|
||||||
|
import 'theme/css/overlayscrollbars.css';
|
||||||
|
|
||||||
type ThemeLocaleProviderProps = {
|
type ThemeLocaleProviderProps = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
Image,
|
Image,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
MenuList,
|
MenuList,
|
||||||
|
Skeleton,
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
useTheme,
|
useTheme,
|
||||||
useToast,
|
useToast,
|
||||||
@ -12,7 +13,7 @@ import {
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
||||||
import { DragEvent, memo, useCallback, useState } from 'react';
|
import { DragEvent, memo, useCallback, useState } from 'react';
|
||||||
import { FaCheck, FaExpand, FaShare, FaTrash } from 'react-icons/fa';
|
import { FaCheck, FaExpand, FaImage, FaShare, FaTrash } from 'react-icons/fa';
|
||||||
import DeleteImageModal from './DeleteImageModal';
|
import DeleteImageModal from './DeleteImageModal';
|
||||||
import { ContextMenu } from 'chakra-ui-contextmenu';
|
import { ContextMenu } from 'chakra-ui-contextmenu';
|
||||||
import * as InvokeAI from 'app/types/invokeai';
|
import * as InvokeAI from 'app/types/invokeai';
|
||||||
@ -268,46 +269,35 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
userSelect="none"
|
userSelect="none"
|
||||||
draggable={true}
|
draggable={true}
|
||||||
onDragStart={handleDragStart}
|
onDragStart={handleDragStart}
|
||||||
|
onClick={handleSelectImage}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
sx={{
|
sx={{
|
||||||
padding: 2,
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
w: 'full',
|
||||||
|
h: 'full',
|
||||||
transition: 'transform 0.2s ease-out',
|
transition: 'transform 0.2s ease-out',
|
||||||
_hover: {
|
aspectRatio: '1/1',
|
||||||
cursor: 'pointer',
|
|
||||||
|
|
||||||
zIndex: 2,
|
|
||||||
},
|
|
||||||
_before: {
|
|
||||||
content: '""',
|
|
||||||
display: 'block',
|
|
||||||
paddingBottom: '100%',
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
|
loading="lazy"
|
||||||
objectFit={
|
objectFit={
|
||||||
shouldUseSingleGalleryColumn ? 'contain' : galleryImageObjectFit
|
shouldUseSingleGalleryColumn ? 'contain' : galleryImageObjectFit
|
||||||
}
|
}
|
||||||
rounded="md"
|
rounded="md"
|
||||||
src={getUrl(thumbnail || url)}
|
src={getUrl(thumbnail || url)}
|
||||||
loading="lazy"
|
fallback={<FaImage />}
|
||||||
sx={{
|
sx={{
|
||||||
position: 'absolute',
|
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
maxHeight: '100%',
|
maxHeight: '100%',
|
||||||
top: '50%',
|
|
||||||
transform: 'translate(-50%,-50%)',
|
|
||||||
...(direction === 'rtl'
|
|
||||||
? { insetInlineEnd: '50%' }
|
|
||||||
: { insetInlineStart: '50%' }),
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{isSelected && (
|
||||||
<Flex
|
<Flex
|
||||||
onClick={handleSelectImage}
|
|
||||||
sx={{
|
sx={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: '0',
|
top: '0',
|
||||||
@ -316,10 +306,11 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
height: '100%',
|
height: '100%',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
pointerEvents: 'none',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isSelected && (
|
|
||||||
<Icon
|
<Icon
|
||||||
|
filter={'drop-shadow(0px 0px 1rem black)'}
|
||||||
as={FaCheck}
|
as={FaCheck}
|
||||||
sx={{
|
sx={{
|
||||||
width: '50%',
|
width: '50%',
|
||||||
@ -327,9 +318,9 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
fill: 'ok.500',
|
fill: 'ok.500',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
{isHovered && galleryImageMinimumWidth >= 64 && (
|
)}
|
||||||
|
{isHovered && galleryImageMinimumWidth >= 100 && (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
@ -1,5 +1,13 @@
|
|||||||
import { ButtonGroup, Flex, Grid, Icon, Image, Text } from '@chakra-ui/react';
|
import {
|
||||||
// import { requestImages } from 'app/socketio/actions';
|
Box,
|
||||||
|
ButtonGroup,
|
||||||
|
Flex,
|
||||||
|
FlexProps,
|
||||||
|
Grid,
|
||||||
|
Icon,
|
||||||
|
Text,
|
||||||
|
forwardRef,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIButton from 'common/components/IAIButton';
|
import IAIButton from 'common/components/IAIButton';
|
||||||
import IAICheckbox from 'common/components/IAICheckbox';
|
import IAICheckbox from 'common/components/IAICheckbox';
|
||||||
@ -15,28 +23,33 @@ import {
|
|||||||
setShouldUseSingleGalleryColumn,
|
setShouldUseSingleGalleryColumn,
|
||||||
} from 'features/gallery/store/gallerySlice';
|
} from 'features/gallery/store/gallerySlice';
|
||||||
import { togglePinGalleryPanel } from 'features/ui/store/uiSlice';
|
import { togglePinGalleryPanel } from 'features/ui/store/uiSlice';
|
||||||
|
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
||||||
|
|
||||||
import { ChangeEvent, useEffect, useRef, useState } from 'react';
|
import {
|
||||||
|
ChangeEvent,
|
||||||
|
PropsWithChildren,
|
||||||
|
memo,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { BsPinAngle, BsPinAngleFill } from 'react-icons/bs';
|
import { BsPinAngle, BsPinAngleFill } from 'react-icons/bs';
|
||||||
import { FaImage, FaUser, FaWrench } from 'react-icons/fa';
|
import { FaImage, FaUser, FaWrench } from 'react-icons/fa';
|
||||||
import { MdPhotoLibrary } from 'react-icons/md';
|
import { MdPhotoLibrary } from 'react-icons/md';
|
||||||
import HoverableImage from './HoverableImage';
|
import HoverableImage from './HoverableImage';
|
||||||
|
|
||||||
import Scrollable from 'features/ui/components/common/Scrollable';
|
|
||||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||||
import {
|
import { resultsAdapter } from '../store/resultsSlice';
|
||||||
resultsAdapter,
|
|
||||||
selectResultsAll,
|
|
||||||
selectResultsTotal,
|
|
||||||
} from '../store/resultsSlice';
|
|
||||||
import {
|
import {
|
||||||
receivedResultImagesPage,
|
receivedResultImagesPage,
|
||||||
receivedUploadImagesPage,
|
receivedUploadImagesPage,
|
||||||
} from 'services/thunks/gallery';
|
} from 'services/thunks/gallery';
|
||||||
import { selectUploadsAll, uploadsAdapter } from '../store/uploadsSlice';
|
import { uploadsAdapter } from '../store/uploadsSlice';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { RootState } from 'app/store/store';
|
import { RootState } from 'app/store/store';
|
||||||
|
import { Virtuoso, VirtuosoGrid } from 'react-virtuoso';
|
||||||
|
|
||||||
const GALLERY_SHOW_BUTTONS_MIN_WIDTH = 290;
|
const GALLERY_SHOW_BUTTONS_MIN_WIDTH = 290;
|
||||||
|
|
||||||
@ -68,16 +81,28 @@ const ImageGalleryContent = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const resizeObserverRef = useRef<HTMLDivElement>(null);
|
const resizeObserverRef = useRef<HTMLDivElement>(null);
|
||||||
const [shouldShouldIconButtons, setShouldShouldIconButtons] = useState(true);
|
const [shouldShouldIconButtons, setShouldShouldIconButtons] = useState(true);
|
||||||
|
const rootRef = useRef(null);
|
||||||
|
const [scroller, setScroller] = useState<HTMLElement | null>(null);
|
||||||
|
const [initialize, osInstance] = useOverlayScrollbars({
|
||||||
|
defer: true,
|
||||||
|
options: {
|
||||||
|
scrollbars: {
|
||||||
|
visibility: 'auto',
|
||||||
|
autoHide: 'leave',
|
||||||
|
autoHideDelay: 1300,
|
||||||
|
theme: 'os-theme-dark',
|
||||||
|
},
|
||||||
|
overflow: { x: 'hidden' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
// images,
|
// images,
|
||||||
currentCategory,
|
currentCategory,
|
||||||
shouldPinGallery,
|
shouldPinGallery,
|
||||||
galleryImageMinimumWidth,
|
galleryImageMinimumWidth,
|
||||||
galleryGridTemplateColumns,
|
|
||||||
galleryImageObjectFit,
|
galleryImageObjectFit,
|
||||||
shouldAutoSwitchToNewImages,
|
shouldAutoSwitchToNewImages,
|
||||||
// areMoreImagesAvailable,
|
|
||||||
shouldUseSingleGalleryColumn,
|
shouldUseSingleGalleryColumn,
|
||||||
selectedImage,
|
selectedImage,
|
||||||
} = useAppSelector(imageGallerySelector);
|
} = useAppSelector(imageGallerySelector);
|
||||||
@ -85,9 +110,6 @@ const ImageGalleryContent = () => {
|
|||||||
const { images, areMoreImagesAvailable, isLoading } =
|
const { images, areMoreImagesAvailable, isLoading } =
|
||||||
useAppSelector(gallerySelector);
|
useAppSelector(gallerySelector);
|
||||||
|
|
||||||
// const handleClickLoadMore = () => {
|
|
||||||
// dispatch(requestImages(currentCategory));
|
|
||||||
// };
|
|
||||||
const handleClickLoadMore = () => {
|
const handleClickLoadMore = () => {
|
||||||
if (currentCategory === 'results') {
|
if (currentCategory === 'results') {
|
||||||
dispatch(receivedResultImagesPage());
|
dispatch(receivedResultImagesPage());
|
||||||
@ -129,6 +151,25 @@ const ImageGalleryContent = () => {
|
|||||||
return () => resizeObserver.disconnect(); // clean up
|
return () => resizeObserver.disconnect(); // clean up
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const { current: root } = rootRef;
|
||||||
|
if (scroller && root) {
|
||||||
|
initialize({
|
||||||
|
target: root,
|
||||||
|
elements: {
|
||||||
|
viewport: scroller,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return () => osInstance()?.destroy();
|
||||||
|
}, [scroller, initialize, osInstance]);
|
||||||
|
|
||||||
|
const setScrollerRef = useCallback((ref: HTMLElement | Window | null) => {
|
||||||
|
if (ref instanceof HTMLElement) {
|
||||||
|
setScroller(ref);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex flexDirection="column" w="full" h="full" gap={4}>
|
<Flex flexDirection="column" w="full" h="full" gap={4}>
|
||||||
<Flex
|
<Flex
|
||||||
@ -241,17 +282,43 @@ const ImageGalleryContent = () => {
|
|||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Scrollable>
|
|
||||||
<Flex direction="column" gap={2} h="full">
|
<Flex direction="column" gap={2} h="full">
|
||||||
{images.length || areMoreImagesAvailable ? (
|
{images.length || areMoreImagesAvailable ? (
|
||||||
<>
|
<>
|
||||||
<Grid
|
<Box ref={rootRef} data-overlayscrollbars="" h="100%">
|
||||||
gap={2}
|
{shouldUseSingleGalleryColumn ? (
|
||||||
style={{ gridTemplateColumns: galleryGridTemplateColumns }}
|
<Virtuoso
|
||||||
>
|
style={{ height: '100%' }}
|
||||||
{images.map((image) => {
|
data={images}
|
||||||
|
scrollerRef={(ref) => setScrollerRef(ref)}
|
||||||
|
itemContent={(index, image) => {
|
||||||
const { name } = image;
|
const { name } = image;
|
||||||
const isSelected = selectedImage?.name === name;
|
const isSelected = selectedImage?.name === name;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex sx={{ pb: 2 }}>
|
||||||
|
<HoverableImage
|
||||||
|
key={`${name}-${image.thumbnail}`}
|
||||||
|
image={image}
|
||||||
|
isSelected={isSelected}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<VirtuosoGrid
|
||||||
|
style={{ height: '100%' }}
|
||||||
|
data={images}
|
||||||
|
components={{
|
||||||
|
Item: ItemContainer,
|
||||||
|
List: ListContainer,
|
||||||
|
}}
|
||||||
|
scrollerRef={setScroller}
|
||||||
|
itemContent={(index, image) => {
|
||||||
|
const { name } = image;
|
||||||
|
const isSelected = selectedImage?.name === name;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HoverableImage
|
<HoverableImage
|
||||||
key={`${name}-${image.thumbnail}`}
|
key={`${name}-${image.thumbnail}`}
|
||||||
@ -259,8 +326,10 @@ const ImageGalleryContent = () => {
|
|||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
}}
|
||||||
</Grid>
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
<IAIButton
|
<IAIButton
|
||||||
onClick={handleClickLoadMore}
|
onClick={handleClickLoadMore}
|
||||||
isDisabled={!areMoreImagesAvailable}
|
isDisabled={!areMoreImagesAvailable}
|
||||||
@ -296,10 +365,36 @@ const ImageGalleryContent = () => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Scrollable>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
ImageGalleryContent.displayName = 'ImageGalleryContent';
|
type ItemContainerProps = PropsWithChildren & FlexProps;
|
||||||
export default ImageGalleryContent;
|
const ItemContainer = forwardRef((props: ItemContainerProps, ref) => (
|
||||||
|
<Box className="item-container" ref={ref}>
|
||||||
|
{props.children}
|
||||||
|
</Box>
|
||||||
|
));
|
||||||
|
|
||||||
|
type ListContainerProps = PropsWithChildren & FlexProps;
|
||||||
|
const ListContainer = forwardRef((props: ListContainerProps, ref) => {
|
||||||
|
const galleryImageMinimumWidth = useAppSelector(
|
||||||
|
(state: RootState) => state.gallery.galleryImageMinimumWidth
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid
|
||||||
|
{...props}
|
||||||
|
className="list-container"
|
||||||
|
ref={ref}
|
||||||
|
sx={{
|
||||||
|
gap: 2,
|
||||||
|
gridTemplateColumns: `repeat(auto-fit, minmax(${galleryImageMinimumWidth}px, 1fr));`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default memo(ImageGalleryContent);
|
||||||
|
48
invokeai/frontend/web/src/theme/css/overlayscrollbars.css
Normal file
48
invokeai/frontend/web/src/theme/css/overlayscrollbars.css
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
.os-scrollbar {
|
||||||
|
/* The size of the scrollbar */
|
||||||
|
/* --os-size: 0; */
|
||||||
|
/* The axis-perpedicular padding of the scrollbar (horizontal: padding-y, vertical: padding-x) */
|
||||||
|
/* --os-padding-perpendicular: 0; */
|
||||||
|
/* The axis padding of the scrollbar (horizontal: padding-x, vertical: padding-y) */
|
||||||
|
/* --os-padding-axis: 0; */
|
||||||
|
/* The border radius of the scrollbar track */
|
||||||
|
/* --os-track-border-radius: 0; */
|
||||||
|
/* The background of the scrollbar track */
|
||||||
|
--os-track-bg: rgba(0, 0, 0, 0.3);
|
||||||
|
/* The :hover background of the scrollbar track */
|
||||||
|
--os-track-bg-hover: rgba(0, 0, 0, 0.3);
|
||||||
|
/* The :active background of the scrollbar track */
|
||||||
|
--os-track-bg-active: rgba(0, 0, 0, 0.3);
|
||||||
|
/* The border of the scrollbar track */
|
||||||
|
/* --os-track-border: none; */
|
||||||
|
/* The :hover background of the scrollbar track */
|
||||||
|
/* --os-track-border-hover: none; */
|
||||||
|
/* The :active background of the scrollbar track */
|
||||||
|
/* --os-track-border-active: none; */
|
||||||
|
/* The border radius of the scrollbar handle */
|
||||||
|
/* --os-handle-border-radius: 0; */
|
||||||
|
/* The background of the scrollbar handle */
|
||||||
|
--os-handle-bg: var(--invokeai-colors-accent-500);
|
||||||
|
/* The :hover background of the scrollbar handle */
|
||||||
|
--os-handle-bg-hover: var(--invokeai-colors-accent-450);
|
||||||
|
/* The :active background of the scrollbar handle */
|
||||||
|
--os-handle-bg-active: var(--invokeai-colors-accent-400);
|
||||||
|
/* The border of the scrollbar handle */
|
||||||
|
/* --os-handle-border: none; */
|
||||||
|
/* The :hover border of the scrollbar handle */
|
||||||
|
/* --os-handle-border-hover: none; */
|
||||||
|
/* The :active border of the scrollbar handle */
|
||||||
|
/* --os-handle-border-active: none; */
|
||||||
|
/* The min size of the scrollbar handle */
|
||||||
|
--os-handle-min-size: 50px;
|
||||||
|
/* The max size of the scrollbar handle */
|
||||||
|
/* --os-handle-max-size: none; */
|
||||||
|
/* The axis-perpedicular size of the scrollbar handle (horizontal: height, vertical: width) */
|
||||||
|
/* --os-handle-perpendicular-size: 100%; */
|
||||||
|
/* The :hover axis-perpedicular size of the scrollbar handle (horizontal: height, vertical: width) */
|
||||||
|
/* --os-handle-perpendicular-size-hover: 100%; */
|
||||||
|
/* The :active axis-perpedicular size of the scrollbar handle (horizontal: height, vertical: width) */
|
||||||
|
/* --os-handle-perpendicular-size-active: 100%; */
|
||||||
|
/* Increases the interactive area of the scrollbar handle. */
|
||||||
|
/* --os-handle-interactive-area-offset: 0; */
|
||||||
|
}
|
@ -5100,6 +5100,16 @@ os-tmpdir@~1.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
||||||
integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==
|
integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==
|
||||||
|
|
||||||
|
overlayscrollbars-react@^0.5.0:
|
||||||
|
version "0.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/overlayscrollbars-react/-/overlayscrollbars-react-0.5.0.tgz#0272bdc6304c7228a58d30e5b678e97fd5c5d8dd"
|
||||||
|
integrity sha512-uCNTnkfWW74veoiEv3kSwoLelKt4e8gTNv65D771X3il0x5g5Yo0fUbro7SpQzR9yNgi23cvB2mQHTTdQH96pA==
|
||||||
|
|
||||||
|
overlayscrollbars@^2.1.1:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/overlayscrollbars/-/overlayscrollbars-2.1.1.tgz#a7414fe9c96cf140dbe4975bbe9312861750388d"
|
||||||
|
integrity sha512-xvs2g8Tcq9+CZDpLEUchN3YUzjJhnTWw9kwqT/qcC53FIkOyP9mqnRMot5sW16tcsPT1KaMyzF0AMXw/7E4a8g==
|
||||||
|
|
||||||
p-cancelable@^1.0.0:
|
p-cancelable@^1.0.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc"
|
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc"
|
||||||
@ -5612,6 +5622,11 @@ react-use@^17.4.0:
|
|||||||
ts-easing "^0.2.0"
|
ts-easing "^0.2.0"
|
||||||
tslib "^2.1.0"
|
tslib "^2.1.0"
|
||||||
|
|
||||||
|
react-virtuoso@^4.3.3:
|
||||||
|
version "4.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-virtuoso/-/react-virtuoso-4.3.3.tgz#04b8d105b5d97365223fb13c9a594f7501f7e975"
|
||||||
|
integrity sha512-x0DeGmVAVOVaTXRMG7jzrHBwK7+dkt7n0G3tNmZXphQUBgkVBYuZoaJltQeZGFN42++3XvrgwStKCtmzgMJ0lA==
|
||||||
|
|
||||||
react-zoom-pan-pinch@^3.0.7:
|
react-zoom-pan-pinch@^3.0.7:
|
||||||
version "3.0.7"
|
version "3.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/react-zoom-pan-pinch/-/react-zoom-pan-pinch-3.0.7.tgz#def52f6886bc11e1b160dedf4250aae95470b94d"
|
resolved "https://registry.yarnpkg.com/react-zoom-pan-pinch/-/react-zoom-pan-pinch-3.0.7.tgz#def52f6886bc11e1b160dedf4250aae95470b94d"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user