[WebUI] Masonry Layout for Gallery

This commit is contained in:
blessedcoolant 2022-10-09 14:08:46 +13:00 committed by Lincoln Stein
parent 5e2b250426
commit 1cb74a6357
13 changed files with 553 additions and 513 deletions

483
frontend/dist/assets/index.27eefde8.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -6,8 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>InvokeAI - A Stable Diffusion Toolkit</title> <title>InvokeAI - A Stable Diffusion Toolkit</title>
<link rel="shortcut icon" type="icon" href="/assets/favicon.0d253ced.ico" /> <link rel="shortcut icon" type="icon" href="/assets/favicon.0d253ced.ico" />
<script type="module" crossorigin src="/assets/index.a564edff.js"></script> <script type="module" crossorigin src="/assets/index.27eefde8.js"></script>
<link rel="stylesheet" href="/assets/index.787a8262.css"> <link rel="stylesheet" href="/assets/index.c04b2fd8.css">
</head> </head>
<body> <body>

View File

@ -25,6 +25,7 @@
"react-dropzone": "^14.2.2", "react-dropzone": "^14.2.2",
"react-hotkeys-hook": "^3.4.7", "react-hotkeys-hook": "^3.4.7",
"react-icons": "^4.4.0", "react-icons": "^4.4.0",
"react-masonry-css": "^1.0.16",
"react-redux": "^8.0.2", "react-redux": "^8.0.2",
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
"socket.io": "^4.5.2", "socket.io": "^4.5.2",

View File

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

View File

