mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
refactor(ui): canvas v2 (wip)
Fix a few more components
This commit is contained in:
parent
bf185339c2
commit
b4daf29bd8
@ -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';
|
|
||||||
|
@ -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">
|
||||||
|
@ -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')}
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
Loading…
x
Reference in New Issue
Block a user