mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): update layer menus
This commit is contained in:
parent
9ee7cad613
commit
d884c15d0c
@ -4,6 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|||||||
import ControlAdapterLayerConfig from 'features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig';
|
import ControlAdapterLayerConfig from 'features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig';
|
||||||
import { LayerTitle } from 'features/regionalPrompts/components/LayerTitle';
|
import { LayerTitle } from 'features/regionalPrompts/components/LayerTitle';
|
||||||
import { RPLayerDeleteButton } from 'features/regionalPrompts/components/RPLayerDeleteButton';
|
import { RPLayerDeleteButton } from 'features/regionalPrompts/components/RPLayerDeleteButton';
|
||||||
|
import { RPLayerMenu } from 'features/regionalPrompts/components/RPLayerMenu';
|
||||||
import { RPLayerVisibilityToggle } from 'features/regionalPrompts/components/RPLayerVisibilityToggle';
|
import { RPLayerVisibilityToggle } from 'features/regionalPrompts/components/RPLayerVisibilityToggle';
|
||||||
import {
|
import {
|
||||||
isControlAdapterLayer,
|
isControlAdapterLayer,
|
||||||
@ -51,6 +52,7 @@ export const ControlAdapterLayerListItem = memo(({ layerId }: Props) => {
|
|||||||
<RPLayerVisibilityToggle layerId={layerId} />
|
<RPLayerVisibilityToggle layerId={layerId} />
|
||||||
<LayerTitle type="control_adapter_layer" />
|
<LayerTitle type="control_adapter_layer" />
|
||||||
<Spacer />
|
<Spacer />
|
||||||
|
<RPLayerMenu layerId={layerId} />
|
||||||
<RPLayerDeleteButton layerId={layerId} />
|
<RPLayerDeleteButton layerId={layerId} />
|
||||||
</Flex>
|
</Flex>
|
||||||
<ControlAdapterLayerConfig id={controlNetId} />
|
<ControlAdapterLayerConfig id={controlNetId} />
|
||||||
|
@ -4,6 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|||||||
import ControlAdapterLayerConfig from 'features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig';
|
import ControlAdapterLayerConfig from 'features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig';
|
||||||
import { LayerTitle } from 'features/regionalPrompts/components/LayerTitle';
|
import { LayerTitle } from 'features/regionalPrompts/components/LayerTitle';
|
||||||
import { RPLayerDeleteButton } from 'features/regionalPrompts/components/RPLayerDeleteButton';
|
import { RPLayerDeleteButton } from 'features/regionalPrompts/components/RPLayerDeleteButton';
|
||||||
|
import { RPLayerMenu } from 'features/regionalPrompts/components/RPLayerMenu';
|
||||||
import { RPLayerVisibilityToggle } from 'features/regionalPrompts/components/RPLayerVisibilityToggle';
|
import { RPLayerVisibilityToggle } from 'features/regionalPrompts/components/RPLayerVisibilityToggle';
|
||||||
import {
|
import {
|
||||||
isIPAdapterLayer,
|
isIPAdapterLayer,
|
||||||
@ -51,6 +52,7 @@ export const IPAdapterLayerListItem = memo(({ layerId }: Props) => {
|
|||||||
<RPLayerVisibilityToggle layerId={layerId} />
|
<RPLayerVisibilityToggle layerId={layerId} />
|
||||||
<LayerTitle type="ip_adapter_layer" />
|
<LayerTitle type="ip_adapter_layer" />
|
||||||
<Spacer />
|
<Spacer />
|
||||||
|
<RPLayerMenu layerId={layerId} />
|
||||||
<RPLayerDeleteButton layerId={layerId} />
|
<RPLayerDeleteButton layerId={layerId} />
|
||||||
</Flex>
|
</Flex>
|
||||||
<ControlAdapterLayerConfig id={ipAdapterId} />
|
<ControlAdapterLayerConfig id={ipAdapterId} />
|
||||||
|
@ -1,78 +1,19 @@
|
|||||||
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 { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { guidanceLayerIPAdapterAdded } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge';
|
import { RPLayerMenuArrangeActions } from 'features/regionalPrompts/components/RPLayerMenuArrangeActions';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { RPLayerMenuMaskedGuidanceActions } from 'features/regionalPrompts/components/RPLayerMenuMaskedGuidanceActions';
|
||||||
import {
|
import { useLayerType } from 'features/regionalPrompts/hooks/layerStateHooks';
|
||||||
isMaskedGuidanceLayer,
|
import { layerDeleted, layerReset } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||||
layerDeleted,
|
import { memo, useCallback } from 'react';
|
||||||
layerMovedBackward,
|
|
||||||
layerMovedForward,
|
|
||||||
layerMovedToBack,
|
|
||||||
layerMovedToFront,
|
|
||||||
layerReset,
|
|
||||||
maskLayerNegativePromptChanged,
|
|
||||||
maskLayerPositivePromptChanged,
|
|
||||||
selectRegionalPromptsSlice,
|
|
||||||
} from 'features/regionalPrompts/store/regionalPromptsSlice';
|
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import { PiArrowCounterClockwiseBold, PiDotsThreeVerticalBold, PiTrashSimpleBold } from 'react-icons/pi';
|
||||||
PiArrowCounterClockwiseBold,
|
|
||||||
PiArrowDownBold,
|
|
||||||
PiArrowLineDownBold,
|
|
||||||
PiArrowLineUpBold,
|
|
||||||
PiArrowUpBold,
|
|
||||||
PiDotsThreeVerticalBold,
|
|
||||||
PiPlusBold,
|
|
||||||
PiTrashSimpleBold,
|
|
||||||
} from 'react-icons/pi';
|
|
||||||
import { assert } from 'tsafe';
|
|
||||||
|
|
||||||
type Props = { layerId: string };
|
type Props = { layerId: string };
|
||||||
|
|
||||||
export const RPLayerMenu = memo(({ layerId }: Props) => {
|
export const RPLayerMenu = memo(({ layerId }: Props) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const selectValidActions = useMemo(
|
const layerType = useLayerType(layerId);
|
||||||
() =>
|
|
||||||
createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => {
|
|
||||||
const layer = regionalPrompts.present.layers.find((l) => l.id === layerId);
|
|
||||||
assert(isMaskedGuidanceLayer(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;
|
|
||||||
return {
|
|
||||||
canAddPositivePrompt: layer.positivePrompt === null,
|
|
||||||
canAddNegativePrompt: layer.negativePrompt === null,
|
|
||||||
canMoveForward: layerIndex < layerCount - 1,
|
|
||||||
canMoveBackward: layerIndex > 0,
|
|
||||||
canMoveToFront: layerIndex < layerCount - 1,
|
|
||||||
canMoveToBack: layerIndex > 0,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
[layerId]
|
|
||||||
);
|
|
||||||
const validActions = useAppSelector(selectValidActions);
|
|
||||||
const addPositivePrompt = useCallback(() => {
|
|
||||||
dispatch(maskLayerPositivePromptChanged({ layerId, prompt: '' }));
|
|
||||||
}, [dispatch, layerId]);
|
|
||||||
const addNegativePrompt = useCallback(() => {
|
|
||||||
dispatch(maskLayerNegativePromptChanged({ layerId, prompt: '' }));
|
|
||||||
}, [dispatch, layerId]);
|
|
||||||
const addIPAdapter = useCallback(() => {
|
|
||||||
dispatch(guidanceLayerIPAdapterAdded(layerId));
|
|
||||||
}, [dispatch, layerId]);
|
|
||||||
const moveForward = useCallback(() => {
|
|
||||||
dispatch(layerMovedForward(layerId));
|
|
||||||
}, [dispatch, layerId]);
|
|
||||||
const moveToFront = useCallback(() => {
|
|
||||||
dispatch(layerMovedToFront(layerId));
|
|
||||||
}, [dispatch, layerId]);
|
|
||||||
const moveBackward = useCallback(() => {
|
|
||||||
dispatch(layerMovedBackward(layerId));
|
|
||||||
}, [dispatch, layerId]);
|
|
||||||
const moveToBack = useCallback(() => {
|
|
||||||
dispatch(layerMovedToBack(layerId));
|
|
||||||
}, [dispatch, layerId]);
|
|
||||||
const resetLayer = useCallback(() => {
|
const resetLayer = useCallback(() => {
|
||||||
dispatch(layerReset(layerId));
|
dispatch(layerReset(layerId));
|
||||||
}, [dispatch, layerId]);
|
}, [dispatch, layerId]);
|
||||||
@ -83,32 +24,23 @@ export const RPLayerMenu = memo(({ layerId }: Props) => {
|
|||||||
<Menu>
|
<Menu>
|
||||||
<MenuButton as={IconButton} aria-label="Layer menu" size="sm" icon={<PiDotsThreeVerticalBold />} />
|
<MenuButton as={IconButton} aria-label="Layer menu" size="sm" icon={<PiDotsThreeVerticalBold />} />
|
||||||
<MenuList>
|
<MenuList>
|
||||||
<MenuItem onClick={addPositivePrompt} isDisabled={!validActions.canAddPositivePrompt} icon={<PiPlusBold />}>
|
{layerType === 'masked_guidance_layer' && (
|
||||||
{t('regionalPrompts.addPositivePrompt')}
|
<>
|
||||||
</MenuItem>
|
<RPLayerMenuMaskedGuidanceActions layerId={layerId} />
|
||||||
<MenuItem onClick={addNegativePrompt} isDisabled={!validActions.canAddNegativePrompt} icon={<PiPlusBold />}>
|
<MenuDivider />
|
||||||
{t('regionalPrompts.addNegativePrompt')}
|
</>
|
||||||
</MenuItem>
|
)}
|
||||||
<MenuItem onClick={addIPAdapter} icon={<PiPlusBold />}>
|
{(layerType === 'masked_guidance_layer' || layerType === 'control_adapter_layer') && (
|
||||||
{t('regionalPrompts.addIPAdapter')}
|
<>
|
||||||
</MenuItem>
|
<RPLayerMenuArrangeActions layerId={layerId} />
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<MenuItem onClick={moveToFront} isDisabled={!validActions.canMoveToFront} icon={<PiArrowLineUpBold />}>
|
</>
|
||||||
{t('regionalPrompts.moveToFront')}
|
)}
|
||||||
</MenuItem>
|
{layerType === 'masked_guidance_layer' && (
|
||||||
<MenuItem onClick={moveForward} isDisabled={!validActions.canMoveForward} icon={<PiArrowUpBold />}>
|
<MenuItem onClick={resetLayer} icon={<PiArrowCounterClockwiseBold />}>
|
||||||
{t('regionalPrompts.moveForward')}
|
{t('accessibility.reset')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem onClick={moveBackward} isDisabled={!validActions.canMoveBackward} icon={<PiArrowDownBold />}>
|
)}
|
||||||
{t('regionalPrompts.moveBackward')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem onClick={moveToBack} isDisabled={!validActions.canMoveToBack} icon={<PiArrowLineDownBold />}>
|
|
||||||
{t('regionalPrompts.moveToBack')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuDivider />
|
|
||||||
<MenuItem onClick={resetLayer} icon={<PiArrowCounterClockwiseBold />}>
|
|
||||||
{t('accessibility.reset')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem onClick={deleteLayer} icon={<PiTrashSimpleBold />} color="error.300">
|
<MenuItem onClick={deleteLayer} icon={<PiTrashSimpleBold />} color="error.300">
|
||||||
{t('common.delete')}
|
{t('common.delete')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
@ -0,0 +1,74 @@
|
|||||||
|
import { MenuItem } from '@invoke-ai/ui-library';
|
||||||
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import {
|
||||||
|
isRenderableLayer,
|
||||||
|
layerMovedBackward,
|
||||||
|
layerMovedForward,
|
||||||
|
layerMovedToBack,
|
||||||
|
layerMovedToFront,
|
||||||
|
selectRegionalPromptsSlice,
|
||||||
|
} from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||||
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import {
|
||||||
|
PiArrowDownBold,
|
||||||
|
PiArrowLineDownBold,
|
||||||
|
PiArrowLineUpBold,
|
||||||
|
PiArrowUpBold,
|
||||||
|
} from 'react-icons/pi';
|
||||||
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
|
type Props = { layerId: string };
|
||||||
|
|
||||||
|
export const RPLayerMenuArrangeActions = memo(({ layerId }: Props) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const selectValidActions = useMemo(
|
||||||
|
() =>
|
||||||
|
createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => {
|
||||||
|
const layer = regionalPrompts.present.layers.find((l) => l.id === layerId);
|
||||||
|
assert(isRenderableLayer(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;
|
||||||
|
return {
|
||||||
|
canMoveForward: layerIndex < layerCount - 1,
|
||||||
|
canMoveBackward: layerIndex > 0,
|
||||||
|
canMoveToFront: layerIndex < layerCount - 1,
|
||||||
|
canMoveToBack: layerIndex > 0,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
[layerId]
|
||||||
|
);
|
||||||
|
const validActions = useAppSelector(selectValidActions);
|
||||||
|
const moveForward = useCallback(() => {
|
||||||
|
dispatch(layerMovedForward(layerId));
|
||||||
|
}, [dispatch, layerId]);
|
||||||
|
const moveToFront = useCallback(() => {
|
||||||
|
dispatch(layerMovedToFront(layerId));
|
||||||
|
}, [dispatch, layerId]);
|
||||||
|
const moveBackward = useCallback(() => {
|
||||||
|
dispatch(layerMovedBackward(layerId));
|
||||||
|
}, [dispatch, layerId]);
|
||||||
|
const moveToBack = useCallback(() => {
|
||||||
|
dispatch(layerMovedToBack(layerId));
|
||||||
|
}, [dispatch, layerId]);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuItem onClick={moveToFront} isDisabled={!validActions.canMoveToFront} icon={<PiArrowLineUpBold />}>
|
||||||
|
{t('regionalPrompts.moveToFront')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={moveForward} isDisabled={!validActions.canMoveForward} icon={<PiArrowUpBold />}>
|
||||||
|
{t('regionalPrompts.moveForward')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={moveBackward} isDisabled={!validActions.canMoveBackward} icon={<PiArrowDownBold />}>
|
||||||
|
{t('regionalPrompts.moveBackward')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={moveToBack} isDisabled={!validActions.canMoveToBack} icon={<PiArrowLineDownBold />}>
|
||||||
|
{t('regionalPrompts.moveToBack')}
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
RPLayerMenuArrangeActions.displayName = 'RPLayerMenuArrangeActions';
|
@ -0,0 +1,58 @@
|
|||||||
|
import { MenuItem } from '@invoke-ai/ui-library';
|
||||||
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
|
import { guidanceLayerIPAdapterAdded } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import {
|
||||||
|
isMaskedGuidanceLayer,
|
||||||
|
maskLayerNegativePromptChanged,
|
||||||
|
maskLayerPositivePromptChanged,
|
||||||
|
selectRegionalPromptsSlice,
|
||||||
|
} from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||||
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PiPlusBold } from 'react-icons/pi';
|
||||||
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
|
type Props = { layerId: string };
|
||||||
|
|
||||||
|
export const RPLayerMenuMaskedGuidanceActions = memo(({ layerId }: Props) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const selectValidActions = useMemo(
|
||||||
|
() =>
|
||||||
|
createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => {
|
||||||
|
const layer = regionalPrompts.present.layers.find((l) => l.id === layerId);
|
||||||
|
assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`);
|
||||||
|
return {
|
||||||
|
canAddPositivePrompt: layer.positivePrompt === null,
|
||||||
|
canAddNegativePrompt: layer.negativePrompt === null,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
[layerId]
|
||||||
|
);
|
||||||
|
const validActions = useAppSelector(selectValidActions);
|
||||||
|
const addPositivePrompt = useCallback(() => {
|
||||||
|
dispatch(maskLayerPositivePromptChanged({ layerId, prompt: '' }));
|
||||||
|
}, [dispatch, layerId]);
|
||||||
|
const addNegativePrompt = useCallback(() => {
|
||||||
|
dispatch(maskLayerNegativePromptChanged({ layerId, prompt: '' }));
|
||||||
|
}, [dispatch, layerId]);
|
||||||
|
const addIPAdapter = useCallback(() => {
|
||||||
|
dispatch(guidanceLayerIPAdapterAdded(layerId));
|
||||||
|
}, [dispatch, layerId]);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuItem onClick={addPositivePrompt} isDisabled={!validActions.canAddPositivePrompt} icon={<PiPlusBold />}>
|
||||||
|
{t('regionalPrompts.addPositivePrompt')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={addNegativePrompt} isDisabled={!validActions.canAddNegativePrompt} icon={<PiPlusBold />}>
|
||||||
|
{t('regionalPrompts.addNegativePrompt')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={addIPAdapter} icon={<PiPlusBold />}>
|
||||||
|
{t('regionalPrompts.addIPAdapter')}
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
RPLayerMenuMaskedGuidanceActions.displayName = 'RPLayerMenuMaskedGuidanceActions';
|
Loading…
Reference in New Issue
Block a user