mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-04 03:15:06 -05:00
Merge branch 'main' into sdxl_t2i_bgr
This commit is contained in:
@@ -1682,6 +1682,8 @@
|
||||
"controlLayer": "Control Layer",
|
||||
"inpaintMask": "Inpaint Mask",
|
||||
"regionalGuidance": "Regional Guidance",
|
||||
"canvasAsRasterLayer": "$t(controlLayers.canvas) as $t(controlLayers.rasterLayer)",
|
||||
"canvasAsControlLayer": "$t(controlLayers.canvas) as $t(controlLayers.controlLayer)",
|
||||
"referenceImage": "Reference Image",
|
||||
"regionalReferenceImage": "Regional Reference Image",
|
||||
"globalReferenceImage": "Global Reference Image",
|
||||
@@ -1894,6 +1896,7 @@
|
||||
"include": "Include",
|
||||
"exclude": "Exclude",
|
||||
"neutral": "Neutral",
|
||||
"apply": "Apply",
|
||||
"reset": "Reset",
|
||||
"saveAs": "Save As",
|
||||
"cancel": "Cancel",
|
||||
|
||||
@@ -13,7 +13,7 @@ export const CanvasAlertsPreserveMask = memo(() => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Alert status="warning" borderRadius="base" fontSize="sm" shadow="md" w="fit-content" alignSelf="flex-end">
|
||||
<Alert status="warning" borderRadius="base" fontSize="sm" shadow="md" w="fit-content">
|
||||
<AlertIcon />
|
||||
<AlertTitle>{t('controlLayers.settings.preserveMask.alert')}</AlertTitle>
|
||||
</Alert>
|
||||
|
||||
@@ -98,7 +98,7 @@ const CanvasAlertsSelectedEntityStatusContent = memo(({ entityIdentifier, adapte
|
||||
}
|
||||
|
||||
return (
|
||||
<Alert status={alert.status} borderRadius="base" fontSize="sm" shadow="md" w="fit-content" alignSelf="flex-end">
|
||||
<Alert status={alert.status} borderRadius="base" fontSize="sm" shadow="md" w="fit-content">
|
||||
<AlertIcon />
|
||||
<AlertTitle>{alert.title}</AlertTitle>
|
||||
</Alert>
|
||||
|
||||
@@ -132,7 +132,6 @@ const AlertWrapper = ({
|
||||
fontSize="sm"
|
||||
shadow="md"
|
||||
w="fit-content"
|
||||
alignSelf="flex-end"
|
||||
>
|
||||
<Flex w="full" alignItems="center">
|
||||
<AlertIcon />
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { MenuGroup } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { ControlLayerMenuItems } from 'features/controlLayers/components/ControlLayer/ControlLayerMenuItems';
|
||||
import { InpaintMaskMenuItems } from 'features/controlLayers/components/InpaintMask/InpaintMaskMenuItems';
|
||||
@@ -8,7 +9,9 @@ import {
|
||||
EntityIdentifierContext,
|
||||
useEntityIdentifierContext,
|
||||
} from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { useEntityTypeString } from 'features/controlLayers/hooks/useEntityTypeString';
|
||||
import { selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { memo } from 'react';
|
||||
import type { Equals } from 'tsafe';
|
||||
import { assert } from 'tsafe';
|
||||
@@ -46,9 +49,20 @@ export const CanvasContextMenuSelectedEntityMenuItems = memo(() => {
|
||||
|
||||
return (
|
||||
<EntityIdentifierContext.Provider value={selectedEntityIdentifier}>
|
||||
<CanvasContextMenuSelectedEntityMenuItemsContent />
|
||||
<CanvasContextMenuSelectedEntityMenuGroup>
|
||||
<CanvasContextMenuSelectedEntityMenuItemsContent />
|
||||
</CanvasContextMenuSelectedEntityMenuGroup>
|
||||
</EntityIdentifierContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
CanvasContextMenuSelectedEntityMenuItems.displayName = 'CanvasContextMenuSelectedEntityMenuItems';
|
||||
|
||||
const CanvasContextMenuSelectedEntityMenuGroup = memo((props: PropsWithChildren) => {
|
||||
const entityIdentifier = useEntityIdentifierContext();
|
||||
const title = useEntityTypeString(entityIdentifier.type);
|
||||
|
||||
return <MenuGroup title={title}>{props.children}</MenuGroup>;
|
||||
});
|
||||
|
||||
CanvasContextMenuSelectedEntityMenuGroup.displayName = 'CanvasContextMenuSelectedEntityMenuGroup';
|
||||
|
||||
@@ -71,12 +71,16 @@ export const CanvasMainPanelContent = memo(() => {
|
||||
>
|
||||
<InvokeCanvasComponent />
|
||||
<CanvasManagerProviderGate>
|
||||
{showHUD && (
|
||||
<Flex position="absolute" top={1} insetInlineStart={1} pointerEvents="none">
|
||||
<CanvasHUD />
|
||||
</Flex>
|
||||
)}
|
||||
<Flex flexDir="column" position="absolute" top={1} insetInlineEnd={1} pointerEvents="none" gap={2}>
|
||||
<Flex
|
||||
position="absolute"
|
||||
flexDir="column"
|
||||
top={1}
|
||||
insetInlineStart={1}
|
||||
pointerEvents="none"
|
||||
gap={2}
|
||||
alignItems="flex-start"
|
||||
>
|
||||
{showHUD && <CanvasHUD />}
|
||||
<CanvasAlertsSelectedEntityStatus />
|
||||
<CanvasAlertsPreserveMask />
|
||||
<CanvasAlertsSendingToGallery />
|
||||
|
||||
@@ -27,11 +27,10 @@ export const ControlLayerMenuItems = memo(() => {
|
||||
<CanvasEntityMenuItemsSelectObject />
|
||||
<ControlLayerMenuItemsTransparencyEffect />
|
||||
<MenuDivider />
|
||||
<ControlLayerMenuItemsCopyToSubMenu />
|
||||
<ControlLayerMenuItemsConvertToSubMenu />
|
||||
<CanvasEntityMenuItemsCropToBbox />
|
||||
<CanvasEntityMenuItemsSave />
|
||||
<MenuDivider />
|
||||
<ControlLayerMenuItemsConvertToSubMenu />
|
||||
<ControlLayerMenuItemsCopyToSubMenu />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
MenuItem,
|
||||
MenuList,
|
||||
Spacer,
|
||||
Spinner,
|
||||
} from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
@@ -25,7 +26,7 @@ import { IMAGE_FILTERS } from 'features/controlLayers/store/filters';
|
||||
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
|
||||
import { memo, useCallback, useMemo, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowsCounterClockwiseBold, PiFloppyDiskBold, PiPlayFill, PiXBold } from 'react-icons/pi';
|
||||
import { PiCaretDownBold } from 'react-icons/pi';
|
||||
|
||||
const FilterContent = memo(
|
||||
({ adapter }: { adapter: CanvasEntityAdapterRasterLayer | CanvasEntityAdapterControlLayer }) => {
|
||||
@@ -115,39 +116,41 @@ const FilterContent = memo(
|
||||
<ButtonGroup isAttached={false} size="sm" w="full">
|
||||
<Button
|
||||
variant="ghost"
|
||||
leftIcon={<PiPlayFill />}
|
||||
onClick={adapter.filterer.processImmediate}
|
||||
isLoading={isProcessing}
|
||||
loadingText={t('controlLayers.filter.process')}
|
||||
isDisabled={!isValid || autoProcess}
|
||||
isDisabled={isProcessing || !isValid || autoProcess}
|
||||
>
|
||||
{t('controlLayers.filter.process')}
|
||||
{isProcessing && <Spinner ms={3} boxSize={5} color="base.600" />}
|
||||
</Button>
|
||||
<Spacer />
|
||||
<Button
|
||||
leftIcon={<PiArrowsCounterClockwiseBold />}
|
||||
onClick={adapter.filterer.reset}
|
||||
isLoading={isProcessing}
|
||||
isDisabled={isProcessing}
|
||||
loadingText={t('controlLayers.filter.reset')}
|
||||
variant="ghost"
|
||||
>
|
||||
{t('controlLayers.filter.reset')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={adapter.filterer.apply}
|
||||
loadingText={t('controlLayers.filter.apply')}
|
||||
variant="ghost"
|
||||
isDisabled={isProcessing || !isValid || !hasProcessed}
|
||||
>
|
||||
{t('controlLayers.filter.apply')}
|
||||
</Button>
|
||||
<Menu>
|
||||
<MenuButton
|
||||
as={Button}
|
||||
leftIcon={<PiFloppyDiskBold />}
|
||||
isLoading={isProcessing}
|
||||
loadingText={t('controlLayers.selectObject.saveAs')}
|
||||
variant="ghost"
|
||||
isDisabled={!isValid || !hasProcessed}
|
||||
isDisabled={isProcessing || !isValid || !hasProcessed}
|
||||
rightIcon={<PiCaretDownBold />}
|
||||
>
|
||||
{t('controlLayers.selectObject.saveAs')}
|
||||
</MenuButton>
|
||||
<MenuList>
|
||||
<MenuItem isDisabled={!isValid || !hasProcessed} onClick={adapter.filterer.apply}>
|
||||
{t('controlLayers.replaceCurrent')}
|
||||
</MenuItem>
|
||||
<MenuItem isDisabled={!isValid || !hasProcessed} onClick={saveAsInpaintMask}>
|
||||
{t('controlLayers.newInpaintMask')}
|
||||
</MenuItem>
|
||||
@@ -162,12 +165,7 @@ const FilterContent = memo(
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
<Button
|
||||
variant="ghost"
|
||||
leftIcon={<PiXBold />}
|
||||
onClick={adapter.filterer.cancel}
|
||||
loadingText={t('controlLayers.filter.cancel')}
|
||||
>
|
||||
<Button variant="ghost" onClick={adapter.filterer.cancel} loadingText={t('controlLayers.filter.cancel')}>
|
||||
{t('controlLayers.filter.cancel')}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
|
||||
@@ -20,10 +20,9 @@ export const InpaintMaskMenuItems = memo(() => {
|
||||
<MenuDivider />
|
||||
<CanvasEntityMenuItemsTransform />
|
||||
<MenuDivider />
|
||||
<CanvasEntityMenuItemsCropToBbox />
|
||||
<MenuDivider />
|
||||
<InpaintMaskMenuItemsConvertToSubMenu />
|
||||
<InpaintMaskMenuItemsCopyToSubMenu />
|
||||
<InpaintMaskMenuItemsConvertToSubMenu />
|
||||
<CanvasEntityMenuItemsCropToBbox />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -25,11 +25,10 @@ export const RasterLayerMenuItems = memo(() => {
|
||||
<CanvasEntityMenuItemsFilter />
|
||||
<CanvasEntityMenuItemsSelectObject />
|
||||
<MenuDivider />
|
||||
<RasterLayerMenuItemsCopyToSubMenu />
|
||||
<RasterLayerMenuItemsConvertToSubMenu />
|
||||
<CanvasEntityMenuItemsCropToBbox />
|
||||
<CanvasEntityMenuItemsSave />
|
||||
<MenuDivider />
|
||||
<RasterLayerMenuItemsConvertToSubMenu />
|
||||
<RasterLayerMenuItemsCopyToSubMenu />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -25,10 +25,9 @@ export const RegionalGuidanceMenuItems = memo(() => {
|
||||
<CanvasEntityMenuItemsTransform />
|
||||
<RegionalGuidanceMenuItemsAutoNegative />
|
||||
<MenuDivider />
|
||||
<CanvasEntityMenuItemsCropToBbox />
|
||||
<MenuDivider />
|
||||
<RegionalGuidanceMenuItemsConvertToSubMenu />
|
||||
<RegionalGuidanceMenuItemsCopyToSubMenu />
|
||||
<RegionalGuidanceMenuItemsConvertToSubMenu />
|
||||
<CanvasEntityMenuItemsCropToBbox />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
MenuItem,
|
||||
MenuList,
|
||||
Spacer,
|
||||
Spinner,
|
||||
Text,
|
||||
Tooltip,
|
||||
UnorderedList,
|
||||
@@ -29,7 +30,7 @@ import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/us
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { PiArrowsCounterClockwiseBold, PiFloppyDiskBold, PiInfoBold, PiPlayFill, PiXBold } from 'react-icons/pi';
|
||||
import { PiCaretDownBold, PiInfoBold } from 'react-icons/pi';
|
||||
|
||||
const SelectObjectContent = memo(
|
||||
({ adapter }: { adapter: CanvasEntityAdapterRasterLayer | CanvasEntityAdapterControlLayer }) => {
|
||||
@@ -42,10 +43,6 @@ const SelectObjectContent = memo(
|
||||
const hasImageState = useStore(adapter.segmentAnything.$hasImageState);
|
||||
const autoProcess = useAppSelector(selectAutoProcess);
|
||||
|
||||
const replaceCurrent = useCallback(() => {
|
||||
adapter.segmentAnything.apply();
|
||||
}, [adapter.segmentAnything]);
|
||||
|
||||
const saveAsInpaintMask = useCallback(() => {
|
||||
adapter.segmentAnything.saveAs('inpaint_mask');
|
||||
}, [adapter.segmentAnything]);
|
||||
@@ -115,58 +112,59 @@ const SelectObjectContent = memo(
|
||||
|
||||
<ButtonGroup isAttached={false} size="sm" w="full">
|
||||
<Button
|
||||
leftIcon={<PiPlayFill />}
|
||||
onClick={adapter.segmentAnything.processImmediate}
|
||||
isLoading={isProcessing}
|
||||
loadingText={t('controlLayers.selectObject.process')}
|
||||
variant="ghost"
|
||||
isDisabled={!hasPoints || autoProcess}
|
||||
isDisabled={isProcessing || !hasPoints || autoProcess}
|
||||
>
|
||||
{t('controlLayers.selectObject.process')}
|
||||
{isProcessing && <Spinner ms={3} boxSize={5} color="base.600" />}
|
||||
</Button>
|
||||
<Spacer />
|
||||
<Button
|
||||
leftIcon={<PiArrowsCounterClockwiseBold />}
|
||||
onClick={adapter.segmentAnything.reset}
|
||||
isLoading={isProcessing}
|
||||
isDisabled={isProcessing || !hasPoints}
|
||||
loadingText={t('controlLayers.selectObject.reset')}
|
||||
variant="ghost"
|
||||
>
|
||||
{t('controlLayers.selectObject.reset')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={adapter.segmentAnything.apply}
|
||||
loadingText={t('controlLayers.selectObject.apply')}
|
||||
variant="ghost"
|
||||
isDisabled={isProcessing || !hasImageState}
|
||||
>
|
||||
{t('controlLayers.selectObject.apply')}
|
||||
</Button>
|
||||
<Menu>
|
||||
<MenuButton
|
||||
as={Button}
|
||||
leftIcon={<PiFloppyDiskBold />}
|
||||
isLoading={isProcessing}
|
||||
loadingText={t('controlLayers.selectObject.saveAs')}
|
||||
variant="ghost"
|
||||
isDisabled={!hasImageState}
|
||||
isDisabled={isProcessing || !hasImageState}
|
||||
rightIcon={<PiCaretDownBold />}
|
||||
>
|
||||
{t('controlLayers.selectObject.saveAs')}
|
||||
</MenuButton>
|
||||
<MenuList>
|
||||
<MenuItem isDisabled={!hasImageState} onClick={replaceCurrent}>
|
||||
{t('controlLayers.replaceCurrent')}
|
||||
</MenuItem>
|
||||
<MenuItem isDisabled={!hasImageState} onClick={saveAsInpaintMask}>
|
||||
<MenuItem isDisabled={isProcessing || !hasImageState} onClick={saveAsInpaintMask}>
|
||||
{t('controlLayers.newInpaintMask')}
|
||||
</MenuItem>
|
||||
<MenuItem isDisabled={!hasImageState} onClick={saveAsRegionalGuidance}>
|
||||
<MenuItem isDisabled={isProcessing || !hasImageState} onClick={saveAsRegionalGuidance}>
|
||||
{t('controlLayers.newRegionalGuidance')}
|
||||
</MenuItem>
|
||||
<MenuItem isDisabled={!hasImageState} onClick={saveAsControlLayer}>
|
||||
<MenuItem isDisabled={isProcessing || !hasImageState} onClick={saveAsControlLayer}>
|
||||
{t('controlLayers.newControlLayer')}
|
||||
</MenuItem>
|
||||
<MenuItem isDisabled={!hasImageState} onClick={saveAsRasterLayer}>
|
||||
<MenuItem isDisabled={isProcessing || !hasImageState} onClick={saveAsRasterLayer}>
|
||||
{t('controlLayers.newRasterLayer')}
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
<Button
|
||||
leftIcon={<PiXBold />}
|
||||
onClick={adapter.segmentAnything.cancel}
|
||||
isLoading={isProcessing}
|
||||
isDisabled={isProcessing}
|
||||
loadingText={t('common.cancel')}
|
||||
variant="ghost"
|
||||
>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button, ButtonGroup, Flex, Heading, Spacer } from '@invoke-ai/ui-library';
|
||||
import { Button, ButtonGroup, Flex, Heading, Spacer, Spinner } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useFocusRegion, useIsRegionFocused } from 'common/hooks/focus';
|
||||
import { CanvasOperationIsolatedLayerPreviewSwitch } from 'features/controlLayers/components/CanvasOperationIsolatedLayerPreviewSwitch';
|
||||
@@ -8,7 +8,6 @@ import type { CanvasEntityAdapter } from 'features/controlLayers/konva/CanvasEnt
|
||||
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
|
||||
import { memo, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowsCounterClockwiseBold, PiCheckBold, PiXBold } from 'react-icons/pi';
|
||||
|
||||
const TransformContent = memo(({ adapter }: { adapter: CanvasEntityAdapter }) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -62,30 +61,28 @@ const TransformContent = memo(({ adapter }: { adapter: CanvasEntityAdapter }) =>
|
||||
|
||||
<TransformFitToBboxButtons adapter={adapter} />
|
||||
|
||||
<ButtonGroup isAttached={false} size="sm" w="full">
|
||||
<ButtonGroup isAttached={false} size="sm" w="full" alignItems="center">
|
||||
{isProcessing && <Spinner ms={3} boxSize={5} color="base.600" />}
|
||||
<Spacer />
|
||||
<Button
|
||||
leftIcon={<PiArrowsCounterClockwiseBold />}
|
||||
onClick={adapter.transformer.resetTransform}
|
||||
isLoading={isProcessing}
|
||||
isDisabled={isProcessing}
|
||||
loadingText={t('controlLayers.transform.reset')}
|
||||
variant="ghost"
|
||||
>
|
||||
{t('controlLayers.transform.reset')}
|
||||
</Button>
|
||||
<Button
|
||||
leftIcon={<PiCheckBold />}
|
||||
onClick={adapter.transformer.applyTransform}
|
||||
isLoading={isProcessing}
|
||||
isDisabled={isProcessing}
|
||||
loadingText={t('controlLayers.transform.apply')}
|
||||
variant="ghost"
|
||||
>
|
||||
{t('controlLayers.transform.apply')}
|
||||
</Button>
|
||||
<Button
|
||||
leftIcon={<PiXBold />}
|
||||
onClick={adapter.transformer.stopTransform}
|
||||
isLoading={isProcessing}
|
||||
isDisabled={isProcessing}
|
||||
loadingText={t('common.cancel')}
|
||||
variant="ghost"
|
||||
>
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useStore } from '@nanostores/react';
|
||||
import type { CanvasEntityAdapter } from 'features/controlLayers/konva/CanvasEntity/types';
|
||||
import { memo, useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowsOutBold } from 'react-icons/pi';
|
||||
import type { Equals } from 'tsafe';
|
||||
import { assert } from 'tsafe';
|
||||
import { z } from 'zod';
|
||||
@@ -60,10 +59,9 @@ export const TransformFitToBboxButtons = memo(({ adapter }: { adapter: CanvasEnt
|
||||
<Combobox options={options} value={value} onChange={onChange} isSearchable={false} isClearable={false} />
|
||||
</FormControl>
|
||||
<Button
|
||||
leftIcon={<PiArrowsOutBold />}
|
||||
size="sm"
|
||||
onClick={onClick}
|
||||
isLoading={isProcessing}
|
||||
isDisabled={isProcessing}
|
||||
loadingText={t('controlLayers.transform.fitToBbox')}
|
||||
variant="ghost"
|
||||
>
|
||||
|
||||
@@ -46,6 +46,8 @@ import { useCallback } from 'react';
|
||||
import { modelConfigsAdapterSelectors, selectModelConfigsQuery } from 'services/api/endpoints/models';
|
||||
import type { ControlNetModelConfig, ImageDTO, IPAdapterModelConfig, T2IAdapterModelConfig } from 'services/api/types';
|
||||
import { isControlNetOrT2IAdapterModelConfig, isIPAdapterModelConfig } from 'services/api/types';
|
||||
import type { Equals } from 'tsafe';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
export const selectDefaultControlAdapter = createSelector(
|
||||
selectModelConfigsQuery,
|
||||
@@ -194,18 +196,31 @@ export const useNewCanvasFromImage = () => {
|
||||
const bboxRect = useAppSelector(selectBboxRect);
|
||||
const base = useAppSelector(selectBboxModelBase);
|
||||
const func = useCallback(
|
||||
(imageDTO: ImageDTO) => {
|
||||
(imageDTO: ImageDTO, type: CanvasRasterLayerState['type'] | CanvasControlLayerState['type']) => {
|
||||
// Calculate the new bbox dimensions to fit the image's aspect ratio at the optimal size
|
||||
const ratio = imageDTO.width / imageDTO.height;
|
||||
const optimalDimension = getOptimalDimension(base);
|
||||
const { width, height } = calculateNewSize(ratio, optimalDimension ** 2, base);
|
||||
|
||||
// The overrides need to include the layer's ID so we can transform the layer it is initialized
|
||||
const overrides = {
|
||||
id: getPrefixedId('raster_layer'),
|
||||
position: { x: bboxRect.x, y: bboxRect.y },
|
||||
objects: [imageDTOToImageObject(imageDTO)],
|
||||
} satisfies Partial<CanvasRasterLayerState>;
|
||||
let overrides: Partial<CanvasRasterLayerState> | Partial<CanvasControlLayerState>;
|
||||
|
||||
if (type === 'raster_layer') {
|
||||
overrides = {
|
||||
id: getPrefixedId('raster_layer'),
|
||||
position: { x: bboxRect.x, y: bboxRect.y },
|
||||
objects: [imageDTOToImageObject(imageDTO)],
|
||||
} satisfies Partial<CanvasRasterLayerState>;
|
||||
} else if (type === 'control_layer') {
|
||||
overrides = {
|
||||
id: getPrefixedId('control_layer'),
|
||||
position: { x: bboxRect.x, y: bboxRect.y },
|
||||
objects: [imageDTOToImageObject(imageDTO)],
|
||||
} satisfies Partial<CanvasControlLayerState>;
|
||||
} else {
|
||||
// Catch unhandled types
|
||||
assert<Equals<typeof type, never>>(false);
|
||||
}
|
||||
|
||||
CanvasEntityAdapterBase.registerInitCallback(async (adapter) => {
|
||||
// Skip the callback if the adapter is not the one we are creating
|
||||
@@ -222,7 +237,16 @@ export const useNewCanvasFromImage = () => {
|
||||
dispatch(canvasReset());
|
||||
// The `bboxChangedFromCanvas` reducer does no validation! Careful!
|
||||
dispatch(bboxChangedFromCanvas({ x: 0, y: 0, width, height }));
|
||||
dispatch(rasterLayerAdded({ overrides, isSelected: true }));
|
||||
|
||||
// The type casts are safe because the type is checked above
|
||||
if (type === 'raster_layer') {
|
||||
dispatch(rasterLayerAdded({ overrides: overrides as Partial<CanvasRasterLayerState>, isSelected: true }));
|
||||
} else if (type === 'control_layer') {
|
||||
dispatch(controlLayerAdded({ overrides: overrides as Partial<CanvasControlLayerState>, isSelected: true }));
|
||||
} else {
|
||||
// Catch unhandled types
|
||||
assert<Equals<typeof type, never>>(false);
|
||||
}
|
||||
},
|
||||
[base, bboxRect.x, bboxRect.y, dispatch]
|
||||
);
|
||||
|
||||
@@ -52,8 +52,9 @@ export const useEntityTransform = (entityIdentifier: CanvasEntityIdentifier | nu
|
||||
if (!adapter) {
|
||||
return;
|
||||
}
|
||||
imageViewer.close();
|
||||
await adapter.transformer.startTransform();
|
||||
}, [isDisabled, entityIdentifier, canvasManager]);
|
||||
}, [isDisabled, entityIdentifier, canvasManager, imageViewer]);
|
||||
|
||||
const fitToBbox = useCallback(async () => {
|
||||
if (isDisabled) {
|
||||
|
||||
@@ -51,10 +51,16 @@ export class CanvasStagingAreaModule extends CanvasModuleBase {
|
||||
/**
|
||||
* Sync the $isStaging flag with the redux state. $isStaging is used by the manager to determine the global busy
|
||||
* state of the canvas.
|
||||
*
|
||||
* We also set the $shouldShowStagedImage flag when we enter staging mode, so that the staged images are shown,
|
||||
* even if the user disabled this in the last staging session.
|
||||
*/
|
||||
this.subscriptions.add(
|
||||
this.manager.stateApi.createStoreSubscription(selectIsStaging, (isStaging) => {
|
||||
this.manager.stateApi.createStoreSubscription(selectIsStaging, (isStaging, oldIsStaging) => {
|
||||
this.$isStaging.set(isStaging);
|
||||
if (isStaging && !oldIsStaging) {
|
||||
this.$shouldShowStagedImage.set(true);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,8 +32,19 @@ export const ImageMenuItemNewFromImageSubMenu = memo(() => {
|
||||
const newRegionalGuidanceFromImage = useNewRegionalGuidanceFromImage();
|
||||
const newCanvasFromImage = useNewCanvasFromImage();
|
||||
|
||||
const onClickNewCanvasFromImage = useCallback(() => {
|
||||
newCanvasFromImage(imageDTO);
|
||||
const onClickNewCanvasWithRasterLayerFromImage = useCallback(() => {
|
||||
newCanvasFromImage(imageDTO, 'raster_layer');
|
||||
dispatch(setActiveTab('canvas'));
|
||||
imageViewer.close();
|
||||
toast({
|
||||
id: 'SENT_TO_CANVAS',
|
||||
title: t('toast.sentToCanvas'),
|
||||
status: 'success',
|
||||
});
|
||||
}, [dispatch, imageDTO, imageViewer, newCanvasFromImage, t]);
|
||||
|
||||
const onClickNewCanvasWithControlLayerFromImage = useCallback(() => {
|
||||
newCanvasFromImage(imageDTO, 'control_layer');
|
||||
dispatch(setActiveTab('canvas'));
|
||||
imageViewer.close();
|
||||
toast({
|
||||
@@ -98,8 +109,15 @@ export const ImageMenuItemNewFromImageSubMenu = memo(() => {
|
||||
<SubMenuButtonContent label="New from Image" />
|
||||
</MenuButton>
|
||||
<MenuList {...subMenu.menuListProps}>
|
||||
<MenuItem icon={<PiFileBold />} onClickCapture={onClickNewCanvasFromImage} isDisabled={isBusy}>
|
||||
{t('controlLayers.canvas')}
|
||||
<MenuItem icon={<PiFileBold />} onClickCapture={onClickNewCanvasWithRasterLayerFromImage} isDisabled={isBusy}>
|
||||
{t('controlLayers.canvasAsRasterLayer')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<PiFileBold />}
|
||||
onClickCapture={onClickNewCanvasWithControlLayerFromImage}
|
||||
isDisabled={isBusy}
|
||||
>
|
||||
{t('controlLayers.canvasAsControlLayer')}
|
||||
</MenuItem>
|
||||
<MenuItem icon={<NewLayerIcon />} onClickCapture={onClickNewInpaintMaskFromImage} isDisabled={isBusy}>
|
||||
{t('controlLayers.inpaintMask')}
|
||||
|
||||
@@ -57,7 +57,7 @@ export const ImageViewer = memo(({ closeButton }: Props) => {
|
||||
{hasImageToCompare && <ImageComparison containerDims={containerDims} />}
|
||||
</Box>
|
||||
<ImageComparisonDroppable />
|
||||
<Box position="absolute" top={14} insetInlineEnd={2}>
|
||||
<Box position="absolute" top={14} insetInlineStart={2}>
|
||||
<CanvasAlertsSendingToCanvas />
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "5.3.0rc2"
|
||||
__version__ = "5.3.0"
|
||||
|
||||
Reference in New Issue
Block a user