mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
tidy(ui): massive cleanup
- create a context for entity identifiers, massively simplifying UI for each entity int he list - consolidate common redux actions - remove now-unused code
This commit is contained in:
parent
8436a44973
commit
894b8a29b9
@ -4,8 +4,8 @@ import type { AppDispatch, RootState } from 'app/store/store';
|
|||||||
import {
|
import {
|
||||||
caImageChanged,
|
caImageChanged,
|
||||||
caProcessedImageChanged,
|
caProcessedImageChanged,
|
||||||
|
entityDeleted,
|
||||||
ipaImageChanged,
|
ipaImageChanged,
|
||||||
layerDeleted,
|
|
||||||
} from 'features/controlLayers/store/canvasV2Slice';
|
} from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
|
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
|
||||||
import { isModalOpenChanged } from 'features/deleteImageModal/store/slice';
|
import { isModalOpenChanged } from 'features/deleteImageModal/store/slice';
|
||||||
@ -66,7 +66,7 @@ const deleteLayerImages = (state: RootState, dispatch: AppDispatch, imageDTO: Im
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (shouldDelete) {
|
if (shouldDelete) {
|
||||||
dispatch(layerDeleted({ id }));
|
dispatch(entityDeleted({ entityIdentifier: { id, type: 'layer' } }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { caIsEnabledToggled, loraDeleted, modelChanged, vaeSelected } from 'features/controlLayers/store/canvasV2Slice';
|
import {
|
||||||
|
entityIsEnabledToggled,
|
||||||
|
loraDeleted,
|
||||||
|
modelChanged,
|
||||||
|
vaeSelected,
|
||||||
|
} from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { modelSelected } from 'features/parameters/store/actions';
|
import { modelSelected } from 'features/parameters/store/actions';
|
||||||
import { zParameterModel } from 'features/parameters/types/parameterSchemas';
|
import { zParameterModel } from 'features/parameters/types/parameterSchemas';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
@ -49,7 +54,7 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) =
|
|||||||
if (ca.model?.base !== newBaseModel) {
|
if (ca.model?.base !== newBaseModel) {
|
||||||
modelsCleared += 1;
|
modelsCleared += 1;
|
||||||
if (ca.isEnabled) {
|
if (ca.isEnabled) {
|
||||||
dispatch(caIsEnabledToggled({ id: ca.id }));
|
dispatch(entityIsEnabledToggled({ entityIdentifier: { id: ca.id, type: 'control_adapter' } }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,28 +1,26 @@
|
|||||||
import { useDisclosure } from '@invoke-ai/ui-library';
|
import { useDisclosure } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
||||||
import { CAHeader } from 'features/controlLayers/components/ControlAdapter/CAEntityHeader';
|
import { CAHeader } from 'features/controlLayers/components/ControlAdapter/CAEntityHeader';
|
||||||
import { CASettings } from 'features/controlLayers/components/ControlAdapter/CASettings';
|
import { CASettings } from 'features/controlLayers/components/ControlAdapter/CASettings';
|
||||||
import { entitySelected } from 'features/controlLayers/store/canvasV2Slice';
|
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import { memo, useCallback } from 'react';
|
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
|
import { memo, useMemo } from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CA = memo(({ id }: Props) => {
|
export const CA = memo(({ id }: Props) => {
|
||||||
const dispatch = useAppDispatch();
|
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'control_adapter' }), [id]);
|
||||||
const isSelected = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier?.id === id);
|
|
||||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||||
const onSelect = useCallback(() => {
|
|
||||||
dispatch(entitySelected({ id, type: 'control_adapter' }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CanvasEntityContainer isSelected={isSelected} onSelect={onSelect}>
|
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
||||||
<CAHeader id={id} isSelected={isSelected} onToggleVisibility={onToggle} />
|
<CanvasEntityContainer>
|
||||||
|
<CAHeader onToggleVisibility={onToggle} />
|
||||||
{isOpen && <CASettings id={id} />}
|
{isOpen && <CASettings id={id} />}
|
||||||
</CanvasEntityContainer>
|
</CanvasEntityContainer>
|
||||||
|
</EntityIdentifierContext.Provider>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,84 +1,14 @@
|
|||||||
import { Menu, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
import { Menu, MenuList } from '@invoke-ai/ui-library';
|
||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { CanvasEntityActionMenuItems } from 'features/controlLayers/components/common/CanvasEntityActionMenuItems';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { CanvasEntityMenuButton } from 'features/controlLayers/components/common/CanvasEntityMenuButton';
|
import { CanvasEntityMenuButton } from 'features/controlLayers/components/common/CanvasEntityMenuButton';
|
||||||
import {
|
import { memo } from 'react';
|
||||||
caDeleted,
|
|
||||||
caMovedBackwardOne,
|
|
||||||
caMovedForwardOne,
|
|
||||||
caMovedToBack,
|
|
||||||
caMovedToFront,
|
|
||||||
selectCanvasV2Slice,
|
|
||||||
} from 'features/controlLayers/store/canvasV2Slice';
|
|
||||||
import { selectCAOrThrow } from 'features/controlLayers/store/controlAdaptersReducers';
|
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import {
|
|
||||||
PiArrowDownBold,
|
|
||||||
PiArrowLineDownBold,
|
|
||||||
PiArrowLineUpBold,
|
|
||||||
PiArrowUpBold,
|
|
||||||
PiTrashSimpleBold,
|
|
||||||
} from 'react-icons/pi';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CAActionsMenu = memo(({ id }: Props) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const selectValidActions = useMemo(
|
|
||||||
() =>
|
|
||||||
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
|
||||||
const ca = selectCAOrThrow(canvasV2, id);
|
|
||||||
const caIndex = canvasV2.controlAdapters.entities.indexOf(ca);
|
|
||||||
const caCount = canvasV2.controlAdapters.entities.length;
|
|
||||||
return {
|
|
||||||
canMoveForward: caIndex < caCount - 1,
|
|
||||||
canMoveBackward: caIndex > 0,
|
|
||||||
canMoveToFront: caIndex < caCount - 1,
|
|
||||||
canMoveToBack: caIndex > 0,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
[id]
|
|
||||||
);
|
|
||||||
const validActions = useAppSelector(selectValidActions);
|
|
||||||
const onDelete = useCallback(() => {
|
|
||||||
dispatch(caDeleted({ id }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
const moveForwardOne = useCallback(() => {
|
|
||||||
dispatch(caMovedForwardOne({ id }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
const moveToFront = useCallback(() => {
|
|
||||||
dispatch(caMovedToFront({ id }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
const moveBackwardOne = useCallback(() => {
|
|
||||||
dispatch(caMovedBackwardOne({ id }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
const moveToBack = useCallback(() => {
|
|
||||||
dispatch(caMovedToBack({ id }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
|
|
||||||
|
export const CAActionsMenu = memo(() => {
|
||||||
return (
|
return (
|
||||||
<Menu>
|
<Menu>
|
||||||
<CanvasEntityMenuButton />
|
<CanvasEntityMenuButton />
|
||||||
<MenuList>
|
<MenuList>
|
||||||
<MenuItem onClick={moveToFront} isDisabled={!validActions.canMoveToFront} icon={<PiArrowLineUpBold />}>
|
<CanvasEntityActionMenuItems />
|
||||||
{t('controlLayers.moveToFront')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem onClick={moveForwardOne} isDisabled={!validActions.canMoveForward} icon={<PiArrowUpBold />}>
|
|
||||||
{t('controlLayers.moveForward')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem onClick={moveBackwardOne} isDisabled={!validActions.canMoveBackward} icon={<PiArrowDownBold />}>
|
|
||||||
{t('controlLayers.moveBackward')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem onClick={moveToBack} isDisabled={!validActions.canMoveToBack} icon={<PiArrowLineDownBold />}>
|
|
||||||
{t('controlLayers.moveToBack')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem onClick={onDelete} icon={<PiTrashSimpleBold />} color="error.300">
|
|
||||||
{t('common.delete')}
|
|
||||||
</MenuItem>
|
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
@ -1,41 +1,25 @@
|
|||||||
import { Spacer } from '@invoke-ai/ui-library';
|
import { Spacer } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
||||||
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
||||||
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
||||||
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
||||||
import { CAActionsMenu } from 'features/controlLayers/components/ControlAdapter/CAActionsMenu';
|
import { CAActionsMenu } from 'features/controlLayers/components/ControlAdapter/CAActionsMenu';
|
||||||
import { CAOpacityAndFilter } from 'features/controlLayers/components/ControlAdapter/CAOpacityAndFilter';
|
import { CAOpacityAndFilter } from 'features/controlLayers/components/ControlAdapter/CAOpacityAndFilter';
|
||||||
import { caDeleted, caIsEnabledToggled } from 'features/controlLayers/store/canvasV2Slice';
|
import { memo } from 'react';
|
||||||
import { selectCAOrThrow } from 'features/controlLayers/store/controlAdaptersReducers';
|
|
||||||
import { memo, useCallback } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
id: string;
|
|
||||||
isSelected: boolean;
|
|
||||||
onToggleVisibility: () => void;
|
onToggleVisibility: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CAHeader = memo(({ id, isSelected, onToggleVisibility }: Props) => {
|
export const CAHeader = memo(({ onToggleVisibility }: Props) => {
|
||||||
const { t } = useTranslation();
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const isEnabled = useAppSelector((s) => selectCAOrThrow(s.canvasV2, id).isEnabled);
|
|
||||||
const onToggleIsEnabled = useCallback(() => {
|
|
||||||
dispatch(caIsEnabledToggled({ id }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
const onDelete = useCallback(() => {
|
|
||||||
dispatch(caDeleted({ id }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CanvasEntityHeader onToggle={onToggleVisibility}>
|
<CanvasEntityHeader onToggle={onToggleVisibility}>
|
||||||
<CanvasEntityEnabledToggle isEnabled={isEnabled} onToggle={onToggleIsEnabled} />
|
<CanvasEntityEnabledToggle />
|
||||||
<CanvasEntityTitle title={t('controlLayers.globalControlAdapter')} isSelected={isSelected} />
|
<CanvasEntityTitle />
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<CAOpacityAndFilter id={id} />
|
<CAOpacityAndFilter />
|
||||||
<CAActionsMenu id={id} />
|
<CAActionsMenu />
|
||||||
<CanvasEntityDeleteButton onDelete={onDelete} />
|
<CanvasEntityDeleteButton />
|
||||||
</CanvasEntityHeader>
|
</CanvasEntityHeader>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
} from '@invoke-ai/ui-library';
|
} from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { stopPropagation } from 'common/util/stopPropagation';
|
import { stopPropagation } from 'common/util/stopPropagation';
|
||||||
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import { caFilterChanged, caOpacityChanged } from 'features/controlLayers/store/canvasV2Slice';
|
import { caFilterChanged, caOpacityChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { selectCAOrThrow } from 'features/controlLayers/store/controlAdaptersReducers';
|
import { selectCAOrThrow } from 'features/controlLayers/store/controlAdaptersReducers';
|
||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
@ -21,14 +22,11 @@ import { memo, useCallback } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiDropHalfFill } from 'react-icons/pi';
|
import { PiDropHalfFill } from 'react-icons/pi';
|
||||||
|
|
||||||
type Props = {
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const marks = [0, 25, 50, 75, 100];
|
const marks = [0, 25, 50, 75, 100];
|
||||||
const formatPct = (v: number | string) => `${v} %`;
|
const formatPct = (v: number | string) => `${v} %`;
|
||||||
|
|
||||||
export const CAOpacityAndFilter = memo(({ id }: Props) => {
|
export const CAOpacityAndFilter = memo(() => {
|
||||||
|
const { id } = useEntityIdentifierContext();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const opacity = useAppSelector((s) => Math.round(selectCAOrThrow(s.canvasV2, id).opacity * 100));
|
const opacity = useAppSelector((s) => Math.round(selectCAOrThrow(s.canvasV2, id).opacity * 100));
|
||||||
|
@ -15,7 +15,7 @@ export const IPA = memo(({ id }: Props) => {
|
|||||||
const isSelected = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier?.id === id);
|
const isSelected = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier?.id === id);
|
||||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||||
const onSelect = useCallback(() => {
|
const onSelect = useCallback(() => {
|
||||||
dispatch(entitySelected({ id, type: 'ip_adapter' }));
|
dispatch(entitySelected({ entityIdentifier: { id, type: 'ip_adapter' } }));
|
||||||
}, [dispatch, id]);
|
}, [dispatch, id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,25 +1,21 @@
|
|||||||
import { useDisclosure } from '@invoke-ai/ui-library';
|
import { useDisclosure } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
|
||||||
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
||||||
import { IMHeader } from 'features/controlLayers/components/InpaintMask/IMHeader';
|
import { IMHeader } from 'features/controlLayers/components/InpaintMask/IMHeader';
|
||||||
import { IMSettings } from 'features/controlLayers/components/InpaintMask/IMSettings';
|
import { IMSettings } from 'features/controlLayers/components/InpaintMask/IMSettings';
|
||||||
import { entitySelected } from 'features/controlLayers/store/canvasV2Slice';
|
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import { memo, useCallback } from 'react';
|
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
|
import { memo, useMemo } from 'react';
|
||||||
|
|
||||||
export const IM = memo(() => {
|
export const IM = memo(() => {
|
||||||
const dispatch = useAppDispatch();
|
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id: 'inpaint_mask', type: 'inpaint_mask' }), []);
|
||||||
const selectedBorderColor = useAppSelector((s) => rgbColorToString(s.canvasV2.inpaintMask.fill));
|
|
||||||
const isSelected = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier?.id === 'inpaint_mask');
|
|
||||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: false });
|
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: false });
|
||||||
const onSelect = useCallback(() => {
|
|
||||||
dispatch(entitySelected({ id: 'inpaint_mask', type: 'inpaint_mask' }));
|
|
||||||
}, [dispatch]);
|
|
||||||
return (
|
return (
|
||||||
<CanvasEntityContainer isSelected={isSelected} onSelect={onSelect} selectedBorderColor={selectedBorderColor}>
|
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
||||||
<IMHeader isSelected={isSelected} onToggleVisibility={onToggle} />
|
<CanvasEntityContainer>
|
||||||
|
<IMHeader onToggleVisibility={onToggle} />
|
||||||
{isOpen && <IMSettings />}
|
{isOpen && <IMSettings />}
|
||||||
</CanvasEntityContainer>
|
</CanvasEntityContainer>
|
||||||
|
</EntityIdentifierContext.Provider>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,25 +1,14 @@
|
|||||||
import { Menu, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
import { Menu, MenuList } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { CanvasEntityActionMenuItems } from 'features/controlLayers/components/common/CanvasEntityActionMenuItems';
|
||||||
import { CanvasEntityMenuButton } from 'features/controlLayers/components/common/CanvasEntityMenuButton';
|
import { CanvasEntityMenuButton } from 'features/controlLayers/components/common/CanvasEntityMenuButton';
|
||||||
import { imReset } from 'features/controlLayers/store/canvasV2Slice';
|
import { memo } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { PiArrowCounterClockwiseBold } from 'react-icons/pi';
|
|
||||||
|
|
||||||
export const IMActionsMenu = memo(() => {
|
export const IMActionsMenu = memo(() => {
|
||||||
const { t } = useTranslation();
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const onReset = useCallback(() => {
|
|
||||||
dispatch(imReset());
|
|
||||||
}, [dispatch]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu>
|
<Menu>
|
||||||
<CanvasEntityMenuButton />
|
<CanvasEntityMenuButton />
|
||||||
<MenuList>
|
<MenuList>
|
||||||
<MenuItem onClick={onReset} icon={<PiArrowCounterClockwiseBold />}>
|
<CanvasEntityActionMenuItems />
|
||||||
{t('accessibility.reset')}
|
|
||||||
</MenuItem>
|
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
@ -1,32 +1,21 @@
|
|||||||
import { Spacer } from '@invoke-ai/ui-library';
|
import { Spacer } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
||||||
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
||||||
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
||||||
import { IMActionsMenu } from 'features/controlLayers/components/InpaintMask/IMActionsMenu';
|
import { IMActionsMenu } from 'features/controlLayers/components/InpaintMask/IMActionsMenu';
|
||||||
import { imIsEnabledToggled } from 'features/controlLayers/store/canvasV2Slice';
|
import { memo } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import { IMMaskFillColorPicker } from './IMMaskFillColorPicker';
|
import { IMMaskFillColorPicker } from './IMMaskFillColorPicker';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isSelected: boolean;
|
|
||||||
onToggleVisibility: () => void;
|
onToggleVisibility: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IMHeader = memo(({ isSelected, onToggleVisibility }: Props) => {
|
export const IMHeader = memo(({ onToggleVisibility }: Props) => {
|
||||||
const { t } = useTranslation();
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const isEnabled = useAppSelector((s) => s.canvasV2.inpaintMask.isEnabled);
|
|
||||||
const onToggleIsEnabled = useCallback(() => {
|
|
||||||
dispatch(imIsEnabledToggled());
|
|
||||||
}, [dispatch]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CanvasEntityHeader onToggle={onToggleVisibility}>
|
<CanvasEntityHeader onToggle={onToggleVisibility}>
|
||||||
<CanvasEntityEnabledToggle isEnabled={isEnabled} onToggle={onToggleIsEnabled} />
|
<CanvasEntityEnabledToggle />
|
||||||
<CanvasEntityTitle title={t('controlLayers.inpaintMask')} isSelected={isSelected} />
|
<CanvasEntityTitle />
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<IMMaskFillColorPicker />
|
<IMMaskFillColorPicker />
|
||||||
<IMActionsMenu />
|
<IMActionsMenu />
|
||||||
|
@ -1,35 +1,33 @@
|
|||||||
import { useDisclosure } from '@invoke-ai/ui-library';
|
import { useDisclosure } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import IAIDroppable from 'common/components/IAIDroppable';
|
import IAIDroppable from 'common/components/IAIDroppable';
|
||||||
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
||||||
import { LayerHeader } from 'features/controlLayers/components/Layer/LayerHeader';
|
import { LayerHeader } from 'features/controlLayers/components/Layer/LayerHeader';
|
||||||
import { LayerSettings } from 'features/controlLayers/components/Layer/LayerSettings';
|
import { LayerSettings } from 'features/controlLayers/components/Layer/LayerSettings';
|
||||||
import { entitySelected } from 'features/controlLayers/store/canvasV2Slice';
|
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
|
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
import type { LayerImageDropData } from 'features/dnd/types';
|
import type { LayerImageDropData } from 'features/dnd/types';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Layer = memo(({ id }: Props) => {
|
export const Layer = memo(({ id }: Props) => {
|
||||||
const dispatch = useAppDispatch();
|
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'layer' }), [id]);
|
||||||
const isSelected = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier?.id === id);
|
|
||||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: false });
|
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: false });
|
||||||
const onSelect = useCallback(() => {
|
|
||||||
dispatch(entitySelected({ id, type: 'layer' }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
const droppableData = useMemo<LayerImageDropData>(
|
const droppableData = useMemo<LayerImageDropData>(
|
||||||
() => ({ id, actionType: 'ADD_LAYER_IMAGE', context: { id } }),
|
() => ({ id, actionType: 'ADD_LAYER_IMAGE', context: { id } }),
|
||||||
[id]
|
[id]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CanvasEntityContainer isSelected={isSelected} onSelect={onSelect}>
|
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
||||||
<LayerHeader id={id} onToggleVisibility={onToggle} isSelected={isSelected} />
|
<CanvasEntityContainer>
|
||||||
{isOpen && <LayerSettings id={id} />}
|
<LayerHeader onToggleVisibility={onToggle} />
|
||||||
|
{isOpen && <LayerSettings />}
|
||||||
<IAIDroppable data={droppableData} />
|
<IAIDroppable data={droppableData} />
|
||||||
</CanvasEntityContainer>
|
</CanvasEntityContainer>
|
||||||
|
</EntityIdentifierContext.Provider>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,84 +1,14 @@
|
|||||||
import { Menu, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
import { Menu, MenuList } from '@invoke-ai/ui-library';
|
||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { CanvasEntityActionMenuItems } from 'features/controlLayers/components/common/CanvasEntityActionMenuItems';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { CanvasEntityMenuButton } from 'features/controlLayers/components/common/CanvasEntityMenuButton';
|
import { CanvasEntityMenuButton } from 'features/controlLayers/components/common/CanvasEntityMenuButton';
|
||||||
import {
|
import { memo } from 'react';
|
||||||
layerDeleted,
|
|
||||||
layerMovedBackwardOne,
|
|
||||||
layerMovedForwardOne,
|
|
||||||
layerMovedToBack,
|
|
||||||
layerMovedToFront,
|
|
||||||
selectCanvasV2Slice,
|
|
||||||
} from 'features/controlLayers/store/canvasV2Slice';
|
|
||||||
import { selectLayerOrThrow } from 'features/controlLayers/store/layersReducers';
|
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import {
|
|
||||||
PiArrowDownBold,
|
|
||||||
PiArrowLineDownBold,
|
|
||||||
PiArrowLineUpBold,
|
|
||||||
PiArrowUpBold,
|
|
||||||
PiTrashSimpleBold,
|
|
||||||
} from 'react-icons/pi';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LayerActionsMenu = memo(({ id }: Props) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const selectValidActions = useMemo(
|
|
||||||
() =>
|
|
||||||
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
|
||||||
const layer = selectLayerOrThrow(canvasV2, id);
|
|
||||||
const layerIndex = canvasV2.layers.entities.indexOf(layer);
|
|
||||||
const layerCount = canvasV2.layers.entities.length;
|
|
||||||
return {
|
|
||||||
canMoveForward: layerIndex < layerCount - 1,
|
|
||||||
canMoveBackward: layerIndex > 0,
|
|
||||||
canMoveToFront: layerIndex < layerCount - 1,
|
|
||||||
canMoveToBack: layerIndex > 0,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
[id]
|
|
||||||
);
|
|
||||||
const validActions = useAppSelector(selectValidActions);
|
|
||||||
const onDelete = useCallback(() => {
|
|
||||||
dispatch(layerDeleted({ id }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
const moveForwardOne = useCallback(() => {
|
|
||||||
dispatch(layerMovedForwardOne({ id }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
const moveToFront = useCallback(() => {
|
|
||||||
dispatch(layerMovedToFront({ id }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
const moveBackwardOne = useCallback(() => {
|
|
||||||
dispatch(layerMovedBackwardOne({ id }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
const moveToBack = useCallback(() => {
|
|
||||||
dispatch(layerMovedToBack({ id }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
|
|
||||||
|
export const LayerActionsMenu = memo(() => {
|
||||||
return (
|
return (
|
||||||
<Menu>
|
<Menu>
|
||||||
<CanvasEntityMenuButton />
|
<CanvasEntityMenuButton />
|
||||||
<MenuList>
|
<MenuList>
|
||||||
<MenuItem onClick={moveToFront} isDisabled={!validActions.canMoveToFront} icon={<PiArrowLineUpBold />}>
|
<CanvasEntityActionMenuItems />
|
||||||
{t('controlLayers.moveToFront')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem onClick={moveForwardOne} isDisabled={!validActions.canMoveForward} icon={<PiArrowUpBold />}>
|
|
||||||
{t('controlLayers.moveForward')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem onClick={moveBackwardOne} isDisabled={!validActions.canMoveBackward} icon={<PiArrowDownBold />}>
|
|
||||||
{t('controlLayers.moveBackward')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem onClick={moveToBack} isDisabled={!validActions.canMoveToBack} icon={<PiArrowLineDownBold />}>
|
|
||||||
{t('controlLayers.moveToBack')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem onClick={onDelete} icon={<PiTrashSimpleBold />} color="error.300">
|
|
||||||
{t('common.delete')}
|
|
||||||
</MenuItem>
|
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
@ -1,46 +1,26 @@
|
|||||||
import { Spacer } from '@invoke-ai/ui-library';
|
import { Spacer } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
||||||
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
||||||
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
||||||
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
||||||
import { LayerActionsMenu } from 'features/controlLayers/components/Layer/LayerActionsMenu';
|
import { LayerActionsMenu } from 'features/controlLayers/components/Layer/LayerActionsMenu';
|
||||||
import { layerDeleted, layerIsEnabledToggled } from 'features/controlLayers/store/canvasV2Slice';
|
import { memo } from 'react';
|
||||||
import { selectLayerOrThrow } from 'features/controlLayers/store/layersReducers';
|
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import { LayerOpacity } from './LayerOpacity';
|
import { LayerOpacity } from './LayerOpacity';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
id: string;
|
|
||||||
isSelected: boolean;
|
|
||||||
onToggleVisibility: () => void;
|
onToggleVisibility: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LayerHeader = memo(({ id, isSelected, onToggleVisibility }: Props) => {
|
export const LayerHeader = memo(({ onToggleVisibility }: Props) => {
|
||||||
const { t } = useTranslation();
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const isEnabled = useAppSelector((s) => selectLayerOrThrow(s.canvasV2, id).isEnabled);
|
|
||||||
const objectCount = useAppSelector((s) => selectLayerOrThrow(s.canvasV2, id).objects.length);
|
|
||||||
const onToggleIsEnabled = useCallback(() => {
|
|
||||||
dispatch(layerIsEnabledToggled({ id }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
const onDelete = useCallback(() => {
|
|
||||||
dispatch(layerDeleted({ id }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
const title = useMemo(() => {
|
|
||||||
return `${t('controlLayers.layer')} (${t('controlLayers.objects', { count: objectCount })})`;
|
|
||||||
}, [objectCount, t]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CanvasEntityHeader onToggle={onToggleVisibility}>
|
<CanvasEntityHeader onToggle={onToggleVisibility}>
|
||||||
<CanvasEntityEnabledToggle isEnabled={isEnabled} onToggle={onToggleIsEnabled} />
|
<CanvasEntityEnabledToggle />
|
||||||
<CanvasEntityTitle title={title} isSelected={isSelected} />
|
<CanvasEntityTitle />
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<LayerOpacity id={id} />
|
<LayerOpacity />
|
||||||
<LayerActionsMenu id={id} />
|
<LayerActionsMenu />
|
||||||
<CanvasEntityDeleteButton onDelete={onDelete} />
|
<CanvasEntityDeleteButton />
|
||||||
</CanvasEntityHeader>
|
</CanvasEntityHeader>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -13,28 +13,26 @@ import {
|
|||||||
} from '@invoke-ai/ui-library';
|
} from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { stopPropagation } from 'common/util/stopPropagation';
|
import { stopPropagation } from 'common/util/stopPropagation';
|
||||||
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import { layerOpacityChanged } from 'features/controlLayers/store/canvasV2Slice';
|
import { layerOpacityChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { selectLayerOrThrow } from 'features/controlLayers/store/layersReducers';
|
import { selectLayerOrThrow } from 'features/controlLayers/store/layersReducers';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiDropHalfFill } from 'react-icons/pi';
|
import { PiDropHalfFill } from 'react-icons/pi';
|
||||||
|
|
||||||
type Props = {
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const marks = [0, 25, 50, 75, 100];
|
const marks = [0, 25, 50, 75, 100];
|
||||||
const formatPct = (v: number | string) => `${v} %`;
|
const formatPct = (v: number | string) => `${v} %`;
|
||||||
|
|
||||||
export const LayerOpacity = memo(({ id }: Props) => {
|
export const LayerOpacity = memo(() => {
|
||||||
|
const entityIdentifier = useEntityIdentifierContext();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const opacity = useAppSelector((s) => Math.round(selectLayerOrThrow(s.canvasV2, id).opacity * 100));
|
const opacity = useAppSelector((s) => Math.round(selectLayerOrThrow(s.canvasV2, entityIdentifier.id).opacity * 100));
|
||||||
const onChangeOpacity = useCallback(
|
const onChangeOpacity = useCallback(
|
||||||
(v: number) => {
|
(v: number) => {
|
||||||
dispatch(layerOpacityChanged({ id, opacity: v / 100 }));
|
dispatch(layerOpacityChanged({ id: entityIdentifier.id, opacity: v / 100 }));
|
||||||
},
|
},
|
||||||
[dispatch, id]
|
[dispatch, entityIdentifier.id]
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<Popover isLazy>
|
<Popover isLazy>
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import { CanvasEntitySettings } from 'features/controlLayers/components/common/CanvasEntitySettings';
|
import { CanvasEntitySettings } from 'features/controlLayers/components/common/CanvasEntitySettings';
|
||||||
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
type Props = {
|
export const LayerSettings = memo(() => {
|
||||||
id: string;
|
const entityIdentifier = useEntityIdentifierContext();
|
||||||
};
|
|
||||||
|
|
||||||
export const LayerSettings = memo(({ id }: Props) => {
|
|
||||||
return <CanvasEntitySettings>PLACEHOLDER</CanvasEntitySettings>;
|
return <CanvasEntitySettings>PLACEHOLDER</CanvasEntitySettings>;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,30 +1,25 @@
|
|||||||
import { useDisclosure } from '@invoke-ai/ui-library';
|
import { useDisclosure } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
|
||||||
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
||||||
import { RGHeader } from 'features/controlLayers/components/RegionalGuidance/RGHeader';
|
import { RGHeader } from 'features/controlLayers/components/RegionalGuidance/RGHeader';
|
||||||
import { RGSettings } from 'features/controlLayers/components/RegionalGuidance/RGSettings';
|
import { RGSettings } from 'features/controlLayers/components/RegionalGuidance/RGSettings';
|
||||||
import { entitySelected } from 'features/controlLayers/store/canvasV2Slice';
|
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RG = memo(({ id }: Props) => {
|
export const RG = memo(({ id }: Props) => {
|
||||||
const dispatch = useAppDispatch();
|
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'regional_guidance' }), [id]);
|
||||||
const selectedBorderColor = useAppSelector((s) => rgbColorToString(selectRGOrThrow(s.canvasV2, id).fill));
|
|
||||||
const isSelected = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier?.id === id);
|
|
||||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||||
const onSelect = useCallback(() => {
|
|
||||||
dispatch(entitySelected({ id, type: 'regional_guidance' }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
return (
|
return (
|
||||||
<CanvasEntityContainer isSelected={isSelected} onSelect={onSelect} selectedBorderColor={selectedBorderColor}>
|
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
||||||
<RGHeader id={id} isSelected={isSelected} onToggleVisibility={onToggle} />
|
<CanvasEntityContainer>
|
||||||
{isOpen && <RGSettings id={id} />}
|
<RGHeader onToggleVisibility={onToggle} />
|
||||||
|
{isOpen && <RGSettings />}
|
||||||
</CanvasEntityContainer>
|
</CanvasEntityContainer>
|
||||||
|
</EntityIdentifierContext.Provider>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,37 +1,22 @@
|
|||||||
import { Menu, MenuDivider, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
import { Menu, MenuDivider, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
||||||
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 { CanvasEntityActionMenuItems } from 'features/controlLayers/components/common/CanvasEntityActionMenuItems';
|
||||||
import { CanvasEntityMenuButton } from 'features/controlLayers/components/common/CanvasEntityMenuButton';
|
import { CanvasEntityMenuButton } from 'features/controlLayers/components/common/CanvasEntityMenuButton';
|
||||||
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import { useAddIPAdapterToRGLayer } from 'features/controlLayers/hooks/addLayerHooks';
|
import { useAddIPAdapterToRGLayer } from 'features/controlLayers/hooks/addLayerHooks';
|
||||||
import {
|
import {
|
||||||
rgDeleted,
|
|
||||||
rgMovedBackwardOne,
|
|
||||||
rgMovedForwardOne,
|
|
||||||
rgMovedToBack,
|
|
||||||
rgMovedToFront,
|
|
||||||
rgNegativePromptChanged,
|
rgNegativePromptChanged,
|
||||||
rgPositivePromptChanged,
|
rgPositivePromptChanged,
|
||||||
rgReset,
|
|
||||||
selectCanvasV2Slice,
|
selectCanvasV2Slice,
|
||||||
} from 'features/controlLayers/store/canvasV2Slice';
|
} from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import { PiPlusBold } from 'react-icons/pi';
|
||||||
PiArrowCounterClockwiseBold,
|
|
||||||
PiArrowDownBold,
|
|
||||||
PiArrowLineDownBold,
|
|
||||||
PiArrowLineUpBold,
|
|
||||||
PiArrowUpBold,
|
|
||||||
PiPlusBold,
|
|
||||||
PiTrashSimpleBold,
|
|
||||||
} from 'react-icons/pi';
|
|
||||||
|
|
||||||
type Props = {
|
export const RGActionsMenu = memo(() => {
|
||||||
id: string;
|
const { id } = useEntityIdentifierContext();
|
||||||
};
|
|
||||||
|
|
||||||
export const RGActionsMenu = memo(({ id }: Props) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const [onAddIPAdapter, isAddIPAdapterDisabled] = useAddIPAdapterToRGLayer(id);
|
const [onAddIPAdapter, isAddIPAdapterDisabled] = useAddIPAdapterToRGLayer(id);
|
||||||
@ -39,13 +24,7 @@ export const RGActionsMenu = memo(({ id }: Props) => {
|
|||||||
() =>
|
() =>
|
||||||
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||||
const rg = selectRGOrThrow(canvasV2, id);
|
const rg = selectRGOrThrow(canvasV2, id);
|
||||||
const rgIndex = canvasV2.regions.entities.indexOf(rg);
|
|
||||||
const rgCount = canvasV2.regions.entities.length;
|
|
||||||
return {
|
return {
|
||||||
isMoveForwardOneDisabled: rgIndex < rgCount - 1,
|
|
||||||
isMoveBackardOneDisabled: rgIndex > 0,
|
|
||||||
isMoveToFrontDisabled: rgIndex < rgCount - 1,
|
|
||||||
isMoveToBackDisabled: rgIndex > 0,
|
|
||||||
isAddPositivePromptDisabled: rg.positivePrompt === null,
|
isAddPositivePromptDisabled: rg.positivePrompt === null,
|
||||||
isAddNegativePromptDisabled: rg.negativePrompt === null,
|
isAddNegativePromptDisabled: rg.negativePrompt === null,
|
||||||
};
|
};
|
||||||
@ -53,29 +32,11 @@ export const RGActionsMenu = memo(({ id }: Props) => {
|
|||||||
[id]
|
[id]
|
||||||
);
|
);
|
||||||
const actions = useAppSelector(selectActionsValidity);
|
const actions = useAppSelector(selectActionsValidity);
|
||||||
const onDelete = useCallback(() => {
|
|
||||||
dispatch(rgDeleted({ id }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
const onReset = useCallback(() => {
|
|
||||||
dispatch(rgReset({ id }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
const onMoveForwardOne = useCallback(() => {
|
|
||||||
dispatch(rgMovedForwardOne({ id }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
const onMoveToFront = useCallback(() => {
|
|
||||||
dispatch(rgMovedToFront({ id }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
const onMoveBackwardOne = useCallback(() => {
|
|
||||||
dispatch(rgMovedBackwardOne({ id }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
const onMoveToBack = useCallback(() => {
|
|
||||||
dispatch(rgMovedToBack({ id }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
const onAddPositivePrompt = useCallback(() => {
|
const onAddPositivePrompt = useCallback(() => {
|
||||||
dispatch(rgPositivePromptChanged({ id, prompt: '' }));
|
dispatch(rgPositivePromptChanged({ id: id, prompt: '' }));
|
||||||
}, [dispatch, id]);
|
}, [dispatch, id]);
|
||||||
const onAddNegativePrompt = useCallback(() => {
|
const onAddNegativePrompt = useCallback(() => {
|
||||||
dispatch(rgNegativePromptChanged({ id, prompt: '' }));
|
dispatch(rgNegativePromptChanged({ id: id, prompt: '' }));
|
||||||
}, [dispatch, id]);
|
}, [dispatch, id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -92,25 +53,7 @@ export const RGActionsMenu = memo(({ id }: Props) => {
|
|||||||
{t('controlLayers.addIPAdapter')}
|
{t('controlLayers.addIPAdapter')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<MenuItem onClick={onMoveToFront} isDisabled={actions.isMoveToFrontDisabled} icon={<PiArrowLineUpBold />}>
|
<CanvasEntityActionMenuItems />
|
||||||
{t('controlLayers.moveToFront')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem onClick={onMoveForwardOne} isDisabled={actions.isMoveForwardOneDisabled} icon={<PiArrowUpBold />}>
|
|
||||||
{t('controlLayers.moveForward')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem onClick={onMoveBackwardOne} isDisabled={actions.isMoveBackardOneDisabled} icon={<PiArrowDownBold />}>
|
|
||||||
{t('controlLayers.moveBackward')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem onClick={onMoveToBack} isDisabled={actions.isMoveToBackDisabled} icon={<PiArrowLineDownBold />}>
|
|
||||||
{t('controlLayers.moveToBack')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuDivider />
|
|
||||||
<MenuItem onClick={onReset} icon={<PiArrowCounterClockwiseBold />}>
|
|
||||||
{t('accessibility.reset')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem onClick={onDelete} icon={<PiTrashSimpleBold />} color="error.300">
|
|
||||||
{t('common.delete')}
|
|
||||||
</MenuItem>
|
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
@ -1,50 +1,41 @@
|
|||||||
import { Badge, Spacer } from '@invoke-ai/ui-library';
|
import { Badge, Spacer } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
||||||
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
||||||
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
||||||
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
||||||
import { RGActionsMenu } from 'features/controlLayers/components/RegionalGuidance/RGActionsMenu';
|
import { RGActionsMenu } from 'features/controlLayers/components/RegionalGuidance/RGActionsMenu';
|
||||||
import { rgDeleted, rgIsEnabledToggled } from 'features/controlLayers/store/canvasV2Slice';
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { RGMaskFillColorPicker } from './RGMaskFillColorPicker';
|
import { RGMaskFillColorPicker } from './RGMaskFillColorPicker';
|
||||||
import { RGSettingsPopover } from './RGSettingsPopover';
|
import { RGSettingsPopover } from './RGSettingsPopover';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
id: string;
|
|
||||||
isSelected: boolean;
|
|
||||||
onToggleVisibility: () => void;
|
onToggleVisibility: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RGHeader = memo(({ id, isSelected, onToggleVisibility }: Props) => {
|
export const RGHeader = memo(({ onToggleVisibility }: Props) => {
|
||||||
|
const { id } = useEntityIdentifierContext();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const isEnabled = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).isEnabled);
|
|
||||||
const autoNegative = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).autoNegative);
|
const autoNegative = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).autoNegative);
|
||||||
const onToggleIsEnabled = useCallback(() => {
|
|
||||||
dispatch(rgIsEnabledToggled({ id }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
const onDelete = useCallback(() => {
|
|
||||||
dispatch(rgDeleted({ id }));
|
|
||||||
}, [dispatch, id]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CanvasEntityHeader onToggle={onToggleVisibility}>
|
<CanvasEntityHeader onToggle={onToggleVisibility}>
|
||||||
<CanvasEntityEnabledToggle isEnabled={isEnabled} onToggle={onToggleIsEnabled} />
|
<CanvasEntityEnabledToggle />
|
||||||
<CanvasEntityTitle title={t('controlLayers.regionalGuidance')} isSelected={isSelected} />
|
<CanvasEntityTitle />
|
||||||
<Spacer />
|
<Spacer />
|
||||||
{autoNegative === 'invert' && (
|
{autoNegative === 'invert' && (
|
||||||
<Badge color="base.300" bg="transparent" borderWidth={1} userSelect="none">
|
<Badge color="base.300" bg="transparent" borderWidth={1} userSelect="none">
|
||||||
{t('controlLayers.autoNegative')}
|
{t('controlLayers.autoNegative')}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
<RGMaskFillColorPicker id={id} />
|
<RGMaskFillColorPicker />
|
||||||
<RGSettingsPopover id={id} />
|
<RGSettingsPopover />
|
||||||
<RGActionsMenu id={id} />
|
<RGActionsMenu />
|
||||||
<CanvasEntityDeleteButton onDelete={onDelete} />
|
<CanvasEntityDeleteButton />
|
||||||
</CanvasEntityHeader>
|
</CanvasEntityHeader>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -3,25 +3,23 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|||||||
import RgbColorPicker from 'common/components/RgbColorPicker';
|
import RgbColorPicker from 'common/components/RgbColorPicker';
|
||||||
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
||||||
import { stopPropagation } from 'common/util/stopPropagation';
|
import { stopPropagation } from 'common/util/stopPropagation';
|
||||||
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import { rgFillChanged } from 'features/controlLayers/store/canvasV2Slice';
|
import { rgFillChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import type { RgbColor } from 'react-colorful';
|
import type { RgbColor } from 'react-colorful';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type Props = {
|
export const RGMaskFillColorPicker = memo(() => {
|
||||||
id: string;
|
const entityIdentifier = useEntityIdentifierContext();
|
||||||
};
|
|
||||||
|
|
||||||
export const RGMaskFillColorPicker = memo(({ id }: Props) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const fill = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).fill);
|
const fill = useAppSelector((s) => selectRGOrThrow(s.canvasV2, entityIdentifier.id).fill);
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(fill: RgbColor) => {
|
(fill: RgbColor) => {
|
||||||
dispatch(rgFillChanged({ id, fill }));
|
dispatch(rgFillChanged({ id: entityIdentifier.id, fill }));
|
||||||
},
|
},
|
||||||
[dispatch, id]
|
[dispatch, entityIdentifier.id]
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<Popover isLazy>
|
<Popover isLazy>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { AddPromptButtons } from 'features/controlLayers/components/AddPromptButtons';
|
import { AddPromptButtons } from 'features/controlLayers/components/AddPromptButtons';
|
||||||
import { CanvasEntitySettings } from 'features/controlLayers/components/common/CanvasEntitySettings';
|
import { CanvasEntitySettings } from 'features/controlLayers/components/common/CanvasEntitySettings';
|
||||||
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
@ -8,11 +9,8 @@ import { RGIPAdapters } from './RGIPAdapters';
|
|||||||
import { RGNegativePrompt } from './RGNegativePrompt';
|
import { RGNegativePrompt } from './RGNegativePrompt';
|
||||||
import { RGPositivePrompt } from './RGPositivePrompt';
|
import { RGPositivePrompt } from './RGPositivePrompt';
|
||||||
|
|
||||||
type Props = {
|
export const RGSettings = memo(() => {
|
||||||
id: string;
|
const { id } = useEntityIdentifierContext();
|
||||||
};
|
|
||||||
|
|
||||||
export const RGSettings = memo(({ id }: Props) => {
|
|
||||||
const hasPositivePrompt = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).positivePrompt !== null);
|
const hasPositivePrompt = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).positivePrompt !== null);
|
||||||
const hasNegativePrompt = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).negativePrompt !== null);
|
const hasNegativePrompt = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).negativePrompt !== null);
|
||||||
const hasIPAdapters = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).ipAdapters.length > 0);
|
const hasIPAdapters = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).ipAdapters.length > 0);
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
} from '@invoke-ai/ui-library';
|
} from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { stopPropagation } from 'common/util/stopPropagation';
|
import { stopPropagation } from 'common/util/stopPropagation';
|
||||||
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import { rgAutoNegativeChanged } from 'features/controlLayers/store/canvasV2Slice';
|
import { rgAutoNegativeChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
@ -19,19 +20,16 @@ import { memo, useCallback } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiGearSixBold } from 'react-icons/pi';
|
import { PiGearSixBold } from 'react-icons/pi';
|
||||||
|
|
||||||
type Props = {
|
export const RGSettingsPopover = memo(() => {
|
||||||
id: string;
|
const entityIdentifier = useEntityIdentifierContext();
|
||||||
};
|
|
||||||
|
|
||||||
export const RGSettingsPopover = memo(({ id }: Props) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const autoNegative = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).autoNegative);
|
const autoNegative = useAppSelector((s) => selectRGOrThrow(s.canvasV2, entityIdentifier.id).autoNegative);
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) => {
|
(e: ChangeEvent<HTMLInputElement>) => {
|
||||||
dispatch(rgAutoNegativeChanged({ id, autoNegative: e.target.checked ? 'invert' : 'off' }));
|
dispatch(rgAutoNegativeChanged({ id: entityIdentifier.id, autoNegative: e.target.checked ? 'invert' : 'off' }));
|
||||||
},
|
},
|
||||||
[dispatch, id]
|
[dispatch, entityIdentifier.id]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -13,10 +13,8 @@ import { useCanvasResetLayerHotkey } from 'features/controlLayers/hooks/useCanva
|
|||||||
export const ToolChooser: React.FC = () => {
|
export const ToolChooser: React.FC = () => {
|
||||||
useCanvasResetLayerHotkey();
|
useCanvasResetLayerHotkey();
|
||||||
useCanvasDeleteLayerHotkey();
|
useCanvasDeleteLayerHotkey();
|
||||||
const isCanvasSessionActive = useAppSelector((s) => s.canvasV2.session.isActive);
|
|
||||||
const isTransforming = useAppSelector((s) => s.canvasV2.tool.isTransforming);
|
const isTransforming = useAppSelector((s) => s.canvasV2.tool.isTransforming);
|
||||||
|
|
||||||
if (isCanvasSessionActive) {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ButtonGroup isAttached isDisabled={isTransforming}>
|
<ButtonGroup isAttached isDisabled={isTransforming}>
|
||||||
@ -30,15 +28,4 @@ export const ToolChooser: React.FC = () => {
|
|||||||
<TransformToolButton />
|
<TransformToolButton />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ButtonGroup isAttached isDisabled={isTransforming}>
|
|
||||||
<BrushToolButton />
|
|
||||||
<EraserToolButton />
|
|
||||||
<RectToolButton />
|
|
||||||
<MoveToolButton />
|
|
||||||
<ViewToolButton />
|
|
||||||
</ButtonGroup>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,127 @@
|
|||||||
|
import { MenuItem } from '@invoke-ai/ui-library';
|
||||||
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
|
import {
|
||||||
|
entityArrangedBackwardOne,
|
||||||
|
entityArrangedForwardOne,
|
||||||
|
entityArrangedToBack,
|
||||||
|
entityArrangedToFront,
|
||||||
|
entityDeleted,
|
||||||
|
entityReset,
|
||||||
|
selectCanvasV2Slice,
|
||||||
|
} from 'features/controlLayers/store/canvasV2Slice';
|
||||||
|
import type { CanvasEntityIdentifier, CanvasV2State } from 'features/controlLayers/store/types';
|
||||||
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import {
|
||||||
|
PiArrowCounterClockwiseBold,
|
||||||
|
PiArrowDownBold,
|
||||||
|
PiArrowLineDownBold,
|
||||||
|
PiArrowLineUpBold,
|
||||||
|
PiArrowUpBold,
|
||||||
|
PiTrashSimpleBold,
|
||||||
|
} from 'react-icons/pi';
|
||||||
|
|
||||||
|
const getIndexAndCount = (
|
||||||
|
canvasV2: CanvasV2State,
|
||||||
|
{ id, type }: CanvasEntityIdentifier
|
||||||
|
): { index: number; count: number } => {
|
||||||
|
if (type === 'layer') {
|
||||||
|
return {
|
||||||
|
index: canvasV2.layers.entities.findIndex((entity) => entity.id === id),
|
||||||
|
count: canvasV2.layers.entities.length,
|
||||||
|
};
|
||||||
|
} else if (type === 'control_adapter') {
|
||||||
|
return {
|
||||||
|
index: canvasV2.controlAdapters.entities.findIndex((entity) => entity.id === id),
|
||||||
|
count: canvasV2.controlAdapters.entities.length,
|
||||||
|
};
|
||||||
|
} else if (type === 'regional_guidance') {
|
||||||
|
return {
|
||||||
|
index: canvasV2.regions.entities.findIndex((entity) => entity.id === id),
|
||||||
|
count: canvasV2.regions.entities.length,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
index: -1,
|
||||||
|
count: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CanvasEntityActionMenuItems = memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const entityIdentifier = useEntityIdentifierContext();
|
||||||
|
const selectValidActions = useMemo(
|
||||||
|
() =>
|
||||||
|
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||||
|
const { index, count } = getIndexAndCount(canvasV2, entityIdentifier);
|
||||||
|
return {
|
||||||
|
isArrangeable:
|
||||||
|
entityIdentifier.type === 'layer' ||
|
||||||
|
entityIdentifier.type === 'control_adapter' ||
|
||||||
|
entityIdentifier.type === 'regional_guidance',
|
||||||
|
isDeleteable: entityIdentifier.type !== 'inpaint_mask',
|
||||||
|
canMoveForwardOne: index < count - 1,
|
||||||
|
canMoveBackwardOne: index > 0,
|
||||||
|
canMoveToFront: index < count - 1,
|
||||||
|
canMoveToBack: index > 0,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
[entityIdentifier]
|
||||||
|
);
|
||||||
|
|
||||||
|
const validActions = useAppSelector(selectValidActions);
|
||||||
|
|
||||||
|
const deleteEntity = useCallback(() => {
|
||||||
|
dispatch(entityDeleted({ entityIdentifier }));
|
||||||
|
}, [dispatch, entityIdentifier]);
|
||||||
|
const resetEntity = useCallback(() => {
|
||||||
|
dispatch(entityReset({ entityIdentifier }));
|
||||||
|
}, [dispatch, entityIdentifier]);
|
||||||
|
const moveForwardOne = useCallback(() => {
|
||||||
|
dispatch(entityArrangedForwardOne({ entityIdentifier }));
|
||||||
|
}, [dispatch, entityIdentifier]);
|
||||||
|
const moveToFront = useCallback(() => {
|
||||||
|
dispatch(entityArrangedToFront({ entityIdentifier }));
|
||||||
|
}, [dispatch, entityIdentifier]);
|
||||||
|
const moveBackwardOne = useCallback(() => {
|
||||||
|
dispatch(entityArrangedBackwardOne({ entityIdentifier }));
|
||||||
|
}, [dispatch, entityIdentifier]);
|
||||||
|
const moveToBack = useCallback(() => {
|
||||||
|
dispatch(entityArrangedToBack({ entityIdentifier }));
|
||||||
|
}, [dispatch, entityIdentifier]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{validActions.isArrangeable && (
|
||||||
|
<>
|
||||||
|
<MenuItem onClick={moveToFront} isDisabled={!validActions.canMoveToFront} icon={<PiArrowLineUpBold />}>
|
||||||
|
{t('controlLayers.moveToFront')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={moveForwardOne} isDisabled={!validActions.canMoveForwardOne} icon={<PiArrowUpBold />}>
|
||||||
|
{t('controlLayers.moveForward')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={moveBackwardOne} isDisabled={!validActions.canMoveBackwardOne} icon={<PiArrowDownBold />}>
|
||||||
|
{t('controlLayers.moveBackward')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={moveToBack} isDisabled={!validActions.canMoveToBack} icon={<PiArrowLineDownBold />}>
|
||||||
|
{t('controlLayers.moveToBack')}
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<MenuItem onClick={resetEntity} icon={<PiArrowCounterClockwiseBold />}>
|
||||||
|
{t('accessibility.reset')}
|
||||||
|
</MenuItem>
|
||||||
|
{validActions.isDeleteable && (
|
||||||
|
<MenuItem onClick={deleteEntity} icon={<PiTrashSimpleBold />} color="error.300">
|
||||||
|
{t('common.delete')}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
CanvasEntityActionMenuItems.displayName = 'CanvasEntityActionMenuItems';
|
@ -1,22 +1,23 @@
|
|||||||
import type { ChakraProps } from '@invoke-ai/ui-library';
|
|
||||||
import { Flex } from '@invoke-ai/ui-library';
|
import { Flex } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
|
import { useEntitySelectionColor } from 'features/controlLayers/hooks/useEntitySelectionColor';
|
||||||
|
import { useIsEntitySelected } from 'features/controlLayers/hooks/useIsEntitySelected';
|
||||||
|
import { entitySelected } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import type { PropsWithChildren } from 'react';
|
import type { PropsWithChildren } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
|
|
||||||
type Props = PropsWithChildren<{
|
export const CanvasEntityContainer = memo((props: PropsWithChildren) => {
|
||||||
isSelected: boolean;
|
const dispatch = useAppDispatch();
|
||||||
onSelect: () => void;
|
const entityIdentifier = useEntityIdentifierContext();
|
||||||
selectedBorderColor?: ChakraProps['bg'];
|
const isSelected = useIsEntitySelected(entityIdentifier);
|
||||||
}>;
|
const selectionColor = useEntitySelectionColor(entityIdentifier);
|
||||||
|
const onClick = useCallback(() => {
|
||||||
export const CanvasEntityContainer = memo((props: Props) => {
|
|
||||||
const { isSelected, onSelect, selectedBorderColor = 'base.400', children } = props;
|
|
||||||
const _onSelect = useCallback(() => {
|
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onSelect();
|
dispatch(entitySelected({ entityIdentifier }));
|
||||||
}, [isSelected, onSelect]);
|
}, [dispatch, entityIdentifier, isSelected]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
@ -24,12 +25,12 @@ export const CanvasEntityContainer = memo((props: Props) => {
|
|||||||
flexDir="column"
|
flexDir="column"
|
||||||
w="full"
|
w="full"
|
||||||
bg={isSelected ? 'base.800' : 'base.850'}
|
bg={isSelected ? 'base.800' : 'base.850'}
|
||||||
onClick={_onSelect}
|
onClick={onClick}
|
||||||
borderInlineStartWidth={5}
|
borderInlineStartWidth={5}
|
||||||
borderColor={isSelected ? selectedBorderColor : 'base.800'}
|
borderColor={isSelected ? selectionColor : 'base.800'}
|
||||||
borderRadius="base"
|
borderRadius="base"
|
||||||
>
|
>
|
||||||
{children}
|
{props.children}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
import { IconButton } from '@invoke-ai/ui-library';
|
import { IconButton } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { stopPropagation } from 'common/util/stopPropagation';
|
import { stopPropagation } from 'common/util/stopPropagation';
|
||||||
import { memo } from 'react';
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
|
import { entityDeleted } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiTrashSimpleBold } from 'react-icons/pi';
|
import { PiTrashSimpleBold } from 'react-icons/pi';
|
||||||
|
|
||||||
type Props = { onDelete: () => void };
|
export const CanvasEntityDeleteButton = memo(() => {
|
||||||
|
|
||||||
export const CanvasEntityDeleteButton = memo(({ onDelete }: Props) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const entityIdentifier = useEntityIdentifierContext();
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
dispatch(entityDeleted({ entityIdentifier }));
|
||||||
|
}, [dispatch, entityIdentifier]);
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
size="sm"
|
size="sm"
|
||||||
@ -15,7 +21,7 @@ export const CanvasEntityDeleteButton = memo(({ onDelete }: Props) => {
|
|||||||
aria-label={t('common.delete')}
|
aria-label={t('common.delete')}
|
||||||
tooltip={t('common.delete')}
|
tooltip={t('common.delete')}
|
||||||
icon={<PiTrashSimpleBold />}
|
icon={<PiTrashSimpleBold />}
|
||||||
onClick={onDelete}
|
onClick={onClick}
|
||||||
onDoubleClick={stopPropagation} // double click expands the layer
|
onDoubleClick={stopPropagation} // double click expands the layer
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
import { IconButton } from '@invoke-ai/ui-library';
|
import { IconButton } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { stopPropagation } from 'common/util/stopPropagation';
|
import { stopPropagation } from 'common/util/stopPropagation';
|
||||||
import { memo } from 'react';
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
|
import { useEntityIsEnabled } from 'features/controlLayers/hooks/useEntityIsEnabled';
|
||||||
|
import { entityIsEnabledToggled } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiCheckBold } from 'react-icons/pi';
|
import { PiCheckBold } from 'react-icons/pi';
|
||||||
|
|
||||||
type Props = {
|
export const CanvasEntityEnabledToggle = memo(() => {
|
||||||
isEnabled: boolean;
|
|
||||||
onToggle: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CanvasEntityEnabledToggle = memo(({ isEnabled, onToggle }: Props) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const entityIdentifier = useEntityIdentifierContext();
|
||||||
|
const isEnabled = useEntityIsEnabled(entityIdentifier);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
dispatch(entityIsEnabledToggled({ entityIdentifier }));
|
||||||
|
}, [dispatch, entityIdentifier]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
@ -19,7 +24,7 @@ export const CanvasEntityEnabledToggle = memo(({ isEnabled, onToggle }: Props) =
|
|||||||
tooltip={t(isEnabled ? 'common.enabled' : 'common.disabled')}
|
tooltip={t(isEnabled ? 'common.enabled' : 'common.disabled')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
icon={isEnabled ? <PiCheckBold /> : undefined}
|
icon={isEnabled ? <PiCheckBold /> : undefined}
|
||||||
onClick={onToggle}
|
onClick={onClick}
|
||||||
colorScheme="base"
|
colorScheme="base"
|
||||||
onDoubleClick={stopPropagation} // double click expands the layer
|
onDoubleClick={stopPropagation} // double click expands the layer
|
||||||
/>
|
/>
|
||||||
|
@ -1,12 +1,30 @@
|
|||||||
import { Text } from '@invoke-ai/ui-library';
|
import { Text } from '@invoke-ai/ui-library';
|
||||||
import { memo } from 'react';
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
|
import { useIsEntitySelected } from 'features/controlLayers/hooks/useIsEntitySelected';
|
||||||
|
import { memo, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
type Props = {
|
export const CanvasEntityTitle = memo(() => {
|
||||||
title: string;
|
const { t } = useTranslation();
|
||||||
isSelected: boolean;
|
const entityIdentifier = useEntityIdentifierContext();
|
||||||
};
|
const isSelected = useIsEntitySelected(entityIdentifier);
|
||||||
|
const title = useMemo(() => {
|
||||||
|
if (entityIdentifier.type === 'inpaint_mask') {
|
||||||
|
return t('controlLayers.inpaintMask');
|
||||||
|
} else if (entityIdentifier.type === 'control_adapter') {
|
||||||
|
return t('controlLayers.globalControlAdapter');
|
||||||
|
} else if (entityIdentifier.type === 'layer') {
|
||||||
|
return t('controlLayers.layer');
|
||||||
|
} else if (entityIdentifier.type === 'ip_adapter') {
|
||||||
|
return t('controlLayers.ipAdapter');
|
||||||
|
} else if (entityIdentifier.type === 'regional_guidance') {
|
||||||
|
return t('controlLayers.regionalGuidance');
|
||||||
|
} else {
|
||||||
|
assert(false, 'Unexpected entity type');
|
||||||
|
}
|
||||||
|
}, [entityIdentifier.type, t]);
|
||||||
|
|
||||||
export const CanvasEntityTitle = memo(({ title, isSelected }: Props) => {
|
|
||||||
return (
|
return (
|
||||||
<Text size="sm" fontWeight="semibold" userSelect="none" color={isSelected ? 'base.100' : 'base.300'}>
|
<Text size="sm" fontWeight="semibold" userSelect="none" color={isSelected ? 'base.100' : 'base.300'}>
|
||||||
{title}
|
{title}
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
|
import { createContext, useContext } from 'react';
|
||||||
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
|
export const EntityIdentifierContext = createContext<CanvasEntityIdentifier | null>(null);
|
||||||
|
|
||||||
|
export const useEntityIdentifierContext = (): CanvasEntityIdentifier => {
|
||||||
|
const entityIdentifier = useContext(EntityIdentifierContext);
|
||||||
|
assert(entityIdentifier, 'useEntityIdentifier must be used within a EntityIdentifierProvider');
|
||||||
|
return entityIdentifier;
|
||||||
|
};
|
@ -2,10 +2,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
||||||
import {
|
import {
|
||||||
caDeleted,
|
entityDeleted,
|
||||||
ipaDeleted,
|
|
||||||
layerDeleted,
|
|
||||||
rgDeleted,
|
|
||||||
selectCanvasV2Slice,
|
selectCanvasV2Slice,
|
||||||
} from 'features/controlLayers/store/canvasV2Slice';
|
} from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
@ -26,19 +23,7 @@ export function useCanvasDeleteLayerHotkey() {
|
|||||||
if (selectedEntityIdentifier === null) {
|
if (selectedEntityIdentifier === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { type, id } = selectedEntityIdentifier;
|
dispatch(entityDeleted({ entityIdentifier: selectedEntityIdentifier }));
|
||||||
if (type === 'layer') {
|
|
||||||
dispatch(layerDeleted({ id }));
|
|
||||||
}
|
|
||||||
if (type === 'regional_guidance') {
|
|
||||||
dispatch(rgDeleted({ id }));
|
|
||||||
}
|
|
||||||
if (type === 'control_adapter') {
|
|
||||||
dispatch(caDeleted({ id }));
|
|
||||||
}
|
|
||||||
if (type === 'ip_adapter') {
|
|
||||||
dispatch(ipaDeleted({ id }));
|
|
||||||
}
|
|
||||||
}, [dispatch, selectedEntityIdentifier]);
|
}, [dispatch, selectedEntityIdentifier]);
|
||||||
|
|
||||||
const isDeleteEnabled = useMemo(
|
const isDeleteEnabled = useMemo(
|
||||||
|
@ -2,9 +2,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
||||||
import {
|
import {
|
||||||
imReset,
|
entityReset,
|
||||||
layerReset,
|
|
||||||
rgReset,
|
|
||||||
selectCanvasV2Slice,
|
selectCanvasV2Slice,
|
||||||
} from 'features/controlLayers/store/canvasV2Slice';
|
} from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
@ -25,16 +23,7 @@ export function useCanvasResetLayerHotkey() {
|
|||||||
if (selectedEntityIdentifier === null) {
|
if (selectedEntityIdentifier === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { type, id } = selectedEntityIdentifier;
|
dispatch(entityReset({ entityIdentifier: selectedEntityIdentifier }));
|
||||||
if (type === 'layer') {
|
|
||||||
dispatch(layerReset({ id }));
|
|
||||||
}
|
|
||||||
if (type === 'regional_guidance') {
|
|
||||||
dispatch(rgReset({ id }));
|
|
||||||
}
|
|
||||||
if (type === 'inpaint_mask') {
|
|
||||||
dispatch(imReset());
|
|
||||||
}
|
|
||||||
}, [dispatch, selectedEntityIdentifier]);
|
}, [dispatch, selectedEntityIdentifier]);
|
||||||
|
|
||||||
const isResetEnabled = useMemo(
|
const isResetEnabled = useMemo(
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
|
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
export const useEntityIsEnabled = (entityIdentifier: CanvasEntityIdentifier) => {
|
||||||
|
const selectIsEnabled = useMemo(
|
||||||
|
() =>
|
||||||
|
createSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||||
|
const entity = selectEntity(canvasV2, entityIdentifier);
|
||||||
|
if (!entity) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return entity.isEnabled;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
[entityIdentifier]
|
||||||
|
);
|
||||||
|
const isEnabled = useAppSelector(selectIsEnabled);
|
||||||
|
return isEnabled;
|
||||||
|
};
|
@ -0,0 +1,27 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
||||||
|
import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
|
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
export const useEntitySelectionColor = (entityIdentifier: CanvasEntityIdentifier) => {
|
||||||
|
const selectSelectionColor = useMemo(
|
||||||
|
() =>
|
||||||
|
createSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||||
|
const entity = selectEntity(canvasV2, entityIdentifier);
|
||||||
|
if (!entity) {
|
||||||
|
return 'base.400';
|
||||||
|
} else if (entity.type === 'inpaint_mask') {
|
||||||
|
return rgbColorToString(entity.fill);
|
||||||
|
} else if (entity.type === 'regional_guidance') {
|
||||||
|
return rgbColorToString(entity.fill);
|
||||||
|
} else {
|
||||||
|
return 'base.400';
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
[entityIdentifier]
|
||||||
|
);
|
||||||
|
const selectionColor = useAppSelector(selectSelectionColor);
|
||||||
|
return selectionColor;
|
||||||
|
};
|
@ -0,0 +1,12 @@
|
|||||||
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
export const useIsEntitySelected = (entityIdentifier: CanvasEntityIdentifier) => {
|
||||||
|
const selectedEntityIdentifier = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier);
|
||||||
|
const isSelected = useMemo(() => {
|
||||||
|
return selectedEntityIdentifier?.id === entityIdentifier.id;
|
||||||
|
}, [selectedEntityIdentifier, entityIdentifier.id]);
|
||||||
|
|
||||||
|
return isSelected;
|
||||||
|
};
|
@ -218,16 +218,9 @@ export class CanvasBbox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const session = this.manager.stateApi.getSession();
|
|
||||||
const bbox = this.manager.stateApi.getBbox();
|
const bbox = this.manager.stateApi.getBbox();
|
||||||
const toolState = this.manager.stateApi.getToolState();
|
const toolState = this.manager.stateApi.getToolState();
|
||||||
|
|
||||||
if (!session.isActive) {
|
|
||||||
this.konva.group.listening(false);
|
|
||||||
this.konva.group.visible(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.konva.group.visible(true);
|
this.konva.group.visible(true);
|
||||||
this.konva.group.listening(toolState.selected === 'bbox');
|
this.konva.group.listening(toolState.selected === 'bbox');
|
||||||
this.konva.rect.setAttrs({
|
this.konva.rect.setAttrs({
|
||||||
|
@ -2,7 +2,12 @@ import { deepClone } from 'common/util/deepClone';
|
|||||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||||
import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
|
import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
|
||||||
import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
|
import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
|
||||||
import type { CanvasLayerState, CanvasV2State, GetLoggingContext } from 'features/controlLayers/store/types';
|
import type {
|
||||||
|
CanvasEntityIdentifier,
|
||||||
|
CanvasLayerState,
|
||||||
|
CanvasV2State,
|
||||||
|
GetLoggingContext,
|
||||||
|
} from 'features/controlLayers/store/types';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
import { get } from 'lodash-es';
|
import { get } from 'lodash-es';
|
||||||
import type { Logger } from 'roarr';
|
import type { Logger } from 'roarr';
|
||||||
@ -48,6 +53,13 @@ export class CanvasLayerAdapter {
|
|||||||
this.state = state;
|
this.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get this entity's entity identifier
|
||||||
|
*/
|
||||||
|
getEntityIdentifier = (): CanvasEntityIdentifier => {
|
||||||
|
return { id: this.id, type: this.type };
|
||||||
|
};
|
||||||
|
|
||||||
destroy = (): void => {
|
destroy = (): void => {
|
||||||
this.log.debug('Destroying layer');
|
this.log.debug('Destroying layer');
|
||||||
// We need to call the destroy method on all children so they can do their own cleanup.
|
// We need to call the destroy method on all children so they can do their own cleanup.
|
||||||
|
@ -2,6 +2,7 @@ import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
|||||||
import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
|
import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
|
||||||
import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
|
import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
|
||||||
import type {
|
import type {
|
||||||
|
CanvasEntityIdentifier,
|
||||||
CanvasInpaintMaskState,
|
CanvasInpaintMaskState,
|
||||||
CanvasRegionalGuidanceState,
|
CanvasRegionalGuidanceState,
|
||||||
CanvasV2State,
|
CanvasV2State,
|
||||||
@ -54,6 +55,13 @@ export class CanvasMaskAdapter {
|
|||||||
this.maskOpacity = this.manager.stateApi.getMaskOpacity();
|
this.maskOpacity = this.manager.stateApi.getMaskOpacity();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get this entity's entity identifier
|
||||||
|
*/
|
||||||
|
getEntityIdentifier = (): CanvasEntityIdentifier => {
|
||||||
|
return { id: this.id, type: this.type };
|
||||||
|
};
|
||||||
|
|
||||||
destroy = (): void => {
|
destroy = (): void => {
|
||||||
this.log.debug('Destroying mask');
|
this.log.debug('Destroying mask');
|
||||||
// We need to call the destroy method on all children so they can do their own cleanup.
|
// We need to call the destroy method on all children so they can do their own cleanup.
|
||||||
|
@ -8,11 +8,7 @@ import type { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLaye
|
|||||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||||
import type { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter';
|
import type { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter';
|
||||||
import { CanvasRectRenderer } from 'features/controlLayers/konva/CanvasRect';
|
import { CanvasRectRenderer } from 'features/controlLayers/konva/CanvasRect';
|
||||||
import {
|
import { getPrefixedId, konvaNodeToBlob, previewBlob } from 'features/controlLayers/konva/util';
|
||||||
getPrefixedId,
|
|
||||||
konvaNodeToBlob,
|
|
||||||
previewBlob,
|
|
||||||
} from 'features/controlLayers/konva/util';
|
|
||||||
import {
|
import {
|
||||||
type CanvasBrushLineState,
|
type CanvasBrushLineState,
|
||||||
type CanvasEraserLineState,
|
type CanvasEraserLineState,
|
||||||
@ -299,11 +295,17 @@ export class CanvasObjectRenderer {
|
|||||||
this.buffer.id = getPrefixedId(this.buffer.type);
|
this.buffer.id = getPrefixedId(this.buffer.type);
|
||||||
|
|
||||||
if (this.buffer.type === 'brush_line') {
|
if (this.buffer.type === 'brush_line') {
|
||||||
this.manager.stateApi.addBrushLine({ id: this.parent.id, brushLine: this.buffer }, this.parent.type);
|
this.manager.stateApi.addBrushLine({
|
||||||
|
entityIdentifier: this.parent.getEntityIdentifier(),
|
||||||
|
brushLine: this.buffer,
|
||||||
|
});
|
||||||
} else if (this.buffer.type === 'eraser_line') {
|
} else if (this.buffer.type === 'eraser_line') {
|
||||||
this.manager.stateApi.addEraserLine({ id: this.parent.id, eraserLine: this.buffer }, this.parent.type);
|
this.manager.stateApi.addEraserLine({
|
||||||
|
entityIdentifier: this.parent.getEntityIdentifier(),
|
||||||
|
eraserLine: this.buffer,
|
||||||
|
});
|
||||||
} else if (this.buffer.type === 'rect') {
|
} else if (this.buffer.type === 'rect') {
|
||||||
this.manager.stateApi.addRect({ id: this.parent.id, rect: this.buffer }, this.parent.type);
|
this.manager.stateApi.addRect({ entityIdentifier: this.parent.getEntityIdentifier(), rect: this.buffer });
|
||||||
} else {
|
} else {
|
||||||
this.log.warn({ buffer: this.buffer }, 'Invalid buffer object type');
|
this.log.warn({ buffer: this.buffer }, 'Invalid buffer object type');
|
||||||
}
|
}
|
||||||
@ -356,10 +358,11 @@ export class CanvasObjectRenderer {
|
|||||||
const imageDTO = await uploadImage(blob, `${this.id}_rasterized.png`, 'other', true);
|
const imageDTO = await uploadImage(blob, `${this.id}_rasterized.png`, 'other', true);
|
||||||
const imageObject = imageDTOToImageObject(imageDTO);
|
const imageObject = imageDTOToImageObject(imageDTO);
|
||||||
await this.renderObject(imageObject, true);
|
await this.renderObject(imageObject, true);
|
||||||
this.manager.stateApi.rasterizeEntity(
|
this.manager.stateApi.rasterizeEntity({
|
||||||
{ id: this.parent.id, imageObject, position: { x: Math.round(rect.x), y: Math.round(rect.y) } },
|
entityIdentifier: this.parent.getEntityIdentifier(),
|
||||||
this.parent.type
|
imageObject,
|
||||||
);
|
position: { x: Math.round(rect.x), y: Math.round(rect.y) },
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,44 +15,31 @@ import {
|
|||||||
$stageAttrs,
|
$stageAttrs,
|
||||||
bboxChanged,
|
bboxChanged,
|
||||||
brushWidthChanged,
|
brushWidthChanged,
|
||||||
caTranslated,
|
entityBrushLineAdded,
|
||||||
|
entityEraserLineAdded,
|
||||||
|
entityMoved,
|
||||||
|
entityRasterized,
|
||||||
|
entityRectAdded,
|
||||||
|
entityReset,
|
||||||
entitySelected,
|
entitySelected,
|
||||||
eraserWidthChanged,
|
eraserWidthChanged,
|
||||||
imBrushLineAdded,
|
|
||||||
imEraserLineAdded,
|
|
||||||
imImageCacheChanged,
|
imImageCacheChanged,
|
||||||
imMoved,
|
|
||||||
imRectAdded,
|
|
||||||
inpaintMaskRasterized,
|
|
||||||
layerBrushLineAdded,
|
|
||||||
layerEraserLineAdded,
|
|
||||||
layerImageCacheChanged,
|
layerImageCacheChanged,
|
||||||
layerRasterized,
|
|
||||||
layerRectAdded,
|
|
||||||
layerReset,
|
|
||||||
layerTranslated,
|
|
||||||
rgBrushLineAdded,
|
|
||||||
rgEraserLineAdded,
|
|
||||||
rgImageCacheChanged,
|
rgImageCacheChanged,
|
||||||
rgMoved,
|
|
||||||
rgRasterized,
|
|
||||||
rgRectAdded,
|
|
||||||
toolBufferChanged,
|
toolBufferChanged,
|
||||||
toolChanged,
|
toolChanged,
|
||||||
} from 'features/controlLayers/store/canvasV2Slice';
|
} from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import type {
|
import type {
|
||||||
CanvasBrushLineState,
|
EntityBrushLineAddedPayload,
|
||||||
CanvasEntityIdentifier,
|
EntityEraserLineAddedPayload,
|
||||||
CanvasEntityState,
|
EntityIdentifierPayload,
|
||||||
CanvasEraserLineState,
|
EntityMovedPayload,
|
||||||
CanvasRectState,
|
EntityRasterizedPayload,
|
||||||
EntityRasterizedArg,
|
EntityRectAddedPayload,
|
||||||
PositionChangedArg,
|
|
||||||
Rect,
|
Rect,
|
||||||
Tool,
|
Tool,
|
||||||
} from 'features/controlLayers/store/types';
|
} from 'features/controlLayers/store/types';
|
||||||
import type { ImageDTO } from 'services/api/types';
|
import type { ImageDTO } from 'services/api/types';
|
||||||
import { assert } from 'tsafe';
|
|
||||||
|
|
||||||
const log = logger('canvas');
|
const log = logger('canvas');
|
||||||
|
|
||||||
@ -69,67 +56,31 @@ export class CanvasStateApi {
|
|||||||
getState = () => {
|
getState = () => {
|
||||||
return this._store.getState().canvasV2;
|
return this._store.getState().canvasV2;
|
||||||
};
|
};
|
||||||
resetEntity = (arg: { id: string }, entityType: CanvasEntityState['type']) => {
|
resetEntity = (arg: EntityIdentifierPayload) => {
|
||||||
log.trace({ arg, entityType }, 'Resetting entity');
|
log.trace(arg, 'Resetting entity');
|
||||||
if (entityType === 'layer') {
|
this._store.dispatch(entityReset(arg));
|
||||||
this._store.dispatch(layerReset(arg));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
setEntityPosition = (arg: PositionChangedArg, entityType: CanvasEntityState['type']) => {
|
setEntityPosition = (arg: EntityMovedPayload) => {
|
||||||
log.trace({ arg, entityType }, 'Setting entity position');
|
log.trace(arg, 'Setting entity position');
|
||||||
if (entityType === 'layer') {
|
this._store.dispatch(entityMoved(arg));
|
||||||
this._store.dispatch(layerTranslated(arg));
|
|
||||||
} else if (entityType === 'regional_guidance') {
|
|
||||||
this._store.dispatch(rgMoved(arg));
|
|
||||||
} else if (entityType === 'inpaint_mask') {
|
|
||||||
this._store.dispatch(imMoved(arg));
|
|
||||||
} else if (entityType === 'control_adapter') {
|
|
||||||
this._store.dispatch(caTranslated(arg));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
addBrushLine = (arg: { id: string; brushLine: CanvasBrushLineState }, entityType: CanvasEntityState['type']) => {
|
addBrushLine = (arg: EntityBrushLineAddedPayload) => {
|
||||||
log.trace({ arg, entityType }, 'Adding brush line');
|
log.trace(arg, 'Adding brush line');
|
||||||
if (entityType === 'layer') {
|
this._store.dispatch(entityBrushLineAdded(arg));
|
||||||
this._store.dispatch(layerBrushLineAdded(arg));
|
|
||||||
} else if (entityType === 'regional_guidance') {
|
|
||||||
this._store.dispatch(rgBrushLineAdded(arg));
|
|
||||||
} else if (entityType === 'inpaint_mask') {
|
|
||||||
this._store.dispatch(imBrushLineAdded(arg));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
addEraserLine = (arg: { id: string; eraserLine: CanvasEraserLineState }, entityType: CanvasEntityState['type']) => {
|
addEraserLine = (arg: EntityEraserLineAddedPayload) => {
|
||||||
log.trace({ arg, entityType }, 'Adding eraser line');
|
log.trace(arg, 'Adding eraser line');
|
||||||
if (entityType === 'layer') {
|
this._store.dispatch(entityEraserLineAdded(arg));
|
||||||
this._store.dispatch(layerEraserLineAdded(arg));
|
|
||||||
} else if (entityType === 'regional_guidance') {
|
|
||||||
this._store.dispatch(rgEraserLineAdded(arg));
|
|
||||||
} else if (entityType === 'inpaint_mask') {
|
|
||||||
this._store.dispatch(imEraserLineAdded(arg));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
addRect = (arg: { id: string; rect: CanvasRectState }, entityType: CanvasEntityState['type']) => {
|
addRect = (arg: EntityRectAddedPayload) => {
|
||||||
log.trace({ arg, entityType }, 'Adding rect');
|
log.trace(arg, 'Adding rect');
|
||||||
if (entityType === 'layer') {
|
this._store.dispatch(entityRectAdded(arg));
|
||||||
this._store.dispatch(layerRectAdded(arg));
|
|
||||||
} else if (entityType === 'regional_guidance') {
|
|
||||||
this._store.dispatch(rgRectAdded(arg));
|
|
||||||
} else if (entityType === 'inpaint_mask') {
|
|
||||||
this._store.dispatch(imRectAdded(arg));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
rasterizeEntity = (arg: EntityRasterizedArg, entityType: CanvasEntityState['type']) => {
|
rasterizeEntity = (arg: EntityRasterizedPayload) => {
|
||||||
log.trace({ arg, entityType }, 'Rasterizing entity');
|
log.trace(arg, 'Rasterizing entity');
|
||||||
if (entityType === 'layer') {
|
this._store.dispatch(entityRasterized(arg));
|
||||||
this._store.dispatch(layerRasterized(arg));
|
|
||||||
} else if (entityType === 'inpaint_mask') {
|
|
||||||
this._store.dispatch(inpaintMaskRasterized(arg));
|
|
||||||
} else if (entityType === 'regional_guidance') {
|
|
||||||
this._store.dispatch(rgRasterized(arg));
|
|
||||||
} else {
|
|
||||||
assert(false, 'Rasterizing not supported for this entity type');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
setSelectedEntity = (arg: CanvasEntityIdentifier) => {
|
setSelectedEntity = (arg: EntityIdentifierPayload) => {
|
||||||
log.trace({ arg }, 'Setting selected entity');
|
log.trace({ arg }, 'Setting selected entity');
|
||||||
this._store.dispatch(entitySelected(arg));
|
this._store.dispatch(entitySelected(arg));
|
||||||
};
|
};
|
||||||
|
@ -356,7 +356,7 @@ export class CanvasTransformer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.log.trace({ position }, 'Position changed');
|
this.log.trace({ position }, 'Position changed');
|
||||||
this.manager.stateApi.setEntityPosition({ id: this.parent.id, position }, this.parent.type);
|
this.manager.stateApi.setEntityPosition({ entityIdentifier: this.parent.getEntityIdentifier(), position });
|
||||||
});
|
});
|
||||||
|
|
||||||
this.subscriptions.add(
|
this.subscriptions.add(
|
||||||
@ -600,7 +600,7 @@ export class CanvasTransformer {
|
|||||||
// We shouldn't reset on the first render - the bbox will be calculated on the next render
|
// We shouldn't reset on the first render - the bbox will be calculated on the next render
|
||||||
if (!this.parent.renderer.hasObjects()) {
|
if (!this.parent.renderer.hasObjects()) {
|
||||||
// The layer is fully transparent but has objects - reset it
|
// The layer is fully transparent but has objects - reset it
|
||||||
this.manager.stateApi.resetEntity({ id: this.parent.id }, this.parent.type);
|
this.manager.stateApi.resetEntity({ entityIdentifier: this.parent.getEntityIdentifier() });
|
||||||
}
|
}
|
||||||
this.syncInteractionState();
|
this.syncInteractionState();
|
||||||
return;
|
return;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { createAction, createSlice } from '@reduxjs/toolkit';
|
import { createAction, createSlice } from '@reduxjs/toolkit';
|
||||||
import type { PersistConfig, RootState } from 'app/store/store';
|
import type { PersistConfig, RootState } from 'app/store/store';
|
||||||
|
import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils';
|
||||||
import { deepClone } from 'common/util/deepClone';
|
import { deepClone } from 'common/util/deepClone';
|
||||||
import { bboxReducers } from 'features/controlLayers/store/bboxReducers';
|
import { bboxReducers } from 'features/controlLayers/store/bboxReducers';
|
||||||
import { compositingReducers } from 'features/controlLayers/store/compositingReducers';
|
import { compositingReducers } from 'features/controlLayers/store/compositingReducers';
|
||||||
@ -20,8 +21,20 @@ import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
|||||||
import { pick } from 'lodash-es';
|
import { pick } from 'lodash-es';
|
||||||
import { atom } from 'nanostores';
|
import { atom } from 'nanostores';
|
||||||
import type { InvocationDenoiseProgressEvent } from 'services/events/types';
|
import type { InvocationDenoiseProgressEvent } from 'services/events/types';
|
||||||
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
import type { CanvasEntityIdentifier, CanvasV2State, Coordinate, StageAttrs } from './types';
|
import type {
|
||||||
|
CanvasEntityIdentifier,
|
||||||
|
CanvasV2State,
|
||||||
|
Coordinate,
|
||||||
|
EntityBrushLineAddedPayload,
|
||||||
|
EntityEraserLineAddedPayload,
|
||||||
|
EntityIdentifierPayload,
|
||||||
|
EntityMovedPayload,
|
||||||
|
EntityRasterizedPayload,
|
||||||
|
EntityRectAddedPayload,
|
||||||
|
StageAttrs,
|
||||||
|
} from './types';
|
||||||
import { RGBA_RED } from './types';
|
import { RGBA_RED } from './types';
|
||||||
|
|
||||||
const initialState: CanvasV2State = {
|
const initialState: CanvasV2State = {
|
||||||
@ -122,6 +135,23 @@ const initialState: CanvasV2State = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function selectEntity(state: CanvasV2State, { id, type }: CanvasEntityIdentifier) {
|
||||||
|
switch (type) {
|
||||||
|
case 'layer':
|
||||||
|
return state.layers.entities.find((layer) => layer.id === id);
|
||||||
|
case 'control_adapter':
|
||||||
|
return state.controlAdapters.entities.find((ca) => ca.id === id);
|
||||||
|
case 'inpaint_mask':
|
||||||
|
return state.inpaintMask;
|
||||||
|
case 'regional_guidance':
|
||||||
|
return state.regions.entities.find((rg) => rg.id === id);
|
||||||
|
case 'ip_adapter':
|
||||||
|
return state.ipAdapters.entities.find((ip) => ip.id === id);
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const canvasV2Slice = createSlice({
|
export const canvasV2Slice = createSlice({
|
||||||
name: 'canvasV2',
|
name: 'canvasV2',
|
||||||
initialState,
|
initialState,
|
||||||
@ -138,8 +168,184 @@ export const canvasV2Slice = createSlice({
|
|||||||
...bboxReducers,
|
...bboxReducers,
|
||||||
...inpaintMaskReducers,
|
...inpaintMaskReducers,
|
||||||
...sessionReducers,
|
...sessionReducers,
|
||||||
entitySelected: (state, action: PayloadAction<CanvasEntityIdentifier>) => {
|
entitySelected: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||||
state.selectedEntityIdentifier = action.payload;
|
const { entityIdentifier } = action.payload;
|
||||||
|
state.selectedEntityIdentifier = entityIdentifier;
|
||||||
|
},
|
||||||
|
entityReset: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||||
|
const { entityIdentifier } = action.payload;
|
||||||
|
const entity = selectEntity(state, entityIdentifier);
|
||||||
|
if (!entity) {
|
||||||
|
return;
|
||||||
|
} else if (entity.type === 'layer') {
|
||||||
|
entity.isEnabled = true;
|
||||||
|
entity.objects = [];
|
||||||
|
entity.position = { x: 0, y: 0 };
|
||||||
|
state.layers.imageCache = null;
|
||||||
|
} else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||||
|
entity.isEnabled = true;
|
||||||
|
entity.objects = [];
|
||||||
|
entity.position = { x: 0, y: 0 };
|
||||||
|
entity.imageCache = null;
|
||||||
|
} else {
|
||||||
|
assert(false, 'Not implemented');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
entityIsEnabledToggled: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||||
|
const { entityIdentifier } = action.payload;
|
||||||
|
const entity = selectEntity(state, entityIdentifier);
|
||||||
|
if (!entity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
entity.isEnabled = !entity.isEnabled;
|
||||||
|
},
|
||||||
|
entityMoved: (state, action: PayloadAction<EntityMovedPayload>) => {
|
||||||
|
const { entityIdentifier, position } = action.payload;
|
||||||
|
const entity = selectEntity(state, entityIdentifier);
|
||||||
|
if (!entity) {
|
||||||
|
return;
|
||||||
|
} else if (entity.type === 'layer') {
|
||||||
|
entity.position = position;
|
||||||
|
state.layers.imageCache = null;
|
||||||
|
} else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||||
|
entity.position = position;
|
||||||
|
entity.imageCache = null;
|
||||||
|
} else {
|
||||||
|
assert(false, 'Not implemented');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
entityRasterized: (state, action: PayloadAction<EntityRasterizedPayload>) => {
|
||||||
|
const { entityIdentifier, imageObject, position } = action.payload;
|
||||||
|
const entity = selectEntity(state, entityIdentifier);
|
||||||
|
if (!entity) {
|
||||||
|
return;
|
||||||
|
} else if (entity.type === 'layer') {
|
||||||
|
entity.objects = [imageObject];
|
||||||
|
entity.position = position;
|
||||||
|
state.layers.imageCache = null;
|
||||||
|
} else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||||
|
entity.objects = [imageObject];
|
||||||
|
entity.position = position;
|
||||||
|
entity.imageCache = null;
|
||||||
|
} else {
|
||||||
|
assert(false, 'Not implemented');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
entityBrushLineAdded: (state, action: PayloadAction<EntityBrushLineAddedPayload>) => {
|
||||||
|
const { entityIdentifier, brushLine } = action.payload;
|
||||||
|
const entity = selectEntity(state, entityIdentifier);
|
||||||
|
if (!entity) {
|
||||||
|
return;
|
||||||
|
} else if (entity.type === 'layer') {
|
||||||
|
entity.objects.push(brushLine);
|
||||||
|
state.layers.imageCache = null;
|
||||||
|
} else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||||
|
entity.objects.push(brushLine);
|
||||||
|
entity.imageCache = null;
|
||||||
|
} else {
|
||||||
|
assert(false, 'Not implemented');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
entityEraserLineAdded: (state, action: PayloadAction<EntityEraserLineAddedPayload>) => {
|
||||||
|
const { entityIdentifier, eraserLine } = action.payload;
|
||||||
|
const entity = selectEntity(state, entityIdentifier);
|
||||||
|
if (!entity) {
|
||||||
|
return;
|
||||||
|
} else if (entity.type === 'layer') {
|
||||||
|
entity.objects.push(eraserLine);
|
||||||
|
state.layers.imageCache = null;
|
||||||
|
} else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||||
|
entity.objects.push(eraserLine);
|
||||||
|
entity.imageCache = null;
|
||||||
|
} else {
|
||||||
|
assert(false, 'Not implemented');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
entityRectAdded: (state, action: PayloadAction<EntityRectAddedPayload>) => {
|
||||||
|
const { entityIdentifier, rect } = action.payload;
|
||||||
|
const entity = selectEntity(state, entityIdentifier);
|
||||||
|
if (!entity) {
|
||||||
|
return;
|
||||||
|
} else if (entity.type === 'layer') {
|
||||||
|
entity.objects.push(rect);
|
||||||
|
state.layers.imageCache = null;
|
||||||
|
} else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||||
|
entity.objects.push(rect);
|
||||||
|
entity.imageCache = null;
|
||||||
|
} else {
|
||||||
|
assert(false, 'Not implemented');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
entityDeleted: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||||
|
const { entityIdentifier } = action.payload;
|
||||||
|
if (entityIdentifier.type === 'layer') {
|
||||||
|
state.layers.entities = state.layers.entities.filter((layer) => layer.id !== entityIdentifier.id);
|
||||||
|
state.layers.imageCache = null;
|
||||||
|
} else if (entityIdentifier.type === 'regional_guidance') {
|
||||||
|
state.regions.entities = state.regions.entities.filter((rg) => rg.id !== entityIdentifier.id);
|
||||||
|
} else {
|
||||||
|
assert(false, 'Not implemented');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
entityArrangedForwardOne: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||||
|
const { entityIdentifier } = action.payload;
|
||||||
|
const entity = selectEntity(state, entityIdentifier);
|
||||||
|
if (!entity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (entity.type === 'layer') {
|
||||||
|
moveOneToEnd(state.layers.entities, entity);
|
||||||
|
state.layers.imageCache = null;
|
||||||
|
} else if (entity.type === 'regional_guidance') {
|
||||||
|
moveOneToEnd(state.regions.entities, entity);
|
||||||
|
} else if (entity.type === 'control_adapter') {
|
||||||
|
moveOneToEnd(state.controlAdapters.entities, entity);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
entityArrangedToFront: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||||
|
const { entityIdentifier } = action.payload;
|
||||||
|
const entity = selectEntity(state, entityIdentifier);
|
||||||
|
if (!entity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (entity.type === 'layer') {
|
||||||
|
moveToEnd(state.layers.entities, entity);
|
||||||
|
state.layers.imageCache = null;
|
||||||
|
} else if (entity.type === 'regional_guidance') {
|
||||||
|
moveToEnd(state.regions.entities, entity);
|
||||||
|
} else if (entity.type === 'control_adapter') {
|
||||||
|
moveToEnd(state.controlAdapters.entities, entity);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
entityArrangedBackwardOne: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||||
|
const { entityIdentifier } = action.payload;
|
||||||
|
const entity = selectEntity(state, entityIdentifier);
|
||||||
|
if (!entity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (entity.type === 'layer') {
|
||||||
|
moveOneToStart(state.layers.entities, entity);
|
||||||
|
state.layers.imageCache = null;
|
||||||
|
} else if (entity.type === 'regional_guidance') {
|
||||||
|
moveOneToStart(state.regions.entities, entity);
|
||||||
|
} else if (entity.type === 'control_adapter') {
|
||||||
|
moveOneToStart(state.controlAdapters.entities, entity);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
entityArrangedToBack: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||||
|
const { entityIdentifier } = action.payload;
|
||||||
|
const entity = selectEntity(state, entityIdentifier);
|
||||||
|
if (!entity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (entity.type === 'layer') {
|
||||||
|
moveToStart(state.layers.entities, entity);
|
||||||
|
state.layers.imageCache = null;
|
||||||
|
} else if (entity.type === 'regional_guidance') {
|
||||||
|
moveToStart(state.regions.entities, entity);
|
||||||
|
} else if (entity.type === 'control_adapter') {
|
||||||
|
moveToStart(state.controlAdapters.entities, entity);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
allEntitiesDeleted: (state) => {
|
allEntitiesDeleted: (state) => {
|
||||||
state.regions.entities = [];
|
state.regions.entities = [];
|
||||||
@ -176,10 +382,23 @@ export const {
|
|||||||
toolChanged,
|
toolChanged,
|
||||||
toolBufferChanged,
|
toolBufferChanged,
|
||||||
maskOpacityChanged,
|
maskOpacityChanged,
|
||||||
entitySelected,
|
|
||||||
allEntitiesDeleted,
|
allEntitiesDeleted,
|
||||||
clipToBboxChanged,
|
clipToBboxChanged,
|
||||||
canvasReset,
|
canvasReset,
|
||||||
|
// All entities
|
||||||
|
entitySelected,
|
||||||
|
entityReset,
|
||||||
|
entityIsEnabledToggled,
|
||||||
|
entityMoved,
|
||||||
|
entityRasterized,
|
||||||
|
entityBrushLineAdded,
|
||||||
|
entityEraserLineAdded,
|
||||||
|
entityRectAdded,
|
||||||
|
entityDeleted,
|
||||||
|
entityArrangedForwardOne,
|
||||||
|
entityArrangedToFront,
|
||||||
|
entityArrangedBackwardOne,
|
||||||
|
entityArrangedToBack,
|
||||||
// bbox
|
// bbox
|
||||||
bboxChanged,
|
bboxChanged,
|
||||||
bboxScaledSizeChanged,
|
bboxScaledSizeChanged,
|
||||||
@ -193,23 +412,10 @@ export const {
|
|||||||
// layers
|
// layers
|
||||||
layerAdded,
|
layerAdded,
|
||||||
layerRecalled,
|
layerRecalled,
|
||||||
layerDeleted,
|
|
||||||
layerReset,
|
|
||||||
layerMovedForwardOne,
|
|
||||||
layerMovedToFront,
|
|
||||||
layerMovedBackwardOne,
|
|
||||||
layerMovedToBack,
|
|
||||||
layerIsEnabledToggled,
|
|
||||||
layerOpacityChanged,
|
layerOpacityChanged,
|
||||||
layerTranslated,
|
|
||||||
layerBboxChanged,
|
|
||||||
layerImageAdded,
|
layerImageAdded,
|
||||||
layerAllDeleted,
|
layerAllDeleted,
|
||||||
layerImageCacheChanged,
|
layerImageCacheChanged,
|
||||||
layerBrushLineAdded,
|
|
||||||
layerEraserLineAdded,
|
|
||||||
layerRectAdded,
|
|
||||||
layerRasterized,
|
|
||||||
// IP Adapters
|
// IP Adapters
|
||||||
ipaAdded,
|
ipaAdded,
|
||||||
ipaRecalled,
|
ipaRecalled,
|
||||||
@ -224,16 +430,8 @@ export const {
|
|||||||
ipaBeginEndStepPctChanged,
|
ipaBeginEndStepPctChanged,
|
||||||
// Control Adapters
|
// Control Adapters
|
||||||
caAdded,
|
caAdded,
|
||||||
caBboxChanged,
|
|
||||||
caDeleted,
|
|
||||||
caAllDeleted,
|
caAllDeleted,
|
||||||
caIsEnabledToggled,
|
|
||||||
caMovedBackwardOne,
|
|
||||||
caMovedForwardOne,
|
|
||||||
caMovedToBack,
|
|
||||||
caMovedToFront,
|
|
||||||
caOpacityChanged,
|
caOpacityChanged,
|
||||||
caTranslated,
|
|
||||||
caRecalled,
|
caRecalled,
|
||||||
caImageChanged,
|
caImageChanged,
|
||||||
caProcessedImageChanged,
|
caProcessedImageChanged,
|
||||||
@ -244,19 +442,10 @@ export const {
|
|||||||
caProcessorPendingBatchIdChanged,
|
caProcessorPendingBatchIdChanged,
|
||||||
caWeightChanged,
|
caWeightChanged,
|
||||||
caBeginEndStepPctChanged,
|
caBeginEndStepPctChanged,
|
||||||
caScaled,
|
|
||||||
// Regions
|
// Regions
|
||||||
rgAdded,
|
rgAdded,
|
||||||
rgRecalled,
|
rgRecalled,
|
||||||
rgReset,
|
|
||||||
rgIsEnabledToggled,
|
|
||||||
rgMoved,
|
|
||||||
rgDeleted,
|
|
||||||
rgAllDeleted,
|
rgAllDeleted,
|
||||||
rgMovedForwardOne,
|
|
||||||
rgMovedToFront,
|
|
||||||
rgMovedBackwardOne,
|
|
||||||
rgMovedToBack,
|
|
||||||
rgPositivePromptChanged,
|
rgPositivePromptChanged,
|
||||||
rgNegativePromptChanged,
|
rgNegativePromptChanged,
|
||||||
rgFillChanged,
|
rgFillChanged,
|
||||||
@ -270,10 +459,6 @@ export const {
|
|||||||
rgIPAdapterMethodChanged,
|
rgIPAdapterMethodChanged,
|
||||||
rgIPAdapterModelChanged,
|
rgIPAdapterModelChanged,
|
||||||
rgIPAdapterCLIPVisionModelChanged,
|
rgIPAdapterCLIPVisionModelChanged,
|
||||||
rgBrushLineAdded,
|
|
||||||
rgEraserLineAdded,
|
|
||||||
rgRectAdded,
|
|
||||||
rgRasterized,
|
|
||||||
// Compositing
|
// Compositing
|
||||||
setInfillMethod,
|
setInfillMethod,
|
||||||
setInfillTileSize,
|
setInfillTileSize,
|
||||||
@ -319,16 +504,9 @@ export const {
|
|||||||
loraIsEnabledChanged,
|
loraIsEnabledChanged,
|
||||||
loraAllDeleted,
|
loraAllDeleted,
|
||||||
// Inpaint mask
|
// Inpaint mask
|
||||||
imReset,
|
|
||||||
imRecalled,
|
imRecalled,
|
||||||
imIsEnabledToggled,
|
|
||||||
imMoved,
|
|
||||||
imFillChanged,
|
imFillChanged,
|
||||||
imImageCacheChanged,
|
imImageCacheChanged,
|
||||||
imBrushLineAdded,
|
|
||||||
imEraserLineAdded,
|
|
||||||
imRectAdded,
|
|
||||||
inpaintMaskRasterized,
|
|
||||||
// Staging
|
// Staging
|
||||||
sessionStartedStaging,
|
sessionStartedStaging,
|
||||||
sessionImageStaged,
|
sessionImageStaged,
|
||||||
|
@ -1,24 +1,20 @@
|
|||||||
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||||
import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils';
|
|
||||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||||
import type { IRect } from 'konva/lib/types';
|
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import type { ControlNetModelConfig, ImageDTO, T2IAdapterModelConfig } from 'services/api/types';
|
import type { ControlNetModelConfig, ImageDTO, T2IAdapterModelConfig } from 'services/api/types';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
CanvasV2State,
|
|
||||||
CanvasControlAdapterState,
|
CanvasControlAdapterState,
|
||||||
|
CanvasControlNetState,
|
||||||
|
CanvasT2IAdapterState,
|
||||||
|
CanvasV2State,
|
||||||
ControlModeV2,
|
ControlModeV2,
|
||||||
ControlNetConfig,
|
ControlNetConfig,
|
||||||
CanvasControlNetState,
|
|
||||||
Filter,
|
Filter,
|
||||||
PositionChangedArg,
|
|
||||||
ProcessorConfig,
|
ProcessorConfig,
|
||||||
ScaleChangedArg,
|
|
||||||
T2IAdapterConfig,
|
T2IAdapterConfig,
|
||||||
CanvasT2IAdapterState,
|
|
||||||
} from './types';
|
} from './types';
|
||||||
import { buildControlAdapterProcessorV2, imageDTOToImageObject } from './types';
|
import { buildControlAdapterProcessorV2, imageDTOToImageObject } from './types';
|
||||||
|
|
||||||
@ -56,58 +52,6 @@ export const controlAdaptersReducers = {
|
|||||||
state.controlAdapters.entities.push(data);
|
state.controlAdapters.entities.push(data);
|
||||||
state.selectedEntityIdentifier = { type: 'control_adapter', id: data.id };
|
state.selectedEntityIdentifier = { type: 'control_adapter', id: data.id };
|
||||||
},
|
},
|
||||||
caIsEnabledToggled: (state, action: PayloadAction<{ id: string }>) => {
|
|
||||||
const { id } = action.payload;
|
|
||||||
const ca = selectCA(state, id);
|
|
||||||
if (!ca) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ca.isEnabled = !ca.isEnabled;
|
|
||||||
},
|
|
||||||
caTranslated: (state, action: PayloadAction<PositionChangedArg>) => {
|
|
||||||
const { id, position } = action.payload;
|
|
||||||
const ca = selectCA(state, id);
|
|
||||||
if (!ca) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ca.position = position;
|
|
||||||
},
|
|
||||||
caScaled: (state, action: PayloadAction<ScaleChangedArg>) => {
|
|
||||||
const { id, scale, position } = action.payload;
|
|
||||||
const ca = selectCA(state, id);
|
|
||||||
if (!ca) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (ca.imageObject) {
|
|
||||||
ca.imageObject.x *= scale;
|
|
||||||
ca.imageObject.y *= scale;
|
|
||||||
ca.imageObject.height *= scale;
|
|
||||||
ca.imageObject.width *= scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ca.processedImageObject) {
|
|
||||||
ca.processedImageObject.x *= scale;
|
|
||||||
ca.processedImageObject.y *= scale;
|
|
||||||
ca.processedImageObject.height *= scale;
|
|
||||||
ca.processedImageObject.width *= scale;
|
|
||||||
}
|
|
||||||
ca.position = position;
|
|
||||||
ca.bboxNeedsUpdate = true;
|
|
||||||
state.layers.imageCache = null;
|
|
||||||
},
|
|
||||||
caBboxChanged: (state, action: PayloadAction<{ id: string; bbox: IRect | null }>) => {
|
|
||||||
const { id, bbox } = action.payload;
|
|
||||||
const ca = selectCA(state, id);
|
|
||||||
if (!ca) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ca.bbox = bbox;
|
|
||||||
ca.bboxNeedsUpdate = false;
|
|
||||||
},
|
|
||||||
caDeleted: (state, action: PayloadAction<{ id: string }>) => {
|
|
||||||
const { id } = action.payload;
|
|
||||||
state.controlAdapters.entities = state.controlAdapters.entities.filter((ca) => ca.id !== id);
|
|
||||||
},
|
|
||||||
caAllDeleted: (state) => {
|
caAllDeleted: (state) => {
|
||||||
state.controlAdapters.entities = [];
|
state.controlAdapters.entities = [];
|
||||||
},
|
},
|
||||||
@ -119,38 +63,6 @@ export const controlAdaptersReducers = {
|
|||||||
}
|
}
|
||||||
ca.opacity = opacity;
|
ca.opacity = opacity;
|
||||||
},
|
},
|
||||||
caMovedForwardOne: (state, action: PayloadAction<{ id: string }>) => {
|
|
||||||
const { id } = action.payload;
|
|
||||||
const ca = selectCA(state, id);
|
|
||||||
if (!ca) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
moveOneToEnd(state.controlAdapters.entities, ca);
|
|
||||||
},
|
|
||||||
caMovedToFront: (state, action: PayloadAction<{ id: string }>) => {
|
|
||||||
const { id } = action.payload;
|
|
||||||
const ca = selectCA(state, id);
|
|
||||||
if (!ca) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
moveToEnd(state.controlAdapters.entities, ca);
|
|
||||||
},
|
|
||||||
caMovedBackwardOne: (state, action: PayloadAction<{ id: string }>) => {
|
|
||||||
const { id } = action.payload;
|
|
||||||
const ca = selectCA(state, id);
|
|
||||||
if (!ca) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
moveOneToStart(state.controlAdapters.entities, ca);
|
|
||||||
},
|
|
||||||
caMovedToBack: (state, action: PayloadAction<{ id: string }>) => {
|
|
||||||
const { id } = action.payload;
|
|
||||||
const ca = selectCA(state, id);
|
|
||||||
if (!ca) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
moveToStart(state.controlAdapters.entities, ca);
|
|
||||||
},
|
|
||||||
caImageChanged: {
|
caImageChanged: {
|
||||||
reducer: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null; objectId: string }>) => {
|
reducer: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null; objectId: string }>) => {
|
||||||
const { id, imageDTO, objectId } = action.payload;
|
const { id, imageDTO, objectId } = action.payload;
|
||||||
|
@ -1,35 +1,16 @@
|
|||||||
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||||
import type {
|
import type { CanvasInpaintMaskState, CanvasV2State } from 'features/controlLayers/store/types';
|
||||||
CanvasBrushLineState,
|
|
||||||
CanvasEraserLineState,
|
|
||||||
CanvasInpaintMaskState,
|
|
||||||
CanvasRectState,
|
|
||||||
CanvasV2State,
|
|
||||||
Coordinate,
|
|
||||||
EntityRasterizedArg,
|
|
||||||
} from 'features/controlLayers/store/types';
|
|
||||||
import { imageDTOToImageWithDims } from 'features/controlLayers/store/types';
|
import { imageDTOToImageWithDims } from 'features/controlLayers/store/types';
|
||||||
import type { ImageDTO } from 'services/api/types';
|
import type { ImageDTO } from 'services/api/types';
|
||||||
|
|
||||||
import type { RgbColor } from './types';
|
import type { RgbColor } from './types';
|
||||||
|
|
||||||
export const inpaintMaskReducers = {
|
export const inpaintMaskReducers = {
|
||||||
imReset: (state) => {
|
|
||||||
state.inpaintMask.objects = [];
|
|
||||||
state.inpaintMask.imageCache = null;
|
|
||||||
},
|
|
||||||
imRecalled: (state, action: PayloadAction<{ data: CanvasInpaintMaskState }>) => {
|
imRecalled: (state, action: PayloadAction<{ data: CanvasInpaintMaskState }>) => {
|
||||||
const { data } = action.payload;
|
const { data } = action.payload;
|
||||||
state.inpaintMask = data;
|
state.inpaintMask = data;
|
||||||
state.selectedEntityIdentifier = { type: 'inpaint_mask', id: data.id };
|
state.selectedEntityIdentifier = { type: 'inpaint_mask', id: data.id };
|
||||||
},
|
},
|
||||||
imIsEnabledToggled: (state) => {
|
|
||||||
state.inpaintMask.isEnabled = !state.inpaintMask.isEnabled;
|
|
||||||
},
|
|
||||||
imMoved: (state, action: PayloadAction<{ position: Coordinate }>) => {
|
|
||||||
const { position } = action.payload;
|
|
||||||
state.inpaintMask.position = position;
|
|
||||||
},
|
|
||||||
imFillChanged: (state, action: PayloadAction<{ fill: RgbColor }>) => {
|
imFillChanged: (state, action: PayloadAction<{ fill: RgbColor }>) => {
|
||||||
const { fill } = action.payload;
|
const { fill } = action.payload;
|
||||||
state.inpaintMask.fill = fill;
|
state.inpaintMask.fill = fill;
|
||||||
@ -38,25 +19,4 @@ export const inpaintMaskReducers = {
|
|||||||
const { imageDTO } = action.payload;
|
const { imageDTO } = action.payload;
|
||||||
state.inpaintMask.imageCache = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
state.inpaintMask.imageCache = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||||
},
|
},
|
||||||
imBrushLineAdded: (state, action: PayloadAction<{ brushLine: CanvasBrushLineState }>) => {
|
|
||||||
const { brushLine } = action.payload;
|
|
||||||
state.inpaintMask.objects.push(brushLine);
|
|
||||||
state.layers.imageCache = null;
|
|
||||||
},
|
|
||||||
imEraserLineAdded: (state, action: PayloadAction<{ eraserLine: CanvasEraserLineState }>) => {
|
|
||||||
const { eraserLine } = action.payload;
|
|
||||||
state.inpaintMask.objects.push(eraserLine);
|
|
||||||
state.layers.imageCache = null;
|
|
||||||
},
|
|
||||||
imRectAdded: (state, action: PayloadAction<{ rect: CanvasRectState }>) => {
|
|
||||||
const { rect } = action.payload;
|
|
||||||
state.inpaintMask.objects.push(rect);
|
|
||||||
state.layers.imageCache = null;
|
|
||||||
},
|
|
||||||
inpaintMaskRasterized: (state, action: PayloadAction<EntityRasterizedArg>) => {
|
|
||||||
const { imageObject, position } = action.payload;
|
|
||||||
state.inpaintMask.objects = [imageObject];
|
|
||||||
state.inpaintMask.position = position;
|
|
||||||
state.inpaintMask.imageCache = null;
|
|
||||||
},
|
|
||||||
} satisfies SliceCaseReducers<CanvasV2State>;
|
} satisfies SliceCaseReducers<CanvasV2State>;
|
||||||
|
@ -1,21 +1,10 @@
|
|||||||
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||||
import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils';
|
|
||||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||||
import type { IRect } from 'konva/lib/types';
|
|
||||||
import { merge } from 'lodash-es';
|
import { merge } from 'lodash-es';
|
||||||
import type { ImageDTO } from 'services/api/types';
|
import type { ImageDTO } from 'services/api/types';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
import type {
|
import type { CanvasLayerState, CanvasV2State, ImageObjectAddedArg } from './types';
|
||||||
CanvasBrushLineState,
|
|
||||||
CanvasEraserLineState,
|
|
||||||
CanvasLayerState,
|
|
||||||
CanvasRectState,
|
|
||||||
CanvasV2State,
|
|
||||||
EntityRasterizedArg,
|
|
||||||
ImageObjectAddedArg,
|
|
||||||
PositionChangedArg,
|
|
||||||
} from './types';
|
|
||||||
import { imageDTOToImageObject, imageDTOToImageWithDims } from './types';
|
import { imageDTOToImageObject, imageDTOToImageWithDims } from './types';
|
||||||
|
|
||||||
export const selectLayer = (state: CanvasV2State, id: string) => state.layers.entities.find((layer) => layer.id === id);
|
export const selectLayer = (state: CanvasV2State, id: string) => state.layers.entities.find((layer) => layer.id === id);
|
||||||
@ -52,52 +41,6 @@ export const layersReducers = {
|
|||||||
state.selectedEntityIdentifier = { type: 'layer', id: data.id };
|
state.selectedEntityIdentifier = { type: 'layer', id: data.id };
|
||||||
state.layers.imageCache = null;
|
state.layers.imageCache = null;
|
||||||
},
|
},
|
||||||
layerIsEnabledToggled: (state, action: PayloadAction<{ id: string }>) => {
|
|
||||||
const { id } = action.payload;
|
|
||||||
const layer = selectLayer(state, id);
|
|
||||||
if (!layer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
layer.isEnabled = !layer.isEnabled;
|
|
||||||
state.layers.imageCache = null;
|
|
||||||
},
|
|
||||||
layerTranslated: (state, action: PayloadAction<PositionChangedArg>) => {
|
|
||||||
const { id, position } = action.payload;
|
|
||||||
const layer = selectLayer(state, id);
|
|
||||||
if (!layer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
layer.position = position;
|
|
||||||
state.layers.imageCache = null;
|
|
||||||
},
|
|
||||||
layerBboxChanged: (state, action: PayloadAction<{ id: string; bbox: IRect | null }>) => {
|
|
||||||
const { id, bbox } = action.payload;
|
|
||||||
const layer = selectLayer(state, id);
|
|
||||||
if (!layer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (bbox === null) {
|
|
||||||
// TODO(psyche): Clear objects when bbox is cleared - right now this doesn't work bc bbox calculation for layers
|
|
||||||
// doesn't work - always returns null
|
|
||||||
// layer.objects = [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
layerReset: (state, action: PayloadAction<{ id: string }>) => {
|
|
||||||
const { id } = action.payload;
|
|
||||||
const layer = selectLayer(state, id);
|
|
||||||
if (!layer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
layer.isEnabled = true;
|
|
||||||
layer.objects = [];
|
|
||||||
state.layers.imageCache = null;
|
|
||||||
layer.position = { x: 0, y: 0 };
|
|
||||||
},
|
|
||||||
layerDeleted: (state, action: PayloadAction<{ id: string }>) => {
|
|
||||||
const { id } = action.payload;
|
|
||||||
state.layers.entities = state.layers.entities.filter((l) => l.id !== id);
|
|
||||||
state.layers.imageCache = null;
|
|
||||||
},
|
|
||||||
layerAllDeleted: (state) => {
|
layerAllDeleted: (state) => {
|
||||||
state.layers.entities = [];
|
state.layers.entities = [];
|
||||||
state.layers.imageCache = null;
|
state.layers.imageCache = null;
|
||||||
@ -111,72 +54,6 @@ export const layersReducers = {
|
|||||||
layer.opacity = opacity;
|
layer.opacity = opacity;
|
||||||
state.layers.imageCache = null;
|
state.layers.imageCache = null;
|
||||||
},
|
},
|
||||||
layerMovedForwardOne: (state, action: PayloadAction<{ id: string }>) => {
|
|
||||||
const { id } = action.payload;
|
|
||||||
const layer = selectLayer(state, id);
|
|
||||||
if (!layer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
moveOneToEnd(state.layers.entities, layer);
|
|
||||||
state.layers.imageCache = null;
|
|
||||||
},
|
|
||||||
layerMovedToFront: (state, action: PayloadAction<{ id: string }>) => {
|
|
||||||
const { id } = action.payload;
|
|
||||||
const layer = selectLayer(state, id);
|
|
||||||
if (!layer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
moveToEnd(state.layers.entities, layer);
|
|
||||||
state.layers.imageCache = null;
|
|
||||||
},
|
|
||||||
layerMovedBackwardOne: (state, action: PayloadAction<{ id: string }>) => {
|
|
||||||
const { id } = action.payload;
|
|
||||||
const layer = selectLayer(state, id);
|
|
||||||
if (!layer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
moveOneToStart(state.layers.entities, layer);
|
|
||||||
state.layers.imageCache = null;
|
|
||||||
},
|
|
||||||
layerMovedToBack: (state, action: PayloadAction<{ id: string }>) => {
|
|
||||||
const { id } = action.payload;
|
|
||||||
const layer = selectLayer(state, id);
|
|
||||||
if (!layer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
moveToStart(state.layers.entities, layer);
|
|
||||||
state.layers.imageCache = null;
|
|
||||||
},
|
|
||||||
layerBrushLineAdded: (state, action: PayloadAction<{ id: string; brushLine: CanvasBrushLineState }>) => {
|
|
||||||
const { id, brushLine } = action.payload;
|
|
||||||
const layer = selectLayer(state, id);
|
|
||||||
if (!layer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
layer.objects.push(brushLine);
|
|
||||||
state.layers.imageCache = null;
|
|
||||||
},
|
|
||||||
layerEraserLineAdded: (state, action: PayloadAction<{ id: string; eraserLine: CanvasEraserLineState }>) => {
|
|
||||||
const { id, eraserLine } = action.payload;
|
|
||||||
const layer = selectLayer(state, id);
|
|
||||||
if (!layer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
layer.objects.push(eraserLine);
|
|
||||||
state.layers.imageCache = null;
|
|
||||||
},
|
|
||||||
layerRectAdded: (state, action: PayloadAction<{ id: string; rect: CanvasRectState }>) => {
|
|
||||||
const { id, rect } = action.payload;
|
|
||||||
const layer = selectLayer(state, id);
|
|
||||||
if (!layer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
layer.objects.push(rect);
|
|
||||||
state.layers.imageCache = null;
|
|
||||||
},
|
|
||||||
layerImageAdded: (
|
layerImageAdded: (
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<ImageObjectAddedArg & { objectId: string; pos?: { x: number; y: number } }>
|
action: PayloadAction<ImageObjectAddedArg & { objectId: string; pos?: { x: number; y: number } }>
|
||||||
@ -198,14 +75,4 @@ export const layersReducers = {
|
|||||||
const { imageDTO } = action.payload;
|
const { imageDTO } = action.payload;
|
||||||
state.layers.imageCache = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
state.layers.imageCache = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||||
},
|
},
|
||||||
layerRasterized: (state, action: PayloadAction<EntityRasterizedArg>) => {
|
|
||||||
const { id, imageObject, position } = action.payload;
|
|
||||||
const layer = selectLayer(state, id);
|
|
||||||
if (!layer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
layer.objects = [imageObject];
|
|
||||||
layer.position = position;
|
|
||||||
state.layers.imageCache = null;
|
|
||||||
},
|
|
||||||
} satisfies SliceCaseReducers<CanvasV2State>;
|
} satisfies SliceCaseReducers<CanvasV2State>;
|
||||||
|
@ -1,16 +1,6 @@
|
|||||||
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||||
import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils';
|
|
||||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||||
import type {
|
import type { CanvasV2State, CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types';
|
||||||
CanvasBrushLineState,
|
|
||||||
CanvasEraserLineState,
|
|
||||||
CanvasRectState,
|
|
||||||
CanvasV2State,
|
|
||||||
CLIPVisionModelV2,
|
|
||||||
EntityRasterizedArg,
|
|
||||||
IPMethodV2,
|
|
||||||
PositionChangedArg,
|
|
||||||
} from 'features/controlLayers/store/types';
|
|
||||||
import { imageDTOToImageObject, imageDTOToImageWithDims } from 'features/controlLayers/store/types';
|
import { imageDTOToImageObject, imageDTOToImageWithDims } from 'features/controlLayers/store/types';
|
||||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||||
import type { ParameterAutoNegative } from 'features/parameters/types/parameterSchemas';
|
import type { ParameterAutoNegative } from 'features/parameters/types/parameterSchemas';
|
||||||
@ -71,73 +61,14 @@ export const regionsReducers = {
|
|||||||
},
|
},
|
||||||
prepare: () => ({ payload: { id: getPrefixedId('regional_guidance') } }),
|
prepare: () => ({ payload: { id: getPrefixedId('regional_guidance') } }),
|
||||||
},
|
},
|
||||||
rgReset: (state, action: PayloadAction<{ id: string }>) => {
|
|
||||||
const { id } = action.payload;
|
|
||||||
const rg = selectRG(state, id);
|
|
||||||
if (!rg) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rg.objects = [];
|
|
||||||
rg.imageCache = null;
|
|
||||||
},
|
|
||||||
rgRecalled: (state, action: PayloadAction<{ data: CanvasRegionalGuidanceState }>) => {
|
rgRecalled: (state, action: PayloadAction<{ data: CanvasRegionalGuidanceState }>) => {
|
||||||
const { data } = action.payload;
|
const { data } = action.payload;
|
||||||
state.regions.entities.push(data);
|
state.regions.entities.push(data);
|
||||||
state.selectedEntityIdentifier = { type: 'regional_guidance', id: data.id };
|
state.selectedEntityIdentifier = { type: 'regional_guidance', id: data.id };
|
||||||
},
|
},
|
||||||
rgIsEnabledToggled: (state, action: PayloadAction<{ id: string }>) => {
|
|
||||||
const { id } = action.payload;
|
|
||||||
const rg = selectRG(state, id);
|
|
||||||
if (rg) {
|
|
||||||
rg.isEnabled = !rg.isEnabled;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rgMoved: (state, action: PayloadAction<PositionChangedArg>) => {
|
|
||||||
const { id, position } = action.payload;
|
|
||||||
const rg = selectRG(state, id);
|
|
||||||
if (rg) {
|
|
||||||
rg.position = position;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rgDeleted: (state, action: PayloadAction<{ id: string }>) => {
|
|
||||||
const { id } = action.payload;
|
|
||||||
state.regions.entities = state.regions.entities.filter((ca) => ca.id !== id);
|
|
||||||
},
|
|
||||||
rgAllDeleted: (state) => {
|
rgAllDeleted: (state) => {
|
||||||
state.regions.entities = [];
|
state.regions.entities = [];
|
||||||
},
|
},
|
||||||
rgMovedForwardOne: (state, action: PayloadAction<{ id: string }>) => {
|
|
||||||
const { id } = action.payload;
|
|
||||||
const rg = selectRG(state, id);
|
|
||||||
if (!rg) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
moveOneToEnd(state.regions.entities, rg);
|
|
||||||
},
|
|
||||||
rgMovedToFront: (state, action: PayloadAction<{ id: string }>) => {
|
|
||||||
const { id } = action.payload;
|
|
||||||
const rg = selectRG(state, id);
|
|
||||||
if (!rg) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
moveToEnd(state.regions.entities, rg);
|
|
||||||
},
|
|
||||||
rgMovedBackwardOne: (state, action: PayloadAction<{ id: string }>) => {
|
|
||||||
const { id } = action.payload;
|
|
||||||
const rg = selectRG(state, id);
|
|
||||||
if (!rg) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
moveOneToStart(state.regions.entities, rg);
|
|
||||||
},
|
|
||||||
rgMovedToBack: (state, action: PayloadAction<{ id: string }>) => {
|
|
||||||
const { id } = action.payload;
|
|
||||||
const rg = selectRG(state, id);
|
|
||||||
if (!rg) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
moveToStart(state.regions.entities, rg);
|
|
||||||
},
|
|
||||||
rgPositivePromptChanged: (state, action: PayloadAction<{ id: string; prompt: string | null }>) => {
|
rgPositivePromptChanged: (state, action: PayloadAction<{ id: string; prompt: string | null }>) => {
|
||||||
const { id, prompt } = action.payload;
|
const { id, prompt } = action.payload;
|
||||||
const rg = selectRG(state, id);
|
const rg = selectRG(state, id);
|
||||||
@ -286,44 +217,4 @@ export const regionsReducers = {
|
|||||||
}
|
}
|
||||||
ipa.clipVisionModel = clipVisionModel;
|
ipa.clipVisionModel = clipVisionModel;
|
||||||
},
|
},
|
||||||
rgBrushLineAdded: (state, action: PayloadAction<{ id: string; brushLine: CanvasBrushLineState }>) => {
|
|
||||||
const { id, brushLine } = action.payload;
|
|
||||||
const rg = selectRG(state, id);
|
|
||||||
if (!rg) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
rg.objects.push(brushLine);
|
|
||||||
state.layers.imageCache = null;
|
|
||||||
},
|
|
||||||
rgEraserLineAdded: (state, action: PayloadAction<{ id: string; eraserLine: CanvasEraserLineState }>) => {
|
|
||||||
const { id, eraserLine } = action.payload;
|
|
||||||
const rg = selectRG(state, id);
|
|
||||||
if (!rg) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
rg.objects.push(eraserLine);
|
|
||||||
state.layers.imageCache = null;
|
|
||||||
},
|
|
||||||
rgRectAdded: (state, action: PayloadAction<{ id: string; rect: CanvasRectState }>) => {
|
|
||||||
const { id, rect } = action.payload;
|
|
||||||
const rg = selectRG(state, id);
|
|
||||||
if (!rg) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
rg.objects.push(rect);
|
|
||||||
state.layers.imageCache = null;
|
|
||||||
},
|
|
||||||
rgRasterized: (state, action: PayloadAction<EntityRasterizedArg>) => {
|
|
||||||
const { id, imageObject, position } = action.payload;
|
|
||||||
const rg = selectRG(state, id);
|
|
||||||
if (!rg) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rg.objects = [imageObject];
|
|
||||||
rg.position = position;
|
|
||||||
rg.imageCache = null;
|
|
||||||
},
|
|
||||||
} satisfies SliceCaseReducers<CanvasV2State>;
|
} satisfies SliceCaseReducers<CanvasV2State>;
|
||||||
|
@ -924,11 +924,20 @@ export type PositionChangedArg = { id: string; position: Coordinate };
|
|||||||
export type ScaleChangedArg = { id: string; scale: Coordinate; position: Coordinate };
|
export type ScaleChangedArg = { id: string; scale: Coordinate; position: Coordinate };
|
||||||
export type BboxChangedArg = { id: string; bbox: Rect | null };
|
export type BboxChangedArg = { id: string; bbox: Rect | null };
|
||||||
|
|
||||||
export type BrushLineAddedArg = { id: string; brushLine: CanvasBrushLineState };
|
export type EntityIdentifierPayload = { entityIdentifier: CanvasEntityIdentifier };
|
||||||
export type EraserLineAddedArg = { id: string; eraserLine: CanvasEraserLineState };
|
export type EntityMovedPayload = { entityIdentifier: CanvasEntityIdentifier; position: Coordinate };
|
||||||
export type RectAddedArg = { id: string; rect: CanvasRectState };
|
export type EntityBrushLineAddedPayload = { entityIdentifier: CanvasEntityIdentifier; brushLine: CanvasBrushLineState };
|
||||||
|
export type EntityEraserLineAddedPayload = {
|
||||||
|
entityIdentifier: CanvasEntityIdentifier;
|
||||||
|
eraserLine: CanvasEraserLineState;
|
||||||
|
};
|
||||||
|
export type EntityRectAddedPayload = { entityIdentifier: CanvasEntityIdentifier; rect: CanvasRectState };
|
||||||
|
export type EntityRasterizedPayload = {
|
||||||
|
entityIdentifier: CanvasEntityIdentifier;
|
||||||
|
imageObject: CanvasImageState;
|
||||||
|
position: Coordinate;
|
||||||
|
};
|
||||||
export type ImageObjectAddedArg = { id: string; imageDTO: ImageDTO; position?: Coordinate };
|
export type ImageObjectAddedArg = { id: string; imageDTO: ImageDTO; position?: Coordinate };
|
||||||
export type EntityRasterizedArg = { id: string; imageObject: CanvasImageState; position: Coordinate };
|
|
||||||
|
|
||||||
//#region Type guards
|
//#region Type guards
|
||||||
export const isLine = (obj: CanvasObjectState): obj is CanvasBrushLineState | CanvasEraserLineState => {
|
export const isLine = (obj: CanvasObjectState): obj is CanvasBrushLineState | CanvasEraserLineState => {
|
||||||
|
Loading…
Reference in New Issue
Block a user