diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json
index 6c4a5b9988..6d3829bc71 100644
--- a/invokeai/frontend/web/public/locales/en.json
+++ b/invokeai/frontend/web/public/locales/en.json
@@ -352,14 +352,11 @@
},
"gallery": {
"alwaysShowImageSizeBadge": "Always Show Image Size Badge",
- "ascending": "Ascending",
"assets": "Assets",
"autoAssignBoardOnClick": "Auto-Assign Board on Click",
"autoSwitchNewImages": "Auto-Switch to New Images",
"copy": "Copy",
- "createdDate": "Created Date",
"currentlyInUse": "This image is currently in use in the following features:",
- "descending": "Descending",
"drop": "Drop",
"dropOrUpload": "$t(gallery.drop) or Upload",
"dropToUpload": "$t(gallery.drop) to Upload",
@@ -375,14 +372,13 @@
"loading": "Loading",
"loadMore": "Load More",
"newestFirst": "Newest First",
+ "oldestFirst": "Oldest First",
+ "sortDirection": "Sort Direction",
+ "showStarredImagesFirst": "Show Starred Images First",
"noImageSelected": "No Image Selected",
"noImagesInGallery": "No Images to Display",
- "oldestFirst": "Oldest First",
"setCurrentImage": "Set as Current Image",
"starImage": "Star Image",
- "starred": "Starred",
- "starredFirst": "Starred First",
- "starredLast": "Starred Last",
"unstarImage": "Unstar Image",
"unableToLoad": "Unable to load Gallery",
"deleteSelection": "Delete Selection",
@@ -403,10 +399,6 @@
"selectAnImageToCompare": "Select an Image to Compare",
"slider": "Slider",
"sideBySide": "Side-by-Side",
- "sortAscending": "Ascending",
- "sortBy": "Sort By",
- "sortingBy": "Sorting by",
- "sortDescending": "Descending",
"hover": "Hover",
"swapImages": "Swap Images",
"compareOptions": "Comparison Options",
diff --git a/invokeai/frontend/web/src/features/gallery/components/GalleryMenu/GalleryMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryMenu/GalleryMenu.tsx
deleted file mode 100644
index b347980612..0000000000
--- a/invokeai/frontend/web/src/features/gallery/components/GalleryMenu/GalleryMenu.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Flex } from '@invoke-ai/ui-library';
-
-import { GalleryBulkSelect } from './GalleryBulkSelect';
-import { GallerySort } from './GallerySort';
-
-export const GalleryMenu = () => {
- return (
-
-
-
-
- );
-};
diff --git a/invokeai/frontend/web/src/features/gallery/components/GalleryMenu/GallerySort.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryMenu/GallerySort.tsx
deleted file mode 100644
index 1fb17ab050..0000000000
--- a/invokeai/frontend/web/src/features/gallery/components/GalleryMenu/GallerySort.tsx
+++ /dev/null
@@ -1,109 +0,0 @@
-import type { ComboboxOption } from '@invoke-ai/ui-library';
-import {
- Button,
- ButtonGroup,
- Combobox,
- Flex,
- FormControl,
- FormLabel,
- IconButton,
- Popover,
- PopoverBody,
- PopoverContent,
- PopoverTrigger,
-} from '@invoke-ai/ui-library';
-import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import type { SingleValue } from 'chakra-react-select';
-import { orderByChanged, orderDirChanged } from 'features/gallery/store/gallerySlice';
-import type { OrderBy, OrderDir } from 'features/gallery/store/types';
-import { t } from 'i18next';
-import { useCallback, useMemo } from 'react';
-import { PiSortAscending, PiSortDescending } from 'react-icons/pi';
-
-const OPTIONS = [
- { value: 'created_at', label: t('gallery.createdDate') },
- { value: 'starred', label: t('gallery.starred') },
-];
-
-export const GallerySort = () => {
- const { orderBy, orderDir } = useAppSelector((s) => s.gallery);
- const dispatch = useAppDispatch();
-
- const handleChangeOrderDir = useCallback(
- (dir: OrderDir) => {
- dispatch(orderDirChanged(dir));
- },
- [dispatch]
- );
-
- const handleChangeOrderBy = useCallback(
- (v: SingleValue) => {
- if (v) {
- dispatch(orderByChanged(v.value as OrderBy));
- }
- },
- [dispatch]
- );
-
- const orderByValue = useMemo(() => {
- return OPTIONS.find((opt) => opt.value === orderBy);
- }, [orderBy]);
-
- const ascendingText = useMemo(() => {
- return orderBy === 'created_at' ? t('gallery.oldestFirst') : t('gallery.starredLast');
- }, [orderBy]);
-
- const descendingText = useMemo(() => {
- return orderBy === 'created_at' ? t('gallery.newestFirst') : t('gallery.starredFirst');
- }, [orderBy]);
-
- const sortTooltip = useMemo(() => {
- if (orderDir === 'ASC') {
- return `${t('gallery.sortingBy')}: ${ascendingText}`;
- } else {
- return `${t('gallery.sortingBy')}: ${descendingText}`;
- }
- }, [orderDir, ascendingText, descendingText]);
-
- return (
-
-
- : }
- aria-label="Sort"
- />
-
-
-
-
-
-
-
-
-
- {t('gallery.sortBy')}
-
-
-
-
-
-
- );
-};
diff --git a/invokeai/frontend/web/src/features/gallery/components/GallerySettingsPopover.tsx b/invokeai/frontend/web/src/features/gallery/components/GallerySettingsPopover.tsx
index 3cc01f0b28..8d50f4a309 100644
--- a/invokeai/frontend/web/src/features/gallery/components/GallerySettingsPopover.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/GallerySettingsPopover.tsx
@@ -1,7 +1,9 @@
-import type { FormLabelProps } from '@invoke-ai/ui-library';
+import type { ComboboxOption, FormLabelProps } from '@invoke-ai/ui-library';
import {
Checkbox,
+ Combobox,
CompositeSlider,
+ Divider,
Flex,
FormControl,
FormControlGroup,
@@ -14,17 +16,21 @@ import {
Switch,
} from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import type { SingleValue } from 'chakra-react-select';
import {
alwaysShowImageSizeBadgeChanged,
autoAssignBoardOnClickChanged,
+ orderDirChanged,
setGalleryImageMinimumWidth,
shouldAutoSwitchChanged,
shouldShowArchivedBoardsChanged,
+ starredFirstChanged,
} from 'features/gallery/store/gallerySlice';
import type { ChangeEvent } from 'react';
-import { memo, useCallback } from 'react';
+import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { RiSettings4Fill } from 'react-icons/ri';
+import { assert } from 'tsafe';
import BoardAutoAddSelect from './Boards/BoardAutoAddSelect';
@@ -40,6 +46,8 @@ const GallerySettingsPopover = () => {
const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick);
const alwaysShowImageSizeBadge = useAppSelector((s) => s.gallery.alwaysShowImageSizeBadge);
const shouldShowArchivedBoards = useAppSelector((s) => s.gallery.shouldShowArchivedBoards);
+ const orderDir = useAppSelector((s) => s.gallery.orderDir);
+ const starredFirst = useAppSelector((s) => s.gallery.starredFirst);
const handleChangeGalleryImageMinimumWidth = useCallback(
(v: number) => {
@@ -72,15 +80,37 @@ const GallerySettingsPopover = () => {
[dispatch]
);
+ const onChangeStarredFirst = useCallback(
+ (e: ChangeEvent) => {
+ dispatch(starredFirstChanged(e.target.checked));
+ },
+ [dispatch]
+ );
+
+ const orderDirOptions = useMemo(
+ () => [
+ { value: 'DESC', label: t('gallery.newestFirst') },
+ { value: 'ASC', label: t('gallery.oldestFirst') },
+ ],
+ [t]
+ );
+
+ const onChangeOrderDir = useCallback(
+ (v: SingleValue) => {
+ assert(v?.value === 'ASC' || v?.value === 'DESC');
+ dispatch(orderDirChanged(v.value));
+ },
+ [dispatch]
+ );
+
+ const orderDirValue = useMemo(() => {
+ return orderDirOptions.find((opt) => opt.value === orderDir);
+ }, [orderDir, orderDirOptions]);
+
return (
- }
- />
+ } />
@@ -98,7 +128,7 @@ const GallerySettingsPopover = () => {
{t('gallery.autoSwitchNewImages')}
-
+
{t('gallery.autoAssignBoardOnClick')}
@@ -114,6 +144,24 @@ const GallerySettingsPopover = () => {
+
+
+
+ {t('gallery.showStarredImagesFirst')}
+
+
+
+
+
+ {t('gallery.sortDirection')}
+
+
+
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx
index 105016539d..1ce64753df 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx
@@ -10,7 +10,6 @@ import { RiServerLine } from 'react-icons/ri';
import BoardsList from './Boards/BoardsList/BoardsList';
import GalleryBoardName from './GalleryBoardName';
-import { GalleryMenu } from './GalleryMenu/GalleryMenu';
import GallerySettingsPopover from './GallerySettingsPopover';
import GalleryImageGrid from './ImageGrid/GalleryImageGrid';
import { GalleryPagination } from './ImageGrid/GalleryPagination';
@@ -81,7 +80,7 @@ const ImageGalleryContent = () => {
-
+
diff --git a/invokeai/frontend/web/src/features/gallery/components/GalleryMenu/GalleryBulkSelect.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryBulkSelect.tsx
similarity index 75%
rename from invokeai/frontend/web/src/features/gallery/components/GalleryMenu/GalleryBulkSelect.tsx
rename to invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryBulkSelect.tsx
index fba5677769..b0c1eaa52c 100644
--- a/invokeai/frontend/web/src/features/gallery/components/GalleryMenu/GalleryBulkSelect.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryBulkSelect.tsx
@@ -1,4 +1,4 @@
-import { Spacer, Tag, TagCloseButton, TagLabel } from '@invoke-ai/ui-library';
+import { Tag, TagCloseButton, TagLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useGalleryImages } from 'features/gallery/hooks/useGalleryImages';
import { selectionChanged } from 'features/gallery/store/gallerySlice';
@@ -23,11 +23,23 @@ export const GalleryBulkSelect = () => {
useHotkeys(['ctrl+a', 'meta+a'], onSelectPage, { preventDefault: true }, [onSelectPage]);
if (selection.length <= 1) {
- return ;
+ return null;
}
return (
-
+
{selection.length} {t('common.selected')}
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx
index b16c13a783..d9e9216621 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx
@@ -2,6 +2,7 @@ import { Box, Flex, Grid } from '@invoke-ai/ui-library';
import { EMPTY_ARRAY } from 'app/store/constants';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
+import { GalleryBulkSelect } from 'features/gallery/components/ImageGrid/GalleryBulkSelect';
import { useGalleryHotkeys } from 'features/gallery/hooks/useGalleryHotkeys';
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
import { limitChanged } from 'features/gallery/store/gallerySlice';
@@ -144,6 +145,7 @@ const Content = () => {
))}
+
);
};
diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts
index f8e47c204b..832da474ca 100644
--- a/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts
+++ b/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts
@@ -20,7 +20,7 @@ export const selectListImagesQueryArgs = createMemoizedSelector(
offset: gallery.offset,
limit: gallery.limit,
is_intermediate: false,
- order_by: gallery.orderBy,
+ starred_first: gallery.starredFirst,
order_dir: gallery.orderDir,
}
: skipToken
diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts
index 30e36c9def..24796cb329 100644
--- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts
+++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts
@@ -4,7 +4,7 @@ import type { PersistConfig, RootState } from 'app/store/store';
import { uniqBy } from 'lodash-es';
import type { ImageDTO } from 'services/api/types';
-import type { BoardId, ComparisonMode, GalleryState, GalleryView, OrderBy, OrderDir } from './types';
+import type { BoardId, ComparisonMode, GalleryState, GalleryView, OrderDir } from './types';
import { IMAGE_LIMIT } from './types';
const initialGalleryState: GalleryState = {
@@ -19,7 +19,7 @@ const initialGalleryState: GalleryState = {
boardSearchText: '',
limit: 20,
offset: 0,
- orderBy: 'starred',
+ starredFirst: true,
orderDir: 'ASC',
isImageViewerOpen: true,
imageToCompare: null,
@@ -114,8 +114,8 @@ export const gallerySlice = createSlice({
shouldShowArchivedBoardsChanged: (state, action: PayloadAction) => {
state.shouldShowArchivedBoards = action.payload;
},
- orderByChanged: (state, action: PayloadAction) => {
- state.orderBy = action.payload;
+ starredFirstChanged: (state, action: PayloadAction) => {
+ state.starredFirst = action.payload;
},
orderDirChanged: (state, action: PayloadAction) => {
state.orderDir = action.payload;
@@ -142,8 +142,9 @@ export const {
comparisonModeCycled,
offsetChanged,
limitChanged,
- orderByChanged,
orderDirChanged,
+ starredFirstChanged,
+ shouldShowArchivedBoardsChanged,
} = gallerySlice.actions;
export const selectGallerySlice = (state: RootState) => state.gallery;
diff --git a/invokeai/frontend/web/src/features/gallery/store/types.ts b/invokeai/frontend/web/src/features/gallery/store/types.ts
index a2f62849f5..751fd5cb8e 100644
--- a/invokeai/frontend/web/src/features/gallery/store/types.ts
+++ b/invokeai/frontend/web/src/features/gallery/store/types.ts
@@ -8,7 +8,6 @@ export type GalleryView = 'images' | 'assets';
export type BoardId = 'none' | (string & Record);
export type ComparisonMode = 'slider' | 'side-by-side' | 'hover';
export type ComparisonFit = 'contain' | 'fill';
-export type OrderBy = 'created_at' | 'starred';
export type OrderDir = 'ASC' | 'DESC';
export type GalleryState = {
@@ -22,7 +21,7 @@ export type GalleryState = {
boardSearchText: string;
offset: number;
limit: number;
- orderBy: OrderBy;
+ starredFirst: boolean;
orderDir: OrderDir;
alwaysShowImageSizeBadge: boolean;
imageToCompare: ImageDTO | null;