Skip to content

Commit 2f9a064

Browse files
psychedelicioushipsterusername
authored andcommitted
feat(ui): ip adapter layers are selectable
This is largely an internal change, and it should have been this way from the start - less tip-toeing around layer types. The user-facing change is when you click an IP Adapter layer, it is highlighted. That's it.
1 parent b180666 commit 2f9a064

File tree

6 files changed

+27
-28
lines changed

6 files changed

+27
-28
lines changed

invokeai/frontend/web/src/features/controlLayers/components/CALayer/CALayer.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ export const CALayer = memo(({ layerId }: Props) => {
1919
const dispatch = useAppDispatch();
2020
const isSelected = useAppSelector((s) => selectCALayerOrThrow(s.controlLayers.present, layerId).isSelected);
2121
const onClick = useCallback(() => {
22-
// Must be capture so that the layer is selected before deleting/resetting/etc
2322
dispatch(layerSelected(layerId));
2423
}, [dispatch, layerId]);
2524
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });

invokeai/frontend/web/src/features/controlLayers/components/IPALayer/IPALayer.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
2+
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
23
import { IPALayerIPAdapterWrapper } from 'features/controlLayers/components/IPALayer/IPALayerIPAdapterWrapper';
34
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
45
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
56
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
67
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
7-
import { memo } from 'react';
8+
import { layerSelected, selectIPALayerOrThrow } from 'features/controlLayers/store/controlLayersSlice';
9+
import { memo, useCallback } from 'react';
810

911
type Props = {
1012
layerId: string;
1113
};
1214

