refactor(ui): canvas v2 (wip)

Fix a few more components
This commit is contained in:
psychedelicious 2024-06-15 18:54:26 +10:00
parent bf185339c2
commit b4daf29bd8
8 changed files with 99 additions and 61 deletions

View File

@ -5,70 +5,73 @@ import { useAppSelector } from 'app/store/storeHooks';
import { IAINoContentFallback } from 'common/components/IAIImageFallback'; import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
import { AddLayerButton } from 'features/controlLayers/components/AddLayerButton'; import { AddLayerButton } from 'features/controlLayers/components/AddLayerButton';
import { CALayer } from 'features/controlLayers/components/CALayer/CALayer'; import { CA } from 'features/controlLayers/components/ControlAdapter/CA';
import { DeleteAllLayersButton } from 'features/controlLayers/components/DeleteAllLayersButton'; import { DeleteAllLayersButton } from 'features/controlLayers/components/DeleteAllLayersButton';
import { IILayer } from 'features/controlLayers/components/IILayer/IILayer'; import { IPA } from 'features/controlLayers/components/IPAdapter/IPA';
import { IPAEntity } from 'features/controlLayers/components/IPALayer/IPALayer'; import { Layer } from 'features/controlLayers/components/Layer/Layer';
import { Layer } from 'features/controlLayers/components/RasterLayer/RasterLayer'; import { RG } from 'features/controlLayers/components/RegionalGuidance/RG';
import { RGLayer } from 'features/controlLayers/components/RGLayer/RGLayer'; import { mapId } from 'features/controlLayers/konva/util';
import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice'; import { selectControlAdaptersV2Slice } from 'features/controlLayers/store/controlAdaptersSlice';
import type { LayerData } from 'features/controlLayers/store/types'; import { selectIPAdaptersSlice } from 'features/controlLayers/store/ipAdaptersSlice';
import { isRenderableLayer } from 'features/controlLayers/store/types'; import { selectLayersSlice } from 'features/controlLayers/store/layersSlice';
import { partition } from 'lodash-es'; import { selectRegionalGuidanceSlice } from 'features/controlLayers/store/regionalGuidanceSlice';
import { memo } from 'react'; import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const selectLayerIdTypePairs = createMemoizedSelector(selectCanvasV2Slice, (controlLayers) => { const selectRGIds = createMemoizedSelector(selectRegionalGuidanceSlice, (rgState) => {
const [renderableLayers, ipAdapterLayers] = partition(canvasV2.layers, isRenderableLayer); return rgState.regions.map(mapId).reverse();
return [...ipAdapterLayers, ...renderableLayers].map((l) => ({ id: l.id, type: l.type })).reverse(); });
const selectCAIds = createMemoizedSelector(selectControlAdaptersV2Slice, (caState) => {
return caState.controlAdapters.map(mapId).reverse();
});
const selectIPAIds = createMemoizedSelector(selectIPAdaptersSlice, (ipaState) => {
return ipaState.ipAdapters.map(mapId).reverse();
});
const selectLayerIds = createMemoizedSelector(selectLayersSlice, (layersState) => {
return layersState.layers.map(mapId).reverse();
}); });
export const ControlLayersPanelContent = memo(() => { export const ControlLayersPanelContent = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const layerIdTypePairs = useAppSelector(selectLayerIdTypePairs); const rgIds = useAppSelector(selectRGIds);
const caIds = useAppSelector(selectCAIds);
const ipaIds = useAppSelector(selectIPAIds);
const layerIds = useAppSelector(selectLayerIds);
const entityCount = useMemo(
() => rgIds.length + caIds.length + ipaIds.length + layerIds.length,
[rgIds.length, caIds.length, ipaIds.length, layerIds.length]
);
return ( return (
<Flex flexDir="column" gap={2} w="full" h="full"> <Flex flexDir="column" gap={2} w="full" h="full">
<Flex justifyContent="space-around"> <Flex justifyContent="space-around">
<AddLayerButton /> <AddLayerButton />
<DeleteAllLayersButton /> <DeleteAllLayersButton />
</Flex> </Flex>
{layerIdTypePairs.length > 0 && ( {entityCount > 0 && (
<ScrollableContent> <ScrollableContent>
<Flex flexDir="column" gap={2} data-testid="control-layers-layer-list"> <Flex flexDir="column" gap={2} data-testid="control-layers-layer-list">
{layerIdTypePairs.map(({ id, type }) => ( {rgIds.map((id) => (
<LayerWrapper key={id} id={id} type={type} /> <RG key={id} id={id} />
))}
{caIds.map((id) => (
<CA key={id} id={id} />
))}
{ipaIds.map((id) => (
<IPA key={id} id={id} />
))}
{layerIds.map((id) => (
<Layer key={id} id={id} />
))} ))}
</Flex> </Flex>
</ScrollableContent> </ScrollableContent>
)} )}
{layerIdTypePairs.length === 0 && <IAINoContentFallback icon={null} label={t('controlLayers.noLayersAdded')} />} {entityCount === 0 && <IAINoContentFallback icon={null} label={t('controlLayers.noLayersAdded')} />}
</Flex> </Flex>
); );
}); });
ControlLayersPanelContent.displayName = 'ControlLayersPanelContent'; ControlLayersPanelContent.displayName = 'ControlLayersPanelContent';
type LayerWrapperProps = {
id: string;
type: LayerData['type'];
};
const LayerWrapper = memo(({ id, type }: LayerWrapperProps) => {
if (type === 'regional_guidance_layer') {
return <RGLayer key={id} layerId={id} />;
}
if (type === 'control_adapter_layer') {
return <CALayer key={id} id={id} />;
}
if (type === 'ip_adapter_layer') {
return <IPAEntity key={id} layerId={id} />;
}
if (type === 'initial_image_layer') {
return <IILayer key={id} layerId={id} />;
}
if (type === 'raster_layer') {
return <Layer key={id} layerId={id} />;
}
});
LayerWrapper.displayName = 'LayerWrapper';

