tidy(ui): tool components & translations

This commit is contained in:
psychedelicious 2024-08-15 20:52:51 +10:00
parent 97c966b04f
commit c172657324
16 changed files with 93 additions and 87 deletions

View File

@ -1697,7 +1697,17 @@
"objects_other": "{{count}} objects", "objects_other": "{{count}} objects",
"filter": "Filter", "filter": "Filter",
"convertToControlLayer": "Convert to Control Layer", "convertToControlLayer": "Convert to Control Layer",
"convertToRasterLayer": "Convert to Raster Layer" "convertToRasterLayer": "Convert to Raster Layer",
"tool": {
"brush": "Brush",
"eraser": "Eraser",
"rectangle": "Rectangle",
"bbox": "Bbox",
"move": "Move",
"view": "View",
"transform": "Transform",
"eyeDropper": "Eye Dropper"
}
}, },
"upscaling": { "upscaling": {
"upscale": "Upscale", "upscale": "Upscale",

View File

@ -2,14 +2,14 @@
import { Flex, Switch } from '@invoke-ai/ui-library'; import { Flex, Switch } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { BrushWidth } from 'features/controlLayers/components/BrushWidth';
import { CanvasResetViewButton } from 'features/controlLayers/components/CanvasResetViewButton'; import { CanvasResetViewButton } from 'features/controlLayers/components/CanvasResetViewButton';
import { CanvasScale } from 'features/controlLayers/components/CanvasScale'; import { CanvasScale } from 'features/controlLayers/components/CanvasScale';
import ControlLayersSettingsPopover from 'features/controlLayers/components/ControlLayersSettingsPopover'; import ControlLayersSettingsPopover from 'features/controlLayers/components/ControlLayersSettingsPopover';
import { EraserWidth } from 'features/controlLayers/components/EraserWidth';
import { FillColorPicker } from 'features/controlLayers/components/FillColorPicker';
import { ResetCanvasButton } from 'features/controlLayers/components/ResetCanvasButton'; import { ResetCanvasButton } from 'features/controlLayers/components/ResetCanvasButton';
import { ToolChooser } from 'features/controlLayers/components/ToolChooser'; import { ToolBrushWidth } from 'features/controlLayers/components/Tool/ToolBrushWidth';
import { ToolChooser } from 'features/controlLayers/components/Tool/ToolChooser';
import { ToolEraserWidth } from 'features/controlLayers/components/Tool/ToolEraserWidth';
import { ToolFillColorPicker } from 'features/controlLayers/components/Tool/ToolFillColorPicker';
import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup'; import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup';
import { $canvasManager } from 'features/controlLayers/konva/CanvasManager'; import { $canvasManager } from 'features/controlLayers/konva/CanvasManager';
import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton'; import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton';
@ -42,15 +42,15 @@ export const ControlLayersToolbar = memo(() => {
</Flex> </Flex>
</Flex> </Flex>
<Flex flex={1} gap={2} justifyContent="center" alignItems="center"> <Flex flex={1} gap={2} justifyContent="center" alignItems="center">
{tool === 'brush' && <BrushWidth />} {tool === 'brush' && <ToolBrushWidth />}
{tool === 'eraser' && <EraserWidth />} {tool === 'eraser' && <ToolEraserWidth />}
</Flex> </Flex>
<CanvasScale /> <CanvasScale />
<CanvasResetViewButton /> <CanvasResetViewButton />
<Switch onChange={onChangeDebugging}>debug</Switch> <Switch onChange={onChangeDebugging}>debug</Switch>
<Flex flex={1} justifyContent="center"> <Flex flex={1} justifyContent="center">
<Flex gap={2} marginInlineStart="auto" alignItems="center"> <Flex gap={2} marginInlineStart="auto" alignItems="center">
<FillColorPicker /> <ToolFillColorPicker />
<UndoRedoButtonGroup /> <UndoRedoButtonGroup />
<ControlLayersSettingsPopover /> <ControlLayersSettingsPopover />
<ResetCanvasButton /> <ResetCanvasButton />

View File

@ -6,7 +6,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiBoundingBoxBold } from 'react-icons/pi'; import { PiBoundingBoxBold } from 'react-icons/pi';
export const BboxToolButton = memo(() => { export const ToolBboxButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const isDisabled = useAppSelector((s) => s.canvasV2.session.isStaging || s.canvasV2.tool.isTransforming); const isDisabled = useAppSelector((s) => s.canvasV2.session.isStaging || s.canvasV2.tool.isTransforming);
@ -20,8 +20,8 @@ export const BboxToolButton = memo(() => {
return ( return (
<IconButton <IconButton
aria-label={`${t('controlLayers.bbox')} (Q)`} aria-label={`${t('controlLayers.tool.bbox')} (Q)`}
tooltip={`${t('controlLayers.bbox')} (Q)`} tooltip={`${t('controlLayers.tool.bbox')} (Q)`}
icon={<PiBoundingBoxBold />} icon={<PiBoundingBoxBold />}
colorScheme={isSelected ? 'invokeBlue' : 'base'} colorScheme={isSelected ? 'invokeBlue' : 'base'}
variant="outline" variant="outline"
@ -31,4 +31,4 @@ export const BboxToolButton = memo(() => {
); );
}); });
BboxToolButton.displayName = 'BboxToolButton'; ToolBboxButton.displayName = 'ToolBboxButton';

View File

@ -7,7 +7,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiPaintBrushBold } from 'react-icons/pi'; import { PiPaintBrushBold } from 'react-icons/pi';
export const BrushToolButton = memo(() => { export const ToolBrushButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'brush'); const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'brush');
@ -26,8 +26,8 @@ export const BrushToolButton = memo(() => {
return ( return (
<IconButton <IconButton
aria-label={`${t('unifiedCanvas.brush')} (B)`} aria-label={`${t('controlLayers.tool.brush')} (B)`}
tooltip={`${t('unifiedCanvas.brush')} (B)`} tooltip={`${t('controlLayers.tool.brush')} (B)`}
icon={<PiPaintBrushBold />} icon={<PiPaintBrushBold />}
colorScheme={isSelected ? 'invokeBlue' : 'base'} colorScheme={isSelected ? 'invokeBlue' : 'base'}
variant="outline" variant="outline"
@ -37,4 +37,4 @@ export const BrushToolButton = memo(() => {
); );
}); });
BrushToolButton.displayName = 'BrushToolButton'; ToolBrushButton.displayName = 'ToolBrushButton';

View File

@ -17,7 +17,7 @@ import { useTranslation } from 'react-i18next';
const marks = [0, 100, 200, 300]; const marks = [0, 100, 200, 300];
const formatPx = (v: number | string) => `${v} px`; const formatPx = (v: number | string) => `${v} px`;
export const BrushWidth = memo(() => { export const ToolBrushWidth = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const width = useAppSelector((s) => s.canvasV2.tool.brush.width); const width = useAppSelector((s) => s.canvasV2.tool.brush.width);
@ -53,4 +53,4 @@ export const BrushWidth = memo(() => {
); );
}); });
BrushWidth.displayName = 'BrushSize'; ToolBrushWidth.displayName = 'ToolBrushWidth';

View File

@ -0,0 +1,34 @@
import { ButtonGroup } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { ToolBboxButton } from 'features/controlLayers/components/Tool/ToolBboxButton';
import { ToolBrushButton } from 'features/controlLayers/components/Tool/ToolBrushButton';
import { ToolEyeDropperButton } from 'features/controlLayers/components/Tool/ToolEyeDropperButton';
import { ToolMoveButton } from 'features/controlLayers/components/Tool/ToolMoveButton';
import { ToolRectButton } from 'features/controlLayers/components/Tool/ToolRectButton';
import { useCanvasDeleteLayerHotkey } from 'features/controlLayers/hooks/useCanvasDeleteLayerHotkey';
import { useCanvasResetLayerHotkey } from 'features/controlLayers/hooks/useCanvasResetLayerHotkey';
import { ToolEraserButton } from './ToolEraserButton';
import { ToolTransformButton } from './ToolTransformButton';
import { ToolViewButton } from './ToolViewButton';
export const ToolChooser: React.FC = () => {
useCanvasResetLayerHotkey();
useCanvasDeleteLayerHotkey();
const isTransforming = useAppSelector((s) => s.canvasV2.tool.isTransforming);
return (
<>
<ButtonGroup isAttached isDisabled={isTransforming}>
<ToolBrushButton />
<ToolEraserButton />
<ToolRectButton />
<ToolMoveButton />
<ToolViewButton />
<ToolBboxButton />
<ToolEyeDropperButton />
</ButtonGroup>
<ToolTransformButton />
</>
);
};

View File

@ -7,7 +7,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiEraserBold } from 'react-icons/pi'; import { PiEraserBold } from 'react-icons/pi';
export const EraserToolButton = memo(() => { export const ToolEraserButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'eraser'); const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'eraser');
@ -26,8 +26,8 @@ export const EraserToolButton = memo(() => {
return ( return (
<IconButton <IconButton
aria-label={`${t('unifiedCanvas.eraser')} (E)`} aria-label={`${t('controlLayers.tool.eraser')} (E)`}
tooltip={`${t('unifiedCanvas.eraser')} (E)`} tooltip={`${t('controlLayers.tool.eraser')} (E)`}
icon={<PiEraserBold />} icon={<PiEraserBold />}
colorScheme={isSelected ? 'invokeBlue' : 'base'} colorScheme={isSelected ? 'invokeBlue' : 'base'}
variant="outline" variant="outline"
@ -37,4 +37,4 @@ export const EraserToolButton = memo(() => {
); );
}); });
EraserToolButton.displayName = 'EraserToolButton'; ToolEraserButton.displayName = 'ToolEraserButton';

View File

@ -17,7 +17,7 @@ import { useTranslation } from 'react-i18next';
const marks = [0, 100, 200, 300]; const marks = [0, 100, 200, 300];
const formatPx = (v: number | string) => `${v} px`; const formatPx = (v: number | string) => `${v} px`;
export const EraserWidth = memo(() => { export const ToolEraserWidth = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const width = useAppSelector((s) => s.canvasV2.tool.eraser.width); const width = useAppSelector((s) => s.canvasV2.tool.eraser.width);
@ -53,4 +53,4 @@ export const EraserWidth = memo(() => {
); );
}); });
EraserWidth.displayName = 'EraserWidth'; ToolEraserWidth.displayName = 'ToolEraserWidth';

