mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
refactor(ui): remove modular imagesize components
This is no longer necessary with canvas v2 and added a ton of extraneous redux actions when changing the image size. Also renamed to document size
This commit is contained in:
parent
a181a684f5
commit
36e94af598
@ -4,16 +4,16 @@ import type { AppDispatch, RootState } from 'app/store/store';
|
||||
import type { JSONObject } from 'common/types';
|
||||
import {
|
||||
caModelChanged,
|
||||
heightChanged,
|
||||
documentHeightChanged,
|
||||
documentWidthChanged,
|
||||
ipaModelChanged,
|
||||
loraDeleted,
|
||||
modelChanged,
|
||||
refinerModelChanged,
|
||||
rgIPAdapterModelChanged,
|
||||
vaeSelected,
|
||||
widthChanged,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||
import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize';
|
||||
import { postProcessingModelChanged, upscaleModelChanged } from 'features/parameters/store/upscaleSlice';
|
||||
import { zParameterModel, zParameterVAEModel } from 'features/parameters/types/parameterSchemas';
|
||||
import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
||||
@ -91,8 +91,8 @@ const handleMainModels: ModelHandler = (models, state, dispatch, log) => {
|
||||
optimalDimension * optimalDimension
|
||||
);
|
||||
|
||||
dispatch(widthChanged({ width }));
|
||||
dispatch(heightChanged({ height }));
|
||||
dispatch(documentWidthChanged({ width }));
|
||||
dispatch(documentHeightChanged({ height }));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import {
|
||||
heightChanged,
|
||||
documentHeightChanged,
|
||||
documentWidthChanged,
|
||||
setCfgRescaleMultiplier,
|
||||
setCfgScale,
|
||||
setScheduler,
|
||||
setSteps,
|
||||
vaePrecisionChanged,
|
||||
vaeSelected,
|
||||
widthChanged,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { setDefaultSettings } from 'features/parameters/store/actions';
|
||||
import {
|
||||
@ -99,13 +99,13 @@ export const addSetDefaultSettingsListener = (startAppListening: AppStartListeni
|
||||
const setSizeOptions = { updateAspectRatio: true, clamp: true };
|
||||
if (width) {
|
||||
if (isParameterWidth(width)) {
|
||||
dispatch(widthChanged({ width, ...setSizeOptions }));
|
||||
dispatch(documentWidthChanged({ width, ...setSizeOptions }));
|
||||
}
|
||||
}
|
||||
|
||||
if (height) {
|
||||
if (isParameterHeight(height)) {
|
||||
dispatch(heightChanged({ height, ...setSizeOptions }));
|
||||
dispatch(documentHeightChanged({ height, ...setSizeOptions }));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,11 +3,11 @@ import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
|
||||
import { heightChanged, widthChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { documentHeightChanged, documentWidthChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
|
||||
import type { ControlAdapterEntity } from 'features/controlLayers/store/types';
|
||||
import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||
import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize';
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowCounterClockwiseBold, PiFloppyDiskBold, PiRulerBold } from 'react-icons/pi';
|
||||
@ -89,15 +89,15 @@ export const CAImagePreview = memo(
|
||||
|
||||
if (shift) {
|
||||
const { width, height } = controlImage;
|
||||
dispatch(widthChanged({ width, ...options }));
|
||||
dispatch(heightChanged({ height, ...options }));
|
||||
dispatch(documentWidthChanged({ width, ...options }));
|
||||
dispatch(documentHeightChanged({ height, ...options }));
|
||||
} else {
|
||||
const { width, height } = calculateNewSize(
|
||||
controlImage.width / controlImage.height,
|
||||
optimalDimension * optimalDimension
|
||||
);
|
||||
dispatch(widthChanged({ width, ...options }));
|
||||
dispatch(heightChanged({ height, ...options }));
|
||||
dispatch(documentWidthChanged({ width, ...options }));
|
||||
dispatch(documentHeightChanged({ height, ...options }));
|
||||
}
|
||||
}, [controlImage, dispatch, optimalDimension, shift]);
|
||||
|
||||
|
@ -3,11 +3,11 @@ import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
|
||||
import { heightChanged, widthChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { documentHeightChanged, documentWidthChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
|
||||
import type { ImageWithDims } from 'features/controlLayers/store/types';
|
||||
import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||
import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize';
|
||||
import { memo, useCallback, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowCounterClockwiseBold, PiRulerBold } from 'react-icons/pi';
|
||||
@ -42,15 +42,15 @@ export const IPAImagePreview = memo(({ image, onChangeImage, ipAdapterId, droppa
|
||||
const options = { updateAspectRatio: true, clamp: true };
|
||||
if (shift) {
|
||||
const { width, height } = controlImage;
|
||||
dispatch(widthChanged({ width, ...options }));
|
||||
dispatch(heightChanged({ height, ...options }));
|
||||
dispatch(documentWidthChanged({ width, ...options }));
|
||||
dispatch(documentHeightChanged({ height, ...options }));
|
||||
} else {
|
||||
const { width, height } = calculateNewSize(
|
||||
controlImage.width / controlImage.height,
|
||||
optimalDimension * optimalDimension
|
||||
);
|
||||
dispatch(widthChanged({ width, ...options }));
|
||||
dispatch(heightChanged({ height, ...options }));
|
||||
dispatch(documentWidthChanged({ width, ...options }));
|
||||
dispatch(documentHeightChanged({ height, ...options }));
|
||||
}
|
||||
}, [controlImage, dispatch, optimalDimension, shift]);
|
||||
|
||||
|
@ -2,10 +2,10 @@ import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import type { PersistConfig, RootState } from 'app/store/store';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { roundDownToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import { bboxReducers } from 'features/controlLayers/store/bboxReducers';
|
||||
import { compositingReducers } from 'features/controlLayers/store/compositingReducers';
|
||||
import { controlAdaptersReducers } from 'features/controlLayers/store/controlAdaptersReducers';
|
||||
import { documentReducers } from 'features/controlLayers/store/documentReducers';
|
||||
import { inpaintMaskReducers } from 'features/controlLayers/store/inpaintMaskReducers';
|
||||
import { ipAdaptersReducers } from 'features/controlLayers/store/ipAdaptersReducers';
|
||||
import { layersReducers } from 'features/controlLayers/store/layersReducers';
|
||||
@ -15,8 +15,7 @@ import { regionsReducers } from 'features/controlLayers/store/regionsReducers';
|
||||
import { sessionReducers } from 'features/controlLayers/store/sessionReducers';
|
||||
import { settingsReducers } from 'features/controlLayers/store/settingsReducers';
|
||||
import { toolReducers } from 'features/controlLayers/store/toolReducers';
|
||||
import { initialAspectRatioState } from 'features/parameters/components/ImageSize/constants';
|
||||
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
|
||||
import { initialAspectRatioState } from 'features/parameters/components/DocumentSize/constants';
|
||||
import { atom } from 'nanostores';
|
||||
import type { InvocationDenoiseProgressEvent } from 'services/events/types';
|
||||
|
||||
@ -145,27 +144,7 @@ export const canvasV2Slice = createSlice({
|
||||
...bboxReducers,
|
||||
...inpaintMaskReducers,
|
||||
...sessionReducers,
|
||||
widthChanged: (state, action: PayloadAction<{ width: number; updateAspectRatio?: boolean; clamp?: boolean }>) => {
|
||||
const { width, updateAspectRatio, clamp } = action.payload;
|
||||
state.document.width = clamp ? Math.max(roundDownToMultiple(width, 8), 64) : width;
|
||||
if (updateAspectRatio) {
|
||||
state.document.aspectRatio.value = state.document.width / state.document.height;
|
||||
state.document.aspectRatio.id = 'Free';
|
||||
state.document.aspectRatio.isLocked = false;
|
||||
}
|
||||
},
|
||||
heightChanged: (state, action: PayloadAction<{ height: number; updateAspectRatio?: boolean; clamp?: boolean }>) => {
|
||||
const { height, updateAspectRatio, clamp } = action.payload;
|
||||
state.document.height = clamp ? Math.max(roundDownToMultiple(height, 8), 64) : height;
|
||||
if (updateAspectRatio) {
|
||||
state.document.aspectRatio.value = state.document.width / state.document.height;
|
||||
state.document.aspectRatio.id = 'Free';
|
||||
state.document.aspectRatio.isLocked = false;
|
||||
}
|
||||
},
|
||||
aspectRatioChanged: (state, action: PayloadAction<AspectRatioState>) => {
|
||||
state.document.aspectRatio = action.payload;
|
||||
},
|
||||
...documentReducers,
|
||||
entitySelected: (state, action: PayloadAction<CanvasEntityIdentifier>) => {
|
||||
state.selectedEntityIdentifier = action.payload;
|
||||
},
|
||||
@ -192,9 +171,6 @@ export const canvasV2Slice = createSlice({
|
||||
});
|
||||
|
||||
export const {
|
||||
widthChanged,
|
||||
heightChanged,
|
||||
aspectRatioChanged,
|
||||
bboxChanged,
|
||||
brushWidthChanged,
|
||||
eraserWidthChanged,
|
||||
@ -209,6 +185,13 @@ export const {
|
||||
bboxScaleMethodChanged,
|
||||
clipToBboxChanged,
|
||||
canvasReset,
|
||||
// document
|
||||
documentWidthChanged,
|
||||
documentHeightChanged,
|
||||
documentAspectRatioLockToggled,
|
||||
documentAspectRatioIdChanged,
|
||||
documentDimensionsSwapped,
|
||||
documentSizeOptimized,
|
||||
// layers
|
||||
layerAdded,
|
||||
layerRecalled,
|
||||
|
@ -0,0 +1,104 @@
|
||||
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { roundDownToMultiple, roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import type { CanvasV2State } from 'features/controlLayers/store/types';
|
||||
import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize';
|
||||
import { ASPECT_RATIO_MAP, initialAspectRatioState } from 'features/parameters/components/DocumentSize/constants';
|
||||
import type { AspectRatioID } from 'features/parameters/components/DocumentSize/types';
|
||||
import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
||||
|
||||
export const documentReducers = {
|
||||
documentWidthChanged: (
|
||||
state,
|
||||
action: PayloadAction<{ width: number; updateAspectRatio?: boolean; clamp?: boolean }>
|
||||
) => {
|
||||
const { width, updateAspectRatio, clamp } = action.payload;
|
||||
state.document.width = clamp ? Math.max(roundDownToMultiple(width, 8), 64) : width;
|
||||
|
||||
if (state.document.aspectRatio.isLocked) {
|
||||
state.document.height = roundToMultiple(state.document.width / state.document.aspectRatio.value, 8);
|
||||
}
|
||||
|
||||
if (updateAspectRatio || !state.document.aspectRatio.isLocked) {
|
||||
state.document.aspectRatio.value = state.document.width / state.document.height;
|
||||
state.document.aspectRatio.id = 'Free';
|
||||
state.document.aspectRatio.isLocked = false;
|
||||
}
|
||||
|
||||
if (!state.session.isActive) {
|
||||
state.bbox.width = state.document.width;
|
||||
state.bbox.height = state.document.height;
|
||||
}
|
||||
},
|
||||
documentHeightChanged: (
|
||||
state,
|
||||
action: PayloadAction<{ height: number; updateAspectRatio?: boolean; clamp?: boolean }>
|
||||
) => {
|
||||
const { height, updateAspectRatio, clamp } = action.payload;
|
||||
|
||||
state.document.height = clamp ? Math.max(roundDownToMultiple(height, 8), 64) : height;
|
||||
|
||||
if (state.document.aspectRatio.isLocked) {
|
||||
state.document.width = roundToMultiple(state.document.height * state.document.aspectRatio.value, 8);
|
||||
}
|
||||
|
||||
if (updateAspectRatio || !state.document.aspectRatio.isLocked) {
|
||||
state.document.aspectRatio.value = state.document.width / state.document.height;
|
||||
state.document.aspectRatio.id = 'Free';
|
||||
state.document.aspectRatio.isLocked = false;
|
||||
}
|
||||
|
||||
if (!state.session.isActive) {
|
||||
state.bbox.width = state.document.width;
|
||||
state.bbox.height = state.document.height;
|
||||
}
|
||||
},
|
||||
documentAspectRatioLockToggled: (state) => {
|
||||
state.document.aspectRatio.isLocked = !state.document.aspectRatio.isLocked;
|
||||
},
|
||||
documentAspectRatioIdChanged: (state, action: PayloadAction<{ id: AspectRatioID }>) => {
|
||||
const { id } = action.payload;
|
||||
state.document.aspectRatio.id = id;
|
||||
if (id === 'Free') {
|
||||
state.document.aspectRatio.isLocked = false;
|
||||
} else {
|
||||
state.document.aspectRatio.isLocked = true;
|
||||
state.document.aspectRatio.value = ASPECT_RATIO_MAP[id].ratio;
|
||||
const { width, height } = calculateNewSize(
|
||||
state.document.aspectRatio.value,
|
||||
state.document.width * state.document.height
|
||||
);
|
||||
state.document.width = width;
|
||||
state.document.height = height;
|
||||
}
|
||||
},
|
||||
documentDimensionsSwapped: (state) => {
|
||||
state.document.aspectRatio.value = 1 / state.document.aspectRatio.value;
|
||||
if (state.document.aspectRatio.id === 'Free') {
|
||||
const newWidth = state.document.height;
|
||||
const newHeight = state.document.width;
|
||||
state.document.width = newWidth;
|
||||
state.document.height = newHeight;
|
||||
} else {
|
||||
const { width, height } = calculateNewSize(
|
||||
state.document.aspectRatio.value,
|
||||
state.document.width * state.document.height
|
||||
);
|
||||
state.document.width = width;
|
||||
state.document.height = height;
|
||||
state.document.aspectRatio.id = ASPECT_RATIO_MAP[state.document.aspectRatio.id].inverseID;
|
||||
}
|
||||
},
|
||||
documentSizeOptimized: (state) => {
|
||||
const optimalDimension = getOptimalDimension(state.params.model);
|
||||
if (state.document.aspectRatio.isLocked) {
|
||||
const { width, height } = calculateNewSize(state.document.aspectRatio.value, optimalDimension ** 2);
|
||||
state.document.width = width;
|
||||
state.document.height = height;
|
||||
} else {
|
||||
state.document.aspectRatio = deepClone(initialAspectRatioState);
|
||||
state.document.width = optimalDimension;
|
||||
state.document.height = optimalDimension;
|
||||
}
|
||||
},
|
||||
} satisfies SliceCaseReducers<CanvasV2State>;
|
@ -1,7 +1,7 @@
|
||||
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||
import type { CanvasV2State } from 'features/controlLayers/store/types';
|
||||
import { getScaledBoundingBoxDimensions } from 'features/controlLayers/util/getScaledBoundingBoxDimensions';
|
||||
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||
import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize';
|
||||
import { CLIP_SKIP_MAP } from 'features/parameters/types/constants';
|
||||
import type {
|
||||
ParameterCFGRescaleMultiplier,
|
||||
|
@ -4,7 +4,7 @@ import { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer';
|
||||
import { CanvasRegion } from 'features/controlLayers/konva/CanvasRegion';
|
||||
import { getImageObjectId } from 'features/controlLayers/konva/naming';
|
||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
|
||||
import type { AspectRatioState } from 'features/parameters/components/DocumentSize/types';
|
||||
import type {
|
||||
ParameterCanvasCoherenceMode,
|
||||
ParameterCFGRescaleMultiplier,
|
||||
|
@ -12,7 +12,8 @@ import {
|
||||
} from 'features/controlLayers/konva/naming';
|
||||
import {
|
||||
caRecalled,
|
||||
heightChanged,
|
||||
documentHeightChanged,
|
||||
documentWidthChanged,
|
||||
ipaRecalled,
|
||||
layerAllDeleted,
|
||||
layerRecalled,
|
||||
@ -37,7 +38,6 @@ import {
|
||||
setSeed,
|
||||
setSteps,
|
||||
vaeSelected,
|
||||
widthChanged,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
import type {
|
||||
ControlAdapterEntity,
|
||||
@ -115,11 +115,11 @@ const recallScheduler: MetadataRecallFunc<ParameterScheduler> = (scheduler) => {
|
||||
const setSizeOptions = { updateAspectRatio: true, clamp: true };
|
||||
|
||||
const recallWidth: MetadataRecallFunc<ParameterWidth> = (width) => {
|
||||
getStore().dispatch(widthChanged({ width, ...setSizeOptions }));
|
||||
getStore().dispatch(documentWidthChanged({ width, ...setSizeOptions }));
|
||||
};
|
||||
|
||||
const recallHeight: MetadataRecallFunc<ParameterHeight> = (height) => {
|
||||
getStore().dispatch(heightChanged({ height, ...setSizeOptions }));
|
||||
getStore().dispatch(documentHeightChanged({ height, ...setSizeOptions }));
|
||||
};
|
||||
|
||||
const recallSteps: MetadataRecallFunc<ParameterSteps> = (steps) => {
|
||||
|
@ -1,15 +1,16 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { documentHeightChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
|
||||
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const ParamHeight = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const ctx = useImageSizeContext();
|
||||
const dispatch = useAppDispatch();
|
||||
const optimalDimension = useAppSelector(selectOptimalDimension);
|
||||
const height = useAppSelector((s) => s.canvasV2.document.height);
|
||||
const sliderMin = useAppSelector((s) => s.config.sd.height.sliderMin);
|
||||
const sliderMax = useAppSelector((s) => s.config.sd.height.sliderMax);
|
||||
const numberInputMin = useAppSelector((s) => s.config.sd.height.numberInputMin);
|
||||
@ -19,9 +20,9 @@ export const ParamHeight = memo(() => {
|
||||
|
||||
const onChange = useCallback(
|
||||
(v: number) => {
|
||||
ctx.heightChanged(v);
|
||||
dispatch(documentHeightChanged({ height: v }));
|
||||
},
|
||||
[ctx]
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const marks = useMemo(() => [sliderMin, optimalDimension, sliderMax], [sliderMin, optimalDimension, sliderMax]);
|
||||
@ -32,7 +33,7 @@ export const ParamHeight = memo(() => {
|
||||
<FormLabel>{t('parameters.height')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<CompositeSlider
|
||||
value={ctx.height}
|
||||
value={height}
|
||||
defaultValue={optimalDimension}
|
||||
onChange={onChange}
|
||||
min={sliderMin}
|
||||
@ -42,7 +43,7 @@ export const ParamHeight = memo(() => {
|
||||
marks={marks}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={ctx.height}
|
||||
value={height}
|
||||
defaultValue={optimalDimension}
|
||||
onChange={onChange}
|
||||
min={numberInputMin}
|
||||
|
@ -1,14 +1,15 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { documentWidthChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
|
||||
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const ParamWidth = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const ctx = useImageSizeContext();
|
||||
const dispatch = useAppDispatch();
|
||||
const width = useAppSelector((s) => s.canvasV2.document.width);
|
||||
const optimalDimension = useAppSelector(selectOptimalDimension);
|
||||
const sliderMin = useAppSelector((s) => s.config.sd.width.sliderMin);
|
||||
const sliderMax = useAppSelector((s) => s.config.sd.width.sliderMax);
|
||||
@ -19,9 +20,9 @@ export const ParamWidth = memo(() => {
|
||||
|
||||
const onChange = useCallback(
|
||||
(v: number) => {
|
||||
ctx.widthChanged(v);
|
||||
dispatch(documentWidthChanged({ width: v }));
|
||||
},
|
||||
[ctx]
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const marks = useMemo(() => [sliderMin, optimalDimension, sliderMax], [sliderMin, optimalDimension, sliderMax]);
|
||||
@ -32,7 +33,7 @@ export const ParamWidth = memo(() => {
|
||||
<FormLabel>{t('parameters.width')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<CompositeSlider
|
||||
value={ctx.width}
|
||||
value={width}
|
||||
onChange={onChange}
|
||||
defaultValue={optimalDimension}
|
||||
min={sliderMin}
|
||||
@ -42,7 +43,7 @@ export const ParamWidth = memo(() => {
|
||||
marks={marks}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={ctx.width}
|
||||
value={width}
|
||||
onChange={onChange}
|
||||
defaultValue={optimalDimension}
|
||||
min={numberInputMin}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { $isPreviewVisible } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { AspectRatioIconPreview } from 'features/parameters/components/ImageSize/AspectRatioIconPreview';
|
||||
import { AspectRatioIconPreview } from 'features/parameters/components/DocumentSize/AspectRatioIconPreview';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const AspectRatioCanvasPreview = memo(() => {
|
@ -1,6 +1,6 @@
|
||||
import { useSize } from '@chakra-ui/react-use-size';
|
||||
import { Flex, Icon } from '@invoke-ai/ui-library';
|
||||
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { memo, useMemo, useRef } from 'react';
|
||||
import { PiFrameCorners } from 'react-icons/pi';
|
||||
@ -16,13 +16,13 @@ import {
|
||||
} from './constants';
|
||||
|
||||
export const AspectRatioIconPreview = memo(() => {
|
||||
const ctx = useImageSizeContext();
|
||||
const document = useAppSelector((s) => s.canvasV2.document);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const containerSize = useSize(containerRef);
|
||||
|
||||
const shouldShowIcon = useMemo(
|
||||
() => ctx.aspectRatioState.value < ICON_HIGH_CUTOFF && ctx.aspectRatioState.value > ICON_LOW_CUTOFF,
|
||||
[ctx.aspectRatioState.value]
|
||||
() => document.aspectRatio.value < ICON_HIGH_CUTOFF && document.aspectRatio.value > ICON_LOW_CUTOFF,
|
||||
[document.aspectRatio.value]
|
||||
);
|
||||
|
||||
const { width, height } = useMemo(() => {
|
||||
@ -30,19 +30,19 @@ export const AspectRatioIconPreview = memo(() => {
|
||||
return { width: 0, height: 0 };
|
||||
}
|
||||
|
||||
let width = ctx.width;
|
||||
let height = ctx.height;
|
||||
let width = document.width;
|
||||
let height = document.height;
|
||||
|
||||
if (ctx.width > ctx.height) {
|
||||
if (document.width > document.height) {
|
||||
width = containerSize.width;
|
||||
height = width / ctx.aspectRatioState.value;
|
||||
height = width / document.aspectRatio.value;
|
||||
} else {
|
||||
height = containerSize.height;
|
||||
width = height * ctx.aspectRatioState.value;
|
||||
width = height * document.aspectRatio.value;
|
||||
}
|
||||
|
||||
return { width, height };
|
||||
}, [containerSize, ctx.width, ctx.height, ctx.aspectRatioState.value]);
|
||||
}, [containerSize, document.width, document.height, document.aspectRatio.value]);
|
||||
|
||||
return (
|
||||
<Flex w="full" h="full" alignItems="center" justifyContent="center" ref={containerRef}>
|
@ -1,31 +1,30 @@
|
||||
import type { ComboboxOption, SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import type { SingleValue } from 'chakra-react-select';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { ASPECT_RATIO_OPTIONS } from 'features/parameters/components/ImageSize/constants';
|
||||
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext';
|
||||
import { isAspectRatioID } from 'features/parameters/components/ImageSize/types';
|
||||
import { documentAspectRatioIdChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { ASPECT_RATIO_OPTIONS } from 'features/parameters/components/DocumentSize/constants';
|
||||
import { isAspectRatioID } from 'features/parameters/components/DocumentSize/types';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const AspectRatioSelect = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const ctx = useImageSizeContext();
|
||||
const dispatch = useAppDispatch();
|
||||
const id = useAppSelector((s) => s.canvasV2.document.aspectRatio.id);
|
||||
|
||||
const onChange = useCallback(
|
||||
(v: SingleValue<ComboboxOption>) => {
|
||||
if (!v || !isAspectRatioID(v.value)) {
|
||||
return;
|
||||
}
|
||||
ctx.aspectRatioSelected(v.value);
|
||||
dispatch(documentAspectRatioIdChanged({ id: v.value }));
|
||||
},
|
||||
[ctx]
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const value = useMemo(
|
||||
() => ASPECT_RATIO_OPTIONS.filter((o) => o.value === ctx.aspectRatioState.id)[0],
|
||||
[ctx.aspectRatioState.id]
|
||||
);
|
||||
const value = useMemo(() => ASPECT_RATIO_OPTIONS.filter((o) => o.value === id)[0], [id]);
|
||||
|
||||
return (
|
||||
<FormControl>
|
@ -0,0 +1,38 @@
|
||||
import type { FormLabelProps } from '@invoke-ai/ui-library';
|
||||
import { Flex, FormControlGroup } from '@invoke-ai/ui-library';
|
||||
import { ParamHeight } from 'features/parameters/components/Core/ParamHeight';
|
||||
import { ParamWidth } from 'features/parameters/components/Core/ParamWidth';
|
||||
import { AspectRatioIconPreview } from 'features/parameters/components/DocumentSize/AspectRatioIconPreview';
|
||||
import { AspectRatioSelect } from 'features/parameters/components/DocumentSize/AspectRatioSelect';
|
||||
import { LockAspectRatioButton } from 'features/parameters/components/DocumentSize/LockAspectRatioButton';
|
||||
import { SetOptimalSizeButton } from 'features/parameters/components/DocumentSize/SetOptimalSizeButton';
|
||||
import { SwapDimensionsButton } from 'features/parameters/components/DocumentSize/SwapDimensionsButton';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const DocumentSize = memo(() => {
|
||||
return (
|
||||
<Flex gap={4} alignItems="center">
|
||||
<Flex gap={4} flexDirection="column" width="full">
|
||||
<FormControlGroup formLabelProps={formLabelProps}>
|
||||
<Flex gap={4}>
|
||||
<AspectRatioSelect />
|
||||
<SwapDimensionsButton />
|
||||
<LockAspectRatioButton />
|
||||
<SetOptimalSizeButton />
|
||||
</Flex>
|
||||
<ParamWidth />
|
||||
<ParamHeight />
|
||||
</FormControlGroup>
|
||||
</Flex>
|
||||
<Flex w="108px" h="108px" flexShrink={0} flexGrow={0}>
|
||||
<AspectRatioIconPreview />
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
DocumentSize.displayName = 'DocumentSize';
|
||||
|
||||
const formLabelProps: FormLabelProps = {
|
||||
minW: 14,
|
||||
};
|
@ -1,24 +1,26 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { documentAspectRatioLockToggled } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiLockSimpleFill, PiLockSimpleOpenBold } from 'react-icons/pi';
|
||||
|
||||
export const LockAspectRatioButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const ctx = useImageSizeContext();
|
||||
const dispatch = useAppDispatch();
|
||||
const isLocked = useAppSelector((s) => s.canvasV2.document.aspectRatio.isLocked);
|
||||
const onClick = useCallback(() => {
|
||||
ctx.isLockedToggled();
|
||||
}, [ctx]);
|
||||
dispatch(documentAspectRatioLockToggled());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
tooltip={t('parameters.lockAspectRatio')}
|
||||
aria-label={t('parameters.lockAspectRatio')}
|
||||
onClick={onClick}
|
||||
variant={ctx.aspectRatioState.isLocked ? 'outline' : 'ghost'}
|
||||
variant={isLocked ? 'outline' : 'ghost'}
|
||||
size="sm"
|
||||
icon={ctx.aspectRatioState.isLocked ? <PiLockSimpleFill /> : <PiLockSimpleOpenBold />}
|
||||
icon={isLocked ? <PiLockSimpleFill /> : <PiLockSimpleOpenBold />}
|
||||
/>
|
||||
);
|
||||
});
|
@ -1,7 +1,7 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { documentSizeOptimized } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
|
||||
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext';
|
||||
import { getIsSizeTooLarge, getIsSizeTooSmall } from 'features/parameters/util/optimalDimension';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -9,19 +9,21 @@ import { RiSparklingFill } from 'react-icons/ri';
|
||||
|
||||
export const SetOptimalSizeButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const ctx = useImageSizeContext();
|
||||
const dispatch = useAppDispatch();
|
||||
const width = useAppSelector((s) => s.canvasV2.document.width);
|
||||
const height = useAppSelector((s) => s.canvasV2.document.height);
|
||||
const optimalDimension = useAppSelector(selectOptimalDimension);
|
||||
const isSizeTooSmall = useMemo(
|
||||
() => getIsSizeTooSmall(ctx.width, ctx.height, optimalDimension),
|
||||
[ctx.height, ctx.width, optimalDimension]
|
||||
() => getIsSizeTooSmall(width, height, optimalDimension),
|
||||
[height, width, optimalDimension]
|
||||
);
|
||||
const isSizeTooLarge = useMemo(
|
||||
() => getIsSizeTooLarge(ctx.width, ctx.height, optimalDimension),
|
||||
[ctx.height, ctx.width, optimalDimension]
|
||||
() => getIsSizeTooLarge(width, height, optimalDimension),
|
||||
[height, width, optimalDimension]
|
||||
);
|
||||
const onClick = useCallback(() => {
|
||||
ctx.setOptimalSize();
|
||||
}, [ctx]);
|
||||
dispatch(documentSizeOptimized());
|
||||
}, [dispatch]);
|
||||
const tooltip = useMemo(() => {
|
||||
if (isSizeTooSmall) {
|
||||
return t('parameters.setToOptimalSizeTooSmall');
|
@ -1,15 +1,16 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { documentDimensionsSwapped } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowsDownUpBold } from 'react-icons/pi';
|
||||
|
||||
export const SwapDimensionsButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const ctx = useImageSizeContext();
|
||||
const dispatch = useAppDispatch();
|
||||
const onClick = useCallback(() => {
|
||||
ctx.dimensionsSwapped();
|
||||
}, [ctx]);
|
||||
dispatch(documentDimensionsSwapped());
|
||||
}, [dispatch]);
|
||||
return (
|
||||
<IconButton
|
||||
tooltip={t('parameters.swapDimensions')}
|
@ -1,47 +0,0 @@
|
||||
import type { FormLabelProps } from '@invoke-ai/ui-library';
|
||||
import { Flex, FormControlGroup } from '@invoke-ai/ui-library';
|
||||
import { AspectRatioSelect } from 'features/parameters/components/ImageSize/AspectRatioSelect';
|
||||
import type { ImageSizeContextInnerValue } from 'features/parameters/components/ImageSize/ImageSizeContext';
|
||||
import { ImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext';
|
||||
import { LockAspectRatioButton } from 'features/parameters/components/ImageSize/LockAspectRatioButton';
|
||||
import { SetOptimalSizeButton } from 'features/parameters/components/ImageSize/SetOptimalSizeButton';
|
||||
import { SwapDimensionsButton } from 'features/parameters/components/ImageSize/SwapDimensionsButton';
|
||||
import type { ReactNode } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
type ImageSizeProps = ImageSizeContextInnerValue & {
|
||||
widthComponent: ReactNode;
|
||||
heightComponent: ReactNode;
|
||||
previewComponent: ReactNode;
|
||||
};
|
||||
|
||||
export const ImageSize = memo((props: ImageSizeProps) => {
|
||||
const { widthComponent, heightComponent, previewComponent, ...ctx } = props;
|
||||
return (
|
||||
<ImageSizeContext.Provider value={ctx}>
|
||||
<Flex gap={4} alignItems="center">
|
||||
<Flex gap={4} flexDirection="column" width="full">
|
||||
<FormControlGroup formLabelProps={formLabelProps}>
|
||||
<Flex gap={4}>
|
||||
<AspectRatioSelect />
|
||||
<SwapDimensionsButton />
|
||||
<LockAspectRatioButton />
|
||||
<SetOptimalSizeButton />
|
||||
</Flex>
|
||||
{widthComponent}
|
||||
{heightComponent}
|
||||
</FormControlGroup>
|
||||
</Flex>
|
||||
<Flex w="108px" h="108px" flexShrink={0} flexGrow={0}>
|
||||
{previewComponent}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</ImageSizeContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
ImageSize.displayName = 'ImageSize';
|
||||
|
||||
const formLabelProps: FormLabelProps = {
|
||||
minW: 14,
|
||||
};
|
@ -1,156 +0,0 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
|
||||
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||
import { ASPECT_RATIO_MAP, initialAspectRatioState } from 'features/parameters/components/ImageSize/constants';
|
||||
import type { AspectRatioID, AspectRatioState } from 'features/parameters/components/ImageSize/types';
|
||||
import { createContext, useCallback, useContext, useMemo } from 'react';
|
||||
|
||||
export type ImageSizeContextInnerValue = {
|
||||
width: number;
|
||||
height: number;
|
||||
aspectRatioState: AspectRatioState;
|
||||
onChangeWidth: (width: number) => void;
|
||||
onChangeHeight: (height: number) => void;
|
||||
onChangeAspectRatioState: (aspectRatioState: AspectRatioState) => void;
|
||||
};
|
||||
|
||||
export type ImageSizeContext = {
|
||||
width: number;
|
||||
height: number;
|
||||
aspectRatioState: AspectRatioState;
|
||||
aspectRatioSelected: (aspectRatioID: AspectRatioID) => void;
|
||||
dimensionsSwapped: () => void;
|
||||
widthChanged: (width: number) => void;
|
||||
heightChanged: (height: number) => void;
|
||||
isLockedToggled: () => void;
|
||||
setOptimalSize: () => void;
|
||||
};
|
||||
|
||||
export const ImageSizeContext = createContext<ImageSizeContextInnerValue | null>(null);
|
||||
|
||||
export const useImageSizeContext = (): ImageSizeContext => {
|
||||
const _ctx = useContext(ImageSizeContext);
|
||||
const optimalDimension = useAppSelector(selectOptimalDimension);
|
||||
|
||||
if (!_ctx) {
|
||||
throw new Error('useImageSizeContext must be used within a ImageSizeContext.Provider');
|
||||
}
|
||||
|
||||
const aspectRatioSelected = useCallback(
|
||||
(aspectRatioID: AspectRatioID) => {
|
||||
const state: AspectRatioState = {
|
||||
..._ctx.aspectRatioState,
|
||||
id: aspectRatioID,
|
||||
};
|
||||
if (state.id === 'Free') {
|
||||
// If the new aspect ratio is free, we only unlock
|
||||
state.isLocked = false;
|
||||
} else {
|
||||
// The new aspect ratio not free, so we need to coerce the size & lock
|
||||
state.isLocked = true;
|
||||
state.value = ASPECT_RATIO_MAP[state.id].ratio;
|
||||
const { width, height } = calculateNewSize(state.value, _ctx.width * _ctx.height);
|
||||
_ctx.onChangeWidth(width);
|
||||
_ctx.onChangeHeight(height);
|
||||
}
|
||||
_ctx.onChangeAspectRatioState(state);
|
||||
},
|
||||
[_ctx]
|
||||
);
|
||||
const dimensionsSwapped = useCallback(() => {
|
||||
const state = {
|
||||
..._ctx.aspectRatioState,
|
||||
};
|
||||
// We always invert the aspect ratio
|
||||
state.value = 1 / state.value;
|
||||
if (state.id === 'Free') {
|
||||
// If the aspect ratio is free, we just swap the dimensions
|
||||
const newWidth = _ctx.height;
|
||||
const newHeight = _ctx.width;
|
||||
_ctx.onChangeWidth(newWidth);
|
||||
_ctx.onChangeHeight(newHeight);
|
||||
} else {
|
||||
// Else we need to calculate the new size
|
||||
const { width, height } = calculateNewSize(state.value, _ctx.width * _ctx.height);
|
||||
_ctx.onChangeWidth(width);
|
||||
_ctx.onChangeHeight(height);
|
||||
// Update the aspect ratio ID to match the new aspect ratio
|
||||
state.id = ASPECT_RATIO_MAP[state.id].inverseID;
|
||||
}
|
||||
_ctx.onChangeAspectRatioState(state);
|
||||
}, [_ctx]);
|
||||
|
||||
const widthChanged = useCallback(
|
||||
(width: number) => {
|
||||
let height = _ctx.height;
|
||||
const state = { ..._ctx.aspectRatioState };
|
||||
if (state.isLocked) {
|
||||
// When locked, we calculate the new height based on the aspect ratio
|
||||
height = roundToMultiple(width / state.value, 8);
|
||||
} else {
|
||||
// Else we unlock, set the aspect ratio to free, and update the aspect ratio itself
|
||||
state.isLocked = false;
|
||||
state.id = 'Free';
|
||||
state.value = width / height;
|
||||
}
|
||||
_ctx.onChangeWidth(width);
|
||||
_ctx.onChangeHeight(height);
|
||||
_ctx.onChangeAspectRatioState(state);
|
||||
},
|
||||
[_ctx]
|
||||
);
|
||||
|
||||
const heightChanged = useCallback(
|
||||
(height: number) => {
|
||||
let width = _ctx.width;
|
||||
const state = { ..._ctx.aspectRatioState };
|
||||
if (state.isLocked) {
|
||||
// When locked, we calculate the new width based on the aspect ratio
|
||||
width = roundToMultiple(height * state.value, 8);
|
||||
} else {
|
||||
// Else we unlock, set the aspect ratio to free, and update the aspect ratio itself
|
||||
state.isLocked = false;
|
||||
state.id = 'Free';
|
||||
state.value = width / height;
|
||||
}
|
||||
_ctx.onChangeWidth(width);
|
||||
_ctx.onChangeHeight(height);
|
||||
_ctx.onChangeAspectRatioState(state);
|
||||
},
|
||||
[_ctx]
|
||||
);
|
||||
|
||||
const isLockedToggled = useCallback(() => {
|
||||
const state = { ..._ctx.aspectRatioState };
|
||||
state.isLocked = !state.isLocked;
|
||||
_ctx.onChangeAspectRatioState(state);
|
||||
}, [_ctx]);
|
||||
|
||||
const setOptimalSize = useCallback(() => {
|
||||
if (_ctx.aspectRatioState.isLocked) {
|
||||
const { width, height } = calculateNewSize(_ctx.aspectRatioState.value, optimalDimension * optimalDimension);
|
||||
_ctx.onChangeWidth(width);
|
||||
_ctx.onChangeHeight(height);
|
||||
} else {
|
||||
_ctx.onChangeAspectRatioState({ ...initialAspectRatioState });
|
||||
_ctx.onChangeWidth(optimalDimension);
|
||||
_ctx.onChangeHeight(optimalDimension);
|
||||
}
|
||||
}, [_ctx, optimalDimension]);
|
||||
|
||||
const ctx = useMemo(
|
||||
() => ({
|
||||
..._ctx,
|
||||
aspectRatioSelected,
|
||||
dimensionsSwapped,
|
||||
widthChanged,
|
||||
heightChanged,
|
||||
isLockedToggled,
|
||||
setOptimalSize,
|
||||
}),
|
||||
[_ctx, aspectRatioSelected, dimensionsSwapped, heightChanged, isLockedToggled, setOptimalSize, widthChanged]
|
||||
);
|
||||
|
||||
return ctx;
|
||||
};
|
@ -9,6 +9,7 @@ import ParamScaleBeforeProcessing from 'features/parameters/components/Canvas/In
|
||||
import ParamScaledHeight from 'features/parameters/components/Canvas/InfillAndScaling/ParamScaledHeight';
|
||||
import ParamScaledWidth from 'features/parameters/components/Canvas/InfillAndScaling/ParamScaledWidth';
|
||||
import ParamImageToImageStrength from 'features/parameters/components/Canvas/ParamImageToImageStrength';
|
||||
import { DocumentSize } from 'features/parameters/components/DocumentSize/DocumentSize';
|
||||
import { ParamSeedNumberInput } from 'features/parameters/components/Seed/ParamSeedNumberInput';
|
||||
import { ParamSeedRandomize } from 'features/parameters/components/Seed/ParamSeedRandomize';
|
||||
import { ParamSeedShuffle } from 'features/parameters/components/Seed/ParamSeedShuffle';
|
||||
@ -17,7 +18,6 @@ import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { ImageSizeLinear } from './ImageSizeLinear';
|
||||
|
||||
const selector = createMemoizedSelector([selectHrfSlice, selectCanvasV2Slice], (hrf, canvasV2) => {
|
||||
const { shouldRandomizeSeed, model } = canvasV2.params;
|
||||
@ -68,7 +68,7 @@ export const ImageSettingsAccordion = memo(() => {
|
||||
>
|
||||
<Flex px={4} pt={4} w="full" h="full" flexDir="column" data-testid="image-settings-accordion">
|
||||
<Flex flexDir="column" gap={4}>
|
||||
<ImageSizeLinear />
|
||||
<DocumentSize />
|
||||
<ParamImageToImageStrength />
|
||||
</Flex>
|
||||
<Expander label={t('accordions.advanced.options')} isOpen={isOpenExpander} onToggle={onToggleExpander}>
|
||||
|
@ -1,58 +0,0 @@
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { aspectRatioChanged, heightChanged, widthChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { ParamHeight } from 'features/parameters/components/Core/ParamHeight';
|
||||
import { ParamWidth } from 'features/parameters/components/Core/ParamWidth';
|
||||
import { AspectRatioCanvasPreview } from 'features/parameters/components/ImageSize/AspectRatioCanvasPreview';
|
||||
import { ImageSize } from 'features/parameters/components/ImageSize/ImageSize';
|
||||
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
export const ImageSizeLinear = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const width = useAppSelector((s) => s.canvasV2.document.width);
|
||||
const height = useAppSelector((s) => s.canvasV2.document.height);
|
||||
const aspectRatioState = useAppSelector((s) => s.canvasV2.document.aspectRatio);
|
||||
|
||||
const onChangeWidth = useCallback(
|
||||
(width: number) => {
|
||||
if (width === 0) {
|
||||
return;
|
||||
}
|
||||
dispatch(widthChanged({ width }));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const onChangeHeight = useCallback(
|
||||
(height: number) => {
|
||||
if (height === 0) {
|
||||
return;
|
||||
}
|
||||
dispatch(heightChanged({ height }));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const onChangeAspectRatioState = useCallback(
|
||||
(aspectRatioState: AspectRatioState) => {
|
||||
dispatch(aspectRatioChanged(aspectRatioState));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<ImageSize
|
||||
width={width}
|
||||
height={height}
|
||||
aspectRatioState={aspectRatioState}
|
||||
heightComponent={<ParamHeight />}
|
||||
widthComponent={<ParamWidth />}
|
||||
previewComponent={<AspectRatioCanvasPreview />}
|
||||
onChangeAspectRatioState={onChangeAspectRatioState}
|
||||
onChangeWidth={onChangeWidth}
|
||||
onChangeHeight={onChangeHeight}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
ImageSizeLinear.displayName = 'ImageSizeLinear';
|
Loading…
Reference in New Issue
Block a user