View File

@ -1,24 +1,18 @@
/* eslint-disable i18next/no-literal-string */ /* eslint-disable i18next/no-literal-string */
import { Flex } from '@invoke-ai/ui-library'; import { Flex } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react'; import { useAppSelector } from 'app/store/storeHooks';
import { BrushColorPicker } from 'features/controlLayers/components/BrushColorPicker'; import { BrushWidth } from 'features/controlLayers/components/BrushWidth';
import { BrushWidth } from 'features/controlLayers/components/BrushSize';
import ControlLayersSettingsPopover from 'features/controlLayers/components/ControlLayersSettingsPopover'; import ControlLayersSettingsPopover from 'features/controlLayers/components/ControlLayersSettingsPopover';
import { EraserWidth } from 'features/controlLayers/components/EraserWidth';
import { FillColorPicker } from 'features/controlLayers/components/FillColorPicker';
import { ToolChooser } from 'features/controlLayers/components/ToolChooser'; import { ToolChooser } from 'features/controlLayers/components/ToolChooser';
import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup'; import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup';
import { $tool } from 'features/controlLayers/store/controlLayersSlice';
import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton'; import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton';
import { ViewerToggleMenu } from 'features/gallery/components/ImageViewer/ViewerToggleMenu'; import { ViewerToggleMenu } from 'features/gallery/components/ImageViewer/ViewerToggleMenu';
import { memo, useMemo } from 'react'; import { memo } from 'react';
export const ControlLayersToolbar = memo(() => { export const ControlLayersToolbar = memo(() => {
const tool = useStore($tool); const tool = useAppSelector((s) => s.canvasV2.tool.selected);
const withBrushSize = useMemo(() => {
return tool === 'brush' || tool === 'eraser';
}, [tool]);
const withBrushColor = useMemo(() => {
return tool === 'brush' || tool === 'rect';
}, [tool]);
return ( return (
<Flex w="full" gap={2}> <Flex w="full" gap={2}>
<Flex flex={1} justifyContent="center"> <Flex flex={1} justifyContent="center">
@ -28,8 +22,9 @@ export const ControlLayersToolbar = memo(() => {
</Flex> </Flex>
</Flex> </Flex>
<Flex flex={1} gap={2} justifyContent="center" alignItems="center"> <Flex flex={1} gap={2} justifyContent="center" alignItems="center">
{withBrushSize && <BrushWidth />} {tool === 'brush' && <BrushWidth />}
{withBrushColor && <BrushColorPicker />} {tool === 'eraser' && <EraserWidth />}
<FillColorPicker />
</Flex> </Flex>
<Flex flex={1} justifyContent="center"> <Flex flex={1} justifyContent="center">
<Flex gap={2} marginInlineStart="auto"> <Flex gap={2} marginInlineStart="auto">

View File

@ -1,6 +1,10 @@
import { Button } from '@invoke-ai/ui-library'; import { Button } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { allLayersDeleted } from 'features/controlLayers/store/controlLayersSlice'; import { caAllDeleted } from 'features/controlLayers/store/controlAdaptersSlice';
import { ipaAllDeleted } from 'features/controlLayers/store/ipAdaptersSlice';
import { layerAllDeleted } from 'features/controlLayers/store/layersSlice';
import { rgAllDeleted } from 'features/controlLayers/store/regionalGuidanceSlice';
import { selectEntityCount } 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 { PiTrashSimpleBold } from 'react-icons/pi'; import { PiTrashSimpleBold } from 'react-icons/pi';
@ -8,9 +12,12 @@ import { PiTrashSimpleBold } from 'react-icons/pi';
export const DeleteAllLayersButton = memo(() => { export const DeleteAllLayersButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const isDisabled = useAppSelector((s) => s.canvasV2.layers.length === 0); const entityCount = useAppSelector(selectEntityCount);
const onClick = useCallback(() => { const onClick = useCallback(() => {
dispatch(allLayersDeleted()); dispatch(caAllDeleted());
dispatch(rgAllDeleted());
dispatch(ipaAllDeleted());
dispatch(layerAllDeleted());
}, [dispatch]); }, [dispatch]);
return ( return (
@ -19,7 +26,7 @@ export const DeleteAllLayersButton = memo(() => {
leftIcon={<PiTrashSimpleBold />} leftIcon={<PiTrashSimpleBold />}
variant="ghost" variant="ghost"
colorScheme="error" colorScheme="error"
isDisabled={isDisabled} isDisabled={entityCount > 0}
data-testid="control-layers-delete-all-layers-button" data-testid="control-layers-delete-all-layers-button"
> >
{t('controlLayers.deleteAll')} {t('controlLayers.deleteAll')}

View File

@ -246,6 +246,9 @@ export const controlAdaptersV2Slice = createSlice({
} }
ca.beginEndStepPct = beginEndStepPct; ca.beginEndStepPct = beginEndStepPct;
}, },
caAllDeleted: (state) => {
state.controlAdapters = [];
},
}, },
}); });
@ -270,6 +273,7 @@ export const {
caProcessorPendingBatchIdChanged, caProcessorPendingBatchIdChanged,
caWeightChanged, caWeightChanged,
caBeginEndStepPctChanged, caBeginEndStepPctChanged,
caAllDeleted,
} = controlAdaptersV2Slice.actions; } = controlAdaptersV2Slice.actions;
export const selectControlAdaptersV2Slice = (state: RootState) => state.controlAdaptersV2; export const selectControlAdaptersV2Slice = (state: RootState) => state.controlAdaptersV2;

View File

@ -114,6 +114,9 @@ export const ipAdaptersSlice = createSlice({
} }
ipa.beginEndStepPct = beginEndStepPct; ipa.beginEndStepPct = beginEndStepPct;
}, },
ipaAllDeleted: (state) => {
state.ipAdapters = [];
},
}, },
}); });
@ -128,6 +131,7 @@ export const {
ipaCLIPVisionModelChanged, ipaCLIPVisionModelChanged,
ipaWeightChanged, ipaWeightChanged,
ipaBeginEndStepPctChanged, ipaBeginEndStepPctChanged,
ipaAllDeleted,
} = ipAdaptersSlice.actions; } = ipAdaptersSlice.actions;
export const selectIPAdaptersSlice = (state: RootState) => state.ipAdapters; export const selectIPAdaptersSlice = (state: RootState) => state.ipAdapters;

