mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Adds Maintain Aspect Ratio
checkbox to ImageGallery
This commit is contained in:
parent
5cae8206f9
commit
38fd0668ba
@ -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 {
|
||||||
|
@ -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%;
|
||||||
}
|
}
|
||||||
|
@ -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'}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user