fix(ui): fix lightbox

This commit is contained in:
psychedelicious 2023-03-12 19:16:11 +11:00
parent 90525b1c43
commit ee6df5852a
9 changed files with 409 additions and 383 deletions

View File

@ -30,6 +30,7 @@ const App = () => {
return ( return (
<Grid w="100vw" h="100vh"> <Grid w="100vw" h="100vh">
<Lightbox />
<ImageUploader> <ImageUploader>
<ProgressBar /> <ProgressBar />
<Grid <Grid
@ -55,7 +56,6 @@ const App = () => {
<Portal> <Portal>
<FloatingGalleryButton /> <FloatingGalleryButton />
</Portal> </Portal>
<Lightbox />
</Grid> </Grid>
); );
}; };

View File

@ -60,6 +60,10 @@ const galleryBlacklist = [
'intermediateImage', 'intermediateImage',
].map((blacklistItem) => `gallery.${blacklistItem}`); ].map((blacklistItem) => `gallery.${blacklistItem}`);
const lightboxBlacklist = ['isLightboxOpen'].map(
(blacklistItem) => `lightbox.${blacklistItem}`
);
const rootReducer = combineReducers({ const rootReducer = combineReducers({
generation: generationReducer, generation: generationReducer,
postprocessing: postprocessingReducer, postprocessing: postprocessingReducer,
@ -74,7 +78,12 @@ const rootPersistConfig = getPersistConfig({
key: 'root', key: 'root',
storage, storage,
rootReducer, rootReducer,
blacklist: [...canvasBlacklist, ...systemBlacklist, ...galleryBlacklist], blacklist: [
...canvasBlacklist,
...systemBlacklist,
...galleryBlacklist,
...lightboxBlacklist,
],
debounce: 300, debounce: 300,
}); });

View File

@ -1,9 +1,10 @@
import { Flex, Image } from '@chakra-ui/react'; import { Box, Flex, Image } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/storeHooks';
import { GalleryState } from 'features/gallery/store/gallerySlice'; import { GalleryState } from 'features/gallery/store/gallerySlice';
import { uiSelector } from 'features/ui/store/uiSelectors'; import { uiSelector } from 'features/ui/store/uiSelectors';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import { APP_METADATA_HEIGHT } from 'theme/util/constants';
import { gallerySelector } from '../store/gallerySelectors'; import { gallerySelector } from '../store/gallerySelectors';
import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer'; import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer';
@ -60,10 +61,22 @@ export default function CurrentImagePreview() {
)} )}
{!shouldShowImageDetails && <NextPrevImageButtons />} {!shouldShowImageDetails && <NextPrevImageButtons />}
{shouldShowImageDetails && imageToDisplay && ( {shouldShowImageDetails && imageToDisplay && (
<Box
sx={{
position: 'absolute',
top: '0',
width: '100%',
height: '100%',
borderRadius: 'base',
overflow: 'scroll',
maxHeight: APP_METADATA_HEIGHT,
}}
>
<ImageMetadataViewer <ImageMetadataViewer
image={imageToDisplay} image={imageToDisplay}
styleClass="current-image-metadata" styleClass="current-image-metadata"
/> />
</Box>
)} )}
</Flex> </Flex>
); );

View File