@ -55,12 +55,31 @@
@include HideScrollbar; @include HideScrollbar;
} }
.image-gallery { .masonry-grid {
display: grid; display: -webkit-box; /* Not needed if autoprefixing */
grid-template-columns: repeat(auto-fill, minmax(80px, auto)); display: -ms-flexbox; /* Not needed if autoprefixing */
gap: 0.5rem; display: flex;
justify-items: center; margin-left: 0.5rem; /* gutter size offset */
width: auto;
} }
.masonry-grid_column {
padding-left: 0.5rem; /* gutter size */
background-clip: padding-box;
}
/* Style your items */
.masonry-grid_column > .hoverable-image {
/* change div to reference your elements you put in <Masonry> */
background: var(--tab-color);
margin-bottom: 0.5rem;
}
// .image-gallery {
// display: grid;
// grid-template-columns: repeat(auto-fill, minmax(80px, auto));
// gap: 0.5rem;
// justify-items: center;
// }
.image-gallery-load-more-btn { .image-gallery-load-more-btn {
background-color: var(--btn-load-more) !important; background-color: var(--btn-load-more) !important;

View File

@ -1,25 +1,25 @@
import { Button, IconButton } from '@chakra-ui/button'; import { Button, IconButton } from '@chakra-ui/button';
import { Resizable } from 're-resizable'; import { Resizable } from 're-resizable';
import React from 'react';
import React, { 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 Masonry from 'react-masonry-css';
import { requestImages } from '../../app/socketio/actions'; import { requestImages } from '../../app/socketio/actions';
import { RootState, useAppDispatch, useAppSelector } from '../../app/store'; import { RootState, useAppDispatch, useAppSelector } from '../../app/store';
import IAIIconButton from '../../common/components/IAIIconButton'; import IAIIconButton from '../../common/components/IAIIconButton';
import { import { selectNextImage, selectPrevImage } from './gallerySlice';
selectNextImage,
selectPrevImage,
setShouldShowGallery,
} from './gallerySlice';
import HoverableImage from './HoverableImage'; import HoverableImage from './HoverableImage';
import { setShouldShowGallery } from '../options/optionsSlice';
export default function ImageGallery() { export default function ImageGallery() {
const { const { images, currentImageUuid, areMoreImagesAvailable } = useAppSelector(
images, (state: RootState) => state.gallery
currentImageUuid, );
areMoreImagesAvailable,
shouldShowGallery, const shouldShowGallery = useAppSelector(
} = useAppSelector((state: RootState) => state.gallery); (state: RootState) => state.options.shouldShowGallery
);
const activeTab = useAppSelector( const activeTab = useAppSelector(
(state: RootState) => state.options.activeTab (state: RootState) => state.options.activeTab
@ -27,6 +27,12 @@ export default function ImageGallery() {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [column, setColumn] = useState<number | undefined>();
const handleResize = (event: MouseEvent | TouchEvent | any) => {
setColumn(Math.floor((window.innerWidth - event.x) / 120));
};
const handleShowGalleryToggle = () => { const handleShowGalleryToggle = () => {
dispatch(setShouldShowGallery(!shouldShowGallery)); dispatch(setShouldShowGallery(!shouldShowGallery));
}; };
@ -83,6 +89,7 @@ export default function ImageGallery() {
minWidth={'300'} minWidth={'300'}
maxWidth={activeTab == 1 ? '300' : '600'} maxWidth={activeTab == 1 ? '300' : '600'}
className="image-gallery-popup" className="image-gallery-popup"
onResize={handleResize}
> >
{/* <div className="image-gallery-popup"></div> */} {/* <div className="image-gallery-popup"></div> */}
<div className="image-gallery-header"> <div className="image-gallery-header">
@ -97,7 +104,12 @@ export default function ImageGallery() {
</div> </div>
<div className="image-gallery-container"> <div className="image-gallery-container">
{images.length ? ( {images.length ? (
<div className="image-gallery"> <Masonry
className="masonry-grid"
columnClassName="masonry-grid_column"
breakpointCols={column}
>
{/* <div className="image-gallery"> */}
{images.map((image) => { {images.map((image) => {
const { uuid } = image; const { uuid } = image;
const isSelected = currentImageUuid === uuid; const isSelected = currentImageUuid === uuid;
@ -109,7 +121,8 @@ export default function ImageGallery() {
/> />
); );
})} })}
</div> {/* </div> */}
</Masonry>
) : ( ) : (
<div className="image-gallery-container-placeholder"> <div className="image-gallery-container-placeholder">
<MdPhotoLibrary /> <MdPhotoLibrary />

View File

@ -11,14 +11,12 @@ export interface GalleryState {
areMoreImagesAvailable: boolean; areMoreImagesAvailable: boolean;
latest_mtime?: number; latest_mtime?: number;
earliest_mtime?: number; earliest_mtime?: number;
shouldShowGallery: boolean;
} }
const initialState: GalleryState = { const initialState: GalleryState = {
currentImageUuid: '', currentImageUuid: '',
images: [], images: [],
areMoreImagesAvailable: true, areMoreImagesAvailable: true,
shouldShowGallery: false,
}; };
export const gallerySlice = createSlice({ export const gallerySlice = createSlice({
@ -140,9 +138,6 @@ export const gallerySlice = createSlice({
state.areMoreImagesAvailable = areMoreImagesAvailable; state.areMoreImagesAvailable = areMoreImagesAvailable;
} }
}, },
setShouldShowGallery: (state, action: PayloadAction<boolean>) => {
state.shouldShowGallery = action.payload;
},
}, },
}); });
@ -155,7 +150,6 @@ export const {
setIntermediateImage, setIntermediateImage,
selectNextImage, selectNextImage,
selectPrevImage, selectPrevImage,
setShouldShowGallery,
} = gallerySlice.actions; } = gallerySlice.actions;
export default gallerySlice.reducer; export default gallerySlice.reducer;

View File

@ -35,6 +35,7 @@ export interface OptionsState {
showAdvancedOptions: boolean; showAdvancedOptions: boolean;
activeTab: number; activeTab: number;
shouldShowImageDetails: boolean; shouldShowImageDetails: boolean;
shouldShowGallery: boolean;
} }
const initialOptionsState: OptionsState = { const initialOptionsState: OptionsState = {
@ -66,6 +67,7 @@ const initialOptionsState: OptionsState = {
showAdvancedOptions: true, showAdvancedOptions: true,
activeTab: 0, activeTab: 0,
shouldShowImageDetails: false, shouldShowImageDetails: false,
shouldShowGallery: false,
}; };
const initialState: OptionsState = initialOptionsState; const initialState: OptionsState = initialOptionsState;
@ -281,6 +283,9 @@ export const optionsSlice = createSlice({
setShouldShowImageDetails: (state, action: PayloadAction<boolean>) => { setShouldShowImageDetails: (state, action: PayloadAction<boolean>) => {
state.shouldShowImageDetails = action.payload; state.shouldShowImageDetails = action.payload;
}, },
setShouldShowGallery: (state, action: PayloadAction<boolean>) => {
state.shouldShowGallery = action.payload;
},
}, },
}); });
@ -317,6 +322,7 @@ export const {
setShowAdvancedOptions, setShowAdvancedOptions,
setActiveTab, setActiveTab,
setShouldShowImageDetails, setShouldShowImageDetails,
setShouldShowGallery,
} = optionsSlice.actions; } = optionsSlice.actions;
export default optionsSlice.reducer; export default optionsSlice.reducer;

View File

@ -6,7 +6,7 @@ import { RootState, useAppSelector } from '../../../app/store';
export default function ImageToImage() { export default function ImageToImage() {
const shouldShowGallery = useAppSelector( const shouldShowGallery = useAppSelector(
(state: RootState) => state.gallery.shouldShowGallery (state: RootState) => state.options.shouldShowGallery
); );
return ( return (

View File

@ -6,7 +6,7 @@ import { RootState, useAppSelector } from '../../../app/store';
export default function TextToImage() { export default function TextToImage() {
const shouldShowGallery = useAppSelector( const shouldShowGallery = useAppSelector(
(state: RootState) => state.gallery.shouldShowGallery (state: RootState) => state.options.shouldShowGallery
); );
return ( return (

View File

@ -2850,6 +2850,11 @@ react-is@^18.0.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
react-masonry-css@^1.0.16:
version "1.0.16"
resolved "https://registry.yarnpkg.com/react-masonry-css/-/react-masonry-css-1.0.16.tgz#72b28b4ae3484e250534700860597553a10f1a2c"
integrity sha512-KSW0hR2VQmltt/qAa3eXOctQDyOu7+ZBevtKgpNDSzT7k5LA/0XntNa9z9HKCdz3QlxmJHglTZ18e4sX4V8zZQ==
react-redux@^8.0.2: react-redux@^8.0.2:
version "8.0.2" version "8.0.2"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.2.tgz#bc2a304bb21e79c6808e3e47c50fe1caf62f7aad" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.2.tgz#bc2a304bb21e79c6808e3e47c50fe1caf62f7aad"