View File

@ -20,8 +20,8 @@ export const ToolEyeDropperButton = memo(() => {
return ( return (
<IconButton <IconButton
aria-label={`${t('controlLayers.eyeDropper')} (I)`} aria-label={`${t('controlLayers.tool.eyeDropper')} (I)`}
tooltip={`${t('controlLayers.eyeDropper')} (I)`} tooltip={`${t('controlLayers.tool.eyeDropper')} (I)`}
icon={<PiEyedropperBold />} icon={<PiEyedropperBold />}
colorScheme={isSelected ? 'invokeBlue' : 'base'} colorScheme={isSelected ? 'invokeBlue' : 'base'}
variant="outline" variant="outline"

View File

@ -7,7 +7,7 @@ import type { RgbaColor } from 'features/controlLayers/store/types';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export const FillColorPicker = memo(() => { export const ToolFillColorPicker = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const fill = useAppSelector((s) => s.canvasV2.tool.fill); const fill = useAppSelector((s) => s.canvasV2.tool.fill);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -41,4 +41,4 @@ export const FillColorPicker = memo(() => {
); );
}); });
FillColorPicker.displayName = 'BrushColorPicker'; ToolFillColorPicker.displayName = 'ToolFillColorPicker';

View File

@ -6,7 +6,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiCursorBold } from 'react-icons/pi'; import { PiCursorBold } from 'react-icons/pi';
export const MoveToolButton = memo(() => { export const ToolMoveButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'move'); const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'move');
@ -22,8 +22,8 @@ export const MoveToolButton = memo(() => {
return ( return (
<IconButton <IconButton
aria-label={`${t('unifiedCanvas.move')} (V)`} aria-label={`${t('controlLayers.tool.move')} (V)`}
tooltip={`${t('unifiedCanvas.move')} (V)`} tooltip={`${t('controlLayers.tool.move')} (V)`}
icon={<PiCursorBold />} icon={<PiCursorBold />}
colorScheme={isSelected ? 'invokeBlue' : 'base'} colorScheme={isSelected ? 'invokeBlue' : 'base'}
variant="outline" variant="outline"
@ -33,4 +33,4 @@ export const MoveToolButton = memo(() => {
); );
}); });
MoveToolButton.displayName = 'MoveToolButton'; ToolMoveButton.displayName = 'ToolMoveButton';

View File

@ -7,7 +7,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiRectangleBold } from 'react-icons/pi'; import { PiRectangleBold } from 'react-icons/pi';
export const RectToolButton = memo(() => { export const ToolRectButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'rect'); const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'rect');
@ -26,8 +26,8 @@ export const RectToolButton = memo(() => {
return ( return (
<IconButton <IconButton
aria-label={`${t('controlLayers.rectangle')} (U)`} aria-label={`${t('controlLayers.tool.rectangle')} (U)`}
tooltip={`${t('controlLayers.rectangle')} (U)`} tooltip={`${t('controlLayers.tool.rectangle')} (U)`}
icon={<PiRectangleBold />} icon={<PiRectangleBold />}
colorScheme={isSelected ? 'invokeBlue' : 'base'} colorScheme={isSelected ? 'invokeBlue' : 'base'}
variant="outline" variant="outline"
@ -37,4 +37,4 @@ export const RectToolButton = memo(() => {
); );
}); });
RectToolButton.displayName = 'RectToolButton'; ToolRectButton.displayName = 'ToolRectButton';