@ -25,6 +25,7 @@ import {
} from 'features/ui/store/uiSelectors'; } from 'features/ui/store/uiSelectors';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
const GALLERY_TAB_WIDTHS: Record< const GALLERY_TAB_WIDTHS: Record<
InvokeTabName, InvokeTabName,
@ -39,10 +40,17 @@ const GALLERY_TAB_WIDTHS: Record<
}; };
const galleryPanelSelector = createSelector( const galleryPanelSelector = createSelector(
[activeTabNameSelector, uiSelector, gallerySelector, isStagingSelector], [
(activeTabName, ui, gallery, isStaging) => { activeTabNameSelector,
uiSelector,
gallerySelector,
isStagingSelector,
lightboxSelector,
],
(activeTabName, ui, gallery, isStaging, lightbox) => {
const { shouldPinGallery, shouldShowGallery } = ui; const { shouldPinGallery, shouldShowGallery } = ui;
const { galleryImageMinimumWidth } = gallery; const { galleryImageMinimumWidth } = gallery;
const { isLightboxOpen } = lightbox;
return { return {
activeTabName, activeTabName,
@ -51,6 +59,7 @@ const galleryPanelSelector = createSelector(
shouldShowGallery, shouldShowGallery,
galleryImageMinimumWidth, galleryImageMinimumWidth,
isResizable: activeTabName !== 'unifiedCanvas', isResizable: activeTabName !== 'unifiedCanvas',
isLightboxOpen,
}; };
}, },
{ {
@ -69,6 +78,7 @@ export default function ImageGalleryPanel() {
activeTabName, activeTabName,
isStaging, isStaging,
isResizable, isResizable,
isLightboxOpen,
} = useAppSelector(galleryPanelSelector); } = useAppSelector(galleryPanelSelector);
const handleSetShouldPinGallery = () => { const handleSetShouldPinGallery = () => {
@ -174,7 +184,7 @@ export default function ImageGalleryPanel() {
isResizable={isResizable || !shouldPinGallery} isResizable={isResizable || !shouldPinGallery}
isOpen={shouldShowGallery} isOpen={shouldShowGallery}
onClose={handleCloseGallery} onClose={handleCloseGallery}
isPinned={shouldPinGallery} isPinned={shouldPinGallery && !isLightboxOpen}
minWidth={ minWidth={
shouldPinGallery shouldPinGallery
? GALLERY_TAB_WIDTHS[activeTabName].galleryMinWidth ? GALLERY_TAB_WIDTHS[activeTabName].galleryMinWidth

View File

@ -170,25 +170,20 @@ const ImageMetadataViewer = memo(
const metadataJSON = JSON.stringify(image.metadata, null, 2); const metadataJSON = JSON.stringify(image.metadata, null, 2);
return ( return (
<Box <Flex
className={styleClass}
sx={{ sx={{
position: 'absolute',
top: '0',
width: '100%',
borderRadius: 'base',
padding: 4, padding: 4,
overflow: 'scroll', gap: 1,
maxHeight: APP_METADATA_HEIGHT, flexDirection: 'column',
height: '100%', width: 'full',
backdropFilter: 'blur(10px)', height: 'full',
backdropFilter: 'blur(20px)',
bg: 'whiteAlpha.600', bg: 'whiteAlpha.600',
_dark: { _dark: {
bg: 'blackAlpha.600', bg: 'blackAlpha.600',
}, },
}} }}
> >
<Flex gap={1} direction="column" width="100%">
<Flex gap={2}> <Flex gap={2}>
<Text fontWeight="semibold">File:</Text> <Text fontWeight="semibold">File:</Text>
<Link href={image.url} isExternal maxW="calc(100% - 3rem)"> <Link href={image.url} isExternal maxW="calc(100% - 3rem)">
@ -341,9 +336,7 @@ const ImageMetadataViewer = memo(
const { scale, strength, denoise_str } = postprocess; const { scale, strength, denoise_str } = postprocess;
return ( return (
<Flex key={i} pl={8} gap={1} direction="column"> <Flex key={i} pl={8} gap={1} direction="column">
<Text size="md">{`${ <Text size="md">{`${i + 1}: Upscale (ESRGAN)`}</Text>
i + 1
}: Upscale (ESRGAN)`}</Text>
<MetadataItem <MetadataItem
label="Scale" label="Scale"
value={scale} value={scale}
@ -419,11 +412,7 @@ const ImageMetadataViewer = memo(
</> </>
)} )}
{dreamPrompt && ( {dreamPrompt && (
<MetadataItem <MetadataItem withCopy label="Dream Prompt" value={dreamPrompt} />
withCopy
label="Dream Prompt"
value={dreamPrompt}
/>
)} )}
<Flex gap={2} direction="column"> <Flex gap={2} direction="column">
<Flex gap={2}> <Flex gap={2}>
@ -434,9 +423,7 @@ const ImageMetadataViewer = memo(
size="xs" size="xs"
variant="ghost" variant="ghost"
fontSize={14} fontSize={14}
onClick={() => onClick={() => navigator.clipboard.writeText(metadataJSON)}
navigator.clipboard.writeText(metadataJSON)
}
/> />
</Tooltip> </Tooltip>
<Text fontWeight="semibold">Metadata JSON:</Text> <Text fontWeight="semibold">Metadata JSON:</Text>
@ -451,8 +438,8 @@ const ImageMetadataViewer = memo(
borderRadius: 'base', borderRadius: 'base',
overflowX: 'scroll', overflowX: 'scroll',
wordBreak: 'break-all', wordBreak: 'break-all',
bg: 'whiteAlpha.200', bg: 'whiteAlpha.500',
_dark: { bg: 'blackAlpha.200' }, _dark: { bg: 'blackAlpha.500' },
}} }}
> >
<pre>{metadataJSON}</pre> <pre>{metadataJSON}</pre>
@ -467,7 +454,6 @@ const ImageMetadataViewer = memo(
</Center> </Center>
)} )}
</Flex> </Flex>
</Box>
); );
}, },
memoEqualityCheck memoEqualityCheck

View File

@ -1,20 +1,21 @@
import { Box, Flex, Grid } from '@chakra-ui/react'; import { Box, chakra, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store'; import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import CurrentImageButtons from 'features/gallery/components/CurrentImageButtons'; import CurrentImageButtons from 'features/gallery/components/CurrentImageButtons';
import ImageGalleryPanel from 'features/gallery/components/ImageGalleryPanel';
import ImageMetadataViewer from 'features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer'; import ImageMetadataViewer from 'features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer';
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons'; import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
import { gallerySelector } from 'features/gallery/store/gallerySelectors'; import { gallerySelector } from 'features/gallery/store/gallerySelectors';
import { setIsLightboxOpen } from 'features/lightbox/store/lightboxSlice'; import { setIsLightboxOpen } from 'features/lightbox/store/lightboxSlice';
import { uiSelector } from 'features/ui/store/uiSelectors'; import { uiSelector } from 'features/ui/store/uiSelectors';
import { AnimatePresence, motion } from 'framer-motion';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { BiExit } from 'react-icons/bi'; import { BiExit } from 'react-icons/bi';
import { TransformWrapper } from 'react-zoom-pan-pinch'; import { TransformWrapper } from 'react-zoom-pan-pinch';
import { PROGRESS_BAR_THICKNESS } from 'theme/util/constants';
import useImageTransform from '../hooks/useImageTransform'; import useImageTransform from '../hooks/useImageTransform';
import ReactPanZoomButtons from './ReactPanZoomButtons'; import ReactPanZoomButtons from './ReactPanZoomButtons';
import ReactPanZoomImage from './ReactPanZoomImage'; import ReactPanZoomImage from './ReactPanZoomImage';
@ -37,6 +38,8 @@ export const lightboxSelector = createSelector(
} }
); );
const ChakraMotionBox = chakra(motion.div);
export default function Lightbox() { export default function Lightbox() {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
@ -67,38 +70,44 @@ export default function Lightbox() {
); );
return ( return (
<AnimatePresence>
{isLightBoxOpen && (
<motion.div
key="lightbox"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15, ease: 'easeInOut' }}
style={{
display: 'flex',
width: '100vw',
height: `calc(100vh - ${PROGRESS_BAR_THICKNESS * 4}px)`,
position: 'fixed',
top: `${PROGRESS_BAR_THICKNESS * 4}px`,
background: 'var(--invokeai-colors-base-900)',
zIndex: 99,
}}
>
<TransformWrapper <TransformWrapper
centerOnInit centerOnInit
minScale={0.1} minScale={0.1}
initialPositionX={50} initialPositionX={50}
initialPositionY={50} initialPositionY={50}
>
<Box
sx={{
width: '100%',
height: '100%',
overflow: 'hidden',
position: 'absolute',
insetInlineStart: 0,
top: 0,
zIndex: 30,
animation: 'popIn 0.3s ease-in',
bg: 'base.800',
}}
> >
<Flex <Flex
sx={{ sx={{
flexDir: 'column', flexDir: 'column',
position: 'absolute', position: 'absolute',
top: 4,
insetInlineStart: 4, insetInlineStart: 4,
gap: 4, gap: 4,
zIndex: 3, zIndex: 3,
top: 4,
}} }}
> >
<IAIIconButton <IAIIconButton
icon={<BiExit />} icon={<BiExit />}
aria-label={t('accessibility.exitViewer')} aria-label="Exit Viewer"
className="lightbox-close-btn"
onClick={() => { onClick={() => {
dispatch(setIsLightboxOpen(false)); dispatch(setIsLightboxOpen(false));
}} }}
@ -112,32 +121,30 @@ export default function Lightbox() {
reset={reset} reset={reset}
/> />
</Flex> </Flex>
<Flex
<Flex>
<Grid
sx={{ sx={{
overflow: 'hidden', position: 'absolute',
gridTemplateColumns: 'auto max-content', top: 4,
placeItems: 'center', zIndex: 3,
width: '100vw', insetInlineStart: '50%',
height: '100vh', transform: 'translate(-50%, 0)',
bg: 'base.850',
}} }}
> >
<CurrentImageButtons />
</Flex>
{viewerImageToDisplay && ( {viewerImageToDisplay && (
<> <>
<ReactPanZoomImage <ReactPanZoomImage
rotation={rotation} rotation={rotation}
scaleX={scaleX} scaleX={scaleX}
scaleY={scaleY} scaleY={scaleY}
image={viewerImageToDisplay.url} image={viewerImageToDisplay}
styleClass="lightbox-image" styleClass="lightbox-image"
/> />
{shouldShowImageDetails && ( {shouldShowImageDetails && (
<ImageMetadataViewer image={viewerImageToDisplay} /> <ImageMetadataViewer image={viewerImageToDisplay} />
)} )}
</>
)}
{!shouldShowImageDetails && ( {!shouldShowImageDetails && (
<Box <Box
@ -145,28 +152,20 @@ export default function Lightbox() {
position: 'absolute', position: 'absolute',
top: 0, top: 0,
insetInlineStart: 0, insetInlineStart: 0,
w: `calc(100vw - ${8 * 2 * 4}px)`, w: '100vw',
h: '100vh', h: '100vh',
mx: 8, px: 16,
pointerEvents: 'none', pointerEvents: 'none',
}} }}
> >
<NextPrevImageButtons /> <NextPrevImageButtons />
</Box> </Box>
)} )}
</>
<Box )}
sx={{
position: 'absolute',
top: 4,
}}
>
<CurrentImageButtons />
</Box>
</Grid>
<ImageGalleryPanel />
</Flex>
</Box>
</TransformWrapper> </TransformWrapper>
</motion.div>
)}
</AnimatePresence>
); );
} }

