feat(ui): restore invoke button (wip)

This commit is contained in:
psychedelicious 2024-04-17 13:59:18 +10:00 committed by Kent Keirsey
parent 1f8f429d55
commit c9bf00b80b
5 changed files with 52 additions and 37 deletions

View File

@ -0,0 +1,15 @@
import { Button } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { allLayersDeleted } from 'features/regionalPrompts/store/regionalPromptsSlice';
import { memo, useCallback } from 'react';
export const DeleteAllLayersButton = memo(() => {
const dispatch = useAppDispatch();
const onClick = useCallback(() => {
dispatch(allLayersDeleted());
}, [dispatch]);
return <Button onClick={onClick}>Delete All</Button>;
});
DeleteAllLayersButton.displayName = 'DeleteAllLayersButton';

View File

@ -4,10 +4,10 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { AddLayerButton } from 'features/regionalPrompts/components/AddLayerButton'; import { AddLayerButton } from 'features/regionalPrompts/components/AddLayerButton';
import { BrushSize } from 'features/regionalPrompts/components/BrushSize'; import { BrushSize } from 'features/regionalPrompts/components/BrushSize';
import { DeleteAllLayersButton } from 'features/regionalPrompts/components/DeleteAllLayersButton';
import { StageComponent } from 'features/regionalPrompts/components/imperative/konvaApiDraft'; import { StageComponent } from 'features/regionalPrompts/components/imperative/konvaApiDraft';
import { LayerListItem } from 'features/regionalPrompts/components/LayerListItem'; import { LayerListItem } from 'features/regionalPrompts/components/LayerListItem';
import { PromptLayerOpacity } from 'features/regionalPrompts/components/PromptLayerOpacity'; import { PromptLayerOpacity } from 'features/regionalPrompts/components/PromptLayerOpacity';
import { RegionalPromptsStage } from 'features/regionalPrompts/components/RegionalPromptsStage';
import { ToolChooser } from 'features/regionalPrompts/components/ToolChooser'; import { ToolChooser } from 'features/regionalPrompts/components/ToolChooser';
import { selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice';
import { getRegionalPromptLayerBlobs } from 'features/regionalPrompts/util/getLayerBlobs'; import { getRegionalPromptLayerBlobs } from 'features/regionalPrompts/util/getLayerBlobs';
@ -27,8 +27,11 @@ export const RegionalPromptsEditor = memo(() => {
return ( return (
<Flex gap={4} w="full" h="full"> <Flex gap={4} w="full" h="full">
<Flex flexDir="column" gap={4} flexShrink={0}> <Flex flexDir="column" gap={4} flexShrink={0}>
<Button onClick={debugBlobs}>DEBUG LAYERS</Button> <Flex>
<Button onClick={debugBlobs}>DEBUG</Button>
<AddLayerButton /> <AddLayerButton />
<DeleteAllLayersButton />
</Flex>
<BrushSize /> <BrushSize />
<PromptLayerOpacity /> <PromptLayerOpacity />
<ImageSizeLinear /> <ImageSizeLinear />

View File

@ -113,7 +113,7 @@ const renderBrushPreview = (
}); });
}; };
const renderLayers = ( export const renderLayers = (
stage: Konva.Stage, stage: Konva.Stage,
reduxLayers: Layer[], reduxLayers: Layer[],
selectedLayerId: string | null, selectedLayerId: string | null,
@ -369,7 +369,11 @@ export const StageComponent = () => {
const container = useStore($container); const container = useStore($container);
return ( return (
<> <>
<chakra.div ref={containerRef} tabIndex={-1} sx={{ borderWidth: 1, borderRadius: 'base' }} /> <chakra.div
ref={containerRef}
tabIndex={-1}
sx={{ borderWidth: 1, borderRadius: 'base', flexGrow: 0, h: 'min-content' }}
/>
<LogicalStage container={container} /> <LogicalStage container={container} />
</> </>
); );

View File

@ -159,6 +159,10 @@ export const regionalPromptsSlice = createSlice({
} }
layer.bbox = bbox; layer.bbox = bbox;
}, },
allLayersDeleted: (state) => {
state.layers = [];
state.selectedLayer = null;
},
promptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string }>) => { promptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string }>) => {
const { layerId, prompt } = action.payload; const { layerId, prompt } = action.payload;
const layer = state.layers.find((l) => l.id === layerId); const layer = state.layers.find((l) => l.id === layerId);
@ -258,6 +262,7 @@ export const {
layerTranslated, layerTranslated,
layerBboxChanged, layerBboxChanged,
promptLayerOpacityChanged, promptLayerOpacityChanged,
allLayersDeleted,
} = regionalPromptsSlice.actions; } = regionalPromptsSlice.actions;
export const selectRegionalPromptsSlice = (state: RootState) => state.regionalPrompts; export const selectRegionalPromptsSlice = (state: RootState) => state.regionalPrompts;

