tidy(ui): organize rp layer components

This commit is contained in:
psychedelicious
2024-04-19 15:42:46 +10:00
committed by Kent Keirsey
parent 642a0de3dd
commit eb781272f7
9 changed files with 99 additions and 92 deletions

View File

@@ -1,47 +0,0 @@
import { Flex, Spacer } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { rgbaColorToString } from 'features/canvas/util/colorToString';
import LayerAutoNegativeCombobox from 'features/regionalPrompts/components/LayerAutoNegativeCombobox';
import { LayerColorPicker } from 'features/regionalPrompts/components/LayerColorPicker';
import { LayerMenu } from 'features/regionalPrompts/components/LayerMenu';
import { LayerVisibilityToggle } from 'features/regionalPrompts/components/LayerVisibilityToggle';
import { RegionalPromptsNegativePrompt } from 'features/regionalPrompts/components/RegionalPromptsNegativePrompt';
import { RegionalPromptsPositivePrompt } from 'features/regionalPrompts/components/RegionalPromptsPositivePrompt';
import { isRegionalPromptLayer, rpLayerSelected } from 'features/regionalPrompts/store/regionalPromptsSlice';
import { memo, useCallback } from 'react';
import { assert } from 'tsafe';
type Props = {
id: string;
};
export const LayerListItem = memo(({ id }: Props) => {
const dispatch = useAppDispatch();
const selectedLayer = useAppSelector((s) => s.regionalPrompts.present.selectedLayer);
const color = useAppSelector((s) => {
const layer = s.regionalPrompts.present.layers.find((l) => l.id === id);
assert(isRegionalPromptLayer(layer), `Layer ${id} not found or not an RP layer`);
return rgbaColorToString({ ...layer.color, a: selectedLayer === id ? 1 : 0.35 });
});
const onClickCapture = useCallback(() => {
// Must be capture so that the layer is selected before deleting/resetting/etc
dispatch(rpLayerSelected(id));
}, [dispatch, id]);
return (
<Flex gap={2} onClickCapture={onClickCapture} bg={color} borderRadius="base" p="1px" ps={3}>
<Flex flexDir="column" gap={2} w="full" bg="base.850" borderRadius="base" p={2}>
<Flex gap={2} alignItems="center">
<LayerColorPicker id={id} />
<LayerVisibilityToggle id={id} />
<Spacer />
<LayerAutoNegativeCombobox layerId={id} />
<LayerMenu id={id} />
</Flex>
<RegionalPromptsPositivePrompt layerId={id} />
<RegionalPromptsNegativePrompt layerId={id} />
</Flex>
</Flex>
);
});
LayerListItem.displayName = 'LayerListItem';

View File

@@ -35,7 +35,7 @@ const useAutoNegative = (layerId: string) => {
return autoNegative; return autoNegative;
}; };
const AutoNegativeCombobox = ({ layerId }: Props) => { export const RPLayerAutoNegativeCombobox = memo(({ layerId }: Props) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const autoNegative = useAutoNegative(layerId); const autoNegative = useAutoNegative(layerId);
@@ -58,6 +58,6 @@ const AutoNegativeCombobox = ({ layerId }: Props) => {
<Combobox value={value} options={options} onChange={onChange} isSearchable={false} sx={{ w: '5.2rem' }} /> <Combobox value={value} options={options} onChange={onChange} isSearchable={false} sx={{ w: '5.2rem' }} />
</FormControl> </FormControl>
); );
}; });
export default memo(AutoNegativeCombobox); RPLayerAutoNegativeCombobox.displayName = 'RPLayerAutoNegativeCombobox';

View File

