mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
fix(ui): fix lightbox
This commit is contained in:
parent
90525b1c43
commit
ee6df5852a
@ -30,6 +30,7 @@ const App = () => {
|
||||
|
||||
return (
|
||||
<Grid w="100vw" h="100vh">
|
||||
<Lightbox />
|
||||
<ImageUploader>
|
||||
<ProgressBar />
|
||||
<Grid
|
||||
@ -55,7 +56,6 @@ const App = () => {
|
||||
<Portal>
|
||||
<FloatingGalleryButton />
|
||||
</Portal>
|
||||
<Lightbox />
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
@ -60,6 +60,10 @@ const galleryBlacklist = [
|
||||
'intermediateImage',
|
||||
].map((blacklistItem) => `gallery.${blacklistItem}`);
|
||||
|
||||
const lightboxBlacklist = ['isLightboxOpen'].map(
|
||||
(blacklistItem) => `lightbox.${blacklistItem}`
|
||||
);
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
generation: generationReducer,
|
||||
postprocessing: postprocessingReducer,
|
||||
@ -74,7 +78,12 @@ const rootPersistConfig = getPersistConfig({
|
||||
key: 'root',
|
||||
storage,
|
||||
rootReducer,
|
||||
blacklist: [...canvasBlacklist, ...systemBlacklist, ...galleryBlacklist],
|
||||
blacklist: [
|
||||
...canvasBlacklist,
|
||||
...systemBlacklist,
|
||||
...galleryBlacklist,
|
||||
...lightboxBlacklist,
|
||||
],
|
||||
debounce: 300,
|
||||
});
|
||||
|
||||
|
@ -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 { useAppSelector } from 'app/storeHooks';
|
||||
import { GalleryState } from 'features/gallery/store/gallerySlice';
|
||||
import { uiSelector } from 'features/ui/store/uiSelectors';
|
||||
import { isEqual } from 'lodash';
|
||||
import { APP_METADATA_HEIGHT } from 'theme/util/constants';
|
||||
|
||||
import { gallerySelector } from '../store/gallerySelectors';
|
||||
import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer';
|
||||
@ -60,10 +61,22 @@ export default function CurrentImagePreview() {
|
||||
)}
|
||||
{!shouldShowImageDetails && <NextPrevImageButtons />}
|
||||
{shouldShowImageDetails && imageToDisplay && (
|
||||
<Box
|
||||
sx={{
|
||||
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>
|
||||
);
|
||||
|
@ -25,6 +25,7 @@ import {
|
||||
} from 'features/ui/store/uiSelectors';
|
||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||
import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
|
||||
|
||||
const GALLERY_TAB_WIDTHS: Record<
|
||||
InvokeTabName,
|
||||
@ -39,10 +40,17 @@ const GALLERY_TAB_WIDTHS: Record<
|
||||
};
|
||||
|
||||
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 { galleryImageMinimumWidth } = gallery;
|
||||
const { isLightboxOpen } = lightbox;
|
||||
|
||||
return {
|
||||
activeTabName,
|
||||
@ -51,6 +59,7 @@ const galleryPanelSelector = createSelector(
|
||||
shouldShowGallery,
|
||||
galleryImageMinimumWidth,
|
||||
isResizable: activeTabName !== 'unifiedCanvas',
|
||||
isLightboxOpen,
|
||||
};
|
||||
},
|
||||
{
|
||||
@ -69,6 +78,7 @@ export default function ImageGalleryPanel() {
|
||||
activeTabName,
|
||||
isStaging,
|
||||
isResizable,
|
||||
isLightboxOpen,
|
||||
} = useAppSelector(galleryPanelSelector);
|
||||
|
||||
const handleSetShouldPinGallery = () => {
|
||||
@ -174,7 +184,7 @@ export default function ImageGalleryPanel() {
|
||||
isResizable={isResizable || !shouldPinGallery}
|
||||
isOpen={shouldShowGallery}
|
||||
onClose={handleCloseGallery}
|
||||
isPinned={shouldPinGallery}
|
||||
isPinned={shouldPinGallery && !isLightboxOpen}
|
||||
minWidth={
|
||||
shouldPinGallery
|
||||
? GALLERY_TAB_WIDTHS[activeTabName].galleryMinWidth
|
||||
|
@ -170,25 +170,20 @@ const ImageMetadataViewer = memo(
|
||||
const metadataJSON = JSON.stringify(image.metadata, null, 2);
|
||||
|
||||
return (
|
||||
<Box
|
||||
className={styleClass}
|
||||
<Flex
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '0',
|
||||
width: '100%',
|
||||
borderRadius: 'base',
|
||||
padding: 4,
|
||||
overflow: 'scroll',
|
||||
maxHeight: APP_METADATA_HEIGHT,
|
||||
height: '100%',
|
||||
backdropFilter: 'blur(10px)',
|
||||
gap: 1,
|
||||
flexDirection: 'column',
|
||||
width: 'full',
|
||||
height: 'full',
|
||||
backdropFilter: 'blur(20px)',
|
||||
bg: 'whiteAlpha.600',
|
||||
_dark: {
|
||||
bg: 'blackAlpha.600',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Flex gap={1} direction="column" width="100%">
|
||||
<Flex gap={2}>
|
||||
<Text fontWeight="semibold">File:</Text>
|
||||
<Link href={image.url} isExternal maxW="calc(100% - 3rem)">
|
||||
@ -341,9 +336,7 @@ const ImageMetadataViewer = memo(
|
||||
const { scale, strength, denoise_str } = postprocess;
|
||||
return (
|
||||
<Flex key={i} pl={8} gap={1} direction="column">
|
||||
<Text size="md">{`${
|
||||
i + 1
|
||||
}: Upscale (ESRGAN)`}</Text>
|
||||
<Text size="md">{`${i + 1}: Upscale (ESRGAN)`}</Text>
|
||||
<MetadataItem
|
||||
label="Scale"
|
||||
value={scale}
|
||||
@ -419,11 +412,7 @@ const ImageMetadataViewer = memo(
|
||||
</>
|
||||
)}
|
||||
{dreamPrompt && (
|
||||
<MetadataItem
|
||||
withCopy
|
||||
label="Dream Prompt"
|
||||
value={dreamPrompt}
|
||||
/>
|
||||
<MetadataItem withCopy label="Dream Prompt" value={dreamPrompt} />
|
||||
)}
|
||||
<Flex gap={2} direction="column">
|
||||
<Flex gap={2}>
|
||||
@ -434,9 +423,7 @@ const ImageMetadataViewer = memo(
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
fontSize={14}
|
||||
onClick={() =>
|
||||
navigator.clipboard.writeText(metadataJSON)
|
||||
}
|
||||
onClick={() => navigator.clipboard.writeText(metadataJSON)}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Text fontWeight="semibold">Metadata JSON:</Text>
|
||||
@ -451,8 +438,8 @@ const ImageMetadataViewer = memo(
|
||||
borderRadius: 'base',
|
||||
overflowX: 'scroll',
|
||||
wordBreak: 'break-all',
|
||||
bg: 'whiteAlpha.200',
|
||||
_dark: { bg: 'blackAlpha.200' },
|
||||
bg: 'whiteAlpha.500',
|
||||
_dark: { bg: 'blackAlpha.500' },
|
||||
}}
|
||||
>
|
||||
<pre>{metadataJSON}</pre>
|
||||
@ -467,7 +454,6 @@ const ImageMetadataViewer = memo(
|
||||
</Center>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
memoEqualityCheck
|
||||
|
@ -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 { RootState } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import CurrentImageButtons from 'features/gallery/components/CurrentImageButtons';
|
||||
import ImageGalleryPanel from 'features/gallery/components/ImageGalleryPanel';
|
||||
import ImageMetadataViewer from 'features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer';
|
||||
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
|
||||
import { gallerySelector } from 'features/gallery/store/gallerySelectors';
|
||||
import { setIsLightboxOpen } from 'features/lightbox/store/lightboxSlice';
|
||||
import { uiSelector } from 'features/ui/store/uiSelectors';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { isEqual } from 'lodash';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { BiExit } from 'react-icons/bi';
|
||||
import { TransformWrapper } from 'react-zoom-pan-pinch';
|
||||
import { PROGRESS_BAR_THICKNESS } from 'theme/util/constants';
|
||||
import useImageTransform from '../hooks/useImageTransform';
|
||||
import ReactPanZoomButtons from './ReactPanZoomButtons';
|
||||
import ReactPanZoomImage from './ReactPanZoomImage';
|
||||
@ -37,6 +38,8 @@ export const lightboxSelector = createSelector(
|
||||
}
|
||||
);
|
||||
|
||||
const ChakraMotionBox = chakra(motion.div);
|
||||
|
||||
export default function Lightbox() {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
@ -67,38 +70,44 @@ export default function Lightbox() {
|
||||
);
|
||||
|
||||
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
|
||||
centerOnInit
|
||||
minScale={0.1}
|
||||
initialPositionX={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
|
||||
sx={{
|
||||
flexDir: 'column',
|
||||
position: 'absolute',
|
||||
top: 4,
|
||||
insetInlineStart: 4,
|
||||
gap: 4,
|
||||
zIndex: 3,
|
||||
top: 4,
|
||||
}}
|
||||
>
|
||||
<IAIIconButton
|
||||
icon={<BiExit />}
|
||||
aria-label={t('accessibility.exitViewer')}
|
||||
aria-label="Exit Viewer"
|
||||
className="lightbox-close-btn"
|
||||
onClick={() => {
|
||||
dispatch(setIsLightboxOpen(false));
|
||||
}}
|
||||
@ -112,32 +121,30 @@ export default function Lightbox() {
|
||||
reset={reset}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Flex>
|
||||
<Grid
|
||||
<Flex
|
||||
sx={{
|
||||
overflow: 'hidden',
|
||||
gridTemplateColumns: 'auto max-content',
|
||||
placeItems: 'center',
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
bg: 'base.850',
|
||||
position: 'absolute',
|
||||
top: 4,
|
||||
zIndex: 3,
|
||||
insetInlineStart: '50%',
|
||||
transform: 'translate(-50%, 0)',
|
||||
}}
|
||||
>
|
||||
<CurrentImageButtons />
|
||||
</Flex>
|
||||
|
||||
{viewerImageToDisplay && (
|
||||
<>
|
||||
<ReactPanZoomImage
|
||||
rotation={rotation}
|
||||
scaleX={scaleX}
|
||||
scaleY={scaleY}
|
||||
image={viewerImageToDisplay.url}
|
||||
image={viewerImageToDisplay}
|
||||
styleClass="lightbox-image"
|
||||
/>
|
||||
{shouldShowImageDetails && (
|
||||
<ImageMetadataViewer image={viewerImageToDisplay} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{!shouldShowImageDetails && (
|
||||
<Box
|
||||
@ -145,28 +152,20 @@ export default function Lightbox() {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
insetInlineStart: 0,
|
||||
w: `calc(100vw - ${8 * 2 * 4}px)`,
|
||||
w: '100vw',
|
||||
h: '100vh',
|
||||
mx: 8,
|
||||
px: 16,
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
>
|
||||
<NextPrevImageButtons />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 4,
|
||||
}}
|
||||
>
|
||||
<CurrentImageButtons />
|
||||
</Box>
|
||||
</Grid>
|
||||
<ImageGalleryPanel />
|
||||
</Flex>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</TransformWrapper>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import { TransformComponent, useTransformContext } from 'react-zoom-pan-pinch';
|
||||
import * as InvokeAI from 'app/invokeai';
|
||||
|
||||
type ReactPanZoomProps = {
|
||||
image: string;
|
||||
image: InvokeAI.Image;
|
||||
styleClass?: string;
|
||||
alt?: string;
|
||||
ref?: React.Ref<HTMLImageElement>;
|
||||
@ -34,7 +35,7 @@ export default function ReactPanZoomImage({
|
||||
transform: `rotate(${rotation}deg) scaleX(${scaleX}) scaleY(${scaleY})`,
|
||||
width: '100%',
|
||||
}}
|
||||
src={image}
|
||||
src={image.url}
|
||||
alt={alt}
|
||||
ref={ref}
|
||||
className={styleClass ? styleClass : ''}
|
||||
|
@ -189,7 +189,7 @@ export default function InvokeTabs() {
|
||||
flexGrow={1}
|
||||
>
|
||||
<TabList>{tabs}</TabList>
|
||||
<TabPanels>{isLightBoxOpen ? <Lightbox /> : tabPanels}</TabPanels>
|
||||
<TabPanels>{tabPanels}</TabPanels>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
@ -18,16 +18,19 @@ import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvas
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { activeTabNameSelector, uiSelector } from '../store/uiSelectors';
|
||||
import { isEqual } from 'lodash';
|
||||
import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
|
||||
|
||||
const parametersPanelSelector = createSelector(
|
||||
[uiSelector, activeTabNameSelector],
|
||||
(ui, activeTabName) => {
|
||||
[uiSelector, activeTabNameSelector, lightboxSelector],
|
||||
(ui, activeTabName, lightbox) => {
|
||||
const { shouldPinParametersPanel, shouldShowParametersPanel } = ui;
|
||||
const { isLightboxOpen } = lightbox;
|
||||
|
||||
return {
|
||||
shouldPinParametersPanel,
|
||||
shouldShowParametersPanel,
|
||||
isResizable: activeTabName !== 'unifiedCanvas',
|
||||
isLightboxOpen,
|
||||
};
|
||||
},
|
||||
{
|
||||
@ -44,8 +47,12 @@ type ParametersPanelProps = {
|
||||
const ParametersPanel = ({ children }: ParametersPanelProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { shouldPinParametersPanel, shouldShowParametersPanel, isResizable } =
|
||||
useAppSelector(parametersPanelSelector);
|
||||
const {
|
||||
shouldPinParametersPanel,
|
||||
shouldShowParametersPanel,
|
||||
isResizable,
|
||||
isLightboxOpen,
|
||||
} = useAppSelector(parametersPanelSelector);
|
||||
|
||||
const closeParametersPanel = () => {
|
||||
dispatch(setShouldShowParametersPanel(false));
|
||||
@ -57,7 +64,8 @@ const ParametersPanel = ({ children }: ParametersPanelProps) => {
|
||||
dispatch(toggleParametersPanel());
|
||||
shouldPinParametersPanel && dispatch(requestCanvasRescale());
|
||||
},
|
||||
[shouldPinParametersPanel]
|
||||
{ enabled: () => !isLightboxOpen },
|
||||
[shouldPinParametersPanel, isLightboxOpen]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
@ -86,7 +94,7 @@ const ParametersPanel = ({ children }: ParametersPanelProps) => {
|
||||
isResizable={isResizable || !shouldPinParametersPanel}
|
||||
isOpen={shouldShowParametersPanel}
|
||||
onClose={closeParametersPanel}
|
||||
isPinned={shouldPinParametersPanel}
|
||||
isPinned={shouldPinParametersPanel || isLightboxOpen}
|
||||
sx={{
|
||||
borderColor: 'base.700',
|
||||
p: shouldPinParametersPanel ? 0 : 4,
|
||||
|
Loading…
Reference in New Issue
Block a user