View File

@ -1,7 +1,8 @@
import { getStore } from 'app/store/nanostores/store';
import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; import openBase64ImageInTab from 'common/util/openBase64ImageInTab';
import { blobToDataURL } from 'features/canvas/util/blobToDataURL'; import { blobToDataURL } from 'features/canvas/util/blobToDataURL';
import { selectPromptLayerObjectGroup } from 'features/regionalPrompts/components/LayerComponent'; import { renderLayers } from 'features/regionalPrompts/components/imperative/konvaApiDraft';
import { getStage, REGIONAL_PROMPT_LAYER_NAME } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { REGIONAL_PROMPT_LAYER_NAME } from 'features/regionalPrompts/store/regionalPromptsSlice';
import Konva from 'konva'; import Konva from 'konva';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
@ -15,40 +16,27 @@ export const getRegionalPromptLayerBlobs = async (
layerIds?: string[], layerIds?: string[],
preview: boolean = false preview: boolean = false
): Promise<Record<string, Blob>> => { ): Promise<Record<string, Blob>> => {
const stage = getStage(); const state = getStore().getState();
const container = document.createElement('div');
// This automatically omits layers that are not rendered. Rendering is controlled by the layer's `isVisible` flag in redux. const stage = new Konva.Stage({ container, width: state.generation.width, height: state.generation.height });
const regionalPromptLayers = stage.getLayers().filter((l) => { renderLayers(stage, state.regionalPrompts.layers, state.regionalPrompts.selectedLayer);
console.log(l.name(), l.id());
const isRegionalPromptLayer = l.name() === REGIONAL_PROMPT_LAYER_NAME;
const isRequestedLayerId = layerIds ? layerIds.includes(l.id()) : true;
return isRegionalPromptLayer && isRequestedLayerId;
});
// We need to reconstruct each layer to only output the desired data. This logic mirrors the logic in
// `getKonvaLayerBbox()` in `invokeai/frontend/web/src/features/regionalPrompts/util/bbox.ts`
const offscreenStageContainer = document.createElement('div');
const offscreenStage = new Konva.Stage({
container: offscreenStageContainer,
width: stage.width(),
height: stage.height(),
});
const layers = stage.find<Konva.Layer>(`.${REGIONAL_PROMPT_LAYER_NAME}`);
const blobs: Record<string, Blob> = {}; const blobs: Record<string, Blob> = {};
for (const layer of regionalPromptLayers) { // First remove all layers
const layerClone = layer.clone(); for (const layer of layers) {
for (const child of layerClone.getChildren()) { layer.remove();
if (selectPromptLayerObjectGroup(child)) {
child.destroy();
} else {
// We need to re-cache to handle children with transparency and multiple objects - like prompt region layers.
child.cache();
} }
// Next render each layer to a blob
for (const layer of layers) {
if (layerIds && !layerIds.includes(layer.id())) {
continue;
} }
offscreenStage.add(layerClone); stage.add(layer);
const blob = await new Promise<Blob>((resolve) => { const blob = await new Promise<Blob>((resolve) => {
offscreenStage.toBlob({ stage.toBlob({
callback: (blob) => { callback: (blob) => {
assert(blob, 'Blob is null'); assert(blob, 'Blob is null');
resolve(blob); resolve(blob);
@ -60,7 +48,7 @@ export const getRegionalPromptLayerBlobs = async (
const base64 = await blobToDataURL(blob); const base64 = await blobToDataURL(blob);
openBase64ImageInTab([{ base64, caption: layer.id() }]); openBase64ImageInTab([{ base64, caption: layer.id() }]);
} }
layerClone.destroy(); layer.remove();
blobs[layer.id()] = blob; blobs[layer.id()] = blob;
} }