@@ -13,26 +13,26 @@ import { PiEyedropperBold } from 'react-icons/pi';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
type Props = { type Props = {
id: string; layerId: string;
}; };
export const LayerColorPicker = memo(({ id }: Props) => { export const RPLayerColorPicker = memo(({ layerId }: Props) => {
const selectColor = useMemo( const selectColor = useMemo(
() => () =>
createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => {
const layer = regionalPrompts.present.layers.find((l) => l.id === id); const layer = regionalPrompts.present.layers.find((l) => l.id === layerId);
assert(isRegionalPromptLayer(layer), `Layer ${id} not found or not an RP layer`); assert(isRegionalPromptLayer(layer), `Layer ${layerId} not found or not an RP layer`);
return layer.color; return layer.color;
}), }),
[id] [layerId]
); );
const color = useAppSelector(selectColor); const color = useAppSelector(selectColor);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const onColorChange = useCallback( const onColorChange = useCallback(
(color: RgbColor) => { (color: RgbColor) => {
dispatch(rpLayerColorChanged({ layerId: id, color })); dispatch(rpLayerColorChanged({ layerId, color }));
}, },
[dispatch, id] [dispatch, layerId]
); );
return ( return (
<Popover isLazy> <Popover isLazy>
@@ -48,4 +48,4 @@ export const LayerColorPicker = memo(({ id }: Props) => {
); );
}); });
LayerColorPicker.displayName = 'LayerColorPicker'; RPLayerColorPicker.displayName = 'RPLayerColorPicker';

View File

@@ -0,0 +1,47 @@
import { Flex, Spacer } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { rgbaColorToString } from 'features/canvas/util/colorToString';
import { RPLayerAutoNegativeCombobox } from 'features/regionalPrompts/components/RPLayerAutoNegativeCombobox';
import { RPLayerColorPicker } from 'features/regionalPrompts/components/RPLayerColorPicker';
import { RPLayerMenu } from 'features/regionalPrompts/components/RPLayerMenu';
import { RPLayerNegativePrompt } from 'features/regionalPrompts/components/RPLayerNegativePrompt';
import { RPLayerPositivePrompt } from 'features/regionalPrompts/components/RPLayerPositivePrompt';
import { RPLayerVisibilityToggle } from 'features/regionalPrompts/components/RPLayerVisibilityToggle';
import { isRegionalPromptLayer, rpLayerSelected } from 'features/regionalPrompts/store/regionalPromptsSlice';
import { memo, useCallback } from 'react';
import { assert } from 'tsafe';
type Props = {
layerId: string;
};
export const RPLayerListItem = memo(({ layerId }: Props) => {
const dispatch = useAppDispatch();
const selectedLayer = useAppSelector((s) => s.regionalPrompts.present.selectedLayer);
const color = useAppSelector((s) => {
const layer = s.regionalPrompts.present.layers.find((l) => l.id === layerId);
assert(isRegionalPromptLayer(layer), `Layer ${layerId} not found or not an RP layer`);
return rgbaColorToString({ ...layer.color, a: selectedLayer === layerId ? 1 : 0.35 });
});
const onClickCapture = useCallback(() => {
// Must be capture so that the layer is selected before deleting/resetting/etc
dispatch(rpLayerSelected(layerId));
}, [dispatch, layerId]);
return (
<Flex gap={2} onClickCapture={onClickCapture} bg={color} borderRadius="base" p="1px" ps={3}>
<Flex flexDir="column" gap={2} w="full" bg="base.850" borderRadius="base" p={2}>
<Flex gap={2} alignItems="center">
<RPLayerColorPicker layerId={layerId} />
<RPLayerVisibilityToggle layerId={layerId} />
<Spacer />
<RPLayerAutoNegativeCombobox layerId={layerId} />
<RPLayerMenu layerId={layerId} />
</Flex>
<RPLayerPositivePrompt layerId={layerId} />
<RPLayerNegativePrompt layerId={layerId} />
</Flex>
</Flex>
);
});
RPLayerListItem.displayName = 'RPLayerListItem';

View File

