feat(ui): more memoization

This commit is contained in:
psychedelicious 2023-12-29 16:52:29 +11:00 committed by Kent Keirsey
parent 10fd4f6a61
commit 3ce8f3d6fe
24 changed files with 161 additions and 79 deletions

View File

@ -30,6 +30,8 @@ module.exports = {
'unused-imports', 'unused-imports',
'simple-import-sort', 'simple-import-sort',
'eslint-plugin-import', 'eslint-plugin-import',
// These rules are too strict for normal usage, but are useful for optimizing rerenders
// '@arthurgeron/react-usememo',
], ],
root: true, root: true,
rules: { rules: {
@ -60,6 +62,18 @@ module.exports = {
argsIgnorePattern: '^_', argsIgnorePattern: '^_',
}, },
], ],
// These rules are too strict for normal usage, but are useful for optimizing rerenders
// '@arthurgeron/react-usememo/require-usememo': [
// 'warn',
// {
// strict: false,
// checkHookReturnObject: false,
// fix: { addImports: true },
// checkHookCalls: false,
// },
// ],
// '@arthurgeron/react-usememo/require-memo': 'warn',
'@typescript-eslint/ban-ts-comment': 'warn', '@typescript-eslint/ban-ts-comment': 'warn',
'@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-empty-interface': [ '@typescript-eslint/no-empty-interface': [

View File

@ -120,6 +120,7 @@
"ts-toolbelt": "^9.6.0" "ts-toolbelt": "^9.6.0"
}, },
"devDependencies": { "devDependencies": {
"@arthurgeron/eslint-plugin-react-usememo": "^2.2.1",
"@chakra-ui/cli": "^2.4.1", "@chakra-ui/cli": "^2.4.1",
"@storybook/addon-docs": "^7.6.6", "@storybook/addon-docs": "^7.6.6",
"@storybook/addon-essentials": "^7.6.6", "@storybook/addon-essentials": "^7.6.6",

View File

@ -184,6 +184,9 @@ dependencies:
version: 2.1.0(zod@3.22.4) version: 2.1.0(zod@3.22.4)
devDependencies: devDependencies:
'@arthurgeron/eslint-plugin-react-usememo':
specifier: ^2.2.1
version: 2.2.1
'@chakra-ui/cli': '@chakra-ui/cli':
specifier: ^2.4.1 specifier: ^2.4.1
version: 2.4.1 version: 2.4.1
@ -339,6 +342,13 @@ packages:
'@jridgewell/trace-mapping': 0.3.20 '@jridgewell/trace-mapping': 0.3.20
dev: true dev: true
/@arthurgeron/eslint-plugin-react-usememo@2.2.1:
resolution: {integrity: sha512-XMYGogzqQYR7PC4F4ve+loBTrvsE7AHnhiBjUTBMW5hbFecbg6ReTQwj36WsV4vRXNnDRD3vfwkO4fH8WHcOog==}
dependencies:
minimatch: 9.0.3
uuid: 9.0.1
dev: true
/@aw-web-design/x-default-browser@1.4.126: /@aw-web-design/x-default-browser@1.4.126:
resolution: {integrity: sha512-Xk1sIhyNC/esHGGVjL/niHLowM0csl/kFO5uawBy4IrWwy0o1G8LGt3jP6nmWGz+USxeeqbihAmp/oVZju6wug==} resolution: {integrity: sha512-Xk1sIhyNC/esHGGVjL/niHLowM0csl/kFO5uawBy4IrWwy0o1G8LGt3jP6nmWGz+USxeeqbihAmp/oVZju6wug==}
hasBin: true hasBin: true

View File

@ -12,7 +12,12 @@
* - increment it in `onPaneClick` (and wherever else we want to close the menu) * - increment it in `onPaneClick` (and wherever else we want to close the menu)
* - `useEffect()` to close the menu when `globalContextMenuCloseTrigger` changes * - `useEffect()` to close the menu when `globalContextMenuCloseTrigger` changes
*/ */
import type { MenuButtonProps, MenuProps, PortalProps } from '@chakra-ui/react'; import type {
ChakraProps,
MenuButtonProps,
MenuProps,
PortalProps,
} from '@chakra-ui/react';
import { Portal, useEventListener } from '@chakra-ui/react'; import { Portal, useEventListener } from '@chakra-ui/react';
import { InvMenu, InvMenuButton } from 'common/components/InvMenu/wrapper'; import { InvMenu, InvMenuButton } from 'common/components/InvMenu/wrapper';
import { useGlobalMenuCloseTrigger } from 'common/hooks/useGlobalMenuCloseTrigger'; import { useGlobalMenuCloseTrigger } from 'common/hooks/useGlobalMenuCloseTrigger';
@ -102,7 +107,7 @@ export const InvContextMenu = typedMemo(
cursor="default" cursor="default"
bg="transparent" bg="transparent"
size="sm" size="sm"
_hover={{ bg: 'transparent' }} _hover={_hover}
{...props.menuButtonProps} {...props.menuButtonProps}
/> />
{props.renderMenu()} {props.renderMenu()}
@ -114,6 +119,8 @@ export const InvContextMenu = typedMemo(
} }
); );
const _hover: ChakraProps['_hover'] = { bg: 'transparent' };
Object.assign(InvContextMenu, { Object.assign(InvContextMenu, {
displayName: 'InvContextMenu', displayName: 'InvContextMenu',
}); });

