diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/LineComponent.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/LineComponent.tsx new file mode 100644 index 0000000000..86785a4219 --- /dev/null +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/LineComponent.tsx @@ -0,0 +1,23 @@ +import { rgbaColorToString } from 'features/canvas/util/colorToString'; +import { useTransform } from 'features/regionalPrompts/hooks/useTransform'; +import type { LineObject } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { Line } from 'react-konva'; + +type Props = { + line: LineObject; +}; + +export const LineComponent = ({ line }: Props) => { + const { shapeRef } = useTransform(line); + + return ( + + ); +}; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RectComponent.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RectComponent.tsx new file mode 100644 index 0000000000..e2b83a47e7 --- /dev/null +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RectComponent.tsx @@ -0,0 +1,25 @@ +import { rgbaColorToString } from 'features/canvas/util/colorToString'; +import { useTransform } from 'features/regionalPrompts/hooks/useTransform'; +import type { FillRectObject } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { Rect } from 'react-konva'; + +type Props = { + rect: FillRectObject; +}; + +export const RectComponent = ({ rect }: Props) => { + const { shapeRef } = useTransform(rect); + + return ( + + ); +}; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptingEditor.stories.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptingEditor.stories.tsx new file mode 100644 index 0000000000..a67e9f58f7 --- /dev/null +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptingEditor.stories.tsx @@ -0,0 +1,19 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { RegionalPromptsEditor } from 'features/regionalPrompts/components/RegionalPromptsEditor'; + +const meta: Meta = { + title: 'Feature/RegionalPrompts', + tags: ['autodocs'], + component: RegionalPromptsEditor, +}; + +export default meta; +type Story = StoryObj; + +const Component = () => { + return ; +}; + +export const Default: Story = { + render: Component, +}; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsEditor.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsEditor.tsx new file mode 100644 index 0000000000..907f8a2c69 --- /dev/null +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsEditor.tsx @@ -0,0 +1,25 @@ +import { Flex } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; +import { useAppSelector } from 'app/store/storeHooks'; +import { RegionalPromptsStage } from 'features/regionalPrompts/components/RegionalPromptsStage'; +import { layersSelectors, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; + +const selectLayers = createSelector(selectRegionalPromptsSlice, (regionalPrompts) => + layersSelectors.selectAll(regionalPrompts) +); + +export const RegionalPromptsEditor = () => { + const layers = useAppSelector(selectLayers); + return ( + + + {layers.map((layer) => ( + {layer.prompt} + ))} + + + + + + ); +}; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsStage.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsStage.tsx new file mode 100644 index 0000000000..b80d142a84 --- /dev/null +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsStage.tsx @@ -0,0 +1,39 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { useAppSelector } from 'app/store/storeHooks'; +import { LineComponent } from 'features/regionalPrompts/components/LineComponent'; +import { RectComponent } from 'features/regionalPrompts/components/RectComponent'; +import { + layerObjectsSelectors, + layersSelectors, + selectRegionalPromptsSlice, +} from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { memo } from 'react'; +import { Group, Layer, Stage } from 'react-konva'; + +const selectLayers = createSelector(selectRegionalPromptsSlice, (regionalPrompts) => + layersSelectors.selectAll(regionalPrompts) +); + +export const RegionalPromptsStage: React.FC = memo(() => { + const layers = useAppSelector(selectLayers); + return ( + + + {layers.map((layer) => ( + + {layerObjectsSelectors.selectAll(layer.objects).map((obj) => { + if (obj.kind === 'line') { + return ; + } + if (obj.kind === 'fillRect') { + return ; + } + })} + + ))} + + + ); +}); + +RegionalPromptsStage.displayName = 'RegionalPromptingEditor'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/hooks/useTransform.ts b/invokeai/frontend/web/src/features/regionalPrompts/hooks/useTransform.ts new file mode 100644 index 0000000000..d04cbb8c55 --- /dev/null +++ b/invokeai/frontend/web/src/features/regionalPrompts/hooks/useTransform.ts @@ -0,0 +1,30 @@ +import type { FillRectObject, LayerObject, LineObject } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import type { Image } from 'konva/lib/shapes/Image'; +import type { Line } from 'konva/lib/shapes/Line'; +import type { Rect } from 'konva/lib/shapes/Rect'; +import type { Transformer } from 'konva/lib/shapes/Transformer'; +import { useEffect, useRef } from 'react'; + +type ShapeType = T extends LineObject ? Line : T extends FillRectObject ? Rect : Image; + +export const useTransform = (object: TObject) => { + const shapeRef = useRef>(null); + const transformerRef = useRef(null); + + useEffect(() => { + if (!object.isSelected) { + return; + } + + if (!transformerRef.current || !shapeRef.current) { + return; + } + + if (object.isSelected) { + transformerRef.current.nodes([shapeRef.current]); + transformerRef.current.getLayer()?.batchDraw(); + } + }, [object.isSelected]); + + return { shapeRef, transformerRef }; +};