@@ -2,6 +2,7 @@ import { IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuList } from '@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import {
isRegionalPromptLayer,
layerDeleted, layerDeleted,
layerMovedBackward, layerMovedBackward,
layerMovedForward, layerMovedForward,
@@ -21,16 +22,19 @@ import {
PiDotsThreeVerticalBold, PiDotsThreeVerticalBold,
PiTrashSimpleBold, PiTrashSimpleBold,
} from 'react-icons/pi'; } from 'react-icons/pi';
import { assert } from 'tsafe';
type Props = { id: string }; type Props = { layerId: string };
export const LayerMenu = memo(({ id }: Props) => { export const RPLayerMenu = memo(({ layerId }: Props) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const selectValidActions = useMemo( const selectValidActions = useMemo(
() => () =>
createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => {
const layerIndex = regionalPrompts.present.layers.findIndex((l) => l.id === id); const layer = regionalPrompts.present.layers.find((l) => l.id === layerId);
assert(isRegionalPromptLayer(layer), `Layer ${layerId} not found or not an RP layer`);
const layerIndex = regionalPrompts.present.layers.findIndex((l) => l.id === layerId);
const layerCount = regionalPrompts.present.layers.length; const layerCount = regionalPrompts.present.layers.length;
return { return {
canMoveForward: layerIndex < layerCount - 1, canMoveForward: layerIndex < layerCount - 1,
@@ -39,27 +43,27 @@ export const LayerMenu = memo(({ id }: Props) => {
canMoveToBack: layerIndex > 0, canMoveToBack: layerIndex > 0,
}; };
}), }),
[id] [layerId]
); );
const validActions = useAppSelector(selectValidActions); const validActions = useAppSelector(selectValidActions);
const moveForward = useCallback(() => { const moveForward = useCallback(() => {
dispatch(layerMovedForward(id)); dispatch(layerMovedForward(layerId));
}, [dispatch, id]); }, [dispatch, layerId]);
const moveToFront = useCallback(() => { const moveToFront = useCallback(() => {
dispatch(layerMovedToFront(id)); dispatch(layerMovedToFront(layerId));
}, [dispatch, id]); }, [dispatch, layerId]);
const moveBackward = useCallback(() => { const moveBackward = useCallback(() => {
dispatch(layerMovedBackward(id)); dispatch(layerMovedBackward(layerId));
}, [dispatch, id]); }, [dispatch, layerId]);
const moveToBack = useCallback(() => { const moveToBack = useCallback(() => {
dispatch(layerMovedToBack(id)); dispatch(layerMovedToBack(layerId));
}, [dispatch, id]); }, [dispatch, layerId]);
const resetLayer = useCallback(() => { const resetLayer = useCallback(() => {
dispatch(rpLayerReset(id)); dispatch(rpLayerReset(layerId));
}, [dispatch, id]); }, [dispatch, layerId]);
const deleteLayer = useCallback(() => { const deleteLayer = useCallback(() => {
dispatch(layerDeleted(id)); dispatch(layerDeleted(layerId));
}, [dispatch, id]); }, [dispatch, layerId]);
return ( return (
<Menu> <Menu>
<MenuButton as={IconButton} aria-label="Layer menu" size="sm" icon={<PiDotsThreeVerticalBold />} /> <MenuButton as={IconButton} aria-label="Layer menu" size="sm" icon={<PiDotsThreeVerticalBold />} />
@@ -88,4 +92,4 @@ export const LayerMenu = memo(({ id }: Props) => {
); );
}); });
LayerMenu.displayName = 'LayerMenu'; RPLayerMenu.displayName = 'RPLayerMenu';

View File

@@ -15,7 +15,7 @@ type Props = {
layerId: string; layerId: string;
}; };
export const RegionalPromptsNegativePrompt = memo((props: Props) => { export const RPLayerNegativePrompt = memo((props: Props) => {
const prompt = useLayerNegativePrompt(props.layerId); const prompt = useLayerNegativePrompt(props.layerId);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
@@ -64,4 +64,4 @@ export const RegionalPromptsNegativePrompt = memo((props: Props) => {
); );
}); });
RegionalPromptsNegativePrompt.displayName = 'RegionalPromptsPrompt'; RPLayerNegativePrompt.displayName = 'RPLayerNegativePrompt';

View File