View File

@ -8,7 +8,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiResizeBold } from 'react-icons/pi'; import { PiResizeBold } from 'react-icons/pi';
export const TransformToolButton = memo(() => { export const ToolTransformButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const canvasManager = useStore($canvasManager); const canvasManager = useStore($canvasManager);
const transformingEntity = useStore($transformingEntity); const transformingEntity = useStore($transformingEntity);
@ -50,8 +50,8 @@ export const TransformToolButton = memo(() => {
return ( return (
<IconButton <IconButton
aria-label={`${t('unifiedCanvas.transform')} (Ctrl+T)`} aria-label={`${t('controlLayers.tool.transform')} (Ctrl+T)`}
tooltip={`${t('unifiedCanvas.transform')} (Ctrl+T)`} tooltip={`${t('controlLayers.tool.transform')} (Ctrl+T)`}
icon={<PiResizeBold />} icon={<PiResizeBold />}
variant="solid" variant="solid"
onClick={onTransform} onClick={onTransform}
@ -60,4 +60,4 @@ export const TransformToolButton = memo(() => {
); );
}); });
TransformToolButton.displayName = 'TransformToolButton'; ToolTransformButton.displayName = 'ToolTransformButton';

View File

@ -6,7 +6,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiHandBold } from 'react-icons/pi'; import { PiHandBold } from 'react-icons/pi';
export const ViewToolButton = memo(() => { export const ToolViewButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'view'); const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'view');
@ -19,8 +19,8 @@ export const ViewToolButton = memo(() => {
return ( return (
<IconButton <IconButton
aria-label={`${t('unifiedCanvas.view')} (H)`} aria-label={`${t('controlLayers.tool.view')} (H)`}
tooltip={`${t('unifiedCanvas.view')} (H)`} tooltip={`${t('controlLayers.tool.view')} (H)`}
icon={<PiHandBold />} icon={<PiHandBold />}
colorScheme={isSelected ? 'invokeBlue' : 'base'} colorScheme={isSelected ? 'invokeBlue' : 'base'}
variant="outline" variant="outline"
@ -30,4 +30,4 @@ export const ViewToolButton = memo(() => {
); );
}); });
ViewToolButton.displayName = 'ViewToolButton'; ToolViewButton.displayName = 'ToolViewButton';

View File

@ -1,33 +0,0 @@
import { ButtonGroup } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { BboxToolButton } from 'features/controlLayers/components/BboxToolButton';
import { BrushToolButton } from 'features/controlLayers/components/BrushToolButton';
import { EraserToolButton } from 'features/controlLayers/components/EraserToolButton';
import { MoveToolButton } from 'features/controlLayers/components/MoveToolButton';
import { RectToolButton } from 'features/controlLayers/components/RectToolButton';
import { ToolEyeDropperButton } from 'features/controlLayers/components/ToolEyeDropperButton';
import { TransformToolButton } from 'features/controlLayers/components/TransformToolButton';
import { ViewToolButton } from 'features/controlLayers/components/ViewToolButton';
import { useCanvasDeleteLayerHotkey } from 'features/controlLayers/hooks/useCanvasDeleteLayerHotkey';
import { useCanvasResetLayerHotkey } from 'features/controlLayers/hooks/useCanvasResetLayerHotkey';
export const ToolChooser: React.FC = () => {
useCanvasResetLayerHotkey();
useCanvasDeleteLayerHotkey();
const isTransforming = useAppSelector((s) => s.canvasV2.tool.isTransforming);
return (
<>
<ButtonGroup isAttached isDisabled={isTransforming}>
<BrushToolButton />
<EraserToolButton />
<RectToolButton />
<MoveToolButton />
<ViewToolButton />
<BboxToolButton />
<ToolEyeDropperButton />
</ButtonGroup>
<TransformToolButton />
</>
);
};

View File

@ -8,6 +8,7 @@ import {
BRUSH_ERASER_BORDER_WIDTH, BRUSH_ERASER_BORDER_WIDTH,
} from 'features/controlLayers/konva/constants'; } from 'features/controlLayers/konva/constants';
import { alignCoordForTool, getPrefixedId } from 'features/controlLayers/konva/util'; import { alignCoordForTool, getPrefixedId } from 'features/controlLayers/konva/util';
import type { Tool } from 'features/controlLayers/store/types';
import { isDrawableEntity } from 'features/controlLayers/store/types'; import { isDrawableEntity } from 'features/controlLayers/store/types';
import Konva from 'konva'; import Konva from 'konva';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
@ -175,7 +176,7 @@ export class CanvasTool {
}); });
}; };
setToolVisibility = (tool: 'brush' | 'eraser' | 'eyeDropper' | 'none') => { setToolVisibility = (tool: Tool) => {
this.konva.brush.group.visible(tool === 'brush'); this.konva.brush.group.visible(tool === 'brush');
this.konva.eraser.group.visible(tool === 'eraser'); this.konva.eraser.group.visible(tool === 'eraser');
this.konva.eyeDropper.group.visible(tool === 'eyeDropper'); this.konva.eyeDropper.group.visible(tool === 'eyeDropper');
@ -252,9 +253,6 @@ export class CanvasTool {
y: cursorPos.y, y: cursorPos.y,
radius: radius + BRUSH_ERASER_BORDER_WIDTH / scale, radius: radius + BRUSH_ERASER_BORDER_WIDTH / scale,
}); });
this.scaleTool();
this.setToolVisibility('brush');
} else if (cursorPos && tool === 'eraser') { } else if (cursorPos && tool === 'eraser') {
const alignedCursorPos = alignCoordForTool(cursorPos, toolState.eraser.width); const alignedCursorPos = alignCoordForTool(cursorPos, toolState.eraser.width);
@ -277,9 +275,6 @@ export class CanvasTool {
y: cursorPos.y, y: cursorPos.y,
radius: radius + BRUSH_ERASER_BORDER_WIDTH / scale, radius: radius + BRUSH_ERASER_BORDER_WIDTH / scale,
}); });
this.scaleTool();
this.setToolVisibility('eraser');
} else if (cursorPos && colorUnderCursor) { } else if (cursorPos && colorUnderCursor) {
this.konva.eyeDropper.fillCircle.setAttrs({ this.konva.eyeDropper.fillCircle.setAttrs({
x: cursorPos.x, x: cursorPos.x,
@ -290,10 +285,10 @@ export class CanvasTool {
x: cursorPos.x, x: cursorPos.x,
y: cursorPos.y, y: cursorPos.y,
}); });
this.setToolVisibility('eyeDropper');
} else {
this.setToolVisibility('none');
} }
this.scaleTool();
this.setToolVisibility(tool);
} }
} }