View File

@ -1,8 +1,9 @@
import * as React from 'react'; import * as React from 'react';
import { TransformComponent, useTransformContext } from 'react-zoom-pan-pinch'; import { TransformComponent, useTransformContext } from 'react-zoom-pan-pinch';
import * as InvokeAI from 'app/invokeai';
type ReactPanZoomProps = { type ReactPanZoomProps = {
image: string; image: InvokeAI.Image;
styleClass?: string; styleClass?: string;
alt?: string; alt?: string;
ref?: React.Ref<HTMLImageElement>; ref?: React.Ref<HTMLImageElement>;
@ -34,7 +35,7 @@ export default function ReactPanZoomImage({
transform: `rotate(${rotation}deg) scaleX(${scaleX}) scaleY(${scaleY})`, transform: `rotate(${rotation}deg) scaleX(${scaleX}) scaleY(${scaleY})`,
width: '100%', width: '100%',
}} }}
src={image} src={image.url}
alt={alt} alt={alt}
ref={ref} ref={ref}
className={styleClass ? styleClass : ''} className={styleClass ? styleClass : ''}

View File

@ -189,7 +189,7 @@ export default function InvokeTabs() {
flexGrow={1} flexGrow={1}
> >
<TabList>{tabs}</TabList> <TabList>{tabs}</TabList>
<TabPanels>{isLightBoxOpen ? <Lightbox /> : tabPanels}</TabPanels> <TabPanels>{tabPanels}</TabPanels>
</Tabs> </Tabs>
); );
} }