@@ -15,7 +15,7 @@ type Props = {
layerId: string; layerId: string;
}; };
export const RegionalPromptsPositivePrompt = memo((props: Props) => { export const RPLayerPositivePrompt = memo((props: Props) => {
const prompt = useLayerPositivePrompt(props.layerId); const prompt = useLayerPositivePrompt(props.layerId);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
@@ -64,4 +64,4 @@ export const RegionalPromptsPositivePrompt = memo((props: Props) => {
); );
}); });
RegionalPromptsPositivePrompt.displayName = 'RegionalPromptsPrompt'; RPLayerPositivePrompt.displayName = 'RPLayerPositivePrompt';

View File

@@ -6,15 +6,15 @@ 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; layerId: string;
}; };
export const LayerVisibilityToggle = memo(({ id }: Props) => { export const RPLayerVisibilityToggle = memo(({ layerId }: Props) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const isVisible = useLayerIsVisible(id); const isVisible = useLayerIsVisible(layerId);
const onClick = useCallback(() => { const onClick = useCallback(() => {
dispatch(rpLayerIsVisibleToggled(id)); dispatch(rpLayerIsVisibleToggled(layerId));
}, [dispatch, id]); }, [dispatch, layerId]);
return ( return (
<IconButton <IconButton
@@ -27,4 +27,4 @@ export const LayerVisibilityToggle = memo(({ id }: Props) => {
); );
}); });
LayerVisibilityToggle.displayName = 'LayerVisibilityToggle'; RPLayerVisibilityToggle.displayName = 'RPLayerVisibilityToggle';

View File

@@ -5,17 +5,20 @@ 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 { DeleteAllLayersButton } from 'features/regionalPrompts/components/DeleteAllLayersButton';
import { LayerListItem } from 'features/regionalPrompts/components/LayerListItem';
import { PromptLayerOpacity } from 'features/regionalPrompts/components/PromptLayerOpacity'; import { PromptLayerOpacity } from 'features/regionalPrompts/components/PromptLayerOpacity';
import { RPLayerListItem } from 'features/regionalPrompts/components/RPLayerListItem';
import { StageComponent } from 'features/regionalPrompts/components/StageComponent'; import { StageComponent } from 'features/regionalPrompts/components/StageComponent';
import { ToolChooser } from 'features/regionalPrompts/components/ToolChooser'; import { ToolChooser } from 'features/regionalPrompts/components/ToolChooser';
import { UndoRedoButtonGroup } from 'features/regionalPrompts/components/UndoRedoButtonGroup'; import { UndoRedoButtonGroup } from 'features/regionalPrompts/components/UndoRedoButtonGroup';
import { selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { isRegionalPromptLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice';
import { getRegionalPromptLayerBlobs } from 'features/regionalPrompts/util/getLayerBlobs'; import { getRegionalPromptLayerBlobs } from 'features/regionalPrompts/util/getLayerBlobs';
import { memo } from 'react'; import { memo } from 'react';
const selectLayerIdsReversed = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => const selectRPLayerIdsReversed = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) =>
regionalPrompts.present.layers.map((l) => l.id).reverse() regionalPrompts.present.layers
.filter(isRegionalPromptLayer)
.map((l) => l.id)
.reverse()
); );
const debugBlobs = () => { const debugBlobs = () => {
@@ -23,7 +26,7 @@ const debugBlobs = () => {
}; };
export const RegionalPromptsEditor = memo(() => { export const RegionalPromptsEditor = memo(() => {
const layerIdsReversed = useAppSelector(selectLayerIdsReversed); const rpLayerIdsReversed = useAppSelector(selectRPLayerIdsReversed);
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}>
@@ -38,8 +41,8 @@ export const RegionalPromptsEditor = memo(() => {
</Flex> </Flex>
<BrushSize /> <BrushSize />
<PromptLayerOpacity /> <PromptLayerOpacity />
{layerIdsReversed.map((id) => ( {rpLayerIdsReversed.map((id) => (
<LayerListItem key={id} id={id} /> <RPLayerListItem key={id} layerId={id} />
))} ))}
</Flex> </Flex>
<StageComponent /> <StageComponent />