mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
perf(ui): memoize & otherwise optimize regional prompts ui
This commit is contained in:
parent
944fa1a847
commit
d1db6198b5
@ -1,10 +1,10 @@
|
|||||||
import { Button } from '@invoke-ai/ui-library';
|
import { Button } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { layerAdded } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
import { layerAdded } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||||
import { useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export const AddLayerButton = () => {
|
export const AddLayerButton = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const onClick = useCallback(() => {
|
const onClick = useCallback(() => {
|
||||||
@ -12,4 +12,6 @@ export const AddLayerButton = () => {
|
|||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
return <Button onClick={onClick}>{t('regionalPrompts.addLayer')}</Button>;
|
return <Button onClick={onClick}>{t('regionalPrompts.addLayer')}</Button>;
|
||||||
};
|
});
|
||||||
|
|
||||||
|
AddLayerButton.displayName = 'AddLayerButton';
|
||||||
|
@ -1,49 +1,38 @@
|
|||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { rgbColorToString } from 'features/canvas/util/colorToString';
|
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
||||||
import { $cursorPosition } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
import { $cursorPosition } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||||
|
import { memo } from 'react';
|
||||||
import { Circle, Group } from 'react-konva';
|
import { Circle, Group } from 'react-konva';
|
||||||
|
|
||||||
const useBrushData = () => {
|
export const BrushPreviewOutline = memo(() => {
|
||||||
const brushSize = useAppSelector((s) => s.regionalPrompts.brushSize);
|
const brushSize = useAppSelector((s) => s.regionalPrompts.brushSize);
|
||||||
const tool = useAppSelector((s) => s.regionalPrompts.tool);
|
const tool = useAppSelector((s) => s.regionalPrompts.tool);
|
||||||
|
const a = useAppSelector((s) => s.regionalPrompts.promptLayerOpacity);
|
||||||
const color = useAppSelector((s) => {
|
const color = useAppSelector((s) => {
|
||||||
const _color = s.regionalPrompts.layers.find((l) => l.id === s.regionalPrompts.selectedLayer)?.color;
|
const _color = s.regionalPrompts.layers.find((l) => l.id === s.regionalPrompts.selectedLayer)?.color;
|
||||||
if (!_color) {
|
if (!_color) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return rgbColorToString(_color);
|
return rgbaColorToString({ ..._color, a });
|
||||||
});
|
});
|
||||||
|
|
||||||
const pos = useStore($cursorPosition);
|
const pos = useStore($cursorPosition);
|
||||||
|
|
||||||
return { brushSize, tool, color, pos };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const BrushPreviewFill = () => {
|
|
||||||
const { brushSize, tool, color, pos } = useBrushData();
|
|
||||||
if (!brushSize || !color || !pos || tool === 'move') {
|
if (!brushSize || !color || !pos || tool === 'move') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Circle
|
<Group listening={false}>
|
||||||
x={pos.x}
|
<Circle
|
||||||
y={pos.y}
|
x={pos.x}
|
||||||
radius={brushSize / 2}
|
y={pos.y}
|
||||||
fill={color}
|
radius={brushSize / 2}
|
||||||
globalCompositeOperation={tool === 'brush' ? 'source-over' : 'destination-out'}
|
fill={color}
|
||||||
/>
|
globalCompositeOperation={tool === 'brush' ? 'source-over' : 'destination-out'}
|
||||||
);
|
listening={false}
|
||||||
};
|
/>
|
||||||
|
|
||||||
export const BrushPreviewOutline = () => {
|
|
||||||
const { brushSize, tool, color, pos } = useBrushData();
|
|
||||||
if (!brushSize || !color || !pos || tool === 'move') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Group>
|
|
||||||
<Circle
|
<Circle
|
||||||
x={pos.x}
|
x={pos.x}
|
||||||
y={pos.y}
|
y={pos.y}
|
||||||
@ -64,4 +53,6 @@ export const BrushPreviewOutline = () => {
|
|||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
|
BrushPreviewOutline.displayName = 'BrushPreviewOutline';
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { brushSizeChanged } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
import { brushSizeChanged } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||||
import { useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export const BrushSize = () => {
|
export const BrushSize = memo(() => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const brushSize = useAppSelector((s) => s.regionalPrompts.brushSize);
|
const brushSize = useAppSelector((s) => s.regionalPrompts.brushSize);
|
||||||
@ -21,4 +21,6 @@ export const BrushSize = () => {
|
|||||||
<CompositeNumberInput min={1} max={500} value={brushSize} onChange={onChange} />
|
<CompositeNumberInput min={1} max={500} value={brushSize} onChange={onChange} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
|
BrushSize.displayName = 'BrushSize';
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { layerSelected, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
import { layerSelected, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { Rect as KonvaRect } from 'react-konva';
|
import { Rect as KonvaRect } from 'react-konva';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
layerId: string;
|
layerId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LayerBoundingBox = ({ layerId }: Props) => {
|
export const LayerBoundingBox = memo(({ layerId }: Props) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const tool = useAppSelector((s) => s.regionalPrompts.tool);
|
const tool = useAppSelector((s) => s.regionalPrompts.tool);
|
||||||
const selectedLayer = useAppSelector((s) => s.regionalPrompts.selectedLayer);
|
const selectedLayer = useAppSelector((s) => s.regionalPrompts.selectedLayer);
|
||||||
@ -19,7 +19,7 @@ export const LayerBoundingBox = ({ layerId }: Props) => {
|
|||||||
|
|
||||||
const selectBbox = useMemo(
|
const selectBbox = useMemo(
|
||||||
() =>
|
() =>
|
||||||
createSelector(
|
createMemoizedSelector(
|
||||||
selectRegionalPromptsSlice,
|
selectRegionalPromptsSlice,
|
||||||
(regionalPrompts) => regionalPrompts.layers.find((layer) => layer.id === layerId)?.bbox ?? null
|
(regionalPrompts) => regionalPrompts.layers.find((layer) => layer.id === layerId)?.bbox ?? null
|
||||||
),
|
),
|
||||||
@ -33,6 +33,7 @@ export const LayerBoundingBox = ({ layerId }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<KonvaRect
|
<KonvaRect
|
||||||
|
name="layer bbox"
|
||||||
onMouseDown={onMouseDown}
|
onMouseDown={onMouseDown}
|
||||||
stroke={selectedLayer === layerId ? 'rgba(153, 187, 189, 1)' : 'rgba(255, 255, 255, 0.149)'}
|
stroke={selectedLayer === layerId ? 'rgba(153, 187, 189, 1)' : 'rgba(255, 255, 255, 0.149)'}
|
||||||
strokeWidth={1}
|
strokeWidth={1}
|
||||||
@ -43,4 +44,6 @@ export const LayerBoundingBox = ({ layerId }: Props) => {
|
|||||||
listening={tool === 'move'}
|
listening={tool === 'move'}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
|
LayerBoundingBox.displayName = 'LayerBoundingBox';
|
||||||
|
@ -1,18 +1,31 @@
|
|||||||
import { IconButton, Popover, PopoverBody, PopoverContent, PopoverTrigger } from '@invoke-ai/ui-library';
|
import { IconButton, Popover, PopoverBody, PopoverContent, PopoverTrigger } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import RgbColorPicker from 'common/components/RgbColorPicker';
|
import RgbColorPicker from 'common/components/RgbColorPicker';
|
||||||
import { useLayer } from 'features/regionalPrompts/hooks/layerStateHooks';
|
import {
|
||||||
import { promptRegionLayerColorChanged } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
promptRegionLayerColorChanged,
|
||||||
import { useCallback } from 'react';
|
selectRegionalPromptsSlice,
|
||||||
|
} from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||||
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import type { RgbColor } from 'react-colorful';
|
import type { RgbColor } from 'react-colorful';
|
||||||
import { PiEyedropperBold } from 'react-icons/pi';
|
import { PiEyedropperBold } from 'react-icons/pi';
|
||||||
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LayerColorPicker = ({ id }: Props) => {
|
export const LayerColorPicker = memo(({ id }: Props) => {
|
||||||
const layer = useLayer(id);
|
const selectColor = useMemo(
|
||||||
|
() =>
|
||||||
|
createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => {
|
||||||
|
const layer = regionalPrompts.layers.find((l) => l.id === id);
|
||||||
|
assert(layer);
|
||||||
|
return layer.color;
|
||||||
|
}),
|
||||||
|
[id]
|
||||||
|
);
|
||||||
|
const color = useAppSelector(selectColor);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const onColorChange = useCallback(
|
const onColorChange = useCallback(
|
||||||
(color: RgbColor) => {
|
(color: RgbColor) => {
|
||||||
@ -27,9 +40,11 @@ export const LayerColorPicker = ({ id }: Props) => {
|
|||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent>
|
<PopoverContent>
|
||||||
<PopoverBody minH={64}>
|
<PopoverBody minH={64}>
|
||||||
<RgbColorPicker color={layer.color} onChange={onColorChange} withNumberInput />
|
<RgbColorPicker color={color} onChange={onColorChange} withNumberInput />
|
||||||
</PopoverBody>
|
</PopoverBody>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
|
LayerColorPicker.displayName = 'LayerColorPicker';
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
|
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
|
||||||
import { BrushPreviewFill } from 'features/regionalPrompts/components/BrushPreview';
|
|
||||||
import { LayerBoundingBox } from 'features/regionalPrompts/components/LayerBoundingBox';
|
import { LayerBoundingBox } from 'features/regionalPrompts/components/LayerBoundingBox';
|
||||||
import { LineComponent } from 'features/regionalPrompts/components/LineComponent';
|
import { LineComponent } from 'features/regionalPrompts/components/LineComponent';
|
||||||
import { RectComponent } from 'features/regionalPrompts/components/RectComponent';
|
import { RectComponent } from 'features/regionalPrompts/components/RectComponent';
|
||||||
@ -17,8 +16,7 @@ import type { Group as KonvaGroupType } from 'konva/lib/Group';
|
|||||||
import type { Layer as KonvaLayerType } from 'konva/lib/Layer';
|
import type { Layer as KonvaLayerType } from 'konva/lib/Layer';
|
||||||
import type { KonvaEventObject, Node as KonvaNodeType, NodeConfig as KonvaNodeConfigType } from 'konva/lib/Node';
|
import type { KonvaEventObject, Node as KonvaNodeType, NodeConfig as KonvaNodeConfigType } from 'konva/lib/Node';
|
||||||
import type { IRect, Vector2d } from 'konva/lib/types';
|
import type { IRect, Vector2d } from 'konva/lib/types';
|
||||||
import type React from 'react';
|
import { memo, useCallback, useEffect, useRef } from 'react';
|
||||||
import { useCallback, useEffect, useRef } from 'react';
|
|
||||||
import { Group as KonvaGroup, Layer as KonvaLayer } from 'react-konva';
|
import { Group as KonvaGroup, Layer as KonvaLayer } from 'react-konva';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -28,15 +26,13 @@ type Props = {
|
|||||||
export const selectPromptLayerObjectGroup = (item: KonvaNodeType<KonvaNodeConfigType>) =>
|
export const selectPromptLayerObjectGroup = (item: KonvaNodeType<KonvaNodeConfigType>) =>
|
||||||
item.name() !== REGIONAL_PROMPT_LAYER_OBJECT_GROUP_NAME;
|
item.name() !== REGIONAL_PROMPT_LAYER_OBJECT_GROUP_NAME;
|
||||||
|
|
||||||
export const LayerComponent: React.FC<Props> = ({ id }) => {
|
export const LayerComponent = memo(({ id }: Props) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const layer = useLayer(id);
|
const layer = useLayer(id);
|
||||||
const selectedLayer = useAppSelector((s) => s.regionalPrompts.selectedLayer);
|
|
||||||
const promptLayerOpacity = useAppSelector((s) => s.regionalPrompts.promptLayerOpacity);
|
const promptLayerOpacity = useAppSelector((s) => s.regionalPrompts.promptLayerOpacity);
|
||||||
const tool = useAppSelector((s) => s.regionalPrompts.tool);
|
const tool = useAppSelector((s) => s.regionalPrompts.tool);
|
||||||
const layerRef = useRef<KonvaLayerType>(null);
|
const layerRef = useRef<KonvaLayerType>(null);
|
||||||
const groupRef = useRef<KonvaGroupType>(null);
|
const groupRef = useRef<KonvaGroupType>(null);
|
||||||
|
|
||||||
const onChangeBbox = useCallback(
|
const onChangeBbox = useCallback(
|
||||||
(bbox: IRect | null) => {
|
(bbox: IRect | null) => {
|
||||||
dispatch(layerBboxChanged({ layerId: layer.id, bbox }));
|
dispatch(layerBboxChanged({ layerId: layer.id, bbox }));
|
||||||
@ -94,39 +90,39 @@ export const LayerComponent: React.FC<Props> = ({ id }) => {
|
|||||||
}
|
}
|
||||||
// Caching the group allows its opacity to apply to all shapes at once. We should cache only when the layer's
|
// Caching the group allows its opacity to apply to all shapes at once. We should cache only when the layer's
|
||||||
// objects or attributes with a visual effect (e.g. color) change.
|
// objects or attributes with a visual effect (e.g. color) change.
|
||||||
|
// TODO: Figure out a more efficient way to handle opacity - maybe a separate rect w/ globalCompositeOperation...
|
||||||
groupRef.current.cache();
|
groupRef.current.cache();
|
||||||
}, [layer.objects, layer.color, layer.isVisible]);
|
}, [layer.objects, layer.color, layer.isVisible]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<KonvaLayer
|
||||||
<KonvaLayer
|
ref={layerRef}
|
||||||
ref={layerRef}
|
id={layer.id}
|
||||||
id={layer.id}
|
name={REGIONAL_PROMPT_LAYER_NAME}
|
||||||
name={REGIONAL_PROMPT_LAYER_NAME}
|
onDragMove={onDragMove}
|
||||||
onDragMove={onDragMove}
|
dragBoundFunc={dragBoundFunc}
|
||||||
dragBoundFunc={dragBoundFunc}
|
visible={layer.isVisible}
|
||||||
visible={layer.isVisible}
|
draggable
|
||||||
draggable
|
>
|
||||||
|
<KonvaGroup
|
||||||
|
id={`layer-${layer.id}-group`}
|
||||||
|
name={REGIONAL_PROMPT_LAYER_OBJECT_GROUP_NAME}
|
||||||
|
ref={groupRef}
|
||||||
|
listening={false}
|
||||||
|
opacity={promptLayerOpacity}
|
||||||
>
|
>
|
||||||
<KonvaGroup
|
{layer.objects.map((obj) => {
|
||||||
id={`layer-${layer.id}-group`}
|
if (obj.kind === 'line') {
|
||||||
name={REGIONAL_PROMPT_LAYER_OBJECT_GROUP_NAME}
|
return <LineComponent key={obj.id} line={obj} color={layer.color} layerId={layer.id} />;
|
||||||
ref={groupRef}
|
}
|
||||||
listening={false}
|
if (obj.kind === 'fillRect') {
|
||||||
opacity={promptLayerOpacity}
|
return <RectComponent key={obj.id} rect={obj} color={layer.color} />;
|
||||||
>
|
}
|
||||||
{layer.objects.map((obj) => {
|
})}
|
||||||
if (obj.kind === 'line') {
|
</KonvaGroup>
|
||||||
return <LineComponent key={obj.id} line={obj} color={layer.color} layerId={layer.id} />;
|
<LayerBoundingBox layerId={layer.id} />
|
||||||
}
|
</KonvaLayer>
|
||||||
if (obj.kind === 'fillRect') {
|
|
||||||
return <RectComponent key={obj.id} rect={obj} color={layer.color} />;
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</KonvaGroup>
|
|
||||||
<LayerBoundingBox layerId={layer.id} />
|
|
||||||
</KonvaLayer>
|
|
||||||
<KonvaLayer name="brushPreviewFill">{layer.id === selectedLayer && <BrushPreviewFill />}</KonvaLayer>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
|
LayerComponent.displayName = 'LayerComponent';
|
||||||
|
@ -6,14 +6,14 @@ import { LayerMenu } from 'features/regionalPrompts/components/LayerMenu';
|
|||||||
import { LayerVisibilityToggle } from 'features/regionalPrompts/components/LayerVisibilityToggle';
|
import { LayerVisibilityToggle } from 'features/regionalPrompts/components/LayerVisibilityToggle';
|
||||||
import { RegionalPromptsPrompt } from 'features/regionalPrompts/components/RegionalPromptsPrompt';
|
import { RegionalPromptsPrompt } from 'features/regionalPrompts/components/RegionalPromptsPrompt';
|
||||||
import { layerSelected } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
import { layerSelected } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||||
import { useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LayerListItem = ({ id }: Props) => {
|
export const LayerListItem = memo(({ id }: Props) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const selectedLayer = useAppSelector((s) => s.regionalPrompts.selectedLayer);
|
const selectedLayer = useAppSelector((s) => s.regionalPrompts.selectedLayer);
|
||||||
@ -46,4 +46,6 @@ export const LayerListItem = ({ id }: Props) => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
|
LayerListItem.displayName = 'LayerListItem';
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
import { IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import {
|
import {
|
||||||
layerDeleted,
|
layerDeleted,
|
||||||
@ -10,8 +10,7 @@ import {
|
|||||||
layerReset,
|
layerReset,
|
||||||
selectRegionalPromptsSlice,
|
selectRegionalPromptsSlice,
|
||||||
} from 'features/regionalPrompts/store/regionalPromptsSlice';
|
} from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||||
import type React from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useCallback, useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
PiArrowCounterClockwiseBold,
|
PiArrowCounterClockwiseBold,
|
||||||
@ -25,12 +24,12 @@ import {
|
|||||||
|
|
||||||
type Props = { id: string };
|
type Props = { id: string };
|
||||||
|
|
||||||
export const LayerMenu: React.FC<Props> = ({ id }) => {
|
export const LayerMenu = memo(({ id }: Props) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const selectValidActions = useMemo(
|
const selectValidActions = useMemo(
|
||||||
() =>
|
() =>
|
||||||
createSelector(selectRegionalPromptsSlice, (regionalPrompts) => {
|
createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => {
|
||||||
const layerIndex = regionalPrompts.layers.findIndex((l) => l.id === id);
|
const layerIndex = regionalPrompts.layers.findIndex((l) => l.id === id);
|
||||||
const layerCount = regionalPrompts.layers.length;
|
const layerCount = regionalPrompts.layers.length;
|
||||||
return {
|
return {
|
||||||
@ -87,4 +86,6 @@ export const LayerMenu: React.FC<Props> = ({ id }) => {
|
|||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
|
LayerMenu.displayName = 'LayerMenu';
|
||||||
|
@ -2,14 +2,14 @@ import { IconButton } from '@invoke-ai/ui-library';
|
|||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { useLayerIsVisible } from 'features/regionalPrompts/hooks/layerStateHooks';
|
import { useLayerIsVisible } from 'features/regionalPrompts/hooks/layerStateHooks';
|
||||||
import { layerIsVisibleToggled } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
import { layerIsVisibleToggled } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||||
import { useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { PiEyeBold, PiEyeClosedBold } from 'react-icons/pi';
|
import { PiEyeBold, PiEyeClosedBold } from 'react-icons/pi';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LayerVisibilityToggle = ({ id }: Props) => {
|
export const LayerVisibilityToggle = memo(({ id }: Props) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const isVisible = useLayerIsVisible(id);
|
const isVisible = useLayerIsVisible(id);
|
||||||
const onClick = useCallback(() => {
|
const onClick = useCallback(() => {
|
||||||
@ -25,4 +25,6 @@ export const LayerVisibilityToggle = ({ id }: Props) => {
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
|
LayerVisibilityToggle.displayName = 'LayerVisibilityToggle';
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { rgbColorToString } from 'features/canvas/util/colorToString';
|
import { rgbColorToString } from 'features/canvas/util/colorToString';
|
||||||
import type { LineObject } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
import type { LineObject } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||||
|
import { memo } from 'react';
|
||||||
import type { RgbColor } from 'react-colorful';
|
import type { RgbColor } from 'react-colorful';
|
||||||
import { Line } from 'react-konva';
|
import { Line } from 'react-konva';
|
||||||
|
|
||||||
@ -9,7 +10,7 @@ type Props = {
|
|||||||
color: RgbColor;
|
color: RgbColor;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LineComponent = ({ layerId, line, color }: Props) => {
|
export const LineComponent = memo(({ layerId, line, color }: Props) => {
|
||||||
return (
|
return (
|
||||||
<Line
|
<Line
|
||||||
id={`layer-${layerId}.line-${line.id}`}
|
id={`layer-${layerId}.line-${line.id}`}
|
||||||
@ -25,4 +26,6 @@ export const LineComponent = ({ layerId, line, color }: Props) => {
|
|||||||
listening={false}
|
listening={false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
|
LineComponent.displayName = 'LineComponent';
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { promptLayerOpacityChanged } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
import { promptLayerOpacityChanged } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||||
import { useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export const PromptLayerOpacity = () => {
|
export const PromptLayerOpacity = memo(() => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const promptLayerOpacity = useAppSelector((s) => s.regionalPrompts.promptLayerOpacity);
|
const promptLayerOpacity = useAppSelector((s) => s.regionalPrompts.promptLayerOpacity);
|
||||||
@ -20,4 +20,6 @@ export const PromptLayerOpacity = () => {
|
|||||||
<CompositeSlider min={0.25} max={1} step={0.01} value={promptLayerOpacity} onChange={onChange} />
|
<CompositeSlider min={0.25} max={1} step={0.01} value={promptLayerOpacity} onChange={onChange} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
|
PromptLayerOpacity.displayName = 'PromptLayerOpacity';
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { rgbColorToString } from 'features/canvas/util/colorToString';
|
import { rgbColorToString } from 'features/canvas/util/colorToString';
|
||||||
import type { FillRectObject } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
import type { FillRectObject } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||||
|
import { memo } from 'react';
|
||||||
import type { RgbColor } from 'react-colorful';
|
import type { RgbColor } from 'react-colorful';
|
||||||
import { Rect } from 'react-konva';
|
import { Rect } from 'react-konva';
|
||||||
|
|
||||||
@ -8,8 +9,18 @@ type Props = {
|
|||||||
color: RgbColor;
|
color: RgbColor;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RectComponent = ({ rect, color }: Props) => {
|
export const RectComponent = memo(({ rect, color }: Props) => {
|
||||||
return (
|
return (
|
||||||
<Rect key={rect.id} x={rect.x} y={rect.y} width={rect.width} height={rect.height} fill={rgbColorToString(color)} />
|
<Rect
|
||||||
|
key={rect.id}
|
||||||
|
x={rect.x}
|
||||||
|
y={rect.y}
|
||||||
|
width={rect.width}
|
||||||
|
height={rect.height}
|
||||||
|
fill={rgbColorToString(color)}
|
||||||
|
listening={false}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
|
RectComponent.displayName = 'RectComponent';
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable i18next/no-literal-string */
|
/* eslint-disable i18next/no-literal-string */
|
||||||
import { Button, Flex } from '@invoke-ai/ui-library';
|
import { Button, Flex } from '@invoke-ai/ui-library';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
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';
|
||||||
@ -11,8 +11,9 @@ 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';
|
||||||
import { ImageSizeLinear } from 'features/settingsAccordions/components/ImageSettingsAccordion/ImageSizeLinear';
|
import { ImageSizeLinear } from 'features/settingsAccordions/components/ImageSettingsAccordion/ImageSizeLinear';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
const selectLayerIdsReversed = createSelector(selectRegionalPromptsSlice, (regionalPrompts) =>
|
const selectLayerIdsReversed = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) =>
|
||||||
regionalPrompts.layers.map((l) => l.id).reverse()
|
regionalPrompts.layers.map((l) => l.id).reverse()
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ const debugBlobs = () => {
|
|||||||
getRegionalPromptLayerBlobs(true);
|
getRegionalPromptLayerBlobs(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RegionalPromptsEditor = () => {
|
export const RegionalPromptsEditor = memo(() => {
|
||||||
const layerIdsReversed = useAppSelector(selectLayerIdsReversed);
|
const layerIdsReversed = useAppSelector(selectLayerIdsReversed);
|
||||||
return (
|
return (
|
||||||
<Flex gap={4}>
|
<Flex gap={4}>
|
||||||
@ -40,4 +41,6 @@ export const RegionalPromptsEditor = () => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
|
RegionalPromptsEditor.displayName = 'RegionalPromptsEditor';
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { chakra } from '@invoke-ai/ui-library';
|
import { chakra } from '@invoke-ai/ui-library';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { BrushPreviewOutline } from 'features/regionalPrompts/components/BrushPreview';
|
import { BrushPreviewOutline } from 'features/regionalPrompts/components/BrushPreview';
|
||||||
import { LayerComponent } from 'features/regionalPrompts/components/LayerComponent';
|
import { LayerComponent } from 'features/regionalPrompts/components/LayerComponent';
|
||||||
@ -15,7 +15,7 @@ import type Konva from 'konva';
|
|||||||
import { memo, useCallback, useMemo, useRef } from 'react';
|
import { memo, useCallback, useMemo, useRef } from 'react';
|
||||||
import { Layer, Stage } from 'react-konva';
|
import { Layer, Stage } from 'react-konva';
|
||||||
|
|
||||||
const selectLayerIds = createSelector(selectRegionalPromptsSlice, (regionalPrompts) =>
|
const selectLayerIds = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) =>
|
||||||
regionalPrompts.layers.map((l) => l.id)
|
regionalPrompts.layers.map((l) => l.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import type { PayloadAction } from '@reduxjs/toolkit';
|
|||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
import type { PersistConfig, RootState } from 'app/store/store';
|
import type { PersistConfig, RootState } from 'app/store/store';
|
||||||
import { moveBackward, moveForward, moveToBack, moveToFront } from 'common/util/arrayUtils';
|
import { moveBackward, moveForward, moveToBack, moveToFront } from 'common/util/arrayUtils';
|
||||||
import type Konva from 'konva';
|
import type { Stage } from 'konva/lib/Stage';
|
||||||
import type { IRect, Vector2d } from 'konva/lib/types';
|
import type { IRect, Vector2d } from 'konva/lib/types';
|
||||||
import { atom } from 'nanostores';
|
import { atom } from 'nanostores';
|
||||||
import type { RgbColor } from 'react-colorful';
|
import type { RgbColor } from 'react-colorful';
|
||||||
@ -270,8 +270,8 @@ export const regionalPromptsPersistConfig: PersistConfig<RegionalPromptsState> =
|
|||||||
export const $isMouseDown = atom(false);
|
export const $isMouseDown = atom(false);
|
||||||
export const $isMouseOver = atom(false);
|
export const $isMouseOver = atom(false);
|
||||||
export const $cursorPosition = atom<Vector2d | null>(null);
|
export const $cursorPosition = atom<Vector2d | null>(null);
|
||||||
export const $stage = atom<Konva.Stage | null>(null);
|
export const $stage = atom<Stage | null>(null);
|
||||||
export const getStage = (): Konva.Stage => {
|
export const getStage = (): Stage => {
|
||||||
const stage = $stage.get();
|
const stage = $stage.get();
|
||||||
assert(stage);
|
assert(stage);
|
||||||
return stage;
|
return stage;
|
||||||
|
Loading…
Reference in New Issue
Block a user