View File

@ -18,16 +18,19 @@ import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvas
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { activeTabNameSelector, uiSelector } from '../store/uiSelectors'; import { activeTabNameSelector, uiSelector } from '../store/uiSelectors';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
const parametersPanelSelector = createSelector( const parametersPanelSelector = createSelector(
[uiSelector, activeTabNameSelector], [uiSelector, activeTabNameSelector, lightboxSelector],
(ui, activeTabName) => { (ui, activeTabName, lightbox) => {
const { shouldPinParametersPanel, shouldShowParametersPanel } = ui; const { shouldPinParametersPanel, shouldShowParametersPanel } = ui;
const { isLightboxOpen } = lightbox;
return { return {
shouldPinParametersPanel, shouldPinParametersPanel,
shouldShowParametersPanel, shouldShowParametersPanel,
isResizable: activeTabName !== 'unifiedCanvas', isResizable: activeTabName !== 'unifiedCanvas',
isLightboxOpen,
}; };
}, },
{ {
@ -44,8 +47,12 @@ type ParametersPanelProps = {
const ParametersPanel = ({ children }: ParametersPanelProps) => { const ParametersPanel = ({ children }: ParametersPanelProps) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { shouldPinParametersPanel, shouldShowParametersPanel, isResizable } = const {
useAppSelector(parametersPanelSelector); shouldPinParametersPanel,
shouldShowParametersPanel,
isResizable,
isLightboxOpen,
} = useAppSelector(parametersPanelSelector);
const closeParametersPanel = () => { const closeParametersPanel = () => {
dispatch(setShouldShowParametersPanel(false)); dispatch(setShouldShowParametersPanel(false));
@ -57,7 +64,8 @@ const ParametersPanel = ({ children }: ParametersPanelProps) => {
dispatch(toggleParametersPanel()); dispatch(toggleParametersPanel());
shouldPinParametersPanel && dispatch(requestCanvasRescale()); shouldPinParametersPanel && dispatch(requestCanvasRescale());
}, },
[shouldPinParametersPanel] { enabled: () => !isLightboxOpen },
[shouldPinParametersPanel, isLightboxOpen]
); );
useHotkeys( useHotkeys(
@ -86,7 +94,7 @@ const ParametersPanel = ({ children }: ParametersPanelProps) => {
isResizable={isResizable || !shouldPinParametersPanel} isResizable={isResizable || !shouldPinParametersPanel}
isOpen={shouldShowParametersPanel} isOpen={shouldShowParametersPanel}
onClose={closeParametersPanel} onClose={closeParametersPanel}
isPinned={shouldPinParametersPanel} isPinned={shouldPinParametersPanel || isLightboxOpen}
sx={{ sx={{
borderColor: 'base.700', borderColor: 'base.700',
p: shouldPinParametersPanel ? 0 : 4, p: shouldPinParametersPanel ? 0 : 4,