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 && (
<ImageMetadataViewer <Box
image={imageToDisplay} sx={{
styleClass="current-image-metadata" position: 'absolute',
/> top: '0',
width: '100%',
height: '100%',
borderRadius: 'base',
overflow: 'scroll',
maxHeight: APP_METADATA_HEIGHT,
}}
>
<ImageMetadataViewer
image={imageToDisplay}
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,304 +170,290 @@ 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)"> {image.url.length > 64
{image.url.length > 64 ? image.url.substring(0, 64).concat('...')
? image.url.substring(0, 64).concat('...') : image.url}
: image.url} <ExternalLinkIcon mx="2px" />
<ExternalLinkIcon mx="2px" /> </Link>
</Link> </Flex>
</Flex> {Object.keys(metadata).length > 0 ? (
{Object.keys(metadata).length > 0 ? ( <>
<> {type && <MetadataItem label="Generation type" value={type} />}
{type && <MetadataItem label="Generation type" value={type} />} {image.metadata?.model_weights && (
{image.metadata?.model_weights && ( <MetadataItem
<MetadataItem label="Model"
label="Model" value={image.metadata.model_weights}
value={image.metadata.model_weights} />
/> )}
)} {['esrgan', 'gfpgan'].includes(type) && (
{['esrgan', 'gfpgan'].includes(type) && ( <MetadataItem label="Original image" value={orig_path} />
<MetadataItem label="Original image" value={orig_path} /> )}
)} {prompt && (
{prompt && ( <MetadataItem
<MetadataItem label="Prompt"
label="Prompt" labelPosition="top"
labelPosition="top" value={
value={ typeof prompt === 'string' ? prompt : promptToString(prompt)
typeof prompt === 'string' ? prompt : promptToString(prompt) }
} onClick={() => setBothPrompts(prompt)}
onClick={() => setBothPrompts(prompt)} />
/> )}
)} {seed !== undefined && (
{seed !== undefined && ( <MetadataItem
<MetadataItem label="Seed"
label="Seed" value={seed}
value={seed} onClick={() => dispatch(setSeed(seed))}
onClick={() => dispatch(setSeed(seed))} />
/> )}
)} {threshold !== undefined && (
{threshold !== undefined && ( <MetadataItem
<MetadataItem label="Noise Threshold"
label="Noise Threshold" value={threshold}
value={threshold} onClick={() => dispatch(setThreshold(threshold))}
onClick={() => dispatch(setThreshold(threshold))} />
/> )}
)} {perlin !== undefined && (
{perlin !== undefined && ( <MetadataItem
<MetadataItem label="Perlin Noise"
label="Perlin Noise" value={perlin}
value={perlin} onClick={() => dispatch(setPerlin(perlin))}
onClick={() => dispatch(setPerlin(perlin))} />
/> )}
)} {sampler && (
{sampler && ( <MetadataItem
<MetadataItem label="Sampler"
label="Sampler" value={sampler}
value={sampler} onClick={() => dispatch(setSampler(sampler))}
onClick={() => dispatch(setSampler(sampler))} />
/> )}
)} {steps && (
{steps && ( <MetadataItem
<MetadataItem label="Steps"
label="Steps" value={steps}
value={steps} onClick={() => dispatch(setSteps(steps))}
onClick={() => dispatch(setSteps(steps))} />
/> )}
)} {cfg_scale !== undefined && (
{cfg_scale !== undefined && ( <MetadataItem
<MetadataItem label="CFG scale"
label="CFG scale" value={cfg_scale}
value={cfg_scale} onClick={() => dispatch(setCfgScale(cfg_scale))}
onClick={() => dispatch(setCfgScale(cfg_scale))} />
/> )}
)} {variations && variations.length > 0 && (
{variations && variations.length > 0 && ( <MetadataItem
<MetadataItem label="Seed-weight pairs"
label="Seed-weight pairs" value={seedWeightsToString(variations)}
value={seedWeightsToString(variations)} onClick={() =>
onClick={() => dispatch(setSeedWeights(seedWeightsToString(variations)))
dispatch(setSeedWeights(seedWeightsToString(variations))) }
} />
/> )}
)} {seamless && (
{seamless && ( <MetadataItem
<MetadataItem label="Seamless"
label="Seamless" value={seamless}
value={seamless} onClick={() => dispatch(setSeamless(seamless))}
onClick={() => dispatch(setSeamless(seamless))} />
/> )}
)} {hires_fix && (
{hires_fix && ( <MetadataItem
<MetadataItem label="High Resolution Optimization"
label="High Resolution Optimization" value={hires_fix}
value={hires_fix} onClick={() => dispatch(setHiresFix(hires_fix))}
onClick={() => dispatch(setHiresFix(hires_fix))} />
/> )}
)} {width && (
{width && ( <MetadataItem
<MetadataItem label="Width"
label="Width" value={width}
value={width} onClick={() => dispatch(setWidth(width))}
onClick={() => dispatch(setWidth(width))} />
/> )}
)} {height && (
{height && ( <MetadataItem
<MetadataItem label="Height"
label="Height" value={height}
value={height} onClick={() => dispatch(setHeight(height))}
onClick={() => dispatch(setHeight(height))} />
/> )}
)} {init_image_path && (
{init_image_path && ( <MetadataItem
<MetadataItem label="Initial image"
label="Initial image" value={init_image_path}
value={init_image_path} isLink
isLink onClick={() => dispatch(setInitialImage(init_image_path))}
onClick={() => dispatch(setInitialImage(init_image_path))} />
/> )}
)} {mask_image_path && (
{mask_image_path && ( <MetadataItem
<MetadataItem label="Mask image"
label="Mask image" value={mask_image_path}
value={mask_image_path} isLink
isLink onClick={() => dispatch(setMaskPath(mask_image_path))}
onClick={() => dispatch(setMaskPath(mask_image_path))} />
/> )}
)} {type === 'img2img' && strength && (
{type === 'img2img' && strength && ( <MetadataItem
<MetadataItem label="Image to image strength"
label="Image to image strength" value={strength}
value={strength} onClick={() => dispatch(setImg2imgStrength(strength))}
onClick={() => dispatch(setImg2imgStrength(strength))} />
/> )}
)} {fit && (
{fit && ( <MetadataItem
<MetadataItem label="Image to image fit"
label="Image to image fit" value={fit}
value={fit} onClick={() => dispatch(setShouldFitToWidthHeight(fit))}
onClick={() => dispatch(setShouldFitToWidthHeight(fit))} />
/> )}
)} {postprocessing && postprocessing.length > 0 && (
{postprocessing && postprocessing.length > 0 && ( <>
<> <Heading size="sm">Postprocessing</Heading>
<Heading size="sm">Postprocessing</Heading> {postprocessing.map(
{postprocessing.map( (
( postprocess: InvokeAI.PostProcessedImageMetadata,
postprocess: InvokeAI.PostProcessedImageMetadata, i: number
i: number ) => {
) => { if (postprocess.type === 'esrgan') {
if (postprocess.type === 'esrgan') { 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">{`${i + 1}: Upscale (ESRGAN)`}</Text>
<Text size="md">{`${ <MetadataItem
i + 1 label="Scale"
}: Upscale (ESRGAN)`}</Text> value={scale}
onClick={() => dispatch(setUpscalingLevel(scale))}
/>
<MetadataItem
label="Strength"
value={strength}
onClick={() =>
dispatch(setUpscalingStrength(strength))
}
/>
{denoise_str !== undefined && (
<MetadataItem <MetadataItem
label="Scale" label="Denoising strength"
value={scale} value={denoise_str}
onClick={() => dispatch(setUpscalingLevel(scale))}
/>
<MetadataItem
label="Strength"
value={strength}
onClick={() => onClick={() =>
dispatch(setUpscalingStrength(strength)) dispatch(setUpscalingDenoising(denoise_str))
} }
/> />
{denoise_str !== undefined && ( )}
<MetadataItem </Flex>
label="Denoising strength" );
value={denoise_str} } else if (postprocess.type === 'gfpgan') {
onClick={() => const { strength } = postprocess;
dispatch(setUpscalingDenoising(denoise_str)) return (
} <Flex key={i} pl={8} gap={1} direction="column">
/> <Text size="md">{`${
)} i + 1
</Flex> }: Face restoration (GFPGAN)`}</Text>
);
} else if (postprocess.type === 'gfpgan') {
const { strength } = postprocess;
return (
<Flex key={i} pl={8} gap={1} direction="column">
<Text size="md">{`${
i + 1
}: Face restoration (GFPGAN)`}</Text>
<MetadataItem <MetadataItem
label="Strength" label="Strength"
value={strength} value={strength}
onClick={() => { onClick={() => {
dispatch(setFacetoolStrength(strength)); dispatch(setFacetoolStrength(strength));
dispatch(setFacetoolType('gfpgan')); dispatch(setFacetoolType('gfpgan'));
}} }}
/> />
</Flex> </Flex>
); );
} else if (postprocess.type === 'codeformer') { } else if (postprocess.type === 'codeformer') {
const { strength, fidelity } = postprocess; const { strength, fidelity } = 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 i + 1
}: Face restoration (Codeformer)`}</Text> }: Face restoration (Codeformer)`}</Text>
<MetadataItem
label="Strength"
value={strength}
onClick={() => {
dispatch(setFacetoolStrength(strength));
dispatch(setFacetoolType('codeformer'));
}}
/>
{fidelity && (
<MetadataItem <MetadataItem
label="Strength" label="Fidelity"
value={strength} value={fidelity}
onClick={() => { onClick={() => {
dispatch(setFacetoolStrength(strength)); dispatch(setCodeformerFidelity(fidelity));
dispatch(setFacetoolType('codeformer')); dispatch(setFacetoolType('codeformer'));
}} }}
/> />
{fidelity && ( )}
<MetadataItem </Flex>
label="Fidelity" );
value={fidelity}
onClick={() => {
dispatch(setCodeformerFidelity(fidelity));
dispatch(setFacetoolType('codeformer'));
}}
/>
)}
</Flex>
);
}
} }
)} }
</> )}
)} </>
{dreamPrompt && ( )}
<MetadataItem {dreamPrompt && (
withCopy <MetadataItem withCopy label="Dream Prompt" value={dreamPrompt} />
label="Dream Prompt" )}
value={dreamPrompt} <Flex gap={2} direction="column">
/> <Flex gap={2}>
)} <Tooltip label="Copy metadata JSON">
<Flex gap={2} direction="column"> <IconButton
<Flex gap={2}> aria-label={t('accessibility.copyMetadataJson')}
<Tooltip label="Copy metadata JSON"> icon={<FaCopy />}
<IconButton size="xs"
aria-label={t('accessibility.copyMetadataJson')} variant="ghost"
icon={<FaCopy />} fontSize={14}
size="xs" onClick={() => navigator.clipboard.writeText(metadataJSON)}
variant="ghost" />
fontSize={14} </Tooltip>
onClick={() => <Text fontWeight="semibold">Metadata JSON:</Text>
navigator.clipboard.writeText(metadataJSON)
}
/>
</Tooltip>
<Text fontWeight="semibold">Metadata JSON:</Text>
</Flex>
<Box
sx={{
mt: 0,
mr: 2,
mb: 4,
ml: 2,
padding: 4,
borderRadius: 'base',
overflowX: 'scroll',
wordBreak: 'break-all',
bg: 'whiteAlpha.200',
_dark: { bg: 'blackAlpha.200' },
}}
>
<pre>{metadataJSON}</pre>
</Box>
</Flex> </Flex>
</> <Box
) : ( sx={{
<Center width="100%" pt={10}> mt: 0,
<Text fontSize="lg" fontWeight="semibold"> mr: 2,
No metadata available mb: 4,
</Text> ml: 2,
</Center> padding: 4,
)} borderRadius: 'base',
</Flex> overflowX: 'scroll',
</Box> wordBreak: 'break-all',
bg: 'whiteAlpha.500',
_dark: { bg: 'blackAlpha.500' },
}}
>
<pre>{metadataJSON}</pre>
</Box>
</Flex>
</>
) : (
<Center width="100%" pt={10}>
<Text fontSize="lg" fontWeight="semibold">
No metadata available
</Text>
</Center>
)}
</Flex>
); );
}, },
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,106 +70,102 @@ export default function Lightbox() {
); );
return ( return (
<TransformWrapper <AnimatePresence>
centerOnInit {isLightBoxOpen && (
minScale={0.1} <motion.div
initialPositionX={50} key="lightbox"
initialPositionY={50} initial={{ opacity: 0 }}
> animate={{ opacity: 1 }}
<Box exit={{ opacity: 0 }}
sx={{ transition={{ duration: 0.15, ease: 'easeInOut' }}
width: '100%', style={{
height: '100%', display: 'flex',
overflow: 'hidden', width: '100vw',
position: 'absolute', height: `calc(100vh - ${PROGRESS_BAR_THICKNESS * 4}px)`,
insetInlineStart: 0, position: 'fixed',
top: 0, top: `${PROGRESS_BAR_THICKNESS * 4}px`,
zIndex: 30, background: 'var(--invokeai-colors-base-900)',
animation: 'popIn 0.3s ease-in', zIndex: 99,
bg: 'base.800',
}}
>
<Flex
sx={{
flexDir: 'column',
position: 'absolute',
top: 4,
insetInlineStart: 4,
gap: 4,
zIndex: 3,
}} }}
> >
<IAIIconButton <TransformWrapper
icon={<BiExit />} centerOnInit
aria-label={t('accessibility.exitViewer')} minScale={0.1}
onClick={() => { initialPositionX={50}
dispatch(setIsLightboxOpen(false)); initialPositionY={50}
}}
fontSize={20}
/>
<ReactPanZoomButtons
flipHorizontally={flipHorizontally}
flipVertically={flipVertically}
rotateCounterClockwise={rotateCounterClockwise}
rotateClockwise={rotateClockwise}
reset={reset}
/>
</Flex>
<Flex>
<Grid
sx={{
overflow: 'hidden',
gridTemplateColumns: 'auto max-content',
placeItems: 'center',
width: '100vw',
height: '100vh',
bg: 'base.850',
}}
> >
<Flex
sx={{
flexDir: 'column',
position: 'absolute',
insetInlineStart: 4,
gap: 4,
zIndex: 3,
top: 4,
}}
>
<IAIIconButton
icon={<BiExit />}
aria-label="Exit Viewer"
className="lightbox-close-btn"
onClick={() => {
dispatch(setIsLightboxOpen(false));
}}
fontSize={20}
/>
<ReactPanZoomButtons
flipHorizontally={flipHorizontally}
flipVertically={flipVertically}
rotateCounterClockwise={rotateCounterClockwise}
rotateClockwise={rotateClockwise}
reset={reset}
/>
</Flex>
<Flex
sx={{
position: 'absolute',
top: 4,
zIndex: 3,
insetInlineStart: '50%',
transform: 'translate(-50%, 0)',
}}
>
<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 && (
<Box
sx={{
position: 'absolute',
top: 0,
insetInlineStart: 0,
w: '100vw',
h: '100vh',
px: 16,
pointerEvents: 'none',
}}
>
<NextPrevImageButtons />
</Box>
)}
</> </>
)} )}
</TransformWrapper>
{!shouldShowImageDetails && ( </motion.div>
<Box )}
sx={{ </AnimatePresence>
position: 'absolute',
top: 0,
insetInlineStart: 0,
w: `calc(100vw - ${8 * 2 * 4}px)`,
h: '100vh',
mx: 8,
pointerEvents: 'none',
}}
>
<NextPrevImageButtons />
</Box>
)}
<Box
sx={{
position: 'absolute',
top: 4,
}}
>
<CurrentImageButtons />
</Box>
</Grid>
<ImageGalleryPanel />
</Flex>
</Box>
</TransformWrapper>
); );
} }

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,