View File

@ -234,6 +234,9 @@ export const layersSlice = createSlice({
}, },
prepare: (payload: ImageObjectAddedArg) => ({ payload: { ...payload, imageId: uuidv4() } }), prepare: (payload: ImageObjectAddedArg) => ({ payload: { ...payload, imageId: uuidv4() } }),
}, },
layerAllDeleted: (state) => {
state.layers = [];
},
}, },
}); });
@ -254,6 +257,7 @@ export const {
layerLinePointAdded, layerLinePointAdded,
layerRectAdded, layerRectAdded,
layerImageAdded, layerImageAdded,
layerAllDeleted,
} = layersSlice.actions; } = layersSlice.actions;
export const selectLayersSlice = (state: RootState) => state.layers; export const selectLayersSlice = (state: RootState) => state.layers;

View File

@ -398,6 +398,9 @@ export const regionalGuidanceSlice = createSlice({
}, },
prepare: (payload: RectShapeAddedArg) => ({ payload: { ...payload, rectId: uuidv4() } }), prepare: (payload: RectShapeAddedArg) => ({ payload: { ...payload, rectId: uuidv4() } }),
}, },
rgAllDeleted: (state) => {
state.regions = [];
},
}, },
}); });
@ -431,6 +434,7 @@ export const {
rgEraserLineAdded, rgEraserLineAdded,
rgLinePointAdded, rgLinePointAdded,
rgRectAdded, rgRectAdded,
rgAllDeleted,
} = regionalGuidanceSlice.actions; } = regionalGuidanceSlice.actions;
export const selectRegionalGuidanceSlice = (state: RootState) => state.regionalGuidance; export const selectRegionalGuidanceSlice = (state: RootState) => state.regionalGuidance;

View File

@ -0,0 +1,17 @@
import { createSelector } from '@reduxjs/toolkit';
import { selectControlAdaptersV2Slice } from 'features/controlLayers/store/controlAdaptersSlice';
import { selectIPAdaptersSlice } from 'features/controlLayers/store/ipAdaptersSlice';
import { selectLayersSlice } from 'features/controlLayers/store/layersSlice';
import { selectRegionalGuidanceSlice } from 'features/controlLayers/store/regionalGuidanceSlice';
export const selectEntityCount = createSelector(
selectRegionalGuidanceSlice,
selectControlAdaptersV2Slice,
selectIPAdaptersSlice,
selectLayersSlice,
(rgState, caState, ipaState, layersState) => {
return (
rgState.regions.length + caState.controlAdapters.length + ipaState.ipAdapters.length + layersState.layers.length
);
}
);