mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
perf(ui): optimize all selectors 1
I learned that the inline selector syntax recreates the selector function on every render: ```ts const val = useAppSelector((s) => s.slice.val) ``` Not good! Better is to create a selector outside the function and use it. Doing that for all selectors now, most of the way through now. Feels snappier.
This commit is contained in:
parent
f126a61f66
commit
a41406ca9a
@ -1,5 +1,7 @@
|
|||||||
import { Button, Flex, Heading, Image, Link, Text } from '@invoke-ai/ui-library';
|
import { Button, Flex, Heading, Image, Link, Text } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { selectConfigSlice } from 'features/system/store/configSlice';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import newGithubIssueUrl from 'new-github-issue-url';
|
import newGithubIssueUrl from 'new-github-issue-url';
|
||||||
import InvokeLogoYellow from 'public/assets/images/invoke-symbol-ylw-lrg.svg';
|
import InvokeLogoYellow from 'public/assets/images/invoke-symbol-ylw-lrg.svg';
|
||||||
@ -13,9 +15,11 @@ type Props = {
|
|||||||
resetErrorBoundary: () => void;
|
resetErrorBoundary: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectIsLocal = createSelector(selectConfigSlice, (config) => config.isLocal);
|
||||||
|
|
||||||
const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
|
const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const isLocal = useAppSelector((s) => s.config.isLocal);
|
const isLocal = useAppSelector(selectIsLocal);
|
||||||
|
|
||||||
const handleCopy = useCallback(() => {
|
const handleCopy = useCallback(() => {
|
||||||
const text = JSON.stringify(serializeError(error), null, 2);
|
const text = JSON.stringify(serializeError(error), null, 2);
|
||||||
|
@ -1,15 +1,21 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { createLogWriter } from '@roarr/browser-log-writer';
|
import { createLogWriter } from '@roarr/browser-log-writer';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { selectSystemSlice } from 'features/system/store/systemSlice';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import { ROARR, Roarr } from 'roarr';
|
import { ROARR, Roarr } from 'roarr';
|
||||||
|
|
||||||
import type { LogNamespace } from './logger';
|
import type { LogNamespace } from './logger';
|
||||||
import { $logger, BASE_CONTEXT, LOG_LEVEL_MAP, logger } from './logger';
|
import { $logger, BASE_CONTEXT, LOG_LEVEL_MAP, logger } from './logger';
|
||||||
|
|
||||||
|
const selectLogLevel = createSelector(selectSystemSlice, (system) => system.logLevel);
|
||||||
|
const selectLogNamespaces = createSelector(selectSystemSlice, (system) => system.logNamespaces);
|
||||||
|
const selectLogIsEnabled = createSelector(selectSystemSlice, (system) => system.logIsEnabled);
|
||||||
|
|
||||||
export const useLogger = (namespace: LogNamespace) => {
|
export const useLogger = (namespace: LogNamespace) => {
|
||||||
const logLevel = useAppSelector((s) => s.system.logLevel);
|
const logLevel = useAppSelector(selectLogLevel);
|
||||||
const logNamespaces = useAppSelector((s) => s.system.logNamespaces);
|
const logNamespaces = useAppSelector(selectLogNamespaces);
|
||||||
const logIsEnabled = useAppSelector((s) => s.system.logIsEnabled);
|
const logIsEnabled = useAppSelector(selectLogIsEnabled);
|
||||||
|
|
||||||
// The provided Roarr browser log writer uses localStorage to config logging to console
|
// The provided Roarr browser log writer uses localStorage to config logging to console
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -13,8 +13,9 @@ import {
|
|||||||
Spacer,
|
Spacer,
|
||||||
Text,
|
Text,
|
||||||
} from '@invoke-ai/ui-library';
|
} from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { setShouldEnableInformationalPopovers } from 'features/system/store/systemSlice';
|
import { selectSystemSlice, setShouldEnableInformationalPopovers } from 'features/system/store/systemSlice';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { merge, omit } from 'lodash-es';
|
import { merge, omit } from 'lodash-es';
|
||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
@ -31,8 +32,10 @@ type Props = {
|
|||||||
children: ReactElement;
|
children: ReactElement;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectShouldEnableInformationalPopovers = createSelector(selectSystemSlice, system => system.shouldEnableInformationalPopovers);
|
||||||
|
|
||||||
export const InformationalPopover = memo(({ feature, children, inPortal = true, ...rest }: Props) => {
|
export const InformationalPopover = memo(({ feature, children, inPortal = true, ...rest }: Props) => {
|
||||||
const shouldEnableInformationalPopovers = useAppSelector((s) => s.system.shouldEnableInformationalPopovers);
|
const shouldEnableInformationalPopovers = useAppSelector(selectShouldEnableInformationalPopovers);
|
||||||
|
|
||||||
const data = useMemo(() => POPOVER_DATA[feature], [feature]);
|
const data = useMemo(() => POPOVER_DATA[feature], [feature]);
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { selectActiveTab } from 'features/ui/store/uiSelectors';
|
import { selectActiveTab } from 'features/ui/store/uiSelectors';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
@ -26,7 +27,7 @@ const selectPostUploadAction = createMemoizedSelector(selectActiveTab, (activeTa
|
|||||||
|
|
||||||
export const useFullscreenDropzone = () => {
|
export const useFullscreenDropzone = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
const autoAddBoardId = useAppSelector(selectAutoAddBoardId);
|
||||||
const [isHandlingUpload, setIsHandlingUpload] = useState<boolean>(false);
|
const [isHandlingUpload, setIsHandlingUpload] = useState<boolean>(false);
|
||||||
const postUploadAction = useAppSelector(selectPostUploadAction);
|
const postUploadAction = useAppSelector(selectPostUploadAction);
|
||||||
const [uploadImage] = useUploadImageMutation();
|
const [uploadImage] = useUploadImageMutation();
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
|
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import type { GroupBase } from 'chakra-react-select';
|
import type { GroupBase } from 'chakra-react-select';
|
||||||
|
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
|
||||||
import type { ModelIdentifierField } from 'features/nodes/types/common';
|
import type { ModelIdentifierField } from 'features/nodes/types/common';
|
||||||
import { groupBy, reduce } from 'lodash-es';
|
import { groupBy, reduce } from 'lodash-es';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
@ -28,11 +30,13 @@ const groupByBaseFunc = <T extends AnyModelConfig>(model: T) => model.base.toUpp
|
|||||||
const groupByBaseAndTypeFunc = <T extends AnyModelConfig>(model: T) =>
|
const groupByBaseAndTypeFunc = <T extends AnyModelConfig>(model: T) =>
|
||||||
`${model.base.toUpperCase()} / ${model.type.replaceAll('_', ' ').toUpperCase()}`;
|
`${model.base.toUpperCase()} / ${model.type.replaceAll('_', ' ').toUpperCase()}`;
|
||||||
|
|
||||||
|
const selectBaseWithSDXLFallback = createSelector(selectParamsSlice, (params) => params.model?.base ?? 'sdxl');
|
||||||
|
|
||||||
export const useGroupedModelCombobox = <T extends AnyModelConfig>(
|
export const useGroupedModelCombobox = <T extends AnyModelConfig>(
|
||||||
arg: UseGroupedModelComboboxArg<T>
|
arg: UseGroupedModelComboboxArg<T>
|
||||||
): UseGroupedModelComboboxReturn => {
|
): UseGroupedModelComboboxReturn => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const base_model = useAppSelector((s) => s.params.model?.base ?? 'sdxl');
|
const base = useAppSelector(selectBaseWithSDXLFallback);
|
||||||
const { modelConfigs, selectedModel, getIsDisabled, onChange, isLoading, groupByType = false } = arg;
|
const { modelConfigs, selectedModel, getIsDisabled, onChange, isLoading, groupByType = false } = arg;
|
||||||
const options = useMemo<GroupBase<ComboboxOption>[]>(() => {
|
const options = useMemo<GroupBase<ComboboxOption>[]>(() => {
|
||||||
if (!modelConfigs) {
|
if (!modelConfigs) {
|
||||||
@ -54,9 +58,9 @@ export const useGroupedModelCombobox = <T extends AnyModelConfig>(
|
|||||||
},
|
},
|
||||||
[] as GroupBase<ComboboxOption>[]
|
[] as GroupBase<ComboboxOption>[]
|
||||||
);
|
);
|
||||||
_options.sort((a) => (a.label?.split('/')[0]?.toLowerCase().includes(base_model) ? -1 : 1));
|
_options.sort((a) => (a.label?.split('/')[0]?.toLowerCase().includes(base) ? -1 : 1));
|
||||||
return _options;
|
return _options;
|
||||||
}, [modelConfigs, groupByType, getIsDisabled, base_model]);
|
}, [modelConfigs, groupByType, getIsDisabled, base]);
|
||||||
|
|
||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useDropzone } from 'react-dropzone';
|
import { useDropzone } from 'react-dropzone';
|
||||||
import { useUploadImageMutation } from 'services/api/endpoints/images';
|
import { useUploadImageMutation } from 'services/api/endpoints/images';
|
||||||
@ -29,7 +30,7 @@ type UseImageUploadButtonArgs = {
|
|||||||
* <input {...getUploadInputProps()} /> // hidden, handles native upload functionality
|
* <input {...getUploadInputProps()} /> // hidden, handles native upload functionality
|
||||||
*/
|
*/
|
||||||
export const useImageUploadButton = ({ postUploadAction, isDisabled }: UseImageUploadButtonArgs) => {
|
export const useImageUploadButton = ({ postUploadAction, isDisabled }: UseImageUploadButtonArgs) => {
|
||||||
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
const autoAddBoardId = useAppSelector(selectAutoAddBoardId);
|
||||||
const [uploadImage] = useUploadImageMutation();
|
const [uploadImage] = useUploadImageMutation();
|
||||||
const onDropAccepted = useCallback(
|
const onDropAccepted = useCallback(
|
||||||
(files: File[]) => {
|
(files: File[]) => {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
|
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
|
||||||
import { Combobox, ConfirmationAlertDialog, Flex, FormControl, Text } from '@invoke-ai/ui-library';
|
import { Combobox, ConfirmationAlertDialog, Flex, FormControl, Text } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import {
|
import {
|
||||||
@ -18,12 +19,17 @@ const selectImagesToChange = createMemoizedSelector(
|
|||||||
(changeBoardModal) => changeBoardModal.imagesToChange
|
(changeBoardModal) => changeBoardModal.imagesToChange
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const selectIsModalOpen = createSelector(
|
||||||
|
selectChangeBoardModalSlice,
|
||||||
|
(changeBoardModal) => changeBoardModal.isModalOpen
|
||||||
|
);
|
||||||
|
|
||||||
const ChangeBoardModal = () => {
|
const ChangeBoardModal = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const [selectedBoard, setSelectedBoard] = useState<string | null>();
|
const [selectedBoard, setSelectedBoard] = useState<string | null>();
|
||||||
const queryArgs = useAppSelector(selectListBoardsQueryArgs);
|
const queryArgs = useAppSelector(selectListBoardsQueryArgs);
|
||||||
const { data: boards, isFetching } = useListAllBoardsQuery(queryArgs);
|
const { data: boards, isFetching } = useListAllBoardsQuery(queryArgs);
|
||||||
const isModalOpen = useAppSelector((s) => s.changeBoardModal.isModalOpen);
|
const isModalOpen = useAppSelector(selectIsModalOpen);
|
||||||
const imagesToChange = useAppSelector(selectImagesToChange);
|
const imagesToChange = useAppSelector(selectImagesToChange);
|
||||||
const [addImagesToBoard] = useAddImagesToBoardMutation();
|
const [addImagesToBoard] = useAddImagesToBoardMutation();
|
||||||
const [removeImagesFromBoard] = useRemoveImagesFromBoardMutation();
|
const [removeImagesFromBoard] = useRemoveImagesFromBoardMutation();
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
rasterLayerAdded,
|
rasterLayerAdded,
|
||||||
rgAdded,
|
rgAdded,
|
||||||
} from 'features/controlLayers/store/canvasSlice';
|
} from 'features/controlLayers/store/canvasSlice';
|
||||||
import { selectEntityCount } from 'features/controlLayers/store/selectors';
|
import { selectHasEntities } from 'features/controlLayers/store/selectors';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiPlusBold, PiTrashSimpleBold } from 'react-icons/pi';
|
import { PiPlusBold, PiTrashSimpleBold } from 'react-icons/pi';
|
||||||
@ -16,10 +16,7 @@ import { PiPlusBold, PiTrashSimpleBold } from 'react-icons/pi';
|
|||||||
export const CanvasEntityListMenuItems = memo(() => {
|
export const CanvasEntityListMenuItems = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const hasEntities = useAppSelector((s) => {
|
const hasEntities = useAppSelector(selectHasEntities);
|
||||||
const count = selectEntityCount(s);
|
|
||||||
return count > 0;
|
|
||||||
});
|
|
||||||
const addInpaintMask = useCallback(() => {
|
const addInpaintMask = useCallback(() => {
|
||||||
dispatch(inpaintMaskAdded({ isSelected: true }));
|
dispatch(inpaintMaskAdded({ isSelected: true }));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
import { Button, ButtonGroup } from '@invoke-ai/ui-library';
|
import { Button, ButtonGroup } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { sessionModeChanged } from 'features/controlLayers/store/canvasSessionSlice';
|
import { selectCanvasSessionSlice, sessionModeChanged } from 'features/controlLayers/store/canvasSessionSlice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const selectCanvasMode = createSelector(selectCanvasSessionSlice, (canvasSession) => canvasSession.mode);
|
||||||
|
|
||||||
export const CanvasModeSwitcher = memo(() => {
|
export const CanvasModeSwitcher = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const mode = useAppSelector((s) => s.canvasSession.mode);
|
const mode = useAppSelector(selectCanvasMode);
|
||||||
const onClickGenerate = useCallback(() => dispatch(sessionModeChanged({ mode: 'generate' })), [dispatch]);
|
const onClickGenerate = useCallback(() => dispatch(sessionModeChanged({ mode: 'generate' })), [dispatch]);
|
||||||
const onClickCompose = useCallback(() => dispatch(sessionModeChanged({ mode: 'compose' })), [dispatch]);
|
const onClickCompose = useCallback(() => dispatch(sessionModeChanged({ mode: 'compose' })), [dispatch]);
|
||||||
|
|
||||||
|
@ -4,11 +4,11 @@ import { CanvasAddEntityButtons } from 'features/controlLayers/components/Canvas
|
|||||||
import { CanvasEntityList } from 'features/controlLayers/components/CanvasEntityList/CanvasEntityList';
|
import { CanvasEntityList } from 'features/controlLayers/components/CanvasEntityList/CanvasEntityList';
|
||||||
import { CanvasEntityListMenuItems } from 'features/controlLayers/components/CanvasEntityList/CanvasEntityListMenuItems';
|
import { CanvasEntityListMenuItems } from 'features/controlLayers/components/CanvasEntityList/CanvasEntityListMenuItems';
|
||||||
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||||
import { selectEntityCount } from 'features/controlLayers/store/selectors';
|
import { selectHasEntities } from 'features/controlLayers/store/selectors';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
|
|
||||||
export const CanvasPanelContent = memo(() => {
|
export const CanvasPanelContent = memo(() => {
|
||||||
const hasEntities = useAppSelector((s) => selectEntityCount(s) > 0);
|
const hasEntities = useAppSelector(selectHasEntities);
|
||||||
const renderMenu = useCallback(
|
const renderMenu = useCallback(
|
||||||
() => (
|
() => (
|
||||||
<MenuList>
|
<MenuList>
|
||||||
|
@ -3,6 +3,7 @@ import { useAppSelector } from 'app/store/storeHooks';
|
|||||||
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
|
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
|
||||||
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
|
import { selectBase } from 'features/controlLayers/store/paramsSlice';
|
||||||
import { IMAGE_FILTERS, isFilterType } from 'features/controlLayers/store/types';
|
import { IMAGE_FILTERS, isFilterType } from 'features/controlLayers/store/types';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -18,7 +19,7 @@ export const ControlLayerControlAdapterModel = memo(({ modelKey, onChange: onCha
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const entityIdentifier = useEntityIdentifierContext();
|
const entityIdentifier = useEntityIdentifierContext();
|
||||||
const canvasManager = useCanvasManager();
|
const canvasManager = useCanvasManager();
|
||||||
const currentBaseModel = useAppSelector((s) => s.params.model?.base);
|
const currentBaseModel = useAppSelector(selectBase);
|
||||||
const [modelConfigs, { isLoading }] = useControlNetAndT2IAdapterModels();
|
const [modelConfigs, { isLoading }] = useControlNetAndT2IAdapterModels();
|
||||||
const selectedModel = useMemo(() => modelConfigs.find((m) => m.key === modelKey), [modelConfigs, modelKey]);
|
const selectedModel = useMemo(() => modelConfigs.find((m) => m.key === modelKey), [modelConfigs, modelKey]);
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList';
|
import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList';
|
||||||
@ -10,8 +11,12 @@ const selectEntityIds = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
|||||||
return canvas.controlLayers.entities.map(mapId).reverse();
|
return canvas.controlLayers.entities.map(mapId).reverse();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const selectIsSelected = createSelector(selectSelectedEntityIdentifier, (selectedEntityIdentifier) => {
|
||||||
|
return selectedEntityIdentifier?.type === 'control_layer';
|
||||||
|
});
|
||||||
|
|
||||||
export const ControlLayerEntityList = memo(() => {
|
export const ControlLayerEntityList = memo(() => {
|
||||||
const isSelected = useAppSelector((s) => selectSelectedEntityIdentifier(s)?.type === 'control_layer');
|
const isSelected = useAppSelector(selectIsSelected);
|
||||||
const layerIds = useAppSelector(selectEntityIds);
|
const layerIds = useAppSelector(selectEntityIds);
|
||||||
|
|
||||||
if (layerIds.length === 0) {
|
if (layerIds.length === 0) {
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||||
import { Combobox, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { Combobox, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||||
import type { FilterConfig } from 'features/controlLayers/store/types';
|
import type { FilterConfig } from 'features/controlLayers/store/types';
|
||||||
import { IMAGE_FILTERS, isFilterType } from 'features/controlLayers/store/types';
|
import { IMAGE_FILTERS, isFilterType } from 'features/controlLayers/store/types';
|
||||||
import { configSelector } from 'features/system/store/configSelectors';
|
import { selectConfigSlice } from 'features/system/store/configSlice';
|
||||||
import { includes, map } from 'lodash-es';
|
import { includes, map } from 'lodash-es';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
const selectDisabledProcessors = createMemoizedSelector(
|
const selectDisabledProcessors = createSelector(selectConfigSlice, (config) => config.sd.disabledControlNetProcessors);
|
||||||
configSelector,
|
|
||||||
(config) => config.sd.disabledControlNetProcessors
|
|
||||||
);
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
filterType: FilterConfig['type'];
|
filterType: FilterConfig['type'];
|
||||||
|
@ -2,6 +2,7 @@ import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
|||||||
import { Combobox, Flex, FormControl, Tooltip } from '@invoke-ai/ui-library';
|
import { Combobox, Flex, FormControl, Tooltip } from '@invoke-ai/ui-library';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
|
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
|
||||||
|
import { selectBase } from 'features/controlLayers/store/paramsSlice';
|
||||||
import type { CLIPVisionModelV2 } from 'features/controlLayers/store/types';
|
import type { CLIPVisionModelV2 } from 'features/controlLayers/store/types';
|
||||||
import { isCLIPVisionModelV2 } from 'features/controlLayers/store/types';
|
import { isCLIPVisionModelV2 } from 'features/controlLayers/store/types';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
@ -24,7 +25,7 @@ type Props = {
|
|||||||
|
|
||||||
export const IPAdapterModel = memo(({ modelKey, onChangeModel, clipVisionModel, onChangeCLIPVisionModel }: Props) => {
|
export const IPAdapterModel = memo(({ modelKey, onChangeModel, clipVisionModel, onChangeCLIPVisionModel }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const currentBaseModel = useAppSelector((s) => s.params.model?.base);
|
const currentBaseModel = useAppSelector(selectBase);
|
||||||
const [modelConfigs, { isLoading }] = useIPAdapterModels();
|
const [modelConfigs, { isLoading }] = useIPAdapterModels();
|
||||||
const selectedModel = useMemo(() => modelConfigs.find((m) => m.key === modelKey), [modelConfigs, modelKey]);
|
const selectedModel = useMemo(() => modelConfigs.find((m) => m.key === modelKey), [modelConfigs, modelKey]);
|
||||||
|
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { settingsAutoSaveToggled } from 'features/controlLayers/store/canvasSettingsSlice';
|
import { selectCanvasSettingsSlice, settingsAutoSaveToggled } from 'features/controlLayers/store/canvasSettingsSlice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const selectAutoSave = createSelector(selectCanvasSettingsSlice, (canvasSettings) => canvasSettings.autoSave);
|
||||||
|
|
||||||
export const CanvasSettingsAutoSaveCheckbox = memo(() => {
|
export const CanvasSettingsAutoSaveCheckbox = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const autoSave = useAppSelector((s) => s.canvasSettings.autoSave);
|
const autoSave = useAppSelector(selectAutoSave);
|
||||||
const onChange = useCallback(() => dispatch(settingsAutoSaveToggled()), [dispatch]);
|
const onChange = useCallback(() => dispatch(settingsAutoSaveToggled()), [dispatch]);
|
||||||
return (
|
return (
|
||||||
<FormControl w="full">
|
<FormControl w="full">
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { clipToBboxChanged } from 'features/controlLayers/store/canvasSettingsSlice';
|
import { clipToBboxChanged, selectCanvasSettingsSlice } from 'features/controlLayers/store/canvasSettingsSlice';
|
||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const selectClipToBbox = createSelector(selectCanvasSettingsSlice, (canvasSettings) => canvasSettings.clipToBbox);
|
||||||
|
|
||||||
export const CanvasSettingsClipToBboxCheckbox = memo(() => {
|
export const CanvasSettingsClipToBboxCheckbox = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const clipToBbox = useAppSelector((s) => s.canvasSettings.clipToBbox);
|
const clipToBbox = useAppSelector(selectClipToBbox);
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) => dispatch(clipToBboxChanged(e.target.checked)),
|
(e: ChangeEvent<HTMLInputElement>) => dispatch(clipToBboxChanged(e.target.checked)),
|
||||||
[dispatch]
|
[dispatch]
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { settingsDynamicGridToggled } from 'features/controlLayers/store/canvasSettingsSlice';
|
import {
|
||||||
|
selectCanvasSettingsSlice,
|
||||||
|
settingsDynamicGridToggled,
|
||||||
|
} from 'features/controlLayers/store/canvasSettingsSlice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const selectDynamicGrid = createSelector(selectCanvasSettingsSlice, (canvasSettings) => canvasSettings.dynamicGrid);
|
||||||
|
|
||||||
export const CanvasSettingsDynamicGridSwitch = memo(() => {
|
export const CanvasSettingsDynamicGridSwitch = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const dynamicGrid = useAppSelector((s) => s.canvasSettings.dynamicGrid);
|
const dynamicGrid = useAppSelector(selectDynamicGrid);
|
||||||
const onChange = useCallback(() => {
|
const onChange = useCallback(() => {
|
||||||
dispatch(settingsDynamicGridToggled());
|
dispatch(settingsDynamicGridToggled());
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { invertScrollChanged } from 'features/controlLayers/store/toolSlice';
|
import { invertScrollChanged, selectToolSlice } from 'features/controlLayers/store/toolSlice';
|
||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const selectInvertScroll = createSelector(selectToolSlice, (tool) => tool.invertScroll);
|
||||||
|
|
||||||
export const CanvasSettingsInvertScrollCheckbox = memo(() => {
|
export const CanvasSettingsInvertScrollCheckbox = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const invertScroll = useAppSelector((s) => s.tool.invertScroll);
|
const invertScroll = useAppSelector(selectInvertScroll);
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) => dispatch(invertScrollChanged(e.target.checked)),
|
(e: ChangeEvent<HTMLInputElement>) => dispatch(invertScrollChanged(e.target.checked)),
|
||||||
[dispatch]
|
[dispatch]
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Flex } from '@invoke-ai/ui-library';
|
import { Flex } from '@invoke-ai/ui-library';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { $socket } from 'app/hooks/useSocketIO';
|
import { $socket } from 'app/hooks/useSocketIO';
|
||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import { useAppStore } from 'app/store/nanostores/store';
|
import { useAppStore } from 'app/store/nanostores/store';
|
||||||
@ -7,6 +8,7 @@ import { useAppSelector } from 'app/store/storeHooks';
|
|||||||
import { HeadsUpDisplay } from 'features/controlLayers/components/HeadsUpDisplay';
|
import { HeadsUpDisplay } from 'features/controlLayers/components/HeadsUpDisplay';
|
||||||
import { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
import { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||||
import { TRANSPARENCY_CHECKER_PATTERN } from 'features/controlLayers/konva/constants';
|
import { TRANSPARENCY_CHECKER_PATTERN } from 'features/controlLayers/konva/constants';
|
||||||
|
import { selectCanvasSettingsSlice } from 'features/controlLayers/store/canvasSettingsSlice';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
import { memo, useCallback, useEffect, useLayoutEffect, useState } from 'react';
|
import { memo, useCallback, useEffect, useLayoutEffect, useState } from 'react';
|
||||||
import { useDevicePixelRatio } from 'use-device-pixel-ratio';
|
import { useDevicePixelRatio } from 'use-device-pixel-ratio';
|
||||||
@ -51,8 +53,10 @@ type Props = {
|
|||||||
asPreview?: boolean;
|
asPreview?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectDynamicGrid = createSelector(selectCanvasSettingsSlice, (canvasSettings) => canvasSettings.dynamicGrid);
|
||||||
|
|
||||||
export const StageComponent = memo(({ asPreview = false }: Props) => {
|
export const StageComponent = memo(({ asPreview = false }: Props) => {
|
||||||
const dynamicGrid = useAppSelector((s) => s.canvasSettings.dynamicGrid);
|
const dynamicGrid = useAppSelector(selectDynamicGrid);
|
||||||
|
|
||||||
const [stage] = useState(
|
const [stage] = useState(
|
||||||
() =>
|
() =>
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { selectIsStaging } from 'features/controlLayers/store/canvasSessionSlice';
|
||||||
import type { PropsWithChildren } from 'react';
|
import type { PropsWithChildren } from 'react';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
export const StagingAreaIsStagingGate = memo((props: PropsWithChildren) => {
|
export const StagingAreaIsStagingGate = memo((props: PropsWithChildren) => {
|
||||||
const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
|
const isStaging = useAppSelector(selectIsStaging);
|
||||||
|
|
||||||
if (!isStaging) {
|
if (!isStaging) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { Button, ButtonGroup, IconButton } from '@invoke-ai/ui-library';
|
import { Button, ButtonGroup, IconButton } from '@invoke-ai/ui-library';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { INTERACTION_SCOPES, useScopeOnMount } from 'common/hooks/interactionScopes';
|
import { INTERACTION_SCOPES, useScopeOnMount } from 'common/hooks/interactionScopes';
|
||||||
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||||
import {
|
import {
|
||||||
|
selectCanvasSessionSlice,
|
||||||
sessionNextStagedImageSelected,
|
sessionNextStagedImageSelected,
|
||||||
sessionPrevStagedImageSelected,
|
sessionPrevStagedImageSelected,
|
||||||
sessionStagedImageDiscarded,
|
sessionStagedImageDiscarded,
|
||||||
@ -25,15 +27,25 @@ import {
|
|||||||
} from 'react-icons/pi';
|
} from 'react-icons/pi';
|
||||||
import { useChangeImageIsIntermediateMutation } from 'services/api/endpoints/images';
|
import { useChangeImageIsIntermediateMutation } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
|
const selectStagedImageIndex = createSelector(
|
||||||
|
selectCanvasSessionSlice,
|
||||||
|
(canvasSession) => canvasSession.selectedStagedImageIndex
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectSelectedImage = createSelector(
|
||||||
|
[selectCanvasSessionSlice, selectStagedImageIndex],
|
||||||
|
(canvasSession, index) => canvasSession.stagedImages[index] ?? null
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectImageCount = createSelector(selectCanvasSessionSlice, (canvasSession) => canvasSession.stagedImages.length);
|
||||||
|
|
||||||
export const StagingAreaToolbar = memo(() => {
|
export const StagingAreaToolbar = memo(() => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const session = useAppSelector((s) => s.canvasSession);
|
|
||||||
const canvasManager = useCanvasManager();
|
const canvasManager = useCanvasManager();
|
||||||
|
const index = useAppSelector(selectStagedImageIndex);
|
||||||
|
const selectedImage = useAppSelector(selectSelectedImage);
|
||||||
|
const imageCount = useAppSelector(selectImageCount);
|
||||||
const shouldShowStagedImage = useStore(canvasManager.stateApi.$shouldShowStagedImage);
|
const shouldShowStagedImage = useStore(canvasManager.stateApi.$shouldShowStagedImage);
|
||||||
const images = useMemo(() => session.stagedImages, [session]);
|
|
||||||
const selectedImage = useMemo(() => {
|
|
||||||
return images[session.selectedStagedImageIndex] ?? null;
|
|
||||||
}, [images, session.selectedStagedImageIndex]);
|
|
||||||
const isCanvasActive = useStore(INTERACTION_SCOPES.canvas.$isActive);
|
const isCanvasActive = useStore(INTERACTION_SCOPES.canvas.$isActive);
|
||||||
const [changeIsImageIntermediate] = useChangeImageIsIntermediateMutation();
|
const [changeIsImageIntermediate] = useChangeImageIsIntermediateMutation();
|
||||||
useScopeOnMount('stagingArea');
|
useScopeOnMount('stagingArea');
|
||||||
@ -52,19 +64,19 @@ export const StagingAreaToolbar = memo(() => {
|
|||||||
if (!selectedImage) {
|
if (!selectedImage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dispatch(sessionStagingAreaImageAccepted({ index: session.selectedStagedImageIndex }));
|
dispatch(sessionStagingAreaImageAccepted({ index }));
|
||||||
}, [dispatch, selectedImage, session.selectedStagedImageIndex]);
|
}, [dispatch, index, selectedImage]);
|
||||||
|
|
||||||
const onDiscardOne = useCallback(() => {
|
const onDiscardOne = useCallback(() => {
|
||||||
if (!selectedImage) {
|
if (!selectedImage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (images.length === 1) {
|
if (imageCount === 1) {
|
||||||
dispatch(sessionStagingAreaReset());
|
dispatch(sessionStagingAreaReset());
|
||||||
} else {
|
} else {
|
||||||
dispatch(sessionStagedImageDiscarded({ index: session.selectedStagedImageIndex }));
|
dispatch(sessionStagedImageDiscarded({ index }));
|
||||||
}
|
}
|
||||||
}, [selectedImage, images.length, dispatch, session.selectedStagedImageIndex]);
|
}, [selectedImage, imageCount, dispatch, index]);
|
||||||
|
|
||||||
const onDiscardAll = useCallback(() => {
|
const onDiscardAll = useCallback(() => {
|
||||||
dispatch(sessionStagingAreaReset());
|
dispatch(sessionStagingAreaReset());
|
||||||
@ -112,12 +124,12 @@ export const StagingAreaToolbar = memo(() => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const counterText = useMemo(() => {
|
const counterText = useMemo(() => {
|
||||||
if (images.length > 0) {
|
if (imageCount > 0) {
|
||||||
return `${(session.selectedStagedImageIndex ?? 0) + 1} of ${images.length}`;
|
return `${(index ?? 0) + 1} of ${imageCount}`;
|
||||||
} else {
|
} else {
|
||||||
return `0 of 0`;
|
return `0 of 0`;
|
||||||
}
|
}
|
||||||
}, [images.length, session.selectedStagedImageIndex]);
|
}, [imageCount, index]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -128,7 +140,7 @@ export const StagingAreaToolbar = memo(() => {
|
|||||||
icon={<PiArrowLeftBold />}
|
icon={<PiArrowLeftBold />}
|
||||||
onClick={onPrev}
|
onClick={onPrev}
|
||||||
colorScheme="invokeBlue"
|
colorScheme="invokeBlue"
|
||||||
isDisabled={images.length <= 1 || !shouldShowStagedImage}
|
isDisabled={imageCount <= 1 || !shouldShowStagedImage}
|
||||||
/>
|
/>
|
||||||
<Button colorScheme="base" pointerEvents="none" minW={28}>
|
<Button colorScheme="base" pointerEvents="none" minW={28}>
|
||||||
{counterText}
|
{counterText}
|
||||||
@ -139,7 +151,7 @@ export const StagingAreaToolbar = memo(() => {
|
|||||||
icon={<PiArrowRightBold />}
|
icon={<PiArrowRightBold />}
|
||||||
onClick={onNext}
|
onClick={onNext}
|
||||||
colorScheme="invokeBlue"
|
colorScheme="invokeBlue"
|
||||||
isDisabled={images.length <= 1 || !shouldShowStagedImage}
|
isDisabled={imageCount <= 1 || !shouldShowStagedImage}
|
||||||
/>
|
/>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
<ButtonGroup borderRadius="base" shadow="dark-lg">
|
<ButtonGroup borderRadius="base" shadow="dark-lg">
|
||||||
|
@ -3,6 +3,7 @@ import { useAppSelector } from 'app/store/storeHooks';
|
|||||||
import { useSelectTool, useToolIsSelected } from 'features/controlLayers/components/Tool/hooks';
|
import { useSelectTool, useToolIsSelected } from 'features/controlLayers/components/Tool/hooks';
|
||||||
import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering';
|
import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering';
|
||||||
import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming';
|
import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming';
|
||||||
|
import { selectIsStaging } from 'features/controlLayers/store/canvasSessionSlice';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -14,7 +15,7 @@ export const ToolBboxButton = memo(() => {
|
|||||||
const isSelected = useToolIsSelected('bbox');
|
const isSelected = useToolIsSelected('bbox');
|
||||||
const isFiltering = useIsFiltering();
|
const isFiltering = useIsFiltering();
|
||||||
const isTransforming = useIsTransforming();
|
const isTransforming = useIsTransforming();
|
||||||
const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
|
const isStaging = useAppSelector(selectIsStaging);
|
||||||
const isDisabled = useMemo(() => {
|
const isDisabled = useMemo(() => {
|
||||||
return isTransforming || isFiltering || isStaging;
|
return isTransforming || isFiltering || isStaging;
|
||||||
}, [isFiltering, isStaging, isTransforming]);
|
}, [isFiltering, isStaging, isTransforming]);
|
||||||
|
@ -9,18 +9,20 @@ import {
|
|||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@invoke-ai/ui-library';
|
} from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { brushWidthChanged } from 'features/controlLayers/store/toolSlice';
|
import { brushWidthChanged, selectToolSlice } from 'features/controlLayers/store/toolSlice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const marks = [0, 100, 200, 300];
|
const marks = [0, 100, 200, 300];
|
||||||
const formatPx = (v: number | string) => `${v} px`;
|
const formatPx = (v: number | string) => `${v} px`;
|
||||||
|
const selectBrushWidth = createSelector(selectToolSlice, (tool) => tool.brush.width);
|
||||||
|
|
||||||
export const ToolBrushWidth = memo(() => {
|
export const ToolBrushWidth = memo(() => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const width = useAppSelector((s) => s.tool.brush.width);
|
const width = useAppSelector(selectBrushWidth);
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(v: number) => {
|
(v: number) => {
|
||||||
dispatch(brushWidthChanged(Math.round(v)));
|
dispatch(brushWidthChanged(Math.round(v)));
|
||||||
|
@ -3,6 +3,7 @@ import { useAppSelector } from 'app/store/storeHooks';
|
|||||||
import { useSelectTool, useToolIsSelected } from 'features/controlLayers/components/Tool/hooks';
|
import { useSelectTool, useToolIsSelected } from 'features/controlLayers/components/Tool/hooks';
|
||||||
import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering';
|
import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering';
|
||||||
import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming';
|
import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming';
|
||||||
|
import { selectIsStaging } from 'features/controlLayers/store/canvasSessionSlice';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -14,7 +15,7 @@ export const ToolColorPickerButton = memo(() => {
|
|||||||
const isTransforming = useIsTransforming();
|
const isTransforming = useIsTransforming();
|
||||||
const selectColorPicker = useSelectTool('colorPicker');
|
const selectColorPicker = useSelectTool('colorPicker');
|
||||||
const isSelected = useToolIsSelected('colorPicker');
|
const isSelected = useToolIsSelected('colorPicker');
|
||||||
const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
|
const isStaging = useAppSelector(selectIsStaging);
|
||||||
|
|
||||||
const isDisabled = useMemo(() => {
|
const isDisabled = useMemo(() => {
|
||||||
return isTransforming || isFiltering || isStaging;
|
return isTransforming || isFiltering || isStaging;
|
||||||
|
@ -9,18 +9,20 @@ import {
|
|||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@invoke-ai/ui-library';
|
} from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { eraserWidthChanged } from 'features/controlLayers/store/toolSlice';
|
import { eraserWidthChanged, selectToolSlice } from 'features/controlLayers/store/toolSlice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const marks = [0, 100, 200, 300];
|
const marks = [0, 100, 200, 300];
|
||||||
const formatPx = (v: number | string) => `${v} px`;
|
const formatPx = (v: number | string) => `${v} px`;
|
||||||
|
const selectEraserWidth = createSelector(selectToolSlice, (tool) => tool.eraser.width);
|
||||||
|
|
||||||
export const ToolEraserWidth = memo(() => {
|
export const ToolEraserWidth = memo(() => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const width = useAppSelector((s) => s.tool.eraser.width);
|
const width = useAppSelector(selectEraserWidth);
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(v: number) => {
|
(v: number) => {
|
||||||
dispatch(eraserWidthChanged(Math.round(v)));
|
dispatch(eraserWidthChanged(Math.round(v)));
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
import { Flex, Popover, PopoverBody, PopoverContent, PopoverTrigger } from '@invoke-ai/ui-library';
|
import { Flex, Popover, PopoverBody, PopoverContent, PopoverTrigger } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIColorPicker from 'common/components/IAIColorPicker';
|
import IAIColorPicker from 'common/components/IAIColorPicker';
|
||||||
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
|
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
|
||||||
import { fillChanged } from 'features/controlLayers/store/toolSlice';
|
import { fillChanged, selectToolSlice } from 'features/controlLayers/store/toolSlice';
|
||||||
import type { RgbaColor } from 'features/controlLayers/store/types';
|
import type { RgbaColor } from 'features/controlLayers/store/types';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const selectFill = createSelector(selectToolSlice, (tool) => tool.fill);
|
||||||
|
|
||||||
export const ToolFillColorPicker = memo(() => {
|
export const ToolFillColorPicker = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const fill = useAppSelector((s) => s.tool.fill);
|
const fill = useAppSelector(selectFill);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(color: RgbaColor) => {
|
(color: RgbaColor) => {
|
||||||
|
@ -3,6 +3,7 @@ import { useAppSelector } from 'app/store/storeHooks';
|
|||||||
import { useSelectTool, useToolIsSelected } from 'features/controlLayers/components/Tool/hooks';
|
import { useSelectTool, useToolIsSelected } from 'features/controlLayers/components/Tool/hooks';
|
||||||
import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering';
|
import { useIsFiltering } from 'features/controlLayers/hooks/useIsFiltering';
|
||||||
import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming';
|
import { useIsTransforming } from 'features/controlLayers/hooks/useIsTransforming';
|
||||||
|
import { selectIsStaging } from 'features/controlLayers/store/canvasSessionSlice';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -12,7 +13,7 @@ export const ToolViewButton = memo(() => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const isTransforming = useIsTransforming();
|
const isTransforming = useIsTransforming();
|
||||||
const isFiltering = useIsFiltering();
|
const isFiltering = useIsFiltering();
|
||||||
const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
|
const isStaging = useAppSelector(selectIsStaging);
|
||||||
const selectView = useSelectTool('view');
|
const selectView = useSelectTool('view');
|
||||||
const isSelected = useToolIsSelected('view');
|
const isSelected = useToolIsSelected('view');
|
||||||
const isDisabled = useMemo(() => {
|
const isDisabled = useMemo(() => {
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@invoke-ai/ui-library';
|
} from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { snapToNearest } from 'features/controlLayers/konva/util';
|
import { snapToNearest } from 'features/controlLayers/konva/util';
|
||||||
import { entityOpacityChanged } from 'features/controlLayers/store/canvasSlice';
|
import { entityOpacityChanged } from 'features/controlLayers/store/canvasSlice';
|
||||||
@ -60,27 +61,29 @@ const sliderDefaultValue = mapOpacityToSliderValue(100);
|
|||||||
|
|
||||||
const snapCandidates = marks.slice(1, marks.length - 1);
|
const snapCandidates = marks.slice(1, marks.length - 1);
|
||||||
|
|
||||||
|
const selectOpacity = createSelector(selectCanvasSlice, (canvas) => {
|
||||||
|
const selectedEntityIdentifier = canvas.selectedEntityIdentifier;
|
||||||
|
if (!selectedEntityIdentifier) {
|
||||||
|
return 100; // fallback to 100% opacity
|
||||||
|
}
|
||||||
|
const selectedEntity = selectEntity(canvas, selectedEntityIdentifier);
|
||||||
|
if (!selectedEntity) {
|
||||||
|
return 100; // fallback to 100% opacity
|
||||||
|
}
|
||||||
|
if (!isDrawableEntity(selectedEntity)) {
|
||||||
|
return 100; // fallback to 100% opacity
|
||||||
|
}
|
||||||
|
// Opacity is a float from 0-1, but we want to display it as a percentage
|
||||||
|
return selectedEntity.opacity * 100;
|
||||||
|
});
|
||||||
|
|
||||||
export const CanvasEntityOpacity = memo(() => {
|
export const CanvasEntityOpacity = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
|
const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
|
||||||
const opacity = useAppSelector((s) => {
|
const opacity = useAppSelector(selectOpacity);
|
||||||
const selectedEntityIdentifier = selectSelectedEntityIdentifier(s);
|
|
||||||
if (!selectedEntityIdentifier) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const canvas = selectCanvasSlice(s);
|
|
||||||
const selectedEntity = selectEntity(canvas, selectedEntityIdentifier);
|
|
||||||
if (!selectedEntity) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!isDrawableEntity(selectedEntity)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return selectedEntity.opacity;
|
|
||||||
});
|
|
||||||
|
|
||||||
const [localOpacity, setLocalOpacity] = useState((opacity ?? 1) * 100);
|
const [localOpacity, setLocalOpacity] = useState(opacity);
|
||||||
|
|
||||||
const onChangeSlider = useCallback(
|
const onChangeSlider = useCallback(
|
||||||
(opacity: number) => {
|
(opacity: number) => {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||||
|
import { selectConfigSlice } from 'features/system/store/configSlice';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
@ -12,15 +14,11 @@ type Props = {
|
|||||||
const formatValue = (v: number) => v.toFixed(2);
|
const formatValue = (v: number) => v.toFixed(2);
|
||||||
const marks = [0, 1, 2];
|
const marks = [0, 1, 2];
|
||||||
|
|
||||||
|
const selectWeightConfig = createSelector(selectConfigSlice, (config) => config.sd.ca.weight);
|
||||||
|
|
||||||
export const Weight = memo(({ weight, onChange }: Props) => {
|
export const Weight = memo(({ weight, onChange }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const initial = useAppSelector((s) => s.config.sd.ca.weight.initial);
|
const config = useAppSelector(selectWeightConfig);
|
||||||
const sliderMin = useAppSelector((s) => s.config.sd.ca.weight.sliderMin);
|
|
||||||
const sliderMax = useAppSelector((s) => s.config.sd.ca.weight.sliderMax);
|
|
||||||
const numberInputMin = useAppSelector((s) => s.config.sd.ca.weight.numberInputMin);
|
|
||||||
const numberInputMax = useAppSelector((s) => s.config.sd.ca.weight.numberInputMax);
|
|
||||||
const coarseStep = useAppSelector((s) => s.config.sd.ca.weight.coarseStep);
|
|
||||||
const fineStep = useAppSelector((s) => s.config.sd.ca.weight.fineStep);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl orientation="horizontal">
|
<FormControl orientation="horizontal">
|
||||||
@ -30,23 +28,23 @@ export const Weight = memo(({ weight, onChange }: Props) => {
|
|||||||
<CompositeSlider
|
<CompositeSlider
|
||||||
value={weight}
|
value={weight}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
defaultValue={initial}
|
defaultValue={config.initial}
|
||||||
min={sliderMin}
|
min={config.sliderMin}
|
||||||
max={sliderMax}
|
max={config.sliderMax}
|
||||||
step={coarseStep}
|
step={config.coarseStep}
|
||||||
fineStep={fineStep}
|
fineStep={config.fineStep}
|
||||||
marks={marks}
|
marks={marks}
|
||||||
formatValue={formatValue}
|
formatValue={formatValue}
|
||||||
/>
|
/>
|
||||||
<CompositeNumberInput
|
<CompositeNumberInput
|
||||||
value={weight}
|
value={weight}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
min={numberInputMin}
|
min={config.numberInputMin}
|
||||||
max={numberInputMax}
|
max={config.numberInputMax}
|
||||||
step={coarseStep}
|
step={config.coarseStep}
|
||||||
fineStep={fineStep}
|
fineStep={config.fineStep}
|
||||||
maxW={20}
|
maxW={20}
|
||||||
defaultValue={initial}
|
defaultValue={config.initial}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
);
|
);
|
||||||
|
@ -1,21 +1,16 @@
|
|||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
||||||
|
import { selectIsStaging } from 'features/controlLayers/store/canvasSessionSlice';
|
||||||
import { entityDeleted } from 'features/controlLayers/store/canvasSlice';
|
import { entityDeleted } from 'features/controlLayers/store/canvasSlice';
|
||||||
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
|
import { selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
|
||||||
const selectSelectedEntityIdentifier = createMemoizedSelector(
|
|
||||||
selectCanvasSlice,
|
|
||||||
(canvasState) => canvasState.selectedEntityIdentifier
|
|
||||||
);
|
|
||||||
|
|
||||||
export function useCanvasDeleteLayerHotkey() {
|
export function useCanvasDeleteLayerHotkey() {
|
||||||
useAssertSingleton(useCanvasDeleteLayerHotkey.name);
|
useAssertSingleton(useCanvasDeleteLayerHotkey.name);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
|
const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
|
||||||
const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
|
const isStaging = useAppSelector(selectIsStaging);
|
||||||
|
|
||||||
const deleteSelectedLayer = useCallback(() => {
|
const deleteSelectedLayer = useCallback(() => {
|
||||||
if (selectedEntityIdentifier === null) {
|
if (selectedEntityIdentifier === null) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { createMemoizedAppSelector } from 'app/store/createMemoizedSelector';
|
import { createMemoizedAppSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { deepClone } from 'common/util/deepClone';
|
import { deepClone } from 'common/util/deepClone';
|
||||||
|
import { selectBase } from 'features/controlLayers/store/paramsSlice';
|
||||||
import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
|
import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
|
||||||
import type {
|
import type {
|
||||||
CanvasEntityIdentifier,
|
CanvasEntityIdentifier,
|
||||||
@ -30,7 +31,7 @@ export const useControlLayerControlAdapter = (entityIdentifier: CanvasEntityIden
|
|||||||
export const useDefaultControlAdapter = (): ControlNetConfig | T2IAdapterConfig => {
|
export const useDefaultControlAdapter = (): ControlNetConfig | T2IAdapterConfig => {
|
||||||
const [modelConfigs] = useControlNetAndT2IAdapterModels();
|
const [modelConfigs] = useControlNetAndT2IAdapterModels();
|
||||||
|
|
||||||
const baseModel = useAppSelector((s) => s.params.model?.base);
|
const baseModel = useAppSelector(selectBase);
|
||||||
|
|
||||||
const defaultControlAdapter = useMemo(() => {
|
const defaultControlAdapter = useMemo(() => {
|
||||||
const compatibleModels = modelConfigs.filter((m) => (baseModel ? m.base === baseModel : true));
|
const compatibleModels = modelConfigs.filter((m) => (baseModel ? m.base === baseModel : true));
|
||||||
@ -51,7 +52,7 @@ export const useDefaultControlAdapter = (): ControlNetConfig | T2IAdapterConfig
|
|||||||
export const useDefaultIPAdapter = (): IPAdapterConfig => {
|
export const useDefaultIPAdapter = (): IPAdapterConfig => {
|
||||||
const [modelConfigs] = useIPAdapterModels();
|
const [modelConfigs] = useIPAdapterModels();
|
||||||
|
|
||||||
const baseModel = useAppSelector((s) => s.params.model?.base);
|
const baseModel = useAppSelector(selectBase);
|
||||||
|
|
||||||
const defaultControlAdapter = useMemo(() => {
|
const defaultControlAdapter = useMemo(() => {
|
||||||
const compatibleModels = modelConfigs.filter((m) => (baseModel ? m.base === baseModel : true));
|
const compatibleModels = modelConfigs.filter((m) => (baseModel ? m.base === baseModel : true));
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
|
import { createSelector, createSlice, type PayloadAction } from '@reduxjs/toolkit';
|
||||||
import type { PersistConfig, RootState } from 'app/store/store';
|
import type { PersistConfig, RootState } from 'app/store/store';
|
||||||
import type { RgbaColor } from 'features/controlLayers/store/types';
|
import type { RgbaColor } from 'features/controlLayers/store/types';
|
||||||
import { CLIP_SKIP_MAP } from 'features/parameters/types/constants';
|
import { CLIP_SKIP_MAP } from 'features/parameters/types/constants';
|
||||||
@ -271,6 +271,7 @@ export const {
|
|||||||
} = paramsSlice.actions;
|
} = paramsSlice.actions;
|
||||||
|
|
||||||
export const selectParamsSlice = (state: RootState) => state.params;
|
export const selectParamsSlice = (state: RootState) => state.params;
|
||||||
|
export const selectBase = createSelector(selectParamsSlice, (params) => params.model?.base);
|
||||||
|
|
||||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||||
const migrate = (state: any): any => {
|
const migrate = (state: any): any => {
|
||||||
|
@ -39,6 +39,11 @@ export const selectEntityCount = createSelector(selectCanvasSlice, (canvas) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects if the canvas has any entities.
|
||||||
|
*/
|
||||||
|
export const selectHasEntities = createSelector(selectEntityCount, (count) => count > 0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selects the optimal dimension for the canvas based on the currently-model
|
* Selects the optimal dimension for the canvas based on the currently-model
|
||||||
*/
|
*/
|
||||||
@ -185,4 +190,3 @@ export const selectIsSelectedEntityDrawable = createSelector(
|
|||||||
return isDrawableEntityType(selectedEntityIdentifier.type);
|
return isDrawableEntityType(selectedEntityIdentifier.type);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
|
import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
|
||||||
import type { PersistConfig } from 'app/store/store';
|
import type { PersistConfig, RootState } from 'app/store/store';
|
||||||
import type { RgbaColor } from 'features/controlLayers/store/types';
|
import type { RgbaColor } from 'features/controlLayers/store/types';
|
||||||
|
|
||||||
export type ToolState = {
|
export type ToolState = {
|
||||||
@ -52,3 +52,5 @@ export const toolPersistConfig: PersistConfig<ToolState> = {
|
|||||||
migrate,
|
migrate,
|
||||||
persistDenylist: [],
|
persistDenylist: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const selectToolSlice = (state: RootState) => state.tool;
|
||||||
|
@ -3,6 +3,7 @@ import { IconButton } from '@invoke-ai/ui-library';
|
|||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { $isConnected } from 'app/hooks/useSocketIO';
|
import { $isConnected } from 'app/hooks/useSocketIO';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { selectSelectionCount } from 'features/gallery/store/gallerySelectors';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiTrashSimpleBold } from 'react-icons/pi';
|
import { PiTrashSimpleBold } from 'react-icons/pi';
|
||||||
@ -15,7 +16,7 @@ export const DeleteImageButton = memo((props: DeleteImageButtonProps) => {
|
|||||||
const { onClick, isDisabled } = props;
|
const { onClick, isDisabled } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const isConnected = useStore($isConnected);
|
const isConnected = useStore($isConnected);
|
||||||
const imageSelectionLength: number = useAppSelector((s) => s.gallery.selection.length);
|
const imageSelectionLength = useAppSelector(selectSelectionCount);
|
||||||
const labelMessage: string = `${t('gallery.deleteImage', { count: imageSelectionLength })} (Del)`;
|
const labelMessage: string = `${t('gallery.deleteImage', { count: imageSelectionLength })} (Del)`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { ConfirmationAlertDialog, Divider, Flex, FormControl, FormLabel, Switch, Text } from '@invoke-ai/ui-library';
|
import { ConfirmationAlertDialog, Divider, Flex, FormControl, FormLabel, Switch, Text } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
|
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
|
||||||
@ -11,7 +12,7 @@ import {
|
|||||||
} from 'features/deleteImageModal/store/slice';
|
} from 'features/deleteImageModal/store/slice';
|
||||||
import type { ImageUsage } from 'features/deleteImageModal/store/types';
|
import type { ImageUsage } from 'features/deleteImageModal/store/types';
|
||||||
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
|
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
|
||||||
import { setShouldConfirmOnDelete } from 'features/system/store/systemSlice';
|
import { selectSystemSlice, setShouldConfirmOnDelete } from 'features/system/store/systemSlice';
|
||||||
import { some } from 'lodash-es';
|
import { some } from 'lodash-es';
|
||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
@ -41,11 +42,17 @@ const selectImageUsages = createMemoizedSelector(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const selectShouldConfirmOnDelete = createSelector(selectSystemSlice, (system) => system.shouldConfirmOnDelete);
|
||||||
|
const selectIsModalOpen = createSelector(
|
||||||
|
selectDeleteImageModalSlice,
|
||||||
|
(deleteImageModal) => deleteImageModal.isModalOpen
|
||||||
|
);
|
||||||
|
|
||||||
const DeleteImageModal = () => {
|
const DeleteImageModal = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const shouldConfirmOnDelete = useAppSelector((s) => s.system.shouldConfirmOnDelete);
|
const shouldConfirmOnDelete = useAppSelector(selectShouldConfirmOnDelete);
|
||||||
const isModalOpen = useAppSelector((s) => s.deleteImageModal.isModalOpen);
|
const isModalOpen = useAppSelector(selectIsModalOpen);
|
||||||
const { imagesToDelete, imagesUsage, imageUsageSummary } = useAppSelector(selectImageUsages);
|
const { imagesToDelete, imagesUsage, imageUsageSummary } = useAppSelector(selectImageUsages);
|
||||||
|
|
||||||
const handleChangeShouldConfirmOnDelete = useCallback(
|
const handleChangeShouldConfirmOnDelete = useCallback(
|
||||||
|
@ -2,6 +2,7 @@ import type { ChakraProps } from '@invoke-ai/ui-library';
|
|||||||
import { Box, Flex, Heading, Image, Text } from '@invoke-ai/ui-library';
|
import { Box, Flex, Heading, Image, Text } from '@invoke-ai/ui-library';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import type { TypesafeDraggableData } from 'features/dnd/types';
|
import type { TypesafeDraggableData } from 'features/dnd/types';
|
||||||
|
import { selectSelectionCount } from 'features/gallery/store/gallerySelectors';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
@ -34,7 +35,7 @@ const multiImageStyles: ChakraProps['sx'] = {
|
|||||||
|
|
||||||
const DragPreview = (props: OverlayDragImageProps) => {
|
const DragPreview = (props: OverlayDragImageProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const selectionCount = useAppSelector((s) => s.gallery.selection.length);
|
const selectionCount = useAppSelector(selectSelectionCount);
|
||||||
if (!props.dragData) {
|
if (!props.dragData) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||||
import { maxPromptsChanged } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
import { maxPromptsChanged, selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
||||||
|
import { selectConfigSlice } from 'features/system/store/configSlice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const selectMaxPrompts = createSelector(selectDynamicPromptsSlice, (dynamicPrompts) => dynamicPrompts.maxPrompts);
|
||||||
|
const selectMaxPromptsConfig = createSelector(selectConfigSlice, (config) => config.sd.dynamicPrompts.maxPrompts);
|
||||||
|
const selectIsDisabled = createSelector(selectDynamicPromptsSlice, (dynamicPrompts) => !dynamicPrompts.combinatorial);
|
||||||
|
|
||||||
const ParamDynamicPromptsMaxPrompts = () => {
|
const ParamDynamicPromptsMaxPrompts = () => {
|
||||||
const maxPrompts = useAppSelector((s) => s.dynamicPrompts.maxPrompts);
|
const maxPrompts = useAppSelector(selectMaxPrompts);
|
||||||
const sliderMin = useAppSelector((s) => s.config.sd.dynamicPrompts.maxPrompts.sliderMin);
|
const config = useAppSelector(selectMaxPromptsConfig);
|
||||||
const sliderMax = useAppSelector((s) => s.config.sd.dynamicPrompts.maxPrompts.sliderMax);
|
const isDisabled = useAppSelector(selectIsDisabled);
|
||||||
const numberInputMin = useAppSelector((s) => s.config.sd.dynamicPrompts.maxPrompts.numberInputMin);
|
|
||||||
const numberInputMax = useAppSelector((s) => s.config.sd.dynamicPrompts.maxPrompts.numberInputMax);
|
|
||||||
const initial = useAppSelector((s) => s.config.sd.dynamicPrompts.maxPrompts.initial);
|
|
||||||
const isDisabled = useAppSelector((s) => !s.dynamicPrompts.combinatorial);
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -29,18 +31,18 @@ const ParamDynamicPromptsMaxPrompts = () => {
|
|||||||
<FormLabel>{t('dynamicPrompts.maxPrompts')}</FormLabel>
|
<FormLabel>{t('dynamicPrompts.maxPrompts')}</FormLabel>
|
||||||
</InformationalPopover>
|
</InformationalPopover>
|
||||||
<CompositeSlider
|
<CompositeSlider
|
||||||
min={sliderMin}
|
min={config.sliderMin}
|
||||||
max={sliderMax}
|
max={config.sliderMax}
|
||||||
value={maxPrompts}
|
value={maxPrompts}
|
||||||
defaultValue={initial}
|
defaultValue={config.initial}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
marks
|
marks
|
||||||
/>
|
/>
|
||||||
<CompositeNumberInput
|
<CompositeNumberInput
|
||||||
min={numberInputMin}
|
min={config.numberInputMin}
|
||||||
max={numberInputMax}
|
max={config.numberInputMax}
|
||||||
value={maxPrompts}
|
value={maxPrompts}
|
||||||
defaultValue={initial}
|
defaultValue={config.initial}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { ChakraProps } from '@invoke-ai/ui-library';
|
import type { ChakraProps } from '@invoke-ai/ui-library';
|
||||||
import { Flex, FormControl, FormLabel, ListItem, OrderedList, Spinner, Text } from '@invoke-ai/ui-library';
|
import { Flex, FormControl, FormLabel, ListItem, OrderedList, Spinner, Text } from '@invoke-ai/ui-library';
|
||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||||
@ -10,17 +10,20 @@ import { memo, useMemo } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiWarningCircleBold } from 'react-icons/pi';
|
import { PiWarningCircleBold } from 'react-icons/pi';
|
||||||
|
|
||||||
const selectPrompts = createMemoizedSelector(selectDynamicPromptsSlice, (dynamicPrompts) => dynamicPrompts.prompts);
|
|
||||||
|
|
||||||
const listItemStyles: ChakraProps['sx'] = {
|
const listItemStyles: ChakraProps['sx'] = {
|
||||||
'&::marker': { color: 'base.500' },
|
'&::marker': { color: 'base.500' },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectPrompts = createSelector(selectDynamicPromptsSlice, (dynamicPrompts) => dynamicPrompts.prompts);
|
||||||
|
const selectParsingError = createSelector(selectDynamicPromptsSlice, (dynamicPrompts) => dynamicPrompts.parsingError);
|
||||||
|
const selectIsError = createSelector(selectDynamicPromptsSlice, (dynamicPrompts) => dynamicPrompts.isError);
|
||||||
|
const selectIsLoading = createSelector(selectDynamicPromptsSlice, (dynamicPrompts) => dynamicPrompts.isLoading);
|
||||||
|
|
||||||
const ParamDynamicPromptsPreview = () => {
|
const ParamDynamicPromptsPreview = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const parsingError = useAppSelector((s) => s.dynamicPrompts.parsingError);
|
const parsingError = useAppSelector(selectParsingError);
|
||||||
const isError = useAppSelector((s) => s.dynamicPrompts.isError);
|
const isError = useAppSelector(selectIsError);
|
||||||
const isLoading = useAppSelector((s) => s.dynamicPrompts.isLoading);
|
const isLoading = useAppSelector(selectIsLoading);
|
||||||
const prompts = useAppSelector(selectPrompts);
|
const prompts = useAppSelector(selectPrompts);
|
||||||
|
|
||||||
const label = useMemo(() => {
|
const label = useMemo(() => {
|
||||||
|
@ -1,15 +1,22 @@
|
|||||||
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
|
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
|
||||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||||
import { isSeedBehaviour, seedBehaviourChanged } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
import {
|
||||||
|
isSeedBehaviour,
|
||||||
|
seedBehaviourChanged,
|
||||||
|
selectDynamicPromptsSlice,
|
||||||
|
} from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const selectSeedBehaviour = createSelector(selectDynamicPromptsSlice, (dynamicPrompts) => dynamicPrompts.seedBehaviour);
|
||||||
|
|
||||||
const ParamDynamicPromptsSeedBehaviour = () => {
|
const ParamDynamicPromptsSeedBehaviour = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const seedBehaviour = useAppSelector((s) => s.dynamicPrompts.seedBehaviour);
|
const seedBehaviour = useAppSelector(selectSeedBehaviour);
|
||||||
|
|
||||||
const options = useMemo<ComboboxOption[]>(() => {
|
const options = useMemo<ComboboxOption[]>(() => {
|
||||||
return [
|
return [
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||||
import { IconButton, spinAnimation, Tooltip } from '@invoke-ai/ui-library';
|
import { IconButton, spinAnimation, Tooltip } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useDynamicPromptsModal } from 'features/dynamicPrompts/hooks/useDynamicPromptsModal';
|
import { useDynamicPromptsModal } from 'features/dynamicPrompts/hooks/useDynamicPromptsModal';
|
||||||
|
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { BsBracesAsterisk } from 'react-icons/bs';
|
import { BsBracesAsterisk } from 'react-icons/bs';
|
||||||
@ -10,10 +12,15 @@ const loadingStyles: SystemStyleObject = {
|
|||||||
svg: { animation: spinAnimation },
|
svg: { animation: spinAnimation },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectIsError = createSelector(selectDynamicPromptsSlice, (dynamicPrompts) =>
|
||||||
|
Boolean(dynamicPrompts.isError || dynamicPrompts.parsingError)
|
||||||
|
);
|
||||||
|
const selectIsLoading = createSelector(selectDynamicPromptsSlice, (dynamicPrompts) => dynamicPrompts.isLoading);
|
||||||
|
|
||||||
export const ShowDynamicPromptsPreviewButton = memo(() => {
|
export const ShowDynamicPromptsPreviewButton = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const isLoading = useAppSelector((s) => s.dynamicPrompts.isLoading);
|
const isLoading = useAppSelector(selectIsLoading);
|
||||||
const isError = useAppSelector((s) => Boolean(s.dynamicPrompts.isError || s.dynamicPrompts.parsingError));
|
const isError = useAppSelector(selectIsError);
|
||||||
const { isOpen, onOpen } = useDynamicPromptsModal();
|
const { isOpen, onOpen } = useDynamicPromptsModal();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
|
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
|
||||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { selectAutoAddBoardId, selectAutoAssignBoardOnClick } from 'features/gallery/store/gallerySelectors';
|
||||||
import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
|
import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -9,8 +10,8 @@ import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
|||||||
const BoardAutoAddSelect = () => {
|
const BoardAutoAddSelect = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
const autoAddBoardId = useAppSelector(selectAutoAddBoardId);
|
||||||
const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick);
|
const autoAssignBoardOnClick = useAppSelector(selectAutoAssignBoardOnClick);
|
||||||
const { options, hasBoards } = useListAllBoardsQuery(
|
const { options, hasBoards } = useListAllBoardsQuery(
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
|
@ -2,7 +2,8 @@ import type { ContextMenuProps } from '@invoke-ai/ui-library';
|
|||||||
import { ContextMenu, MenuGroup, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
import { ContextMenu, MenuGroup, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { autoAddBoardIdChanged, selectGallerySlice } from 'features/gallery/store/gallerySlice';
|
import { selectAutoAddBoardId, selectAutoAssignBoardOnClick } from 'features/gallery/store/gallerySelectors';
|
||||||
|
import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
|
||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
@ -24,9 +25,9 @@ type Props = {
|
|||||||
const BoardContextMenu = ({ board, setBoardToDelete, children }: Props) => {
|
const BoardContextMenu = ({ board, setBoardToDelete, children }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick);
|
const autoAssignBoardOnClick = useAppSelector(selectAutoAssignBoardOnClick);
|
||||||
const selectIsSelectedForAutoAdd = useMemo(
|
const selectIsSelectedForAutoAdd = useMemo(
|
||||||
() => createSelector(selectGallerySlice, (gallery) => board.board_id === gallery.autoAddBoardId),
|
() => createSelector(selectAutoAddBoardId, (autoAddBoardId) => board.board_id === autoAddBoardId),
|
||||||
[board.board_id]
|
[board.board_id]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { IconButton } from '@invoke-ai/ui-library';
|
import { IconButton } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { boardIdSelected, boardSearchTextChanged } from 'features/gallery/store/gallerySlice';
|
import { boardIdSelected, boardSearchTextChanged } from 'features/gallery/store/gallerySlice';
|
||||||
|
import { selectAllowPrivateBoards } from 'features/system/store/configSelectors';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiPlusBold } from 'react-icons/pi';
|
import { PiPlusBold } from 'react-icons/pi';
|
||||||
@ -13,7 +14,7 @@ type Props = {
|
|||||||
const AddBoardButton = ({ isPrivateBoard }: Props) => {
|
const AddBoardButton = ({ isPrivateBoard }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const allowPrivateBoards = useAppSelector((s) => s.config.allowPrivateBoards);
|
const allowPrivateBoards = useAppSelector(selectAllowPrivateBoards);
|
||||||
const [createBoard, { isLoading }] = useCreateBoardMutation();
|
const [createBoard, { isLoading }] = useCreateBoardMutation();
|
||||||
const label = useMemo(() => {
|
const label = useMemo(() => {
|
||||||
if (!allowPrivateBoards) {
|
if (!allowPrivateBoards) {
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
import { Button, Collapse, Flex, Icon, Text, useDisclosure } from '@invoke-ai/ui-library';
|
import { Button, Collapse, Flex, Icon, Text, useDisclosure } from '@invoke-ai/ui-library';
|
||||||
import { EMPTY_ARRAY } from 'app/store/constants';
|
import { EMPTY_ARRAY } from 'app/store/constants';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors';
|
import {
|
||||||
|
selectBoardSearchText,
|
||||||
|
selectListBoardsQueryArgs,
|
||||||
|
selectSelectedBoardId,
|
||||||
|
} from 'features/gallery/store/gallerySelectors';
|
||||||
|
import { selectAllowPrivateBoards } from 'features/system/store/configSelectors';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiCaretDownBold } from 'react-icons/pi';
|
import { PiCaretDownBold } from 'react-icons/pi';
|
||||||
@ -17,13 +22,14 @@ type Props = {
|
|||||||
setBoardToDelete: (board?: BoardDTO) => void;
|
setBoardToDelete: (board?: BoardDTO) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const BoardsList = ({ isPrivate, setBoardToDelete }: Props) => {
|
export const BoardsList = ({ isPrivate, setBoardToDelete }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId);
|
const selectedBoardId = useAppSelector(selectSelectedBoardId);
|
||||||
const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText);
|
const boardSearchText = useAppSelector(selectBoardSearchText);
|
||||||
const queryArgs = useAppSelector(selectListBoardsQueryArgs);
|
const queryArgs = useAppSelector(selectListBoardsQueryArgs);
|
||||||
const { data: boards } = useListAllBoardsQuery(queryArgs);
|
const { data: boards } = useListAllBoardsQuery(queryArgs);
|
||||||
const allowPrivateBoards = useAppSelector((s) => s.config.allowPrivateBoards);
|
const allowPrivateBoards = useAppSelector(selectAllowPrivateBoards);
|
||||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||||
|
|
||||||
const filteredBoards = useMemo(() => {
|
const filteredBoards = useMemo(() => {
|
||||||
|
@ -2,6 +2,7 @@ import { Box } from '@invoke-ai/ui-library';
|
|||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
|
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
|
||||||
import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModal';
|
import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModal';
|
||||||
|
import { selectAllowPrivateBoards } from 'features/system/store/configSelectors';
|
||||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { memo, useState } from 'react';
|
import { memo, useState } from 'react';
|
||||||
@ -15,7 +16,7 @@ const overlayScrollbarsStyles: CSSProperties = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const BoardsListWrapper = () => {
|
const BoardsListWrapper = () => {
|
||||||
const allowPrivateBoards = useAppSelector((s) => s.config.allowPrivateBoards);
|
const allowPrivateBoards = useAppSelector(selectAllowPrivateBoards);
|
||||||
const [boardToDelete, setBoardToDelete] = useState<BoardDTO>();
|
const [boardToDelete, setBoardToDelete] = useState<BoardDTO>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { IconButton, Input, InputGroup, InputRightElement } from '@invoke-ai/ui-library';
|
import { IconButton, Input, InputGroup, InputRightElement } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { selectBoardSearchText } from 'features/gallery/store/gallerySelectors';
|
||||||
import { boardSearchTextChanged } from 'features/gallery/store/gallerySlice';
|
import { boardSearchTextChanged } from 'features/gallery/store/gallerySlice';
|
||||||
import type { ChangeEvent, KeyboardEvent } from 'react';
|
import type { ChangeEvent, KeyboardEvent } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
@ -8,7 +9,7 @@ import { PiXBold } from 'react-icons/pi';
|
|||||||
|
|
||||||
const BoardsSearch = () => {
|
const BoardsSearch = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText);
|
const boardSearchText = useAppSelector(selectBoardSearchText);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleBoardSearch = useCallback(
|
const handleBoardSearch = useCallback(
|
||||||
|
@ -18,6 +18,11 @@ import type { AddToBoardDropData } from 'features/dnd/types';
|
|||||||
import { AutoAddBadge } from 'features/gallery/components/Boards/AutoAddBadge';
|
import { AutoAddBadge } from 'features/gallery/components/Boards/AutoAddBadge';
|
||||||
import BoardContextMenu from 'features/gallery/components/Boards/BoardContextMenu';
|
import BoardContextMenu from 'features/gallery/components/Boards/BoardContextMenu';
|
||||||
import { BoardTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTooltip';
|
import { BoardTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTooltip';
|
||||||
|
import {
|
||||||
|
selectAutoAddBoardId,
|
||||||
|
selectAutoAssignBoardOnClick,
|
||||||
|
selectSelectedBoardId,
|
||||||
|
} from 'features/gallery/store/gallerySelectors';
|
||||||
import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice';
|
import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice';
|
||||||
import type { MouseEvent, MouseEventHandler, MutableRefObject } from 'react';
|
import type { MouseEvent, MouseEventHandler, MutableRefObject } from 'react';
|
||||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
@ -49,9 +54,9 @@ interface GalleryBoardProps {
|
|||||||
const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps) => {
|
const GalleryBoard = ({ board, isSelected, setBoardToDelete }: GalleryBoardProps) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
const autoAddBoardId = useAppSelector(selectAutoAddBoardId);
|
||||||
const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick);
|
const autoAssignBoardOnClick = useAppSelector(selectAutoAssignBoardOnClick);
|
||||||
const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId);
|
const selectedBoardId = useAppSelector(selectSelectedBoardId);
|
||||||
const editingDisclosure = useDisclosure();
|
const editingDisclosure = useDisclosure();
|
||||||
const [localBoardName, setLocalBoardName] = useState(board.board_name);
|
const [localBoardName, setLocalBoardName] = useState(board.board_name);
|
||||||
const onStartEditingRef = useRef<MouseEventHandler | undefined>(undefined);
|
const onStartEditingRef = useRef<MouseEventHandler | undefined>(undefined);
|
||||||
|
@ -6,6 +6,11 @@ import type { RemoveFromBoardDropData } from 'features/dnd/types';
|
|||||||
import { AutoAddBadge } from 'features/gallery/components/Boards/AutoAddBadge';
|
import { AutoAddBadge } from 'features/gallery/components/Boards/AutoAddBadge';
|
||||||
import { BoardTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTooltip';
|
import { BoardTooltip } from 'features/gallery/components/Boards/BoardsList/BoardTooltip';
|
||||||
import NoBoardBoardContextMenu from 'features/gallery/components/Boards/NoBoardBoardContextMenu';
|
import NoBoardBoardContextMenu from 'features/gallery/components/Boards/NoBoardBoardContextMenu';
|
||||||
|
import {
|
||||||
|
selectAutoAddBoardId,
|
||||||
|
selectAutoAssignBoardOnClick,
|
||||||
|
selectBoardSearchText,
|
||||||
|
} from 'features/gallery/store/gallerySelectors';
|
||||||
import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice';
|
import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -27,9 +32,9 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
|
|||||||
return { imagesTotal: data?.total ?? 0 };
|
return { imagesTotal: data?.total ?? 0 };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
const autoAddBoardId = useAppSelector(selectAutoAddBoardId);
|
||||||
const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick);
|
const autoAssignBoardOnClick = useAppSelector(selectAutoAssignBoardOnClick);
|
||||||
const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText);
|
const boardSearchText = useAppSelector(selectBoardSearchText);
|
||||||
const boardName = useBoardName('none');
|
const boardName = useBoardName('none');
|
||||||
const handleSelectBoard = useCallback(() => {
|
const handleSelectBoard = useCallback(() => {
|
||||||
dispatch(boardIdSelected({ boardId: 'none' }));
|
dispatch(boardIdSelected({ boardId: 'none' }));
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import type { ContextMenuProps } from '@invoke-ai/ui-library';
|
import type { ContextMenuProps } from '@invoke-ai/ui-library';
|
||||||
import { ContextMenu, MenuGroup, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
import { ContextMenu, MenuGroup, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { selectAutoAddBoardId, selectAutoAssignBoardOnClick } from 'features/gallery/store/gallerySelectors';
|
||||||
import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
|
import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
|
||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
@ -12,11 +14,13 @@ type Props = {
|
|||||||
children: ContextMenuProps<HTMLDivElement>['children'];
|
children: ContextMenuProps<HTMLDivElement>['children'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectIsSelectedForAutoAdd = createSelector(selectAutoAddBoardId, (autoAddBoardId) => autoAddBoardId === 'none');
|
||||||
|
|
||||||
const NoBoardBoardContextMenu = ({ children }: Props) => {
|
const NoBoardBoardContextMenu = ({ children }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick);
|
const autoAssignBoardOnClick = useAppSelector(selectAutoAssignBoardOnClick);
|
||||||
const isSelectedForAutoAdd = useAppSelector((s) => s.gallery.autoAddBoardId === 'none');
|
const isSelectedForAutoAdd = useAppSelector(selectIsSelectedForAutoAdd);
|
||||||
const isBulkDownloadEnabled = useFeatureStatus('bulkDownload');
|
const isBulkDownloadEnabled = useFeatureStatus('bulkDownload');
|
||||||
|
|
||||||
const [bulkDownload] = useBulkDownloadImagesMutation();
|
const [bulkDownload] = useBulkDownloadImagesMutation();
|
||||||
|
@ -11,9 +11,11 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
} from '@invoke-ai/ui-library';
|
} from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useGallerySearchTerm } from 'features/gallery/components/ImageGrid/useGallerySearchTerm';
|
import { useGallerySearchTerm } from 'features/gallery/components/ImageGrid/useGallerySearchTerm';
|
||||||
import { galleryViewChanged } from 'features/gallery/store/gallerySlice';
|
import { selectSelectedBoardId } from 'features/gallery/store/gallerySelectors';
|
||||||
|
import { galleryViewChanged, selectGallerySlice } from 'features/gallery/store/gallerySlice';
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -38,11 +40,14 @@ const SELECTED_STYLES: ChakraProps['sx'] = {
|
|||||||
|
|
||||||
const COLLAPSE_STYLES: CSSProperties = { flexShrink: 0, minHeight: 0 };
|
const COLLAPSE_STYLES: CSSProperties = { flexShrink: 0, minHeight: 0 };
|
||||||
|
|
||||||
|
const selectGalleryView = createSelector(selectGallerySlice, (gallery) => gallery.galleryView);
|
||||||
|
const selectSearchTerm = createSelector(selectGallerySlice, (gallery) => gallery.searchTerm);
|
||||||
|
|
||||||
export const Gallery = () => {
|
export const Gallery = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const galleryView = useAppSelector((s) => s.gallery.galleryView);
|
const galleryView = useAppSelector(selectGalleryView);
|
||||||
const initialSearchTerm = useAppSelector((s) => s.gallery.searchTerm);
|
const initialSearchTerm = useAppSelector(selectSearchTerm);
|
||||||
const searchDisclosure = useDisclosure({ defaultIsOpen: initialSearchTerm.length > 0 });
|
const searchDisclosure = useDisclosure({ defaultIsOpen: initialSearchTerm.length > 0 });
|
||||||
const [searchTerm, onChangeSearchTerm, onResetSearchTerm] = useGallerySearchTerm();
|
const [searchTerm, onChangeSearchTerm, onResetSearchTerm] = useGallerySearchTerm();
|
||||||
|
|
||||||
@ -59,7 +64,7 @@ export const Gallery = () => {
|
|||||||
onResetSearchTerm();
|
onResetSearchTerm();
|
||||||
}, [onResetSearchTerm, searchDisclosure]);
|
}, [onResetSearchTerm, searchDisclosure]);
|
||||||
|
|
||||||
const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId);
|
const selectedBoardId = useAppSelector(selectSelectedBoardId);
|
||||||
const boardName = useBoardName(selectedBoardId);
|
const boardName = useBoardName(selectedBoardId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -2,6 +2,7 @@ import { Box, Button, Collapse, Divider, Flex, IconButton, useDisclosure } from
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useScopeOnFocus } from 'common/hooks/interactionScopes';
|
import { useScopeOnFocus } from 'common/hooks/interactionScopes';
|
||||||
import { GalleryHeader } from 'features/gallery/components/GalleryHeader';
|
import { GalleryHeader } from 'features/gallery/components/GalleryHeader';
|
||||||
|
import { selectBoardSearchText } from 'features/gallery/store/gallerySelectors';
|
||||||
import { boardSearchTextChanged } from 'features/gallery/store/gallerySlice';
|
import { boardSearchTextChanged } from 'features/gallery/store/gallerySlice';
|
||||||
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
|
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
|
||||||
import { usePanel, type UsePanelOptions } from 'features/ui/hooks/usePanel';
|
import { usePanel, type UsePanelOptions } from 'features/ui/hooks/usePanel';
|
||||||
@ -21,7 +22,7 @@ const COLLAPSE_STYLES: CSSProperties = { flexShrink: 0, minHeight: 0 };
|
|||||||
|
|
||||||
const GalleryPanelContent = () => {
|
const GalleryPanelContent = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText);
|
const boardSearchText = useAppSelector(selectBoardSearchText);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const boardSearchDisclosure = useDisclosure({ defaultIsOpen: !!boardSearchText.length });
|
const boardSearchDisclosure = useDisclosure({ defaultIsOpen: !!boardSearchText.length });
|
||||||
const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
|
const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
|
||||||
|
@ -1,14 +1,20 @@
|
|||||||
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { alwaysShowImageSizeBadgeChanged } from 'features/gallery/store/gallerySlice';
|
import { alwaysShowImageSizeBadgeChanged, selectGallerySlice } from 'features/gallery/store/gallerySlice';
|
||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const selectAlwaysShowImageSizeBadge = createSelector(
|
||||||
|
selectGallerySlice,
|
||||||
|
(gallery) => gallery.alwaysShowImageSizeBadge
|
||||||
|
);
|
||||||
|
|
||||||
const GallerySettingsPopover = () => {
|
const GallerySettingsPopover = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const alwaysShowImageSizeBadge = useAppSelector((s) => s.gallery.alwaysShowImageSizeBadge);
|
const alwaysShowImageSizeBadge = useAppSelector(selectAlwaysShowImageSizeBadge);
|
||||||
|
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) => dispatch(alwaysShowImageSizeBadgeChanged(e.target.checked)),
|
(e: ChangeEvent<HTMLInputElement>) => dispatch(alwaysShowImageSizeBadgeChanged(e.target.checked)),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { selectAutoAssignBoardOnClick } from 'features/gallery/store/gallerySelectors';
|
||||||
import { autoAssignBoardOnClickChanged } from 'features/gallery/store/gallerySlice';
|
import { autoAssignBoardOnClickChanged } from 'features/gallery/store/gallerySlice';
|
||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
@ -8,7 +9,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
const GallerySettingsPopover = () => {
|
const GallerySettingsPopover = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const autoAssignBoardOnClick = useAppSelector((s) => s.gallery.autoAssignBoardOnClick);
|
const autoAssignBoardOnClick = useAppSelector(selectAutoAssignBoardOnClick);
|
||||||
|
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) => dispatch(autoAssignBoardOnClickChanged(e.target.checked)),
|
(e: ChangeEvent<HTMLInputElement>) => dispatch(autoAssignBoardOnClickChanged(e.target.checked)),
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { shouldAutoSwitchChanged } from 'features/gallery/store/gallerySlice';
|
import { selectGallerySlice, shouldAutoSwitchChanged } from 'features/gallery/store/gallerySlice';
|
||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const selectShouldAutoSwitch = createSelector(selectGallerySlice, (gallery) => gallery.shouldAutoSwitch);
|
||||||
|
|
||||||
const GallerySettingsPopover = () => {
|
const GallerySettingsPopover = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const shouldAutoSwitch = useAppSelector((s) => s.gallery.shouldAutoSwitch);
|
const shouldAutoSwitch = useAppSelector(selectShouldAutoSwitch);
|
||||||
|
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) => {
|
(e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { selectGalleryImageMinimumWidth } from 'features/gallery/store/gallerySelectors';
|
||||||
import { setGalleryImageMinimumWidth } from 'features/gallery/store/gallerySlice';
|
import { setGalleryImageMinimumWidth } from 'features/gallery/store/gallerySlice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -7,7 +8,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
const GallerySettingsPopover = () => {
|
const GallerySettingsPopover = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const galleryImageMinimumWidth = useAppSelector((s) => s.gallery.galleryImageMinimumWidth);
|
const galleryImageMinimumWidth = useAppSelector(selectGalleryImageMinimumWidth);
|
||||||
|
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(v: number) => {
|
(v: number) => {
|
||||||
|
@ -1,14 +1,20 @@
|
|||||||
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { shouldShowArchivedBoardsChanged } from 'features/gallery/store/gallerySlice';
|
import { selectGallerySlice, shouldShowArchivedBoardsChanged } from 'features/gallery/store/gallerySlice';
|
||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const selectShouldShowArchivedBoards = createSelector(
|
||||||
|
selectGallerySlice,
|
||||||
|
(gallery) => gallery.shouldShowArchivedBoards
|
||||||
|
);
|
||||||
|
|
||||||
const GallerySettingsPopover = () => {
|
const GallerySettingsPopover = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const shouldShowArchivedBoards = useAppSelector((s) => s.gallery.shouldShowArchivedBoards);
|
const shouldShowArchivedBoards = useAppSelector(selectShouldShowArchivedBoards);
|
||||||
|
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) => {
|
(e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { starredFirstChanged } from 'features/gallery/store/gallerySlice';
|
import { selectGallerySlice, starredFirstChanged } from 'features/gallery/store/gallerySlice';
|
||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const selectStarredFirst = createSelector(selectGallerySlice, (gallery) => gallery.starredFirst);
|
||||||
|
|
||||||
const GallerySettingsPopover = () => {
|
const GallerySettingsPopover = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const starredFirst = useAppSelector((s) => s.gallery.starredFirst);
|
const starredFirst = useAppSelector(selectStarredFirst);
|
||||||
|
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) => {
|
(e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
import type { ComboboxOption } from '@invoke-ai/ui-library';
|
import type { ComboboxOption } from '@invoke-ai/ui-library';
|
||||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import type { SingleValue } from 'chakra-react-select';
|
import type { SingleValue } from 'chakra-react-select';
|
||||||
import { orderDirChanged } from 'features/gallery/store/gallerySlice';
|
import { orderDirChanged, selectGallerySlice } from 'features/gallery/store/gallerySlice';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
|
const selectOrderDir = createSelector(selectGallerySlice, (gallery) => gallery.orderDir);
|
||||||
|
|
||||||
const GallerySettingsPopover = () => {
|
const GallerySettingsPopover = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const orderDir = useAppSelector((s) => s.gallery.orderDir);
|
const orderDir = useAppSelector(selectOrderDir);
|
||||||
|
|
||||||
const options = useMemo<ComboboxOption[]>(
|
const options = useMemo<ComboboxOption[]>(
|
||||||
() => [
|
() => [
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type { ContextMenuProps } from '@invoke-ai/ui-library';
|
import type { ContextMenuProps } from '@invoke-ai/ui-library';
|
||||||
import { ContextMenu, MenuList } from '@invoke-ai/ui-library';
|
import { ContextMenu, MenuList } from '@invoke-ai/ui-library';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { selectSelectionCount } from 'features/gallery/store/gallerySelectors';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import type { ImageDTO } from 'services/api/types';
|
import type { ImageDTO } from 'services/api/types';
|
||||||
|
|
||||||
@ -13,7 +14,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ImageContextMenu = ({ imageDTO, children }: Props) => {
|
const ImageContextMenu = ({ imageDTO, children }: Props) => {
|
||||||
const selectionCount = useAppSelector((s) => s.gallery.selection.length);
|
const selectionCount = useAppSelector(selectSelectionCount);
|
||||||
|
|
||||||
const renderMenuFunc = useCallback(() => {
|
const renderMenuFunc = useCallback(() => {
|
||||||
if (!imageDTO) {
|
if (!imageDTO) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Flex, MenuDivider, MenuItem, Spinner } from '@invoke-ai/ui-library';
|
import { Flex, MenuDivider, MenuItem, Spinner } from '@invoke-ai/ui-library';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { $customStarUI } from 'app/store/nanostores/customStarUI';
|
import { $customStarUI } from 'app/store/nanostores/customStarUI';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useCopyImageToClipboard } from 'common/hooks/useCopyImageToClipboard';
|
import { useCopyImageToClipboard } from 'common/hooks/useCopyImageToClipboard';
|
||||||
@ -8,14 +9,14 @@ import { imagesToChangeSelected, isModalOpenChanged } from 'features/changeBoard
|
|||||||
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
|
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
|
||||||
import { useImageActions } from 'features/gallery/hooks/useImageActions';
|
import { useImageActions } from 'features/gallery/hooks/useImageActions';
|
||||||
import { sentImageToCanvas, sentImageToImg2Img } from 'features/gallery/store/actions';
|
import { sentImageToCanvas, sentImageToImg2Img } from 'features/gallery/store/actions';
|
||||||
import { imageToCompareChanged } from 'features/gallery/store/gallerySlice';
|
import { imageToCompareChanged, selectGallerySlice } from 'features/gallery/store/gallerySlice';
|
||||||
import { $templates } from 'features/nodes/store/nodesSlice';
|
import { $templates } from 'features/nodes/store/nodesSlice';
|
||||||
import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice';
|
import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { setActiveTab } from 'features/ui/store/uiSlice';
|
import { setActiveTab } from 'features/ui/store/uiSlice';
|
||||||
import { useGetAndLoadEmbeddedWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadEmbeddedWorkflow';
|
import { useGetAndLoadEmbeddedWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadEmbeddedWorkflow';
|
||||||
import { size } from 'lodash-es';
|
import { size } from 'lodash-es';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
PiArrowsCounterClockwiseBold,
|
PiArrowsCounterClockwiseBold,
|
||||||
@ -42,7 +43,11 @@ type SingleSelectionMenuItemsProps = {
|
|||||||
|
|
||||||
const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
||||||
const { imageDTO } = props;
|
const { imageDTO } = props;
|
||||||
const maySelectForCompare = useAppSelector((s) => s.gallery.imageToCompare?.image_name !== imageDTO.image_name);
|
const selectMaySelectForCompare = useMemo(
|
||||||
|
() => createSelector(selectGallerySlice, (gallery) => gallery.imageToCompare?.image_name !== imageDTO.image_name),
|
||||||
|
[imageDTO.image_name]
|
||||||
|
);
|
||||||
|
const maySelectForCompare = useAppSelector(selectMaySelectForCompare);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const customStarUi = useStore($customStarUI);
|
const customStarUi = useStore($customStarUI);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||||
import { Box, Flex, Text, useShiftModifier } from '@invoke-ai/ui-library';
|
import { Box, Flex, Text, useShiftModifier } from '@invoke-ai/ui-library';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { $customStarUI } from 'app/store/nanostores/customStarUI';
|
import { $customStarUI } from 'app/store/nanostores/customStarUI';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIDndImage from 'common/components/IAIDndImage';
|
import IAIDndImage from 'common/components/IAIDndImage';
|
||||||
@ -11,7 +12,12 @@ import type { GallerySelectionDraggableData, ImageDraggableData, TypesafeDraggab
|
|||||||
import { getGalleryImageDataTestId } from 'features/gallery/components/ImageGrid/getGalleryImageDataTestId';
|
import { getGalleryImageDataTestId } from 'features/gallery/components/ImageGrid/getGalleryImageDataTestId';
|
||||||
import { useMultiselect } from 'features/gallery/hooks/useMultiselect';
|
import { useMultiselect } from 'features/gallery/hooks/useMultiselect';
|
||||||
import { useScrollIntoView } from 'features/gallery/hooks/useScrollIntoView';
|
import { useScrollIntoView } from 'features/gallery/hooks/useScrollIntoView';
|
||||||
import { imageToCompareChanged, isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice';
|
import { selectSelectedBoardId } from 'features/gallery/store/gallerySelectors';
|
||||||
|
import {
|
||||||
|
imageToCompareChanged,
|
||||||
|
isImageViewerOpenChanged,
|
||||||
|
selectGallerySlice,
|
||||||
|
} from 'features/gallery/store/gallerySlice';
|
||||||
import type { MouseEvent, MouseEventHandler } from 'react';
|
import type { MouseEvent, MouseEventHandler } from 'react';
|
||||||
import { memo, useCallback, useMemo, useState } from 'react';
|
import { memo, useCallback, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -38,11 +44,20 @@ interface HoverableImageProps {
|
|||||||
index: number;
|
index: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectAlwaysShouldImageSizeBadge = createSelector(
|
||||||
|
selectGallerySlice,
|
||||||
|
(gallery) => gallery.alwaysShowImageSizeBadge
|
||||||
|
);
|
||||||
|
|
||||||
const GalleryImage = ({ index, imageDTO }: HoverableImageProps) => {
|
const GalleryImage = ({ index, imageDTO }: HoverableImageProps) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const selectedBoardId = useAppSelector((s) => s.gallery.selectedBoardId);
|
const selectedBoardId = useAppSelector(selectSelectedBoardId);
|
||||||
const alwaysShowImageSizeBadge = useAppSelector((s) => s.gallery.alwaysShowImageSizeBadge);
|
const selectIsSelectedForCompare = useMemo(
|
||||||
const isSelectedForCompare = useAppSelector((s) => s.gallery.imageToCompare?.image_name === imageDTO.image_name);
|
() => createSelector(selectGallerySlice, (gallery) => gallery.imageToCompare?.image_name === imageDTO.image_name),
|
||||||
|
[imageDTO.image_name]
|
||||||
|
);
|
||||||
|
const alwaysShowImageSizeBadge = useAppSelector(selectAlwaysShouldImageSizeBadge);
|
||||||
|
const isSelectedForCompare = useAppSelector(selectIsSelectedForCompare);
|
||||||
const { handleClick, isSelected, areMultiplesSelected } = useMultiselect(imageDTO);
|
const { handleClick, isSelected, areMultiplesSelected } = useMultiselect(imageDTO);
|
||||||
|
|
||||||
const customStarUi = useStore($customStarUI);
|
const customStarUi = useStore($customStarUI);
|
||||||
|
@ -4,7 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|||||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||||
import { GallerySelectionCountTag } from 'features/gallery/components/ImageGrid/GallerySelectionCountTag';
|
import { GallerySelectionCountTag } from 'features/gallery/components/ImageGrid/GallerySelectionCountTag';
|
||||||
import { useGalleryHotkeys } from 'features/gallery/hooks/useGalleryHotkeys';
|
import { useGalleryHotkeys } from 'features/gallery/hooks/useGalleryHotkeys';
|
||||||
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
|
import { selectGalleryImageMinimumWidth, selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
|
||||||
import { limitChanged } from 'features/gallery/store/gallerySlice';
|
import { limitChanged } from 'features/gallery/store/gallerySlice';
|
||||||
import { debounce } from 'lodash-es';
|
import { debounce } from 'lodash-es';
|
||||||
import { memo, useEffect, useMemo, useState } from 'react';
|
import { memo, useEffect, useMemo, useState } from 'react';
|
||||||
@ -59,7 +59,7 @@ export default memo(GalleryImageGrid);
|
|||||||
|
|
||||||
const Content = () => {
|
const Content = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const galleryImageMinimumWidth = useAppSelector((s) => s.gallery.galleryImageMinimumWidth);
|
const galleryImageMinimumWidth = useAppSelector(selectGalleryImageMinimumWidth);
|
||||||
|
|
||||||
const queryArgs = useAppSelector(selectListImagesQueryArgs);
|
const queryArgs = useAppSelector(selectListImagesQueryArgs);
|
||||||
const { imageDTOs } = useListImagesQuery(queryArgs, {
|
const { imageDTOs } = useListImagesQuery(queryArgs, {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
||||||
|
import { selectSearchTerm } from 'features/gallery/store/gallerySelectors';
|
||||||
import { searchTermChanged } from 'features/gallery/store/gallerySlice';
|
import { searchTermChanged } from 'features/gallery/store/gallerySlice';
|
||||||
import { debounce } from 'lodash-es';
|
import { debounce } from 'lodash-es';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
@ -9,7 +10,7 @@ export const useGallerySearchTerm = () => {
|
|||||||
useAssertSingleton('gallery-search-state');
|
useAssertSingleton('gallery-search-state');
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const searchTerm = useAppSelector((s) => s.gallery.searchTerm);
|
const searchTerm = useAppSelector(selectSearchTerm);
|
||||||
|
|
||||||
const [localSearchTerm, setLocalSearchTerm] = useState(searchTerm);
|
const [localSearchTerm, setLocalSearchTerm] = useState(searchTerm);
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
UnorderedList,
|
UnorderedList,
|
||||||
} from '@invoke-ai/ui-library';
|
} from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { selectComparisonFit, selectComparisonMode } from 'features/gallery/store/gallerySelectors';
|
||||||
import {
|
import {
|
||||||
comparedImagesSwapped,
|
comparedImagesSwapped,
|
||||||
comparisonFitChanged,
|
comparisonFitChanged,
|
||||||
@ -25,8 +26,8 @@ import { PiArrowsOutBold, PiQuestion, PiSwapBold, PiXBold } from 'react-icons/pi
|
|||||||
export const CompareToolbar = memo(() => {
|
export const CompareToolbar = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const comparisonMode = useAppSelector((s) => s.gallery.comparisonMode);
|
const comparisonMode = useAppSelector(selectComparisonMode);
|
||||||
const comparisonFit = useAppSelector((s) => s.gallery.comparisonFit);
|
const comparisonFit = useAppSelector(selectComparisonFit);
|
||||||
const setComparisonModeSlider = useCallback(() => {
|
const setComparisonModeSlider = useCallback(() => {
|
||||||
dispatch(comparisonModeChanged('slider'));
|
dispatch(comparisonModeChanged('slider'));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Box, Flex } from '@invoke-ai/ui-library';
|
import { Box, Flex } from '@invoke-ai/ui-library';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import { skipToken } from '@reduxjs/toolkit/query';
|
import { skipToken } from '@reduxjs/toolkit/query';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIDndImage from 'common/components/IAIDndImage';
|
import IAIDndImage from 'common/components/IAIDndImage';
|
||||||
@ -8,7 +7,8 @@ import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
|||||||
import type { TypesafeDraggableData } from 'features/dnd/types';
|
import type { TypesafeDraggableData } from 'features/dnd/types';
|
||||||
import ImageMetadataViewer from 'features/gallery/components/ImageMetadataViewer/ImageMetadataViewer';
|
import ImageMetadataViewer from 'features/gallery/components/ImageMetadataViewer/ImageMetadataViewer';
|
||||||
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
|
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
|
||||||
import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors';
|
import { selectLastSelectedImageName } from 'features/gallery/store/gallerySelectors';
|
||||||
|
import { selectShouldShowImageDetails, selectShouldShowProgressInViewer } from 'features/ui/store/uiSelectors';
|
||||||
import type { AnimationProps } from 'framer-motion';
|
import type { AnimationProps } from 'framer-motion';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import { memo, useCallback, useMemo, useRef, useState } from 'react';
|
import { memo, useCallback, useMemo, useRef, useState } from 'react';
|
||||||
@ -19,17 +19,12 @@ import { $hasProgress } from 'services/events/setEventListeners';
|
|||||||
|
|
||||||
import ProgressImage from './ProgressImage';
|
import ProgressImage from './ProgressImage';
|
||||||
|
|
||||||
const selectLastSelectedImageName = createSelector(
|
|
||||||
selectLastSelectedImage,
|
|
||||||
(lastSelectedImage) => lastSelectedImage?.image_name
|
|
||||||
);
|
|
||||||
|
|
||||||
const CurrentImagePreview = () => {
|
const CurrentImagePreview = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const shouldShowImageDetails = useAppSelector((s) => s.ui.shouldShowImageDetails);
|
const shouldShowImageDetails = useAppSelector(selectShouldShowImageDetails);
|
||||||
const imageName = useAppSelector(selectLastSelectedImageName);
|
const imageName = useAppSelector(selectLastSelectedImageName);
|
||||||
const hasDenoiseProgress = useStore($hasProgress);
|
const hasDenoiseProgress = useStore($hasProgress);
|
||||||
const shouldShowProgressInViewer = useAppSelector((s) => s.ui.shouldShowProgressInViewer);
|
const shouldShowProgressInViewer = useAppSelector(selectShouldShowProgressInViewer);
|
||||||
|
|
||||||
const { currentData: imageDTO } = useGetImageDTOQuery(imageName ?? skipToken);
|
const { currentData: imageDTO } = useGetImageDTOQuery(imageName ?? skipToken);
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import { selectComparisonImages } from 'features/gallery/components/ImageViewer/
|
|||||||
import { ImageComparisonHover } from 'features/gallery/components/ImageViewer/ImageComparisonHover';
|
import { ImageComparisonHover } from 'features/gallery/components/ImageViewer/ImageComparisonHover';
|
||||||
import { ImageComparisonSideBySide } from 'features/gallery/components/ImageViewer/ImageComparisonSideBySide';
|
import { ImageComparisonSideBySide } from 'features/gallery/components/ImageViewer/ImageComparisonSideBySide';
|
||||||
import { ImageComparisonSlider } from 'features/gallery/components/ImageViewer/ImageComparisonSlider';
|
import { ImageComparisonSlider } from 'features/gallery/components/ImageViewer/ImageComparisonSlider';
|
||||||
|
import { selectComparisonMode } from 'features/gallery/store/gallerySelectors';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiImagesBold } from 'react-icons/pi';
|
import { PiImagesBold } from 'react-icons/pi';
|
||||||
@ -15,7 +16,7 @@ type Props = {
|
|||||||
|
|
||||||
export const ImageComparison = memo(({ containerDims }: Props) => {
|
export const ImageComparison = memo(({ containerDims }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const comparisonMode = useAppSelector((s) => s.gallery.comparisonMode);
|
const comparisonMode = useAppSelector(selectComparisonMode);
|
||||||
const { firstImage, secondImage } = useAppSelector(selectComparisonImages);
|
const { firstImage, secondImage } = useAppSelector(selectComparisonImages);
|
||||||
|
|
||||||
if (!firstImage || !secondImage) {
|
if (!firstImage || !secondImage) {
|
||||||
|
@ -5,13 +5,14 @@ import { preventDefault } from 'common/util/stopPropagation';
|
|||||||
import { TRANSPARENCY_CHECKER_PATTERN } from 'features/controlLayers/konva/constants';
|
import { TRANSPARENCY_CHECKER_PATTERN } from 'features/controlLayers/konva/constants';
|
||||||
import type { Dimensions } from 'features/controlLayers/store/types';
|
import type { Dimensions } from 'features/controlLayers/store/types';
|
||||||
import { ImageComparisonLabel } from 'features/gallery/components/ImageViewer/ImageComparisonLabel';
|
import { ImageComparisonLabel } from 'features/gallery/components/ImageViewer/ImageComparisonLabel';
|
||||||
|
import { selectComparisonFit } from 'features/gallery/store/gallerySelectors';
|
||||||
import { memo, useMemo, useRef } from 'react';
|
import { memo, useMemo, useRef } from 'react';
|
||||||
|
|
||||||
import type { ComparisonProps } from './common';
|
import type { ComparisonProps } from './common';
|
||||||
import { fitDimsToContainer, getSecondImageDims } from './common';
|
import { fitDimsToContainer, getSecondImageDims } from './common';
|
||||||
|
|
||||||
export const ImageComparisonHover = memo(({ firstImage, secondImage, containerDims }: ComparisonProps) => {
|
export const ImageComparisonHover = memo(({ firstImage, secondImage, containerDims }: ComparisonProps) => {
|
||||||
const comparisonFit = useAppSelector((s) => s.gallery.comparisonFit);
|
const comparisonFit = useAppSelector(selectComparisonFit);
|
||||||
const imageContainerRef = useRef<HTMLDivElement>(null);
|
const imageContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const mouseOver = useBoolean(false);
|
const mouseOver = useBoolean(false);
|
||||||
const fittedDims = useMemo<Dimensions>(
|
const fittedDims = useMemo<Dimensions>(
|
||||||
|
@ -4,6 +4,7 @@ import { preventDefault } from 'common/util/stopPropagation';
|
|||||||
import { TRANSPARENCY_CHECKER_PATTERN } from 'features/controlLayers/konva/constants';
|
import { TRANSPARENCY_CHECKER_PATTERN } from 'features/controlLayers/konva/constants';
|
||||||
import type { Dimensions } from 'features/controlLayers/store/types';
|
import type { Dimensions } from 'features/controlLayers/store/types';
|
||||||
import { ImageComparisonLabel } from 'features/gallery/components/ImageViewer/ImageComparisonLabel';
|
import { ImageComparisonLabel } from 'features/gallery/components/ImageViewer/ImageComparisonLabel';
|
||||||
|
import { selectComparisonFit } from 'features/gallery/store/gallerySelectors';
|
||||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { PiCaretLeftBold, PiCaretRightBold } from 'react-icons/pi';
|
import { PiCaretLeftBold, PiCaretRightBold } from 'react-icons/pi';
|
||||||
|
|
||||||
@ -19,7 +20,7 @@ const HANDLE_INNER_LEFT_PX = `${HANDLE_HITBOX / 2 - HANDLE_WIDTH / 2}px`;
|
|||||||
const HANDLE_LEFT_INITIAL_PX = `calc(${INITIAL_POS} - ${HANDLE_HITBOX / 2}px)`;
|
const HANDLE_LEFT_INITIAL_PX = `calc(${INITIAL_POS} - ${HANDLE_HITBOX / 2}px)`;
|
||||||
|
|
||||||
export const ImageComparisonSlider = memo(({ firstImage, secondImage, containerDims }: ComparisonProps) => {
|
export const ImageComparisonSlider = memo(({ firstImage, secondImage, containerDims }: ComparisonProps) => {
|
||||||
const comparisonFit = useAppSelector((s) => s.gallery.comparisonFit);
|
const comparisonFit = useAppSelector(selectComparisonFit);
|
||||||
// How far the handle is from the left - this will be a CSS calculation that takes into account the handle width
|
// How far the handle is from the left - this will be a CSS calculation that takes into account the handle width
|
||||||
const [left, setLeft] = useState(HANDLE_LEFT_INITIAL_PX);
|
const [left, setLeft] = useState(HANDLE_LEFT_INITIAL_PX);
|
||||||
// How wide the first image is
|
// How wide the first image is
|
||||||
|
@ -6,11 +6,12 @@ import CurrentImagePreview from 'features/gallery/components/ImageViewer/Current
|
|||||||
import { ImageComparison } from 'features/gallery/components/ImageViewer/ImageComparison';
|
import { ImageComparison } from 'features/gallery/components/ImageViewer/ImageComparison';
|
||||||
import { ImageComparisonDroppable } from 'features/gallery/components/ImageViewer/ImageComparisonDroppable';
|
import { ImageComparisonDroppable } from 'features/gallery/components/ImageViewer/ImageComparisonDroppable';
|
||||||
import { ViewerToolbar } from 'features/gallery/components/ImageViewer/ViewerToolbar';
|
import { ViewerToolbar } from 'features/gallery/components/ImageViewer/ViewerToolbar';
|
||||||
|
import { selectHasImageToCompare } from 'features/gallery/store/gallerySelectors';
|
||||||
import { memo, useRef } from 'react';
|
import { memo, useRef } from 'react';
|
||||||
import { useMeasure } from 'react-use';
|
import { useMeasure } from 'react-use';
|
||||||
|
|
||||||
export const ImageViewer = memo(() => {
|
export const ImageViewer = memo(() => {
|
||||||
const isComparing = useAppSelector((s) => s.gallery.imageToCompare !== null);
|
const hasImageToCompare = useAppSelector(selectHasImageToCompare);
|
||||||
const [containerRef, containerDims] = useMeasure<HTMLDivElement>();
|
const [containerRef, containerDims] = useMeasure<HTMLDivElement>();
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
useScopeOnFocus('imageViewer', ref);
|
useScopeOnFocus('imageViewer', ref);
|
||||||
@ -33,11 +34,11 @@ export const ImageViewer = memo(() => {
|
|||||||
alignItems="center"
|
alignItems="center"
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
>
|
>
|
||||||
{isComparing && <CompareToolbar />}
|
{hasImageToCompare && <CompareToolbar />}
|
||||||
{!isComparing && <ViewerToolbar />}
|
{!hasImageToCompare && <ViewerToolbar />}
|
||||||
<Box ref={containerRef} w="full" h="full">
|
<Box ref={containerRef} w="full" h="full">
|
||||||
{!isComparing && <CurrentImagePreview />}
|
{!hasImageToCompare && <CurrentImagePreview />}
|
||||||
{isComparing && <ImageComparison containerDims={containerDims} />}
|
{hasImageToCompare && <ImageComparison containerDims={containerDims} />}
|
||||||
</Box>
|
</Box>
|
||||||
<ImageComparisonDroppable />
|
<ImageComparisonDroppable />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -1,13 +1,20 @@
|
|||||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||||
import { Image } from '@invoke-ai/ui-library';
|
import { Image } from '@invoke-ai/ui-library';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { selectSystemSlice } from 'features/system/store/systemSlice';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { $progressImage } from 'services/events/setEventListeners';
|
import { $progressImage } from 'services/events/setEventListeners';
|
||||||
|
|
||||||
|
const selectShouldAntialiasProgressImage = createSelector(
|
||||||
|
selectSystemSlice,
|
||||||
|
(system) => system.shouldAntialiasProgressImage
|
||||||
|
);
|
||||||
|
|
||||||
const CurrentImagePreview = () => {
|
const CurrentImagePreview = () => {
|
||||||
const progressImage = useStore($progressImage);
|
const progressImage = useStore($progressImage);
|
||||||
const shouldAntialiasProgressImage = useAppSelector((s) => s.system.shouldAntialiasProgressImage);
|
const shouldAntialiasProgressImage = useAppSelector(selectShouldAntialiasProgressImage);
|
||||||
|
|
||||||
const sx = useMemo<SystemStyleObject>(
|
const sx = useMemo<SystemStyleObject>(
|
||||||
() => ({
|
() => ({
|
||||||
|
@ -2,6 +2,7 @@ import { IconButton } from '@invoke-ai/ui-library';
|
|||||||
import { skipToken } from '@reduxjs/toolkit/query';
|
import { skipToken } from '@reduxjs/toolkit/query';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors';
|
import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors';
|
||||||
|
import { selectShouldShowImageDetails } from 'features/ui/store/uiSelectors';
|
||||||
import { setShouldShowImageDetails } from 'features/ui/store/uiSlice';
|
import { setShouldShowImageDetails } from 'features/ui/store/uiSlice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
@ -11,7 +12,7 @@ import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
|||||||
|
|
||||||
export const ToggleMetadataViewerButton = memo(() => {
|
export const ToggleMetadataViewerButton = memo(() => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const shouldShowImageDetails = useAppSelector((s) => s.ui.shouldShowImageDetails);
|
const shouldShowImageDetails = useAppSelector(selectShouldShowImageDetails);
|
||||||
const lastSelectedImage = useAppSelector(selectLastSelectedImage);
|
const lastSelectedImage = useAppSelector(selectLastSelectedImage);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { IconButton } from '@invoke-ai/ui-library';
|
import { IconButton } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { selectShouldShowProgressInViewer } from 'features/ui/store/uiSelectors';
|
||||||
import { setShouldShowProgressInViewer } from 'features/ui/store/uiSlice';
|
import { setShouldShowProgressInViewer } from 'features/ui/store/uiSlice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -7,7 +8,7 @@ import { PiHourglassHighBold } from 'react-icons/pi';
|
|||||||
|
|
||||||
export const ToggleProgressButton = memo(() => {
|
export const ToggleProgressButton = memo(() => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const shouldShowProgressInViewer = useAppSelector((s) => s.ui.shouldShowProgressInViewer);
|
const shouldShowProgressInViewer = useAppSelector(selectShouldShowProgressInViewer);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const onClick = useCallback(() => {
|
const onClick = useCallback(() => {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Flex } from '@invoke-ai/ui-library';
|
import { Flex } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { ToggleMetadataViewerButton } from 'features/gallery/components/ImageViewer/ToggleMetadataViewerButton';
|
import { ToggleMetadataViewerButton } from 'features/gallery/components/ImageViewer/ToggleMetadataViewerButton';
|
||||||
import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton';
|
import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton';
|
||||||
@ -8,14 +9,15 @@ import { memo } from 'react';
|
|||||||
import CurrentImageButtons from './CurrentImageButtons';
|
import CurrentImageButtons from './CurrentImageButtons';
|
||||||
import { ViewerToggleMenu } from './ViewerToggleMenu';
|
import { ViewerToggleMenu } from './ViewerToggleMenu';
|
||||||
|
|
||||||
|
const selectShowToggle = createSelector(selectActiveTab, (tab) => {
|
||||||
|
if (tab === 'upscaling' || tab === 'workflows') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
export const ViewerToolbar = memo(() => {
|
export const ViewerToolbar = memo(() => {
|
||||||
const showToggle = useAppSelector((s) => {
|
const showToggle = useAppSelector(selectShowToggle);
|
||||||
const tab = selectActiveTab(s);
|
|
||||||
if (tab === 'upscaling' || tab === 'workflows') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
return (
|
return (
|
||||||
<Flex w="full" gap={2}>
|
<Flex w="full" gap={2}>
|
||||||
<Flex flex={1} justifyContent="center">
|
<Flex flex={1} justifyContent="center">
|
||||||
|
@ -1,35 +1,47 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { imageToCompareChanged, isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice';
|
import { selectHasImageToCompare, selectIsImageViewerOpen } from 'features/gallery/store/gallerySelectors';
|
||||||
|
import {
|
||||||
|
imageToCompareChanged,
|
||||||
|
isImageViewerOpenChanged,
|
||||||
|
selectGallerySlice,
|
||||||
|
} from 'features/gallery/store/gallerySlice';
|
||||||
|
import { selectWorkflowSlice } from 'features/nodes/store/workflowSlice';
|
||||||
|
import { selectUiSlice } from 'features/ui/store/uiSlice';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
const selectIsOpen = createSelector(selectUiSlice, selectWorkflowSlice, selectGallerySlice, (ui, workflow, gallery) => {
|
||||||
|
const tab = ui.activeTab;
|
||||||
|
const workflowsMode = workflow.mode;
|
||||||
|
if (tab === 'models' || tab === 'queue') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (tab === 'workflows' && workflowsMode === 'edit') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (tab === 'workflows' && workflowsMode === 'view') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (tab === 'upscaling') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return gallery.isImageViewerOpen;
|
||||||
|
});
|
||||||
|
|
||||||
export const useIsImageViewerOpen = () => {
|
export const useIsImageViewerOpen = () => {
|
||||||
const isOpen = useAppSelector((s) => {
|
const isOpen = useAppSelector(selectIsOpen);
|
||||||
const tab = s.ui.activeTab;
|
|
||||||
const workflowsMode = s.workflow.mode;
|
|
||||||
if (tab === 'models' || tab === 'queue') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (tab === 'workflows' && workflowsMode === 'edit') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (tab === 'workflows' && workflowsMode === 'view') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (tab === 'upscaling') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return s.gallery.isImageViewerOpen;
|
|
||||||
});
|
|
||||||
return isOpen;
|
return isOpen;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectIsForcedOpen = createSelector(selectUiSlice, selectWorkflowSlice, (ui, workflow) => {
|
||||||
|
return ui.activeTab === 'upscaling' || (ui.activeTab === 'workflows' && workflow.mode === 'view');
|
||||||
|
});
|
||||||
|
|
||||||
export const useImageViewer = () => {
|
export const useImageViewer = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const isComparing = useAppSelector((s) => s.gallery.imageToCompare !== null);
|
const isComparing = useAppSelector(selectHasImageToCompare);
|
||||||
const isNaturallyOpen = useAppSelector((s) => s.gallery.isImageViewerOpen);
|
const isNaturallyOpen = useAppSelector(selectIsImageViewerOpen);
|
||||||
const isForcedOpen = useAppSelector(
|
const isForcedOpen = useAppSelector(selectIsForcedOpen);
|
||||||
(s) => s.ui.activeTab === 'upscaling' || (s.ui.activeTab === 'workflows' && s.workflow.mode === 'view')
|
|
||||||
);
|
|
||||||
|
|
||||||
const onClose = useCallback(() => {
|
const onClose = useCallback(() => {
|
||||||
if (isForcedOpen) {
|
if (isForcedOpen) {
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { useAltModifier } from '@invoke-ai/ui-library';
|
import { useAltModifier } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { GALLERY_GRID_CLASS_NAME } from 'features/gallery/components/ImageGrid/constants';
|
import { GALLERY_GRID_CLASS_NAME } from 'features/gallery/components/ImageGrid/constants';
|
||||||
import { GALLERY_IMAGE_CLASS_NAME } from 'features/gallery/components/ImageGrid/GalleryImage';
|
import { GALLERY_IMAGE_CLASS_NAME } from 'features/gallery/components/ImageGrid/GalleryImage';
|
||||||
import { getGalleryImageDataTestId } from 'features/gallery/components/ImageGrid/getGalleryImageDataTestId';
|
import { getGalleryImageDataTestId } from 'features/gallery/components/ImageGrid/getGalleryImageDataTestId';
|
||||||
import { virtuosoGridRefs } from 'features/gallery/components/ImageGrid/types';
|
import { virtuosoGridRefs } from 'features/gallery/components/ImageGrid/types';
|
||||||
import { useGalleryImages } from 'features/gallery/hooks/useGalleryImages';
|
import { useGalleryImages } from 'features/gallery/hooks/useGalleryImages';
|
||||||
|
import { selectImageToCompare, selectLastSelectedImage } from 'features/gallery/store/gallerySelectors';
|
||||||
import { imageSelected, imageToCompareChanged } from 'features/gallery/store/gallerySlice';
|
import { imageSelected, imageToCompareChanged } from 'features/gallery/store/gallerySlice';
|
||||||
import { getIsVisible } from 'features/gallery/util/getIsVisible';
|
import { getIsVisible } from 'features/gallery/util/getIsVisible';
|
||||||
import { getScrollToIndexAlign } from 'features/gallery/util/getScrollToIndexAlign';
|
import { getScrollToIndexAlign } from 'features/gallery/util/getScrollToIndexAlign';
|
||||||
@ -127,14 +129,18 @@ type UseGalleryNavigationReturn = {
|
|||||||
export const useGalleryNavigation = (): UseGalleryNavigationReturn => {
|
export const useGalleryNavigation = (): UseGalleryNavigationReturn => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const alt = useAltModifier();
|
const alt = useAltModifier();
|
||||||
const lastSelectedImage = useAppSelector((s) => {
|
const selectImage = useMemo(
|
||||||
const lastSelected = s.gallery.selection.slice(-1)[0] ?? null;
|
() =>
|
||||||
if (alt) {
|
createSelector(selectLastSelectedImage, selectImageToCompare, (lastSelectedImage, imageToCompare) => {
|
||||||
return s.gallery.imageToCompare ?? lastSelected;
|
if (alt) {
|
||||||
} else {
|
return imageToCompare ?? lastSelectedImage;
|
||||||
return lastSelected;
|
} else {
|
||||||
}
|
return lastSelectedImage;
|
||||||
});
|
}
|
||||||
|
}),
|
||||||
|
[alt]
|
||||||
|
);
|
||||||
|
const lastSelectedImage = useAppSelector(selectImage);
|
||||||
const { imageDTOs } = useGalleryImages();
|
const { imageDTOs } = useGalleryImages();
|
||||||
const loadedImagesCount = useMemo(() => imageDTOs.length, [imageDTOs.length]);
|
const loadedImagesCount = useMemo(() => imageDTOs.length, [imageDTOs.length]);
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
|
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
|
||||||
import { offsetChanged } from 'features/gallery/store/gallerySlice';
|
import { offsetChanged, selectGallerySlice } from 'features/gallery/store/gallerySlice';
|
||||||
import { throttle } from 'lodash-es';
|
import { throttle } from 'lodash-es';
|
||||||
import { useCallback, useEffect, useMemo } from 'react';
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
import { useListImagesQuery } from 'services/api/endpoints/images';
|
import { useListImagesQuery } from 'services/api/endpoints/images';
|
||||||
@ -56,9 +57,13 @@ const getRange = (currentPage: number, totalPages: number, siblingCount: number)
|
|||||||
return fullRange as (number | 'ellipsis')[];
|
return fullRange as (number | 'ellipsis')[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectOffset = createSelector(selectGallerySlice, (gallery) => gallery.offset);
|
||||||
|
const selectLimit = createSelector(selectGallerySlice, (gallery) => gallery.limit);
|
||||||
|
|
||||||
export const useGalleryPagination = () => {
|
export const useGalleryPagination = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { offset, limit } = useAppSelector((s) => s.gallery);
|
const offset = useAppSelector(selectOffset);
|
||||||
|
const limit = useAppSelector(selectLimit);
|
||||||
const queryArgs = useAppSelector(selectListImagesQueryArgs);
|
const queryArgs = useAppSelector(selectListImagesQueryArgs);
|
||||||
|
|
||||||
const { count, total } = useListImagesQuery(queryArgs, {
|
const { count, total } = useListImagesQuery(queryArgs, {
|
||||||
|
@ -2,7 +2,7 @@ import { skipToken } from '@reduxjs/toolkit/query';
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { handlers, parseAndRecallAllMetadata, parseAndRecallPrompts } from 'features/metadata/util/handlers';
|
import { handlers, parseAndRecallAllMetadata, parseAndRecallPrompts } from 'features/metadata/util/handlers';
|
||||||
import { $stylePresetModalState } from 'features/stylePresets/store/stylePresetModal';
|
import { $stylePresetModalState } from 'features/stylePresets/store/stylePresetModal';
|
||||||
import { activeStylePresetIdChanged } from 'features/stylePresets/store/stylePresetSlice';
|
import { activeStylePresetIdChanged, selectActivePresetId } from 'features/stylePresets/store/stylePresetSlice';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { selectActiveTab } from 'features/ui/store/uiSelectors';
|
import { selectActiveTab } from 'features/ui/store/uiSelectors';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
@ -13,7 +13,7 @@ import { useDebouncedMetadata } from 'services/api/hooks/useDebouncedMetadata';
|
|||||||
export const useImageActions = (image_name?: string) => {
|
export const useImageActions = (image_name?: string) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const activeStylePresetId = useAppSelector((s) => s.stylePreset.activeStylePresetId);
|
const activeStylePresetId = useAppSelector(selectActivePresetId);
|
||||||
const activeTabName = useAppSelector(selectActiveTab);
|
const activeTabName = useAppSelector(selectActiveTab);
|
||||||
const { metadata, isLoading: isLoadingMetadata } = useDebouncedMetadata(image_name);
|
const { metadata, isLoading: isLoadingMetadata } = useDebouncedMetadata(image_name);
|
||||||
const [hasMetadata, setHasMetadata] = useState(false);
|
const [hasMetadata, setHasMetadata] = useState(false);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { galleryImageClicked } from 'app/store/middleware/listenerMiddleware/listeners/galleryImageClicked';
|
import { galleryImageClicked } from 'app/store/middleware/listenerMiddleware/listeners/galleryImageClicked';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { selectHasSelection } from 'features/gallery/store/gallerySelectors';
|
||||||
import { selectGallerySlice, selectionChanged } from 'features/gallery/store/gallerySlice';
|
import { selectGallerySlice, selectionChanged } from 'features/gallery/store/gallerySlice';
|
||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
import type { MouseEvent } from 'react';
|
import type { MouseEvent } from 'react';
|
||||||
@ -9,7 +10,7 @@ import type { ImageDTO } from 'services/api/types';
|
|||||||
|
|
||||||
export const useMultiselect = (imageDTO?: ImageDTO) => {
|
export const useMultiselect = (imageDTO?: ImageDTO) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const areMultiplesSelected = useAppSelector((s) => s.gallery.selection.length > 1);
|
const areMultiplesSelected = useAppSelector(selectHasSelection);
|
||||||
const selectIsSelected = useMemo(
|
const selectIsSelected = useMemo(
|
||||||
() =>
|
() =>
|
||||||
createSelector(selectGallerySlice, (gallery) =>
|
createSelector(selectGallerySlice, (gallery) =>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import type { SkipToken } from '@reduxjs/toolkit/query';
|
import type { SkipToken } from '@reduxjs/toolkit/query';
|
||||||
import { skipToken } from '@reduxjs/toolkit/query';
|
import { skipToken } from '@reduxjs/toolkit/query';
|
||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
@ -5,10 +6,11 @@ import { selectGallerySlice } from 'features/gallery/store/gallerySlice';
|
|||||||
import { ASSETS_CATEGORIES, IMAGE_CATEGORIES } from 'features/gallery/store/types';
|
import { ASSETS_CATEGORIES, IMAGE_CATEGORIES } from 'features/gallery/store/types';
|
||||||
import type { ListBoardsArgs, ListImagesArgs } from 'services/api/types';
|
import type { ListBoardsArgs, ListImagesArgs } from 'services/api/types';
|
||||||
|
|
||||||
export const selectLastSelectedImage = createMemoizedSelector(
|
export const selectLastSelectedImage = createSelector(
|
||||||
selectGallerySlice,
|
selectGallerySlice,
|
||||||
(gallery) => gallery.selection[gallery.selection.length - 1]
|
(gallery) => gallery.selection[gallery.selection.length - 1]
|
||||||
);
|
);
|
||||||
|
export const selectLastSelectedImageName = createSelector(selectLastSelectedImage, (image) => image?.image_name);
|
||||||
|
|
||||||
export const selectListImagesQueryArgs = createMemoizedSelector(
|
export const selectListImagesQueryArgs = createMemoizedSelector(
|
||||||
selectGallerySlice,
|
selectGallerySlice,
|
||||||
@ -33,3 +35,26 @@ export const selectListBoardsQueryArgs = createMemoizedSelector(
|
|||||||
include_archived: gallery.shouldShowArchivedBoards ? true : undefined,
|
include_archived: gallery.shouldShowArchivedBoards ? true : undefined,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const selectAutoAddBoardId = createSelector(selectGallerySlice, (gallery) => gallery.autoAddBoardId);
|
||||||
|
export const selectSelectedBoardId = createSelector(selectGallerySlice, (gallery) => gallery.selectedBoardId);
|
||||||
|
export const selectAutoAssignBoardOnClick = createSelector(
|
||||||
|
selectGallerySlice,
|
||||||
|
(gallery) => gallery.autoAssignBoardOnClick
|
||||||
|
);
|
||||||
|
export const selectBoardSearchText = createSelector(selectGallerySlice, (gallery) => gallery.boardSearchText);
|
||||||
|
export const selectSearchTerm = createSelector(selectGallerySlice, (gallery) => gallery.searchTerm);
|
||||||
|
export const selectSelectionCount = createSelector(selectGallerySlice, (gallery) => gallery.selection.length);
|
||||||
|
export const selectHasSelection = createSelector(selectSelectionCount, (count) => count > 0);
|
||||||
|
export const selectGalleryImageMinimumWidth = createSelector(
|
||||||
|
selectGallerySlice,
|
||||||
|
(gallery) => gallery.galleryImageMinimumWidth
|
||||||
|
);
|
||||||
|
|
||||||
|
export const selectComparisonMode = createSelector(selectGallerySlice, (gallery) => gallery.comparisonMode);
|
||||||
|
export const selectComparisonFit = createSelector(selectGallerySlice, (gallery) => gallery.comparisonFit);
|
||||||
|
export const selectImageToCompare = createSelector(selectGallerySlice, (gallery) => gallery.imageToCompare);
|
||||||
|
export const selectHasImageToCompare = createSelector(selectImageToCompare, (imageToCompare) =>
|
||||||
|
Boolean(imageToCompare)
|
||||||
|
);
|
||||||
|
export const selectIsImageViewerOpen = createSelector(selectGallerySlice, (gallery) => gallery.isImageViewerOpen);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { FormControlGroup } from '@invoke-ai/ui-library';
|
import { FormControlGroup } from '@invoke-ai/ui-library';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { selectHrfEnabled } from 'features/hrf/store/hrfSlice';
|
||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
@ -9,7 +10,7 @@ import ParamHrfToggle from './ParamHrfToggle';
|
|||||||
|
|
||||||
export const HrfSettings = memo(() => {
|
export const HrfSettings = memo(() => {
|
||||||
const isHRFFeatureEnabled = useFeatureStatus('hrf');
|
const isHRFFeatureEnabled = useFeatureStatus('hrf');
|
||||||
const hrfEnabled = useAppSelector((s) => s.hrf.hrfEnabled);
|
const hrfEnabled = useAppSelector(selectHrfEnabled);
|
||||||
|
|
||||||
if (!isHRFFeatureEnabled) {
|
if (!isHRFFeatureEnabled) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -2,7 +2,7 @@ import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
|
|||||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||||
import { setHrfMethod } from 'features/hrf/store/hrfSlice';
|
import { selectHrfMethod, setHrfMethod } from 'features/hrf/store/hrfSlice';
|
||||||
import { isParameterHRFMethod } from 'features/parameters/types/parameterSchemas';
|
import { isParameterHRFMethod } from 'features/parameters/types/parameterSchemas';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -15,7 +15,7 @@ const options: ComboboxOption[] = [
|
|||||||
const ParamHrfMethodSelect = () => {
|
const ParamHrfMethodSelect = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const hrfMethod = useAppSelector((s) => s.hrf.hrfMethod);
|
const hrfMethod = useAppSelector(selectHrfMethod);
|
||||||
|
|
||||||
const onChange = useCallback<ComboboxOnChange>(
|
const onChange = useCallback<ComboboxOnChange>(
|
||||||
(v) => {
|
(v) => {
|
||||||
|
@ -1,19 +1,17 @@
|
|||||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||||
import { setHrfStrength } from 'features/hrf/store/hrfSlice';
|
import { selectHrfStrength, setHrfStrength } from 'features/hrf/store/hrfSlice';
|
||||||
|
import { selectConfigSlice } from 'features/system/store/configSlice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const selectHrfStrengthConfig = createSelector(selectConfigSlice, (config) => config.sd.hrfStrength);
|
||||||
|
|
||||||
const ParamHrfStrength = () => {
|
const ParamHrfStrength = () => {
|
||||||
const hrfStrength = useAppSelector((s) => s.hrf.hrfStrength);
|
const hrfStrength = useAppSelector(selectHrfStrength);
|
||||||
const initial = useAppSelector((s) => s.config.sd.hrfStrength.initial);
|
const config = useAppSelector(selectHrfStrengthConfig);
|
||||||
const sliderMin = useAppSelector((s) => s.config.sd.hrfStrength.sliderMin);
|
|
||||||
const sliderMax = useAppSelector((s) => s.config.sd.hrfStrength.sliderMax);
|
|
||||||
const numberInputMin = useAppSelector((s) => s.config.sd.hrfStrength.numberInputMin);
|
|
||||||
const numberInputMax = useAppSelector((s) => s.config.sd.hrfStrength.numberInputMax);
|
|
||||||
const coarseStep = useAppSelector((s) => s.config.sd.hrfStrength.coarseStep);
|
|
||||||
const fineStep = useAppSelector((s) => s.config.sd.hrfStrength.fineStep);
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -30,22 +28,22 @@ const ParamHrfStrength = () => {
|
|||||||
<FormLabel>{`${t('parameters.denoisingStrength')}`}</FormLabel>
|
<FormLabel>{`${t('parameters.denoisingStrength')}`}</FormLabel>
|
||||||
</InformationalPopover>
|
</InformationalPopover>
|
||||||
<CompositeSlider
|
<CompositeSlider
|
||||||
min={sliderMin}
|
min={config.sliderMin}
|
||||||
max={sliderMax}
|
max={config.sliderMax}
|
||||||
step={coarseStep}
|
step={config.coarseStep}
|
||||||
fineStep={fineStep}
|
fineStep={config.fineStep}
|
||||||
value={hrfStrength}
|
value={hrfStrength}
|
||||||
defaultValue={initial}
|
defaultValue={config.initial}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
marks
|
marks
|
||||||
/>
|
/>
|
||||||
<CompositeNumberInput
|
<CompositeNumberInput
|
||||||
min={numberInputMin}
|
min={config.numberInputMin}
|
||||||
max={numberInputMax}
|
max={config.numberInputMax}
|
||||||
step={coarseStep}
|
step={config.coarseStep}
|
||||||
fineStep={fineStep}
|
fineStep={config.fineStep}
|
||||||
value={hrfStrength}
|
value={hrfStrength}
|
||||||
defaultValue={initial}
|
defaultValue={config.initial}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||||
import { setHrfEnabled } from 'features/hrf/store/hrfSlice';
|
import { selectHrfEnabled, setHrfEnabled } from 'features/hrf/store/hrfSlice';
|
||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -10,7 +10,7 @@ const ParamHrfToggle = () => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const hrfEnabled = useAppSelector((s) => s.hrf.hrfEnabled);
|
const hrfEnabled = useAppSelector(selectHrfEnabled);
|
||||||
|
|
||||||
const handleHrfEnabled = useCallback(
|
const handleHrfEnabled = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) => dispatch(setHrfEnabled(e.target.checked)),
|
(e: ChangeEvent<HTMLInputElement>) => dispatch(setHrfEnabled(e.target.checked)),
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSelector, createSlice } from '@reduxjs/toolkit';
|
||||||
import type { PersistConfig, RootState } from 'app/store/store';
|
import type { PersistConfig, RootState } from 'app/store/store';
|
||||||
import type { ParameterHRFMethod, ParameterStrength } from 'features/parameters/types/parameterSchemas';
|
import type { ParameterHRFMethod, ParameterStrength } from 'features/parameters/types/parameterSchemas';
|
||||||
|
|
||||||
@ -35,8 +35,6 @@ export const hrfSlice = createSlice({
|
|||||||
|
|
||||||
export const { setHrfEnabled, setHrfStrength, setHrfMethod } = hrfSlice.actions;
|
export const { setHrfEnabled, setHrfStrength, setHrfMethod } = hrfSlice.actions;
|
||||||
|
|
||||||
export const selectHrfSlice = (state: RootState) => state.hrf;
|
|
||||||
|
|
||||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||||
const migrateHRFState = (state: any): any => {
|
const migrateHRFState = (state: any): any => {
|
||||||
if (!('_version' in state)) {
|
if (!('_version' in state)) {
|
||||||
@ -51,3 +49,8 @@ export const hrfPersistConfig: PersistConfig<HRFState> = {
|
|||||||
migrate: migrateHRFState,
|
migrate: migrateHRFState,
|
||||||
persistDenylist: [],
|
persistDenylist: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const selectHrfSlice = (state: RootState) => state.hrf;
|
||||||
|
export const selectHrfEnabled = createSelector(selectHrfSlice, (hrf) => hrf.hrfEnabled);
|
||||||
|
export const selectHrfMethod = createSelector(selectHrfSlice, (hrf) => hrf.hrfMethod);
|
||||||
|
export const selectHrfStrength = createSelector(selectHrfSlice, (hrf) => hrf.hrfStrength);
|
||||||
|
@ -1,23 +1,24 @@
|
|||||||
import type { ChakraProps } from '@invoke-ai/ui-library';
|
import type { ChakraProps } from '@invoke-ai/ui-library';
|
||||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||||
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
|
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
|
||||||
import { loraAdded, selectLoRAsSlice } from 'features/controlLayers/store/lorasSlice';
|
import { loraAdded, selectLoRAsSlice } from 'features/controlLayers/store/lorasSlice';
|
||||||
|
import { selectBase } from 'features/controlLayers/store/paramsSlice';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useLoRAModels } from 'services/api/hooks/modelsByType';
|
import { useLoRAModels } from 'services/api/hooks/modelsByType';
|
||||||
import type { LoRAModelConfig } from 'services/api/types';
|
import type { LoRAModelConfig } from 'services/api/types';
|
||||||
|
|
||||||
const selectLoRAs = createMemoizedSelector(selectLoRAsSlice, (loras) => loras.loras);
|
const selectLoRAs = createSelector(selectLoRAsSlice, (loras) => loras.loras);
|
||||||
|
|
||||||
const LoRASelect = () => {
|
const LoRASelect = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const [modelConfigs, { isLoading }] = useLoRAModels();
|
const [modelConfigs, { isLoading }] = useLoRAModels();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const addedLoRAs = useAppSelector(selectLoRAs);
|
const addedLoRAs = useAppSelector(selectLoRAs);
|
||||||
const currentBaseModel = useAppSelector((s) => s.params.model?.base);
|
const currentBaseModel = useAppSelector(selectBase);
|
||||||
|
|
||||||
const getIsDisabled = (model: LoRAModelConfig): boolean => {
|
const getIsDisabled = (model: LoRAModelConfig): boolean => {
|
||||||
const isCompatible = currentBaseModel === model.base;
|
const isCompatible = currentBaseModel === model.base;
|
||||||
|
@ -4,6 +4,7 @@ import { useAppSelector } from 'app/store/storeHooks';
|
|||||||
import IAIDndImage from 'common/components/IAIDndImage';
|
import IAIDndImage from 'common/components/IAIDndImage';
|
||||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||||
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
|
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
|
||||||
|
import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors';
|
||||||
import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper';
|
import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper';
|
||||||
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
|
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
|
||||||
import type { AnimationProps } from 'framer-motion';
|
import type { AnimationProps } from 'framer-motion';
|
||||||
@ -15,7 +16,7 @@ import type { NodeProps } from 'reactflow';
|
|||||||
import { $lastProgressEvent } from 'services/events/setEventListeners';
|
import { $lastProgressEvent } from 'services/events/setEventListeners';
|
||||||
|
|
||||||
const CurrentImageNode = (props: NodeProps) => {
|
const CurrentImageNode = (props: NodeProps) => {
|
||||||
const imageDTO = useAppSelector((s) => s.gallery.selection[s.gallery.selection.length - 1]);
|
const imageDTO = useAppSelector(selectLastSelectedImage);
|
||||||
const lastProgressEvent = useStore($lastProgressEvent);
|
const lastProgressEvent = useStore($lastProgressEvent);
|
||||||
|
|
||||||
if (lastProgressEvent?.progress_image) {
|
if (lastProgressEvent?.progress_image) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSelector, createSlice } from '@reduxjs/toolkit';
|
||||||
import type { PersistConfig } from 'app/store/store';
|
import type { PersistConfig, RootState } from 'app/store/store';
|
||||||
import { stylePresetsApi } from 'services/api/endpoints/stylePresets';
|
import { stylePresetsApi } from 'services/api/endpoints/stylePresets';
|
||||||
|
|
||||||
import type { StylePresetState } from './types';
|
import type { StylePresetState } from './types';
|
||||||
@ -63,3 +63,9 @@ export const stylePresetPersistConfig: PersistConfig<StylePresetState> = {
|
|||||||
migrate: migrateStylePresetState,
|
migrate: migrateStylePresetState,
|
||||||
persistDenylist: [],
|
persistDenylist: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const selectStylePresetSlice = (state: RootState) => state.stylePreset;
|
||||||
|
export const selectActivePresetId = createSelector(
|
||||||
|
selectStylePresetSlice,
|
||||||
|
(stylePreset) => stylePreset.activeStylePresetId
|
||||||
|
);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
import type { RootState } from 'app/store/store';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { selectConfigSlice } from 'features/system/store/configSlice';
|
||||||
|
|
||||||
export const configSelector = (state: RootState) => state.config;
|
export const selectAllowPrivateBoards = createSelector(selectConfigSlice, (config) => config.allowPrivateBoards);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Flex } from '@invoke-ai/ui-library';
|
import { Flex } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useScopeOnFocus } from 'common/hooks/interactionScopes';
|
import { useScopeOnFocus } from 'common/hooks/interactionScopes';
|
||||||
import { CanvasEditor } from 'features/controlLayers/components/ControlLayersEditor';
|
import { CanvasEditor } from 'features/controlLayers/components/ControlLayersEditor';
|
||||||
@ -18,7 +19,7 @@ import { VerticalNavBar } from 'features/ui/components/VerticalNavBar';
|
|||||||
import type { UsePanelOptions } from 'features/ui/hooks/usePanel';
|
import type { UsePanelOptions } from 'features/ui/hooks/usePanel';
|
||||||
import { usePanel } from 'features/ui/hooks/usePanel';
|
import { usePanel } from 'features/ui/hooks/usePanel';
|
||||||
import { usePanelStorage } from 'features/ui/hooks/usePanelStorage';
|
import { usePanelStorage } from 'features/ui/hooks/usePanelStorage';
|
||||||
import { $isGalleryPanelOpen, $isParametersPanelOpen } from 'features/ui/store/uiSlice';
|
import { $isGalleryPanelOpen, $isParametersPanelOpen, selectUiSlice } from 'features/ui/store/uiSlice';
|
||||||
import type { TabName } from 'features/ui/store/uiTypes';
|
import type { TabName } from 'features/ui/store/uiTypes';
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { memo, useMemo, useRef } from 'react';
|
import { memo, useMemo, useRef } from 'react';
|
||||||
@ -41,11 +42,18 @@ const OPTIONS_PANEL_MIN_SIZE_PCT = 20;
|
|||||||
const onGalleryPanelCollapse = (isCollapsed: boolean) => $isGalleryPanelOpen.set(!isCollapsed);
|
const onGalleryPanelCollapse = (isCollapsed: boolean) => $isGalleryPanelOpen.set(!isCollapsed);
|
||||||
const onParametersPanelCollapse = (isCollapsed: boolean) => $isParametersPanelOpen.set(!isCollapsed);
|
const onParametersPanelCollapse = (isCollapsed: boolean) => $isParametersPanelOpen.set(!isCollapsed);
|
||||||
|
|
||||||
|
const selectShouldShowGalleryPanel = createSelector(selectUiSlice, (ui) =>
|
||||||
|
TABS_WITH_GALLERY_PANEL.includes(ui.activeTab)
|
||||||
|
);
|
||||||
|
const selectShouldShowOptionsPanel = createSelector(selectUiSlice, (ui) =>
|
||||||
|
TABS_WITH_OPTIONS_PANEL.includes(ui.activeTab)
|
||||||
|
);
|
||||||
|
|
||||||
export const AppContent = memo(() => {
|
export const AppContent = memo(() => {
|
||||||
const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
|
const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
|
||||||
const isImageViewerOpen = useIsImageViewerOpen();
|
const isImageViewerOpen = useIsImageViewerOpen();
|
||||||
const shouldShowGalleryPanel = useAppSelector((s) => TABS_WITH_GALLERY_PANEL.includes(s.ui.activeTab));
|
const shouldShowGalleryPanel = useAppSelector(selectShouldShowGalleryPanel);
|
||||||
const shouldShowOptionsPanel = useAppSelector((s) => TABS_WITH_OPTIONS_PANEL.includes(s.ui.activeTab));
|
const shouldShowOptionsPanel = useAppSelector(selectShouldShowOptionsPanel);
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
useScopeOnFocus('gallery', ref);
|
useScopeOnFocus('gallery', ref);
|
||||||
|
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppStore } from 'app/store/nanostores/store';
|
||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { panelsChanged } from 'features/ui/store/uiSlice';
|
import { panelsChanged } from 'features/ui/store/uiSlice';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
export const usePanelStorage = () => {
|
export const usePanelStorage = () => {
|
||||||
|
const store = useAppStore();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const panels = useAppSelector((s) => s.ui.panels);
|
const getItem = useCallback(
|
||||||
const getItem = useCallback((name: string) => panels[name] ?? '', [panels]);
|
(name: string) => {
|
||||||
|
const panels = store.getState().ui.panels;
|
||||||
|
return panels[name] ?? '';
|
||||||
|
},
|
||||||
|
[store]
|
||||||
|
);
|
||||||
const setItem = useCallback(
|
const setItem = useCallback(
|
||||||
(name: string, value: string) => {
|
(name: string, value: string) => {
|
||||||
dispatch(panelsChanged({ name, value }));
|
dispatch(panelsChanged({ name, value }));
|
||||||
|
@ -2,3 +2,5 @@ import { createSelector } from '@reduxjs/toolkit';
|
|||||||
import { selectUiSlice } from 'features/ui/store/uiSlice';
|
import { selectUiSlice } from 'features/ui/store/uiSlice';
|
||||||
|
|
||||||
export const selectActiveTab = createSelector(selectUiSlice, (ui) => ui.activeTab);
|
export const selectActiveTab = createSelector(selectUiSlice, (ui) => ui.activeTab);
|
||||||
|
export const selectShouldShowImageDetails = createSelector(selectUiSlice, (ui) => ui.shouldShowImageDetails);
|
||||||
|
export const selectShouldShowProgressInViewer = createSelector(selectUiSlice, (ui) => ui.shouldShowProgressInViewer);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user