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

View File

@ -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,
});

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 { 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>
);

View File

@ -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

View File

@ -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

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 { 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>
);
}

View File

@ -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 : ''}

View File

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

View File

@ -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,