View File

@ -1,5 +1,5 @@
// https://stackoverflow.com/a/73731908 // https://stackoverflow.com/a/73731908
import { useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
export function useSingleAndDoubleClick( export function useSingleAndDoubleClick(
handleSingleClick: () => void, handleSingleClick: () => void,
@ -23,5 +23,7 @@ export function useSingleAndDoubleClick(
return () => clearTimeout(timer); return () => clearTimeout(timer);
}, [click, handleSingleClick, handleDoubleClick, delay]); }, [click, handleSingleClick, handleDoubleClick, delay]);
return () => setClick((prev) => prev + 1); const onClick = useCallback(() => setClick((prev) => prev + 1), []);
return onClick;
} }

View File

@ -175,6 +175,8 @@ const IAICanvas = () => {
[stageCursor] [stageCursor]
); );
const scale= useMemo(()=>({x: stageScale, y: stageScale}),[stageScale])
return ( return (
<Flex <Flex
id="canvas-container" id="canvas-container"
@ -193,7 +195,7 @@ const IAICanvas = () => {
y={stageCoordinates.y} y={stageCoordinates.y}
width={stageDimensions.width} width={stageDimensions.width}
height={stageDimensions.height} height={stageDimensions.height}
scale={{ x: stageScale, y: stageScale }} scale={scale}
onTouchStart={handleMouseDown} onTouchStart={handleMouseDown}
onTouchMove={handleMouseMove} onTouchMove={handleMouseMove}
onTouchEnd={handleMouseUp} onTouchEnd={handleMouseUp}

View File

@ -5,7 +5,7 @@ import { rgbaColorToString } from 'features/canvas/util/colorToString';
import type Konva from 'konva'; import type Konva from 'konva';
import type { RectConfig } from 'konva/lib/shapes/Rect'; import type { RectConfig } from 'konva/lib/shapes/Rect';
import { isNumber } from 'lodash-es'; import { isNumber } from 'lodash-es';
import { memo, useCallback, useEffect, useRef, useState } from 'react'; import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Rect } from 'react-konva'; import { Rect } from 'react-konva';
export const canvasMaskCompositerSelector = createMemoizedSelector( export const canvasMaskCompositerSelector = createMemoizedSelector(
@ -147,6 +147,11 @@ const IAICanvasMaskCompositer = (props: IAICanvasMaskCompositerProps) => {
return () => clearInterval(timer); return () => clearInterval(timer);
}, []); }, []);
const fillPatternScale = useMemo(
() => ({ x: 1 / stageScale, y: 1 / stageScale }),
[stageScale]
);
if ( if (
!fillPatternImage || !fillPatternImage ||
!isNumber(stageCoordinates.x) || !isNumber(stageCoordinates.x) ||
@ -168,7 +173,7 @@ const IAICanvasMaskCompositer = (props: IAICanvasMaskCompositerProps) => {
fillPatternImage={fillPatternImage} fillPatternImage={fillPatternImage}
fillPatternOffsetY={!isNumber(offset) ? 0 : offset} fillPatternOffsetY={!isNumber(offset) ? 0 : offset}
fillPatternRepeat="repeat" fillPatternRepeat="repeat"
fillPatternScale={{ x: 1 / stageScale, y: 1 / stageScale }} fillPatternScale={fillPatternScale}
listening={true} listening={true}
globalCompositeOperation="source-in" globalCompositeOperation="source-in"
{...rest} {...rest}

View File

@ -7,6 +7,8 @@ import { Group, Rect } from 'react-konva';
import IAICanvasImage from './IAICanvasImage'; import IAICanvasImage from './IAICanvasImage';
const dash = [4, 4]
const selector = createMemoizedSelector([stateSelector], ({ canvas }) => { const selector = createMemoizedSelector([stateSelector], ({ canvas }) => {
const { const {
layerState, layerState,
@ -69,7 +71,7 @@ const IAICanvasStagingArea = (props: Props) => {
y={y} y={y}
width={width} width={width}
height={height} height={height}
dash={[4, 4]} dash={dash}
strokeWidth={1} strokeWidth={1}
stroke="black" stroke="black"
strokeScaleEnabled={false} strokeScaleEnabled={false}

View File

@ -21,6 +21,8 @@ import { memo, useCallback, useEffect, useRef, useState } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { Group, Rect, Transformer } from 'react-konva'; import { Group, Rect, Transformer } from 'react-konva';
const borderDash = [4, 4];
const boundingBoxPreviewSelector = createMemoizedSelector( const boundingBoxPreviewSelector = createMemoizedSelector(
[stateSelector], [stateSelector],
({ canvas, generation }) => { ({ canvas, generation }) => {
@ -289,11 +291,11 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
anchorFill="rgba(212,216,234,1)" anchorFill="rgba(212,216,234,1)"
anchorSize={15} anchorSize={15}
anchorStroke="rgb(42,42,42)" anchorStroke="rgb(42,42,42)"
borderDash={[4, 4]} borderDash={borderDash}
borderEnabled={true} borderEnabled={true}
borderStroke="black" borderStroke="black"
draggable={false} draggable={false}
enabledAnchors={tool === 'move' ? undefined : []} enabledAnchors={tool === 'move' ? undefined : emptyArray}
flipEnabled={false} flipEnabled={false}
ignoreStroke={true} ignoreStroke={true}
keepRatio={false} keepRatio={false}
@ -311,3 +313,5 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
}; };
export default memo(IAICanvasBoundingBox); export default memo(IAICanvasBoundingBox);
const emptyArray: string[] = []

View File

@ -261,7 +261,7 @@ const IAICanvasToolChooserOptions = () => {
max={100} max={100}
step={1} step={1}
onChange={handleChangeBrushSize} onChange={handleChangeBrushSize}
marks={[1, 25, 50, 75, 100]} marks={marks}
/> />
<InvNumberInput <InvNumberInput
value={brushSize} value={brushSize}
@ -288,3 +288,5 @@ const IAICanvasToolChooserOptions = () => {
}; };
export default memo(IAICanvasToolChooserOptions); export default memo(IAICanvasToolChooserOptions);
const marks = [1, 25, 50, 75, 100];

View File

@ -8,50 +8,52 @@ import {
getCanvasStage, getCanvasStage,
} from 'features/canvas/util/konvaInstanceProvider'; } from 'features/canvas/util/konvaInstanceProvider';
import Konva from 'konva'; import Konva from 'konva';
import { useCallback } from 'react';
const useColorPicker = () => { const useColorPicker = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const canvasBaseLayer = getCanvasBaseLayer(); const canvasBaseLayer = getCanvasBaseLayer();
const stage = getCanvasStage(); const stage = getCanvasStage();
return { const updateColorUnderCursor = useCallback(() => {
updateColorUnderCursor: () => { if (!stage || !canvasBaseLayer) {
if (!stage || !canvasBaseLayer) { return;
return; }
}
const position = stage.getPointerPosition(); const position = stage.getPointerPosition();
if (!position) { if (!position) {
return; return;
} }
const pixelRatio = Konva.pixelRatio; const pixelRatio = Konva.pixelRatio;
const [r, g, b, a] = canvasBaseLayer const [r, g, b, a] = canvasBaseLayer
.getContext() .getContext()
.getImageData( .getImageData(
position.x * pixelRatio, position.x * pixelRatio,
position.y * pixelRatio, position.y * pixelRatio,
1, 1,
1 1
).data; ).data;
if ( if (
r === undefined || r === undefined ||
g === undefined || g === undefined ||
b === undefined || b === undefined ||
a === undefined a === undefined
) { ) {
return; return;
} }
dispatch(setColorPickerColor({ r, g, b, a })); dispatch(setColorPickerColor({ r, g, b, a }));
}, }, [canvasBaseLayer, dispatch, stage]);
commitColorUnderCursor: () => {
dispatch(commitColorPickerColor()); const commitColorUnderCursor = useCallback(() => {
}, dispatch(commitColorPickerColor());
}; }, [dispatch]);
return { updateColorUnderCursor, commitColorUnderCursor };
}; };
export default useColorPicker; export default useColorPicker;

View File

@ -1,3 +1,4 @@
import type { SystemStyleObject } from '@chakra-ui/react';
import { Box, Flex, Spinner } from '@chakra-ui/react'; import { Box, Flex, Spinner } from '@chakra-ui/react';
import { skipToken } from '@reduxjs/toolkit/query'; import { skipToken } from '@reduxjs/toolkit/query';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
@ -220,13 +221,13 @@ const ControlAdapterImagePreview = ({ isSmall, id }: Props) => {
onClick={handleSaveControlImage} onClick={handleSaveControlImage}
icon={controlImage ? <FaSave size={16} /> : undefined} icon={controlImage ? <FaSave size={16} /> : undefined}
tooltip={t('controlnet.saveControlImage')} tooltip={t('controlnet.saveControlImage')}
styleOverrides={{ marginTop: 6 }} styleOverrides={saveControlImageStyleOverrides}
/> />
<IAIDndImageIcon <IAIDndImageIcon
onClick={handleSetControlImageToDimensions} onClick={handleSetControlImageToDimensions}
icon={controlImage ? <FaRulerVertical size={16} /> : undefined} icon={controlImage ? <FaRulerVertical size={16} /> : undefined}
tooltip={t('controlnet.setControlImageDimensions')} tooltip={t('controlnet.setControlImageDimensions')}
styleOverrides={{ marginTop: 12 }} styleOverrides={setControlImageDimensionsStyleOverrides}
/> />
</> </>
@ -251,3 +252,6 @@ const ControlAdapterImagePreview = ({ isSmall, id }: Props) => {
}; };
export default memo(ControlAdapterImagePreview); export default memo(ControlAdapterImagePreview);
const saveControlImageStyleOverrides: SystemStyleObject = { mt: 6 };
const setControlImageDimensionsStyleOverrides: SystemStyleObject = { mt: 12 };

View File

@ -7,7 +7,7 @@ import {
controlAdapterBeginStepPctChanged, controlAdapterBeginStepPctChanged,
controlAdapterEndStepPctChanged, controlAdapterEndStepPctChanged,
} from 'features/controlAdapters/store/controlAdaptersSlice'; } from 'features/controlAdapters/store/controlAdaptersSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
type Props = { type Props = {
@ -55,6 +55,11 @@ export const ParamControlAdapterBeginEnd = memo(({ id }: Props) => {
); );
}, [dispatch, id]); }, [dispatch, id]);
const value = useMemo<[number, number]>(
() => [stepPcts?.beginStepPct ?? 0, stepPcts?.endStepPct ?? 1],
[stepPcts]
);
if (!stepPcts) { if (!stepPcts) {
return null; return null;
} }
@ -67,8 +72,8 @@ export const ParamControlAdapterBeginEnd = memo(({ id }: Props) => {
orientation="vertical" orientation="vertical"
> >
<InvRangeSlider <InvRangeSlider
aria-label={['Begin Step %', 'End Step %']} aria-label={ariaLabel}
value={[stepPcts.beginStepPct, stepPcts.endStepPct]} value={value}
onChange={onChange} onChange={onChange}
onReset={onReset} onReset={onReset}
min={0} min={0}
@ -83,3 +88,5 @@ export const ParamControlAdapterBeginEnd = memo(({ id }: Props) => {
}); });
ParamControlAdapterBeginEnd.displayName = 'ParamControlAdapterBeginEnd'; ParamControlAdapterBeginEnd.displayName = 'ParamControlAdapterBeginEnd';
const ariaLabel = ['Begin Step %', 'End Step %'];

View File

@ -42,10 +42,12 @@ const ParamControlAdapterWeight = ({ id }: ParamControlAdapterWeightProps) => {
min={0} min={0}
max={2} max={2}
step={0.01} step={0.01}
marks={[0, 1, 2]} marks={marks}
/> />
</InvControl> </InvControl>
); );
}; };
export default memo(ParamControlAdapterWeight); export default memo(ParamControlAdapterWeight);
const marks = [0, 1, 2];

View File

@ -83,7 +83,7 @@ const MlsdImageProcessor = (props: Props) => {
onReset={handleDetectResolutionReset} onReset={handleDetectResolutionReset}
min={0} min={0}
max={4096} max={4096}
marks={[0, 4096]} marks={marks0to4096}
withNumberInput withNumberInput
/> />
</InvControl> </InvControl>
@ -97,7 +97,7 @@ const MlsdImageProcessor = (props: Props) => {
onReset={handleImageResolutionReset} onReset={handleImageResolutionReset}
min={0} min={0}
max={4096} max={4096}
marks={[0, 4096]} marks={marks0to4096}
withNumberInput withNumberInput
/> />
</InvControl> </InvControl>
@ -109,7 +109,7 @@ const MlsdImageProcessor = (props: Props) => {
min={0} min={0}
max={1} max={1}
step={0.01} step={0.01}
marks={[0, 1]} marks={marks0to1}
withNumberInput withNumberInput
/> />
</InvControl> </InvControl>
@ -121,7 +121,7 @@ const MlsdImageProcessor = (props: Props) => {
min={0} min={0}
max={1} max={1}
step={0.01} step={0.01}
marks={[0, 1]} marks={marks0to1}
withNumberInput withNumberInput
/> />
</InvControl> </InvControl>
@ -130,3 +130,6 @@ const MlsdImageProcessor = (props: Props) => {
}; };
export default memo(MlsdImageProcessor); export default memo(MlsdImageProcessor);
const marks0to4096 = [0, 4096];
const marks0to1 = [0, 1];

View File

@ -19,7 +19,7 @@ import { customPointerWithin } from 'features/dnd/util/customPointerWithin';
import type { AnimationProps } from 'framer-motion'; import type { AnimationProps } from 'framer-motion';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import type { CSSProperties, PropsWithChildren } from 'react'; import type { CSSProperties, PropsWithChildren } from 'react';
import { memo, useCallback, useState } from 'react'; import { memo, useCallback, useMemo, useState } from 'react';
import { DndContextTypesafe } from './DndContextTypesafe'; import { DndContextTypesafe } from './DndContextTypesafe';
import DragPreview from './DragPreview'; import DragPreview from './DragPreview';
@ -78,6 +78,7 @@ const AppDndContext = (props: PropsWithChildren) => {
const sensors = useSensors(mouseSensor, touchSensor); const sensors = useSensors(mouseSensor, touchSensor);
const scaledModifier = useScaledModifer(); const scaledModifier = useScaledModifer();
const modifiers = useMemo(() => [scaledModifier], [scaledModifier]);
return ( return (
<DndContextTypesafe <DndContextTypesafe
@ -90,7 +91,7 @@ const AppDndContext = (props: PropsWithChildren) => {
{props.children} {props.children}
<DragOverlay <DragOverlay
dropAnimation={null} dropAnimation={null}
modifiers={[scaledModifier]} modifiers={modifiers}
style={dragOverlayStyles} style={dragOverlayStyles}
> >
<AnimatePresence> <AnimatePresence>

View File

@ -1,6 +1,7 @@
import type { RootState } from 'app/store/store'; import type { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InvControl } from 'common/components/InvControl/InvControl'; import { InvControl } from 'common/components/InvControl/InvControl';
import type { InvLabelProps } from 'common/components/InvControl/types';
import { InvSwitch } from 'common/components/InvSwitch/wrapper'; import { InvSwitch } from 'common/components/InvSwitch/wrapper';
import { setHrfEnabled } from 'features/hrf/store/hrfSlice'; import { setHrfEnabled } from 'features/hrf/store/hrfSlice';
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';
@ -22,10 +23,12 @@ export default function ParamHrfToggle() {
return ( return (
<InvControl <InvControl
label={t('hrf.enableHrf')} label={t('hrf.enableHrf')}
labelProps={{ flexGrow: 1 }} labelProps={labelProps}
w="full" w="full"
> >
<InvSwitch isChecked={hrfEnabled} onChange={handleHrfEnabled} /> <InvSwitch isChecked={hrfEnabled} onChange={handleHrfEnabled} />
</InvControl> </InvControl>
); );
} }
const labelProps: InvLabelProps = { flexGrow: 1 };

View File

@ -62,7 +62,7 @@ export const LoRACard = memo((props: LoRACardProps) => {
max={2} max={2}
step={0.01} step={0.01}
onReset={onReset} onReset={onReset}
marks={[-1, 0, 1, 2]} marks={marks}
/> />
<InvNumberInput <InvNumberInput
value={lora.weight} value={lora.weight}
@ -79,3 +79,5 @@ export const LoRACard = memo((props: LoRACardProps) => {
}); });
LoRACard.displayName = 'LoRACard'; LoRACard.displayName = 'LoRACard';
const marks = [-1, 0, 1, 2];

View File

@ -55,10 +55,7 @@ const EditableNodeTitle = ({ nodeId, title }: Props) => {
fontWeight="semibold" fontWeight="semibold"
> >
<EditablePreview noOfLines={1} /> <EditablePreview noOfLines={1} />
<EditableInput <EditableInput className="nodrag" />
className="nodrag"
_focusVisible={{ boxShadow: 'none' }}
/>
</Editable> </Editable>
</Flex> </Flex>
); );

View File

@ -5,7 +5,7 @@ import { InvControl } from 'common/components/InvControl/InvControl';
import { InvNumberInput } from 'common/components/InvNumberInput/InvNumberInput'; import { InvNumberInput } from 'common/components/InvNumberInput/InvNumberInput';
import { InvSlider } from 'common/components/InvSlider/InvSlider'; import { InvSlider } from 'common/components/InvSlider/InvSlider';
import { heightChanged } from 'features/parameters/store/generationSlice'; import { heightChanged } from 'features/parameters/store/generationSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const selector = createMemoizedSelector( const selector = createMemoizedSelector(
@ -49,6 +49,8 @@ export const ParamHeight = memo(() => {
dispatch(heightChanged(initial)); dispatch(heightChanged(initial));
}, [dispatch, initial]); }, [dispatch, initial]);
const marks = useMemo(()=>[min, initial, max], [min, initial, max])
return ( return (
<InvControl label={t('parameters.height')}> <InvControl label={t('parameters.height')}>
<InvSlider <InvSlider
@ -59,7 +61,7 @@ export const ParamHeight = memo(() => {
max={max} max={max}
step={step} step={step}
fineStep={fineStep} fineStep={fineStep}
marks={[min, initial, max]} marks={marks}
/> />
<InvNumberInput <InvNumberInput
value={height} value={height}

View File

@ -1,5 +1,6 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InvNumberInput } from 'common/components/InvNumberInput/InvNumberInput'; import { InvNumberInput } from 'common/components/InvNumberInput/InvNumberInput';
import type { InvNumberInputFieldProps } from 'common/components/InvNumberInput/types';
import { setIterations } from 'features/parameters/store/generationSlice'; import { setIterations } from 'features/parameters/store/generationSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
@ -23,14 +24,16 @@ const ParamIterations = () => {
value={iterations} value={iterations}
h="full" h="full"
w="216px" w="216px"
numberInputFieldProps={{ numberInputFieldProps={numberInputFieldProps}
ps: '144px',
borderInlineStartRadius: 'base',
h: 'full',
textAlign: 'center',
}}
/> />
); );
}; };
export default memo(ParamIterations); export default memo(ParamIterations);
const numberInputFieldProps: InvNumberInputFieldProps = {
ps: '144px',
borderInlineStartRadius: 'base',
h: 'full',
textAlign: 'center',
};

View File

@ -5,7 +5,7 @@ import { InvControl } from 'common/components/InvControl/InvControl';
import { InvNumberInput } from 'common/components/InvNumberInput/InvNumberInput'; import { InvNumberInput } from 'common/components/InvNumberInput/InvNumberInput';
import { InvSlider } from 'common/components/InvSlider/InvSlider'; import { InvSlider } from 'common/components/InvSlider/InvSlider';
import { widthChanged } from 'features/parameters/store/generationSlice'; import { widthChanged } from 'features/parameters/store/generationSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const selector = createMemoizedSelector( const selector = createMemoizedSelector(
@ -48,6 +48,8 @@ export const ParamWidth = memo(() => {
dispatch(widthChanged(initial)); dispatch(widthChanged(initial));
}, [dispatch, initial]); }, [dispatch, initial]);
const marks = useMemo(() => [min, initial, max], [min, initial, max]);
return ( return (
<InvControl label={t('parameters.width')}> <InvControl label={t('parameters.width')}>
<InvSlider <InvSlider
@ -58,7 +60,7 @@ export const ParamWidth = memo(() => {
max={max} max={max}
step={step} step={step}
fineStep={fineStep} fineStep={fineStep}
marks={[min, initial, max]} marks={marks}
/> />
<InvNumberInput <InvNumberInput
value={width} value={width}

View File

@ -1,12 +1,15 @@
import type { RootState } from 'app/store/store'; import type { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InvControl } from 'common/components/InvControl/InvControl'; import { InvControl } from 'common/components/InvControl/InvControl';
import type { InvLabelProps } from 'common/components/InvControl/types';
import { InvSwitch } from 'common/components/InvSwitch/wrapper'; import { InvSwitch } from 'common/components/InvSwitch/wrapper';
import { setShouldFitToWidthHeight } from 'features/parameters/store/generationSlice'; import { setShouldFitToWidthHeight } from 'features/parameters/store/generationSlice';
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const labelProps: InvLabelProps = { flexGrow: 1 };
export default function ImageToImageFit() { export default function ImageToImageFit() {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -26,7 +29,7 @@ export default function ImageToImageFit() {
return ( return (
<InvControl <InvControl
label={t('parameters.imageFit')} label={t('parameters.imageFit')}
labelProps={{ flexGrow: 1 }} labelProps={labelProps}
w="full" w="full"
> >
<InvSwitch <InvSwitch

View File

@ -1,4 +1,4 @@
import type { ChakraProps } from '@chakra-ui/react'; import type { ChakraProps, CollapseProps } from '@chakra-ui/react';
import { Collapse, Flex } from '@chakra-ui/react'; import { Collapse, Flex } from '@chakra-ui/react';
import { InvButtonGroup } from 'common/components/InvButtonGroup/InvButtonGroup'; import { InvButtonGroup } from 'common/components/InvButtonGroup/InvButtonGroup';
import { InvIconButton } from 'common/components/InvIconButton/InvIconButton'; import { InvIconButton } from 'common/components/InvIconButton/InvIconButton';
@ -63,6 +63,7 @@ const QueueItemComponent = ({ index, item, context }: InnerItemProps) => {
[item.status] [item.status]
); );
const icon = useMemo(() => (<FaTimes />), []);
return ( return (
<Flex <Flex
flexDir="column" flexDir="column"
@ -137,21 +138,22 @@ const QueueItemComponent = ({ index, item, context }: InnerItemProps) => {
isDisabled={isCanceled} isDisabled={isCanceled}
isLoading={isLoading} isLoading={isLoading}
aria-label={t('queue.cancelItem')} aria-label={t('queue.cancelItem')}
icon={<FaTimes />} icon={icon}
/> />
</InvButtonGroup> </InvButtonGroup>
</Flex> </Flex>
</Flex> </Flex>
<Collapse <Collapse in={isOpen} transition={transition} unmountOnExit={true}>
in={isOpen}
transition={{ enter: { duration: 0.1 }, exit: { duration: 0.1 } }}
unmountOnExit={true}
>
<QueueItemDetail queueItemDTO={item} /> <QueueItemDetail queueItemDTO={item} />
</Collapse> </Collapse>
</Flex> </Flex>
); );
}; };
const transition: CollapseProps['transition'] = {
enter: { duration: 0.1 },
exit: { duration: 0.1 },
};
export default memo(QueueItemComponent); export default memo(QueueItemComponent);