1315
export const IPALayer = memo(({ layerId }: Props) => {
16+
const dispatch = useAppDispatch();
17+
const isSelected = useAppSelector((s) => selectIPALayerOrThrow(s.controlLayers.present, layerId).isSelected);
1418
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
19+
const onClick = useCallback(() => {
20+
dispatch(layerSelected(layerId));
21+
}, [dispatch, layerId]);
1522
return (
16-
<LayerWrapper borderColor="base.800">
23+
<LayerWrapper onClick={onClick} borderColor={isSelected ? 'base.400' : 'base.800'}>
1724
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
1825
<LayerVisibilityToggle layerId={layerId} />
1926
<LayerTitle type="ip_adapter_layer" />

invokeai/frontend/web/src/features/controlLayers/store/controlLayersSlice.ts

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,11 @@ const getVectorMaskPreviewColor = (state: ControlLayersState): RgbColor => {
124124
const lastColor = rgLayers[rgLayers.length - 1]?.previewColor;
125125
return LayerColors.next(lastColor);
126126
};
127-
const deselectAllLayers = (state: ControlLayersState) => {
128-
for (const layer of state.layers.filter(isRenderableLayer)) {
129-
layer.isSelected = false;
127+
const exclusivelySelectLayer = (state: ControlLayersState, layerId: string) => {
128+
for (const layer of state.layers) {
129+
layer.isSelected = layer.id === layerId;
130130
}
131-
state.selectedLayerId = null;
131+
state.selectedLayerId = layerId;
132132
};
133133

134134
export const controlLayersSlice = createSlice({
@@ -137,12 +137,7 @@ export const controlLayersSlice = createSlice({
137137
reducers: {
138138
//#region Any Layer Type
139139
layerSelected: (state, action: PayloadAction<string>) => {
140-
deselectAllLayers(state);
141-
const layer = state.layers.find((l) => l.id === action.payload);
142-
if (isRenderableLayer(layer)) {
143-
layer.isSelected = true;
144-
state.selectedLayerId = layer.id;
145-
}
140+
exclusivelySelectLayer(state, action.payload);
146141
},
147142
layerVisibilityToggled: (state, action: PayloadAction<string>) => {
148143
const layer = state.layers.find((l) => l.id === action.payload);
@@ -232,7 +227,6 @@ export const controlLayersSlice = createSlice({
232227
action: PayloadAction<{ layerId: string; controlAdapter: ControlNetConfigV2 | T2IAdapterConfigV2 }>
233228
) => {
234229
const { layerId, controlAdapter } = action.payload;
235-
deselectAllLayers(state);
236230
const layer: ControlAdapterLayer = {
237231
id: getCALayerId(layerId),
238232
type: 'control_adapter_layer',
@@ -247,16 +241,15 @@ export const controlLayersSlice = createSlice({
247241
controlAdapter,
248242
};
249243
state.layers.push(layer);
250-
state.selectedLayerId = layer.id;
244+
exclusivelySelectLayer(state, layer.id);
251245
},
252246
prepare: (controlAdapter: ControlNetConfigV2 | T2IAdapterConfigV2) => ({
253247
payload: { layerId: uuidv4(), controlAdapter },
254248
}),
255249
},
256250
caLayerRecalled: (state, action: PayloadAction<ControlAdapterLayer>) => {
257-
deselectAllLayers(state);
258251
state.layers.push({ ...action.payload, isSelected: true });
259-
state.selectedLayerId = action.payload.id;
252+
exclusivelySelectLayer(state, action.payload.id);
260253
},
261254
caLayerImageChanged: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
262255
const { layerId, imageDTO } = action.payload;
@@ -359,9 +352,11 @@ export const controlLayersSlice = createSlice({
359352
id: getIPALayerId(layerId),
360353
type: 'ip_adapter_layer',
361354
isEnabled: true,
355+
isSelected: true,
362356
ipAdapter,
363357
};
364358
state.layers.push(layer);
359+
exclusivelySelectLayer(state, layer.id);
365360
},
366361
prepare: (ipAdapter: IPAdapterConfigV2) => ({ payload: { layerId: uuidv4(), ipAdapter } }),
367362
},
@@ -431,7 +426,6 @@ export const controlLayersSlice = createSlice({
431426
rgLayerAdded: {
432427
reducer: (state, action: PayloadAction<{ layerId: string }>) => {
433428
const { layerId } = action.payload;
434-
deselectAllLayers(state);
435429
const layer: RegionalGuidanceLayer = {
436430
id: getRGLayerId(layerId),
437431
type: 'regional_guidance_layer',
@@ -450,14 +444,13 @@ export const controlLayersSlice = createSlice({
450444
uploadedMaskImage: null,
451445
};
452446
state.layers.push(layer);
453-
state.selectedLayerId = layer.id;
447+
exclusivelySelectLayer(state, layer.id);
454448
},
455449
prepare: () => ({ payload: { layerId: uuidv4() } }),
456450
},
457451
rgLayerRecalled: (state, action: PayloadAction<RegionalGuidanceLayer>) => {
458-
deselectAllLayers(state);
459452
state.layers.push({ ...action.payload, isSelected: true });
460-
state.selectedLayerId = action.payload.id;
453+
exclusivelySelectLayer(state, action.payload.id);
461454
},
462455
rgLayerPositivePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => {
463456
const { layerId, prompt } = action.payload;
@@ -622,7 +615,6 @@ export const controlLayersSlice = createSlice({
622615
//#region Initial Image Layer
623616
iiLayerAdded: {
624617
reducer: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
625-
deselectAllLayers(state);
626618
const { layerId, imageDTO } = action.payload;
627619
// Highlander! There can be only one!
628620
state.layers = state.layers.filter((l) => (isInitialImageLayer(l) ? false : true));
@@ -640,15 +632,14 @@ export const controlLayersSlice = createSlice({
640632
denoisingStrength: 0.75,
641633
};
642634
state.layers.push(layer);
643-
state.selectedLayerId = layer.id;
635+
exclusivelySelectLayer(state, layer.id);
644636
},
645637
prepare: (imageDTO: ImageDTO | null) => ({ payload: { layerId: INITIAL_IMAGE_LAYER_ID, imageDTO } }),
646638
},
647639
iiLayerRecalled: (state, action: PayloadAction<InitialImageLayer>) => {
648-
deselectAllLayers(state);
649640
state.layers = state.layers.filter((l) => (isInitialImageLayer(l) ? false : true));
650641
state.layers.push({ ...action.payload, isSelected: true });
651-
state.selectedLayerId = action.payload.id;
642+
exclusivelySelectLayer(state, action.payload.id);
652643
},
653644
iiLayerImageChanged: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
654645
const { layerId, imageDTO } = action.payload;

invokeai/frontend/web/src/features/controlLayers/store/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ export type VectorMaskRect = z.infer<typeof zVectorMaskRect>;
4848

4949
const zLayerBase = z.object({
5050
id: z.string(),
51-
isEnabled: z.boolean(),
51+
isEnabled: z.boolean().default(true),
52+
isSelected: z.boolean().default(true),
5253
});
5354

5455
const zRect = z.object({
@@ -62,7 +63,6 @@ const zRenderableLayerBase = zLayerBase.extend({
6263
y: z.number(),
6364
bbox: zRect.nullable(),
6465
bboxNeedsUpdate: z.boolean(),
65-
isSelected: z.boolean(),
6666
});
6767

6868
const zControlAdapterLayer = zRenderableLayerBase.extend({

invokeai/frontend/web/src/features/controlLayers/util/renderers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,7 @@ const renderLayers = (
702702
if (isInitialImageLayer(reduxLayer)) {
703703
renderInitialImageLayer(stage, reduxLayer);
704704
}
705+
// IP Adapter layers are not rendered
705706
}
706707
};
707708

invokeai/frontend/web/src/features/metadata/util/parsers.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -692,8 +692,9 @@ const parseIPAdapterToIPAdapterLayer: MetadataParseFunc<IPAdapterLayer> = async
692692

693693
const layer: IPAdapterLayer = {
694694
id: getIPALayerId(uuidv4()),
695-
isEnabled: true,
696695
type: 'ip_adapter_layer',
696+
isEnabled: true,
697+
isSelected: true,
697698
ipAdapter: {
698699
id: uuidv4(),
699700
type: 'ip_adapter',

0 commit comments

Comments
 (0)