Adds Maintain Aspect Ratio checkbox to ImageGallery

This commit is contained in:
psychedelicious 2022-10-28 00:48:37 +11:00
parent 5cae8206f9
commit 38fd0668ba
7 changed files with 77 additions and 33 deletions

View File

@ -1,6 +1,8 @@
.invokeai__checkbox { .invokeai__checkbox {
.chakra-checkbox__label { .chakra-checkbox__label {
margin-top: 1px; margin-top: 1px;
color: var(--text-color-secondary);
font-size: 0.9rem;
} }
.chakra-checkbox__control { .chakra-checkbox__control {

View File

@ -12,7 +12,6 @@
.hoverable-image-image { .hoverable-image-image {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover;
max-width: 100%; max-width: 100%;
max-height: 100%; max-height: 100%;
} }

View File

@ -6,7 +6,7 @@ import {
Tooltip, Tooltip,
useToast, useToast,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { RootState, useAppDispatch, useAppSelector } from '../../app/store'; import { useAppDispatch, useAppSelector } from '../../app/store';
import { setCurrentImage } from './gallerySlice'; import { setCurrentImage } from './gallerySlice';
import { FaCheck, FaTrashAlt } from 'react-icons/fa'; import { FaCheck, FaTrashAlt } from 'react-icons/fa';
import DeleteImageModal from './DeleteImageModal'; import DeleteImageModal from './DeleteImageModal';
@ -40,7 +40,7 @@ const memoEqualityCheck = (
*/ */
const HoverableImage = memo((props: HoverableImageProps) => { const HoverableImage = memo((props: HoverableImageProps) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { activeTabName } = useAppSelector(hoverableImageSelector); const { activeTabName, galleryImageObjectFit } = useAppSelector(hoverableImageSelector);
const [isHovered, setIsHovered] = useState<boolean>(false); const [isHovered, setIsHovered] = useState<boolean>(false);
@ -148,7 +148,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
> >
<Image <Image
className="hoverable-image-image" className="hoverable-image-image"
objectFit="cover" objectFit={galleryImageObjectFit}
rounded={'md'} rounded={'md'}
src={url} src={url}
loading={'lazy'} loading={'lazy'}

View File

@ -62,10 +62,17 @@
} }
} }
.image-gallery-size-popover { .image-gallery-settings-popover {
display: grid; display: flex;
grid-template-columns: repeat(2, max-content); flex-direction: column;
column-gap: 0.5rem; row-gap: 0.5rem;
div {
display: flex;
column-gap: 0.5rem;
align-items: center;
justify-content: space-between;
}
} }
h1 { h1 {

View File

@ -1,7 +1,7 @@
import { Button } from '@chakra-ui/button'; import { Button } from '@chakra-ui/button';
import { NumberSize, Resizable, Size } from 're-resizable'; import { NumberSize, Resizable, Size } from 're-resizable';
import React, { useEffect, useRef, useState } from 'react'; import React, { ChangeEvent, useEffect, useRef, useState } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { MdClear, MdPhotoLibrary } from 'react-icons/md'; import { MdClear, MdPhotoLibrary } from 'react-icons/md';
import { BsPinAngleFill } from 'react-icons/bs'; import { BsPinAngleFill } from 'react-icons/bs';
@ -12,6 +12,7 @@ import {
selectNextImage, selectNextImage,
selectPrevImage, selectPrevImage,
setGalleryImageMinimumWidth, setGalleryImageMinimumWidth,
setGalleryImageObjectFit,
setGalleryScrollPosition, setGalleryScrollPosition,
setShouldPinGallery, setShouldPinGallery,
} from './gallerySlice'; } from './gallerySlice';
@ -25,6 +26,7 @@ import { FaWrench } from 'react-icons/fa';
import IAIPopover from '../../common/components/IAIPopover'; import IAIPopover from '../../common/components/IAIPopover';
import IAISlider from '../../common/components/IAISlider'; import IAISlider from '../../common/components/IAISlider';
import { BiReset } from 'react-icons/bi'; import { BiReset } from 'react-icons/bi';
import IAICheckbox from '../../common/components/IAICheckbox';
export default function ImageGallery() { export default function ImageGallery() {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -40,6 +42,7 @@ export default function ImageGallery() {
galleryImageMinimumWidth, galleryImageMinimumWidth,
galleryGridTemplateColumns, galleryGridTemplateColumns,
activeTabName, activeTabName,
galleryImageObjectFit,
} = useAppSelector(imageGallerySelector); } = useAppSelector(imageGallerySelector);
const [gallerySize, setGallerySize] = useState<Size>({ const [gallerySize, setGallerySize] = useState<Size>({
@ -310,6 +313,7 @@ export default function ImageGallery() {
<IAIPopover <IAIPopover
trigger="click" trigger="click"
hasArrow={activeTabName === 'inpainting' ? false : true} hasArrow={activeTabName === 'inpainting' ? false : true}
// styleClass="image-gallery-settings-popover"
triggerComponent={ triggerComponent={
<IAIIconButton <IAIIconButton
size={'sm'} size={'sm'}
@ -319,29 +323,47 @@ export default function ImageGallery() {
cursor={'pointer'} cursor={'pointer'}
/> />
} }
styleClass="image-gallery-size-popover"
> >
<IAISlider <div className="image-gallery-settings-popover">
value={galleryImageMinimumWidth} <div>
onChange={handleChangeGalleryImageMinimumWidth} <IAISlider
min={32} value={galleryImageMinimumWidth}
max={256} onChange={handleChangeGalleryImageMinimumWidth}
width={100} min={32}
label={'Image Size'} max={256}
formLabelProps={{ style: { fontSize: '0.9rem' } }} width={100}
sliderThumbTooltipProps={{ label={'Image Size'}
label: `${galleryImageMinimumWidth}px`, formLabelProps={{ style: { fontSize: '0.9rem' } }}
}} sliderThumbTooltipProps={{
/> label: `${galleryImageMinimumWidth}px`,
<IAIIconButton }}
size={'sm'} />
aria-label={'Reset'} <IAIIconButton
tooltip={'Reset Size'} size={'sm'}
onClick={() => dispatch(setGalleryImageMinimumWidth(64))} aria-label={'Reset'}
icon={<BiReset />} tooltip={'Reset Size'}
data-selected={shouldPinGallery} onClick={() => dispatch(setGalleryImageMinimumWidth(64))}
styleClass="image-gallery-icon-btn" icon={<BiReset />}
/> data-selected={shouldPinGallery}
styleClass="image-gallery-icon-btn"
/>
</div>
<div>
<IAICheckbox
label="Maintain Aspect Ratio"
isChecked={galleryImageObjectFit === 'contain'}
onChange={() =>
dispatch(
setGalleryImageObjectFit(
galleryImageObjectFit === 'contain'
? 'cover'
: 'contain'
)
)
}
/>
</div>
</div>
</IAIPopover> </IAIPopover>
<IAIIconButton <IAIIconButton

View File

@ -3,6 +3,8 @@ import type { PayloadAction } from '@reduxjs/toolkit';
import _, { clamp } from 'lodash'; import _, { clamp } from 'lodash';
import * as InvokeAI from '../../app/invokeai'; import * as InvokeAI from '../../app/invokeai';
type GalleryImageObjectFitType = 'contain' | 'cover';
export interface GalleryState { export interface GalleryState {
currentImage?: InvokeAI.Image; currentImage?: InvokeAI.Image;
currentImageUuid: string; currentImageUuid: string;
@ -15,6 +17,7 @@ export interface GalleryState {
shouldShowGallery: boolean; shouldShowGallery: boolean;
galleryScrollPosition: number; galleryScrollPosition: number;
galleryImageMinimumWidth: number; galleryImageMinimumWidth: number;
galleryImageObjectFit: GalleryImageObjectFitType;
} }
const initialState: GalleryState = { const initialState: GalleryState = {
@ -25,6 +28,7 @@ const initialState: GalleryState = {
shouldShowGallery: true, shouldShowGallery: true,
galleryScrollPosition: 0, galleryScrollPosition: 0,
galleryImageMinimumWidth: 64, galleryImageMinimumWidth: 64,
galleryImageObjectFit: 'contain',
}; };
export const gallerySlice = createSlice({ export const gallerySlice = createSlice({
@ -171,6 +175,12 @@ export const gallerySlice = createSlice({
setGalleryImageMinimumWidth: (state, action: PayloadAction<number>) => { setGalleryImageMinimumWidth: (state, action: PayloadAction<number>) => {
state.galleryImageMinimumWidth = action.payload; state.galleryImageMinimumWidth = action.payload;
}, },
setGalleryImageObjectFit: (
state,
action: PayloadAction<GalleryImageObjectFitType>
) => {
state.galleryImageObjectFit = action.payload;
},
}, },
}); });
@ -187,6 +197,7 @@ export const {
setShouldShowGallery, setShouldShowGallery,
setGalleryScrollPosition, setGalleryScrollPosition,
setGalleryImageMinimumWidth, setGalleryImageMinimumWidth,
setGalleryImageObjectFit,
} = gallerySlice.actions; } = gallerySlice.actions;
export default gallerySlice.reducer; export default gallerySlice.reducer;

View File

@ -15,6 +15,7 @@ export const imageGallerySelector = createSelector(
shouldShowGallery, shouldShowGallery,
galleryScrollPosition, galleryScrollPosition,
galleryImageMinimumWidth, galleryImageMinimumWidth,
galleryImageObjectFit,
} = gallery; } = gallery;
const { activeTab } = options; const { activeTab } = options;
@ -27,6 +28,7 @@ export const imageGallerySelector = createSelector(
shouldShowGallery, shouldShowGallery,
galleryScrollPosition, galleryScrollPosition,
galleryImageMinimumWidth, galleryImageMinimumWidth,
galleryImageObjectFit,
galleryGridTemplateColumns: `repeat(auto-fill, minmax(${galleryImageMinimumWidth}px, auto))`, galleryGridTemplateColumns: `repeat(auto-fill, minmax(${galleryImageMinimumWidth}px, auto))`,
activeTabName: tabMap[activeTab], activeTabName: tabMap[activeTab],
}; };
@ -34,9 +36,10 @@ export const imageGallerySelector = createSelector(
); );
export const hoverableImageSelector = createSelector( export const hoverableImageSelector = createSelector(
(state: RootState) => state.options, [(state: RootState) => state.options, (state: RootState) => state.gallery],
(options: OptionsState) => { (options: OptionsState, gallery: GalleryState) => {
return { return {
galleryImageObjectFit: gallery.galleryImageObjectFit,
activeTabName: tabMap[options.activeTab], activeTabName: tabMap[options.activeTab],
}; };
} }