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 2cc60f253a
commit 8a6690a57c
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 ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
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 { IILayer } from 'features/controlLayers/components/IILayer/IILayer';
import { IPAEntity } from 'features/controlLayers/components/IPALayer/IPALayer';
import { Layer } from 'features/controlLayers/components/RasterLayer/RasterLayer';
import { RGLayer } from 'features/controlLayers/components/RGLayer/RGLayer';
import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
import type { LayerData } from 'features/controlLayers/store/types';
import { isRenderableLayer } from 'features/controlLayers/store/types';
import { partition } from 'lodash-es';
import { memo } from 'react';
import { IPA } from 'features/controlLayers/components/IPAdapter/IPA';
import { Layer } from 'features/controlLayers/components/Layer/Layer';
import { RG } from 'features/controlLayers/components/RegionalGuidance/RG';
import { mapId } from 'features/controlLayers/konva/util';
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';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
const selectLayerIdTypePairs = createMemoizedSelector(selectCanvasV2Slice, (controlLayers) => {
const [renderableLayers, ipAdapterLayers] = partition(canvasV2.layers, isRenderableLayer);
return [...ipAdapterLayers, ...renderableLayers].map((l) => ({ id: l.id, type: l.type })).reverse();
const selectRGIds = createMemoizedSelector(selectRegionalGuidanceSlice, (rgState) => {
return rgState.regions.map(mapId).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(() => {
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 (
<Flex flexDir="column" gap={2} w="full" h="full">
<Flex justifyContent="space-around">
<AddLayerButton />
<DeleteAllLayersButton />
</Flex>
{layerIdTypePairs.length > 0 && (
{entityCount > 0 && (
<ScrollableContent>
<Flex flexDir="column" gap={2} data-testid="control-layers-layer-list">
{layerIdTypePairs.map(({ id, type }) => (
<LayerWrapper key={id} id={id} type={type} />
{rgIds.map((id) => (
<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>
</ScrollableContent>
)}
{layerIdTypePairs.length === 0 && <IAINoContentFallback icon={null} label={t('controlLayers.noLayersAdded')} />}
{entityCount === 0 && <IAINoContentFallback icon={null} label={t('controlLayers.noLayersAdded')} />}
</Flex>
);
});
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 */
import { Flex } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { BrushColorPicker } from 'features/controlLayers/components/BrushColorPicker';
import { BrushWidth } from 'features/controlLayers/components/BrushSize';
import { useAppSelector } from 'app/store/storeHooks';
import { BrushWidth } from 'features/controlLayers/components/BrushWidth';
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 { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup';
import { $tool } from 'features/controlLayers/store/controlLayersSlice';
import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton';
import { ViewerToggleMenu } from 'features/gallery/components/ImageViewer/ViewerToggleMenu';
import { memo, useMemo } from 'react';
import { memo } from 'react';
export const ControlLayersToolbar = memo(() => {
const tool = useStore($tool);
const withBrushSize = useMemo(() => {
return tool === 'brush' || tool === 'eraser';
}, [tool]);
const withBrushColor = useMemo(() => {
return tool === 'brush' || tool === 'rect';
}, [tool]);
const tool = useAppSelector((s) => s.canvasV2.tool.selected);
return (
<Flex w="full" gap={2}>
<Flex flex={1} justifyContent="center">
@ -28,8 +22,9 @@ export const ControlLayersToolbar = memo(() => {
</Flex>
</Flex>
<Flex flex={1} gap={2} justifyContent="center" alignItems="center">
{withBrushSize && <BrushWidth />}
{withBrushColor && <BrushColorPicker />}
{tool === 'brush' && <BrushWidth />}
{tool === 'eraser' && <EraserWidth />}
<FillColorPicker />
</Flex>
<Flex flex={1} justifyContent="center">
<Flex gap={2} marginInlineStart="auto">

View File

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

View File

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

View File

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

View File

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

View File

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