[WebUI] Localization Support (#2050)

* Initial Localization Implementation

* Fix Initial Spinner

* Language Picker Dropdown

* RU Localization Update

Co-Authored-By: Artur <83028930+netsvetaev@users.noreply.github.com>

* Fixed localization breaking themes

* useUpdateTranslation Hook

To force trigger translations for data objects

* Localize Tab Data

* Localize Prompt Input & Current Image Buttons

* Localize Gallery & Bug FIxes

Fix a bug where the delete image from the context menu wasn't working. Removed tooltips that were broken as they don't work in context menu.

* Fix localization breaking in production

* Add Toast Localization Support

* Localize Unified Canvas

* Localize WIP Tabs

* Localize Hotkeys

* Localize Settings

* RU Localization Update

Co-Authored-By: Artur <83028930+netsvetaev@users.noreply.github.com>

* Add Support for Italian and Portuguese

* Localize Toasts

* Fix width of language picker items

* Localize Backend Messages

* Disable Debug Messages

* Add Support for French

* Fix missing localization for a string in the SettingsModal

* Disable French

* Styling updates to normalize text and accommodate other langs

* Add Portuguese Brazilian

* Fix Hotkey headers not being localized.

* Fix styling issue on models tag in Settings

* Fix Slider Styling to accommodate different languages

* Fix slider styling in light mode.

* Add German

* Add Italian

* Add Polish

* Update Italian

* Localized Frontend Build

* Updated RU Translations

* Fresh Build with updated RU changes

* Bug Fixes and Loc Updates

* Updated Frontend Build

* Fresh Build

Co-authored-by: Artur <83028930+netsvetaev@users.noreply.github.com>
This commit is contained in:
blessedcoolant
2022-12-25 07:23:21 +13:00
committed by GitHub
parent f961e865f5
commit 1d34405f4f
234 changed files with 7114 additions and 1246 deletions

View File

@@ -2,30 +2,29 @@ import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAIAlertDialog from 'common/components/IAIAlertDialog';
import IAIButton from 'common/components/IAIButton';
import { clearCanvasHistory } from 'features/canvas/store/canvasSlice';
import { useTranslation } from 'react-i18next';
import { FaTrash } from 'react-icons/fa';
import { isStagingSelector } from '../store/canvasSelectors';
const ClearCanvasHistoryButtonModal = () => {
const isStaging = useAppSelector(isStagingSelector);
const dispatch = useAppDispatch();
const { t } = useTranslation();
return (
<IAIAlertDialog
title={'Clear Canvas History'}
title={t('unifiedcanvas:clearCanvasHistory')}
acceptCallback={() => dispatch(clearCanvasHistory())}
acceptButtonText={'Clear History'}
acceptButtonText={t('unifiedcanvas:clearHistory')}
triggerComponent={
<IAIButton size={'sm'} leftIcon={<FaTrash />} isDisabled={isStaging}>
Clear Canvas History
{t('unifiedcanvas:clearCanvasHistory')}
</IAIButton>
}
>
<p>
Clearing the canvas history leaves your current canvas intact, but
irreversibly clears the undo and redo history.
</p>
<p>{t('unifiedcanvas:clearCanvasHistoryMessage')}</p>
<br />
<p>Are you sure you want to clear the canvas history?</p>
<p>{t('unifiedcanvas:clearCanvasHistoryConfirm')}</p>
</IAIAlertDialog>
);
};

View File

@@ -24,6 +24,7 @@ import {
} from 'features/canvas/store/canvasSlice';
import { useHotkeys } from 'react-hotkeys-hook';
import { saveStagingAreaImageToGallery } from 'app/socketio/actions';
import { useTranslation } from 'react-i18next';
const selector = createSelector(
[canvasSelector],
@@ -61,6 +62,8 @@ const IAICanvasStagingAreaToolbar = () => {
shouldShowStagingImage,
} = useAppSelector(selector);
const { t } = useTranslation();
const handleMouseOver = useCallback(() => {
dispatch(setShouldShowStagingOutline(true));
}, [dispatch]);
@@ -121,31 +124,31 @@ const IAICanvasStagingAreaToolbar = () => {
>
<ButtonGroup isAttached>
<IAIIconButton
tooltip="Previous (Left)"
aria-label="Previous (Left)"
tooltip={`${t('unifiedcanvas:previous')} (Left)`}
aria-label={`${t('unifiedcanvas:previous')} (Left)`}
icon={<FaArrowLeft />}
onClick={handlePrevImage}
data-selected={true}
isDisabled={isOnFirstImage}
/>
<IAIIconButton
tooltip="Next (Right)"
aria-label="Next (Right)"
tooltip={`${t('unifiedcanvas:next')} (Right)`}
aria-label={`${t('unifiedcanvas:next')} (Right)`}
icon={<FaArrowRight />}
onClick={handleNextImage}
data-selected={true}
isDisabled={isOnLastImage}
/>
<IAIIconButton
tooltip="Accept (Enter)"
aria-label="Accept (Enter)"
tooltip={`${t('unifiedcanvas:accept')} (Enter)`}
aria-label={`${t('unifiedcanvas:accept')} (Enter)`}
icon={<FaCheck />}
onClick={handleAccept}
data-selected={true}
/>
<IAIIconButton
tooltip="Show/Hide"
aria-label="Show/Hide"
tooltip={t('unifiedcanvas:showHide')}
aria-label={t('unifiedcanvas:showHide')}
data-alert={!shouldShowStagingImage}
icon={shouldShowStagingImage ? <FaEye /> : <FaEyeSlash />}
onClick={() =>
@@ -154,8 +157,8 @@ const IAICanvasStagingAreaToolbar = () => {
data-selected={true}
/>
<IAIIconButton
tooltip="Save to Gallery"
aria-label="Save to Gallery"
tooltip={t('unifiedcanvas:saveToGallery')}
aria-label={t('unifiedcanvas:saveToGallery')}
icon={<FaSave />}
onClick={() =>
dispatch(
@@ -165,8 +168,8 @@ const IAICanvasStagingAreaToolbar = () => {
data-selected={true}
/>
<IAIIconButton
tooltip="Discard All"
aria-label="Discard All"
tooltip={t('unifiedcanvas:discardAll')}
aria-label={t('unifiedcanvas:discardAll')}
icon={<FaPlus style={{ transform: 'rotate(45deg)' }} />}
onClick={() => dispatch(discardStagedImages())}
data-selected={true}

View File

@@ -4,6 +4,7 @@ import _ from 'lodash';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import IAICanvasStatusTextCursorPos from './IAICanvasStatusText/IAICanvasStatusTextCursorPos';
import roundToHundreth from '../util/roundToHundreth';
import { useTranslation } from 'react-i18next';
const selector = createSelector(
[canvasSelector],
@@ -79,33 +80,45 @@ const IAICanvasStatusText = () => {
shouldShowBoundingBox,
} = useAppSelector(selector);
const { t } = useTranslation();
return (
<div className="canvas-status-text">
<div
style={{
color: activeLayerColor,
}}
>{`Active Layer: ${activeLayerString}`}</div>
<div>{`Canvas Scale: ${canvasScaleString}%`}</div>
>{`${t('unifiedcanvas:activeLayer')}: ${activeLayerString}`}</div>
<div>{`${t('unifiedcanvas:canvasScale')}: ${canvasScaleString}%`}</div>
{shouldShowBoundingBox && (
<div
style={{
color: boundingBoxColor,
}}
>{`Bounding Box: ${boundingBoxDimensionsString}`}</div>
>{`${t(
'unifiedcanvas:boundingBox'
)}: ${boundingBoxDimensionsString}`}</div>
)}
{shouldShowScaledBoundingBox && (
<div
style={{
color: boundingBoxColor,
}}
>{`Scaled Bounding Box: ${scaledBoundingBoxDimensionsString}`}</div>
>{`${t(
'unifiedcanvas:scaledBoundingBox'
)}: ${scaledBoundingBoxDimensionsString}`}</div>
)}
{shouldShowCanvasDebugInfo && (
<>
<div>{`Bounding Box Position: ${boundingBoxCoordinatesString}`}</div>
<div>{`Canvas Dimensions: ${canvasDimensionsString}`}</div>
<div>{`Canvas Position: ${canvasCoordinatesString}`}</div>
<div>{`${t(
'unifiedcanvas:boundingBoxPosition'
)}: ${boundingBoxCoordinatesString}`}</div>
<div>{`${t(
'unifiedcanvas:canvasDimensions'
)}: ${canvasDimensionsString}`}</div>
<div>{`${t(
'unifiedcanvas:canvasPosition'
)}: ${canvasCoordinatesString}`}</div>
<IAICanvasStatusTextCursorPos />
</>
)}

View File

@@ -4,6 +4,7 @@ import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import React from 'react';
import _ from 'lodash';
import roundToHundreth from 'features/canvas/util/roundToHundreth';
import { useTranslation } from 'react-i18next';
const cursorPositionSelector = createSelector(
[canvasSelector],
@@ -29,6 +30,11 @@ const cursorPositionSelector = createSelector(
export default function IAICanvasStatusTextCursorPos() {
const { cursorCoordinatesString } = useAppSelector(cursorPositionSelector);
const { t } = useTranslation();
return <div>{`Cursor Position: ${cursorCoordinatesString}`}</div>;
return (
<div>{`${t(
'unifiedcanvas:cursorPosition'
)}: ${cursorCoordinatesString}`}</div>
);
}

View File

@@ -21,6 +21,7 @@ import {
} from 'features/canvas/store/canvasSelectors';
import { useHotkeys } from 'react-hotkeys-hook';
import { rgbaColorToString } from 'features/canvas/util/colorToString';
import { useTranslation } from 'react-i18next';
export const selector = createSelector(
[canvasSelector, isStagingSelector],
@@ -45,6 +46,8 @@ export const selector = createSelector(
);
const IAICanvasMaskOptions = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const {
layer,
maskColor,
@@ -104,8 +107,8 @@ const IAICanvasMaskOptions = () => {
triggerComponent={
<ButtonGroup>
<IAIIconButton
aria-label="Masking Options"
tooltip="Masking Options"
aria-label={t('unifiedcanvas:maskingOptions')}
tooltip={t('unifiedcanvas:maskingOptions')}
icon={<FaMask />}
style={
layer === 'mask'
@@ -119,12 +122,12 @@ const IAICanvasMaskOptions = () => {
>
<Flex direction={'column'} gap={'0.5rem'}>
<IAICheckbox
label="Enable Mask (H)"
label={`${t('unifiedcanvas:enableMask')} (H)`}
isChecked={isMaskEnabled}
onChange={handleToggleEnableMask}
/>
<IAICheckbox
label="Preserve Masked Area"
label={t('unifiedcanvas:preserveMaskedArea')}
isChecked={shouldPreserveMaskedArea}
onChange={(e) =>
dispatch(setShouldPreserveMaskedArea(e.target.checked))
@@ -136,7 +139,7 @@ const IAICanvasMaskOptions = () => {
onChange={(newColor) => dispatch(setMaskColor(newColor))}
/>
<IAIButton size={'sm'} leftIcon={<FaTrash />} onClick={handleClearMask}>
Clear Mask (Shift+C)
{t('unifiedcanvas:clearMask')} (Shift+C)
</IAIButton>
</Flex>
</IAIPopover>

View File

@@ -9,6 +9,7 @@ import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import _ from 'lodash';
import { redo } from 'features/canvas/store/canvasSlice';
import { systemSelector } from 'features/system/store/systemSelectors';
import { useTranslation } from 'react-i18next';
const canvasRedoSelector = createSelector(
[canvasSelector, activeTabNameSelector, systemSelector],
@@ -31,6 +32,8 @@ export default function IAICanvasRedoButton() {
const dispatch = useAppDispatch();
const { canRedo, activeTabName } = useAppSelector(canvasRedoSelector);
const { t } = useTranslation();
const handleRedo = () => {
dispatch(redo());
};
@@ -49,8 +52,8 @@ export default function IAICanvasRedoButton() {
return (
<IAIIconButton
aria-label="Redo (Ctrl+Shift+Z)"
tooltip="Redo (Ctrl+Shift+Z)"
aria-label={`${t('unifiedcanvas:redo')} (Ctrl+Shift+Z)`}
tooltip={`${t('unifiedcanvas:redo')} (Ctrl+Shift+Z)`}
icon={<FaRedo />}
onClick={handleRedo}
isDisabled={!canRedo}

View File

@@ -21,6 +21,7 @@ import EmptyTempFolderButtonModal from 'features/system/components/ClearTempFold
import ClearCanvasHistoryButtonModal from '../ClearCanvasHistoryButtonModal';
import { ChangeEvent } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
export const canvasControlsSelector = createSelector(
[canvasSelector],
@@ -56,6 +57,8 @@ export const canvasControlsSelector = createSelector(
const IAICanvasSettingsButtonPopover = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const {
shouldAutoSave,
shouldCropToBoundingBoxOnSave,
@@ -87,58 +90,58 @@ const IAICanvasSettingsButtonPopover = () => {
trigger="hover"
triggerComponent={
<IAIIconButton
tooltip="Canvas Settings"
aria-label="Canvas Settings"
tooltip={t('unifiedcanvas:canvasSettings')}
aria-label={t('unifiedcanvas:canvasSettings')}
icon={<FaWrench />}
/>
}
>
<Flex direction={'column'} gap={'0.5rem'}>
<IAICheckbox
label="Show Intermediates"
label={t('unifiedcanvas:showIntermediates')}
isChecked={shouldShowIntermediates}
onChange={(e) =>
dispatch(setShouldShowIntermediates(e.target.checked))
}
/>
<IAICheckbox
label="Show Grid"
label={t('unifiedcanvas:showGrid')}
isChecked={shouldShowGrid}
onChange={(e) => dispatch(setShouldShowGrid(e.target.checked))}
/>
<IAICheckbox
label="Snap to Grid"
label={t('unifiedcanvas:snapToGrid')}
isChecked={shouldSnapToGrid}
onChange={handleChangeShouldSnapToGrid}
/>
<IAICheckbox
label="Darken Outside Selection"
label={t('unifiedcanvas:darkenOutsideSelection')}
isChecked={shouldDarkenOutsideBoundingBox}
onChange={(e) =>
dispatch(setShouldDarkenOutsideBoundingBox(e.target.checked))
}
/>
<IAICheckbox
label="Auto Save to Gallery"
label={t('unifiedcanvas:autoSaveToGallery')}
isChecked={shouldAutoSave}
onChange={(e) => dispatch(setShouldAutoSave(e.target.checked))}
/>
<IAICheckbox
label="Save Box Region Only"
label={t('unifiedcanvas:saveBoxRegionOnly')}
isChecked={shouldCropToBoundingBoxOnSave}
onChange={(e) =>
dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked))
}
/>
<IAICheckbox
label="Limit Strokes to Box"
label={t('unifiedcanvas:limitStrokesToBox')}
isChecked={shouldRestrictStrokesToBox}
onChange={(e) =>
dispatch(setShouldRestrictStrokesToBox(e.target.checked))
}
/>
<IAICheckbox
label="Show Canvas Debug Info"
label={t('unifiedcanvas:showCanvasDebugInfo')}
isChecked={shouldShowCanvasDebugInfo}
onChange={(e) =>
dispatch(setShouldShowCanvasDebugInfo(e.target.checked))

View File

@@ -27,6 +27,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
import IAIPopover from 'common/components/IAIPopover';
import IAISlider from 'common/components/IAISlider';
import IAIColorPicker from 'common/components/IAIColorPicker';
import { useTranslation } from 'react-i18next';
export const selector = createSelector(
[canvasSelector, isStagingSelector, systemSelector],
@@ -52,6 +53,7 @@ export const selector = createSelector(
const IAICanvasToolChooserOptions = () => {
const dispatch = useAppDispatch();
const { tool, brushColor, brushSize, isStaging } = useAppSelector(selector);
const { t } = useTranslation();
useHotkeys(
['b'],
@@ -178,38 +180,38 @@ const IAICanvasToolChooserOptions = () => {
return (
<ButtonGroup isAttached>
<IAIIconButton
aria-label="Brush Tool (B)"
tooltip="Brush Tool (B)"
aria-label={`${t('unifiedcanvas:brush')} (B)`}
tooltip={`${t('unifiedcanvas:brush')} (B)`}
icon={<FaPaintBrush />}
data-selected={tool === 'brush' && !isStaging}
onClick={handleSelectBrushTool}
isDisabled={isStaging}
/>
<IAIIconButton
aria-label="Eraser Tool (E)"
tooltip="Eraser Tool (E)"
aria-label={`${t('unifiedcanvas:eraser')} (E)`}
tooltip={`${t('unifiedcanvas:eraser')} (E)`}
icon={<FaEraser />}
data-selected={tool === 'eraser' && !isStaging}
isDisabled={isStaging}
onClick={handleSelectEraserTool}
/>
<IAIIconButton
aria-label="Fill Bounding Box (Shift+F)"
tooltip="Fill Bounding Box (Shift+F)"
aria-label={`${t('unifiedcanvas:fillBoundingBox')} (Shift+F)`}
tooltip={`${t('unifiedcanvas:fillBoundingBox')} (Shift+F)`}
icon={<FaFillDrip />}
isDisabled={isStaging}
onClick={handleFillRect}
/>
<IAIIconButton
aria-label="Erase Bounding Box Area (Delete/Backspace)"
tooltip="Erase Bounding Box Area (Delete/Backspace)"
aria-label={`${t('unifiedcanvas:eraseBoundingBox')} (Del/Backspace)`}
tooltip={`${t('unifiedcanvas:eraseBoundingBox')} (Del/Backspace)`}
icon={<FaPlus style={{ transform: 'rotate(45deg)' }} />}
isDisabled={isStaging}
onClick={handleEraseBoundingBox}
/>
<IAIIconButton
aria-label="Color Picker (C)"
tooltip="Color Picker (C)"
aria-label={`${t('unifiedcanvas:colorPicker')} (C)`}
tooltip={`${t('unifiedcanvas:colorPicker')} (C)`}
icon={<FaEyeDropper />}
data-selected={tool === 'colorPicker' && !isStaging}
isDisabled={isStaging}
@@ -219,8 +221,8 @@ const IAICanvasToolChooserOptions = () => {
trigger="hover"
triggerComponent={
<IAIIconButton
aria-label="Brush Options"
tooltip="Brush Options"
aria-label={t('unifiedcanvas:brushOptions')}
tooltip={t('unifiedcanvas:brushOptions')}
icon={<FaSlidersH />}
/>
}
@@ -233,7 +235,7 @@ const IAICanvasToolChooserOptions = () => {
>
<Flex gap={'1rem'} justifyContent="space-between">
<IAISlider
label="Size"
label={t('unifiedcanvas:brushSize')}
value={brushSize}
withInput
onChange={(newSize) => dispatch(setBrushSize(newSize))}

View File

@@ -41,6 +41,7 @@ import {
LAYER_NAMES_DICT,
} from 'features/canvas/store/canvasTypes';
import { ChangeEvent } from 'react';
import { useTranslation } from 'react-i18next';
export const selector = createSelector(
[systemSelector, canvasSelector, isStagingSelector],
@@ -77,6 +78,8 @@ const IAICanvasOutpaintingControls = () => {
} = useAppSelector(selector);
const canvasBaseLayer = getCanvasBaseLayer();
const { t } = useTranslation();
const { openUploader } = useImageUploader();
useHotkeys(
@@ -221,7 +224,7 @@ const IAICanvasOutpaintingControls = () => {
return (
<div className="inpainting-settings">
<IAISelect
tooltip={'Layer (Q)'}
tooltip={`${t('unifiedcanvas:layer')} (Q)`}
tooltipProps={{ hasArrow: true, placement: 'top' }}
value={layer}
validValues={LAYER_NAMES_DICT}
@@ -234,15 +237,15 @@ const IAICanvasOutpaintingControls = () => {
<ButtonGroup isAttached>
<IAIIconButton
aria-label="Move Tool (V)"
tooltip="Move Tool (V)"
aria-label={`${t('unifiedcanvas:move')} (V)`}
tooltip={`${t('unifiedcanvas:move')} (V)`}
icon={<FaArrowsAlt />}
data-selected={tool === 'move' || isStaging}
onClick={handleSelectMoveTool}
/>
<IAIIconButton
aria-label="Reset View (R)"
tooltip="Reset View (R)"
aria-label={`${t('unifiedcanvas:resetView')} (R)`}
tooltip={`${t('unifiedcanvas:resetView')} (R)`}
icon={<FaCrosshairs />}
onClick={handleResetCanvasView}
/>
@@ -250,29 +253,29 @@ const IAICanvasOutpaintingControls = () => {
<ButtonGroup isAttached>
<IAIIconButton
aria-label="Merge Visible (Shift+M)"
tooltip="Merge Visible (Shift+M)"
aria-label={`${t('unifiedcanvas:mergeVisible')} (Shift+M)`}
tooltip={`${t('unifiedcanvas:mergeVisible')} (Shift+M)`}
icon={<FaLayerGroup />}
onClick={handleMergeVisible}
isDisabled={isStaging}
/>
<IAIIconButton
aria-label="Save to Gallery (Shift+S)"
tooltip="Save to Gallery (Shift+S)"
aria-label={`${t('unifiedcanvas:saveToGallery')} (Shift+S)`}
tooltip={`${t('unifiedcanvas:saveToGallery')} (Shift+S)`}
icon={<FaSave />}
onClick={handleSaveToGallery}
isDisabled={isStaging}
/>
<IAIIconButton
aria-label="Copy to Clipboard (Cmd/Ctrl+C)"
tooltip="Copy to Clipboard (Cmd/Ctrl+C)"
aria-label={`${t('unifiedcanvas:copyToClipboard')} (Cmd/Ctrl+C)`}
tooltip={`${t('unifiedcanvas:copyToClipboard')} (Cmd/Ctrl+C)`}
icon={<FaCopy />}
onClick={handleCopyImageToClipboard}
isDisabled={isStaging}
/>
<IAIIconButton
aria-label="Download as Image (Shift+D)"
tooltip="Download as Image (Shift+D)"
aria-label={`${t('unifiedcanvas:downloadAsImage')} (Shift+D)`}
tooltip={`${t('unifiedcanvas:downloadAsImage')} (Shift+D)`}
icon={<FaDownload />}
onClick={handleDownloadAsImage}
isDisabled={isStaging}
@@ -285,15 +288,15 @@ const IAICanvasOutpaintingControls = () => {
<ButtonGroup isAttached>
<IAIIconButton
aria-label="Upload"
tooltip="Upload"
aria-label={`${t('common:upload')}`}
tooltip={`${t('common:upload')}`}
icon={<FaUpload />}
onClick={openUploader}
isDisabled={isStaging}
/>
<IAIIconButton
aria-label="Clear Canvas"
tooltip="Clear Canvas"
aria-label={`${t('unifiedcanvas:clearCanvas')}`}
tooltip={`${t('unifiedcanvas:clearCanvas')}`}
icon={<FaTrash />}
onClick={handleResetCanvas}
style={{ backgroundColor: 'var(--btn-delete-image)' }}

View File

@@ -9,6 +9,7 @@ import _ from 'lodash';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import { undo } from 'features/canvas/store/canvasSlice';
import { systemSelector } from 'features/system/store/systemSelectors';
import { useTranslation } from 'react-i18next';
const canvasUndoSelector = createSelector(
[canvasSelector, activeTabNameSelector, systemSelector],
@@ -30,6 +31,8 @@ const canvasUndoSelector = createSelector(
export default function IAICanvasUndoButton() {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const { canUndo, activeTabName } = useAppSelector(canvasUndoSelector);
const handleUndo = () => {
@@ -50,8 +53,8 @@ export default function IAICanvasUndoButton() {
return (
<IAIIconButton
aria-label="Undo (Ctrl+Z)"
tooltip="Undo (Ctrl+Z)"
aria-label={`${t('unifiedcanvas:undo')} (Ctrl+Z)`}
tooltip={`${t('unifiedcanvas:undo')} (Ctrl+Z)`}
icon={<FaUndo />}
onClick={handleUndo}
isDisabled={!canUndo}

View File

@@ -16,6 +16,7 @@ import {
import { addImage } from 'features/gallery/store/gallerySlice';
import { setMergedCanvas } from '../canvasSlice';
import { CanvasState } from '../canvasTypes';
import i18n from 'i18n';
type MergeAndUploadCanvasConfig = {
cropVisible?: boolean;
@@ -114,7 +115,7 @@ export const mergeAndUploadCanvas =
downloadFile(url);
dispatch(
addToast({
title: 'Image Download Started',
title: i18n.t('toast:downloadImageStarted'),
status: 'success',
duration: 2500,
isClosable: true,
@@ -126,7 +127,7 @@ export const mergeAndUploadCanvas =
copyImage(url, width, height);
dispatch(
addToast({
title: 'Image Copied',
title: i18n.t('toast:imageCopied'),
status: 'success',
duration: 2500,
isClosable: true,
@@ -138,7 +139,7 @@ export const mergeAndUploadCanvas =
dispatch(addImage({ image: newImage, category: 'result' }));
dispatch(
addToast({
title: 'Image Saved to Gallery',
title: i18n.t('toast:imageSavedToGallery'),
status: 'success',
duration: 2500,
isClosable: true,
@@ -157,7 +158,7 @@ export const mergeAndUploadCanvas =
);
dispatch(
addToast({
title: 'Canvas Merged',
title: i18n.t('toast:canvasMerged'),
status: 'success',
duration: 2500,
isClosable: true,
@@ -166,6 +167,6 @@ export const mergeAndUploadCanvas =
}
dispatch(setIsProcessing(false));
dispatch(setCurrentStatus('Connected'));
dispatch(setCurrentStatus(i18n.t('common:statusConnected')));
dispatch(setIsCancelable(true));
};

View File

@@ -43,6 +43,7 @@ import {
import { GalleryState } from 'features/gallery/store/gallerySlice';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import IAIPopover from 'common/components/IAIPopover';
import { useTranslation } from 'react-i18next';
const systemSelector = createSelector(
[
@@ -111,6 +112,7 @@ const CurrentImageButtons = () => {
} = useAppSelector(systemSelector);
const toast = useToast();
const { t } = useTranslation();
const handleClickUseAsInitialImage = () => {
if (!currentImage) return;
@@ -126,7 +128,7 @@ const CurrentImageButtons = () => {
)
.then(() => {
toast({
title: 'Image Link Copied',
title: t('toast:imageLinkCopied'),
status: 'success',
duration: 2500,
isClosable: true,
@@ -140,15 +142,15 @@ const CurrentImageButtons = () => {
if (currentImage) {
handleClickUseAsInitialImage();
toast({
title: 'Sent To Image To Image',
title: t('toast:sentToImageToImage'),
status: 'success',
duration: 2500,
isClosable: true,
});
} else {
toast({
title: 'No Image Loaded',
description: 'No image found to send to image to image module.',
title: t('toast:imageNotLoaded'),
description: t('toast:imageNotLoadedDesc'),
status: 'error',
duration: 2500,
isClosable: true,
@@ -176,15 +178,15 @@ const CurrentImageButtons = () => {
) {
handleClickUseAllParameters();
toast({
title: 'Parameters Set',
title: t('toast:parametersSet'),
status: 'success',
duration: 2500,
isClosable: true,
});
} else {
toast({
title: 'Parameters Not Set',
description: 'No metadata found for this image.',
title: t('toast:parametersNotSet'),
description: t('toast:parametersNotSetDesc'),
status: 'error',
duration: 2500,
isClosable: true,
@@ -205,15 +207,15 @@ const CurrentImageButtons = () => {
if (currentImage?.metadata?.image?.seed) {
handleClickUseSeed();
toast({
title: 'Seed Set',
title: t('toast:seedSet'),
status: 'success',
duration: 2500,
isClosable: true,
});
} else {
toast({
title: 'Seed Not Set',
description: 'Could not find seed for this image.',
title: t('toast:seedNotSet'),
description: t('toast:seedNotSetDesc'),
status: 'error',
duration: 2500,
isClosable: true,
@@ -233,15 +235,15 @@ const CurrentImageButtons = () => {
if (currentImage?.metadata?.image?.prompt) {
handleClickUsePrompt();
toast({
title: 'Prompt Set',
title: t('toast:promptSet'),
status: 'success',
duration: 2500,
isClosable: true,
});
} else {
toast({
title: 'Prompt Not Set',
description: 'Could not find prompt for this image.',
title: t('toast:promptNotSet'),
description: t('toast:promptNotSetDesc'),
status: 'error',
duration: 2500,
isClosable: true,
@@ -268,7 +270,7 @@ const CurrentImageButtons = () => {
handleClickUpscale();
} else {
toast({
title: 'Upscaling Failed',
title: t('toast:upscalingFailed'),
status: 'error',
duration: 2500,
isClosable: true,
@@ -302,7 +304,7 @@ const CurrentImageButtons = () => {
handleClickFixFaces();
} else {
toast({
title: 'Face Restoration Failed',
title: t('toast:faceRestoreFailed'),
status: 'error',
duration: 2500,
isClosable: true,
@@ -334,7 +336,7 @@ const CurrentImageButtons = () => {
}
toast({
title: 'Sent to Unified Canvas',
title: t('toast:sentToUnifiedCanvas'),
status: 'success',
duration: 2500,
isClosable: true,
@@ -348,7 +350,7 @@ const CurrentImageButtons = () => {
handleClickShowImageDetails();
} else {
toast({
title: 'Failed to load metadata',
title: t('toast:metadataLoadFailed'),
status: 'error',
duration: 2500,
isClosable: true,
@@ -368,7 +370,10 @@ const CurrentImageButtons = () => {
<IAIPopover
trigger="hover"
triggerComponent={
<IAIIconButton aria-label="Send to..." icon={<FaShareAlt />} />
<IAIIconButton
aria-label={`${t('options:sendTo')}...`}
icon={<FaShareAlt />}
/>
}
>
<div className="current-image-send-to-popover">
@@ -377,35 +382,41 @@ const CurrentImageButtons = () => {
onClick={handleClickUseAsInitialImage}
leftIcon={<FaShare />}
>
Send to Image to Image
{t('options:sendToImg2Img')}
</IAIButton>
<IAIButton
size={'sm'}
onClick={handleSendToCanvas}
leftIcon={<FaShare />}
>
Send to Unified Canvas
{t('options:sendToUnifiedCanvas')}
</IAIButton>
<IAIButton
size={'sm'}
onClick={handleCopyImageLink}
leftIcon={<FaCopy />}
>
Copy Link to Image
{t('options:copyImageToLink')}
</IAIButton>
<IAIButton leftIcon={<FaDownload />} size={'sm'}>
<Link download={true} href={currentImage?.url}>
Download Image
{t('options:downloadImage')}
</Link>
</IAIButton>
</div>
</IAIPopover>
<IAIIconButton
icon={<FaExpand />}
tooltip={!isLightBoxOpen ? 'Open In Viewer (Z)' : 'Close Viewer (Z)'}
tooltip={
!isLightBoxOpen
? `${t('options:openInViewer')} (Z)`
: `${t('options:closeViewer')} (Z)`
}
aria-label={
!isLightBoxOpen ? 'Open In Viewer (Z)' : 'Close Viewer (Z)'
!isLightBoxOpen
? `${t('options:openInViewer')} (Z)`
: `${t('options:closeViewer')} (Z)`
}
data-selected={isLightBoxOpen}
onClick={handleLightBox}
@@ -415,24 +426,24 @@ const CurrentImageButtons = () => {
<ButtonGroup isAttached={true}>
<IAIIconButton
icon={<FaQuoteRight />}
tooltip="Use Prompt (P)"
aria-label="Use Prompt (P)"
tooltip={`${t('options:usePrompt')} (P)`}
aria-label={`${t('options:usePrompt')} (P)`}
isDisabled={!currentImage?.metadata?.image?.prompt}
onClick={handleClickUsePrompt}
/>
<IAIIconButton
icon={<FaSeedling />}
tooltip="Use Seed (S)"
aria-label="Use Seed (S)"
tooltip={`${t('options:useSeed')} (S)`}
aria-label={`${t('options:useSeed')} (S)`}
isDisabled={!currentImage?.metadata?.image?.seed}
onClick={handleClickUseSeed}
/>
<IAIIconButton
icon={<FaAsterisk />}
tooltip="Use All (A)"
aria-label="Use All (A)"
tooltip={`${t('options:useAll')} (A)`}
aria-label={`${t('options:useAll')} (A)`}
isDisabled={
!['txt2img', 'img2img'].includes(
currentImage?.metadata?.image?.type
@@ -446,7 +457,10 @@ const CurrentImageButtons = () => {
<IAIPopover
trigger="hover"
triggerComponent={
<IAIIconButton icon={<FaGrinStars />} aria-label="Restore Faces" />
<IAIIconButton
icon={<FaGrinStars />}
aria-label={t('options:restoreFaces')}
/>
}
>
<div className="current-image-postprocessing-popover">
@@ -460,7 +474,7 @@ const CurrentImageButtons = () => {
}
onClick={handleClickFixFaces}
>
Restore Faces
{t('options:restoreFaces')}
</IAIButton>
</div>
</IAIPopover>
@@ -468,7 +482,10 @@ const CurrentImageButtons = () => {
<IAIPopover
trigger="hover"
triggerComponent={
<IAIIconButton icon={<FaExpandArrowsAlt />} aria-label="Upscale" />
<IAIIconButton
icon={<FaExpandArrowsAlt />}
aria-label={t('options:upscale')}
/>
}
>
<div className="current-image-postprocessing-popover">
@@ -482,7 +499,7 @@ const CurrentImageButtons = () => {
}
onClick={handleClickUpscale}
>
Upscale Image
{t('options:upscaleImage')}
</IAIButton>
</div>
</IAIPopover>
@@ -491,8 +508,8 @@ const CurrentImageButtons = () => {
<ButtonGroup isAttached={true}>
<IAIIconButton
icon={<FaCode />}
tooltip="Info (I)"
aria-label="Info (I)"
tooltip={`${t('options:info')} (I)`}
aria-label={`${t('options:info')} (I)`}
data-selected={shouldShowImageDetails}
onClick={handleClickShowImageDetails}
/>
@@ -501,8 +518,8 @@ const CurrentImageButtons = () => {
<DeleteImageModal image={currentImage}>
<IAIIconButton
icon={<FaTrash />}
tooltip="Delete Image"
aria-label="Delete Image"
tooltip={`${t('options:deleteImage')} (Del)`}
aria-label={`${t('options:deleteImage')} (Del)`}
isDisabled={!currentImage || !isConnected || isProcessing}
style={{ backgroundColor: 'var(--btn-delete-image)' }}
/>

View File

@@ -1,11 +1,4 @@
import {
Box,
Icon,
IconButton,
Image,
Tooltip,
useToast,
} from '@chakra-ui/react';
import { Box, Icon, IconButton, Image, useToast } from '@chakra-ui/react';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import {
setCurrentImage,
@@ -30,6 +23,7 @@ import {
setInitialCanvasImage,
} from 'features/canvas/store/canvasSlice';
import { hoverableImageSelector } from 'features/gallery/store/gallerySliceSelectors';
import { useTranslation } from 'react-i18next';
interface HoverableImageProps {
image: InvokeAI.Image;
@@ -61,6 +55,8 @@ const HoverableImage = memo((props: HoverableImageProps) => {
const toast = useToast();
const { t } = useTranslation();
const handleMouseOver = () => setIsHovered(true);
const handleMouseOut = () => setIsHovered(false);
@@ -68,7 +64,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
const handleUsePrompt = () => {
image.metadata && dispatch(setPrompt(image.metadata.image.prompt));
toast({
title: 'Prompt Set',
title: t('toast:promptSet'),
status: 'success',
duration: 2500,
isClosable: true,
@@ -78,7 +74,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
const handleUseSeed = () => {
image.metadata && dispatch(setSeed(image.metadata.image.seed));
toast({
title: 'Seed Set',
title: t('toast:seedSet'),
status: 'success',
duration: 2500,
isClosable: true,
@@ -92,7 +88,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
dispatch(setActiveTab('img2img'));
}
toast({
title: 'Sent to Image To Image',
title: t('toast:sentToImageToImage'),
status: 'success',
duration: 2500,
isClosable: true,
@@ -111,7 +107,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
}
toast({
title: 'Sent to Unified Canvas',
title: t('toast:sentToUnifiedCanvas'),
status: 'success',
duration: 2500,
isClosable: true,
@@ -121,7 +117,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
const handleUseAllParameters = () => {
metadata && dispatch(setAllTextToImageParameters(metadata));
toast({
title: 'Parameters Set',
title: t('toast:parametersSet'),
status: 'success',
duration: 2500,
isClosable: true,
@@ -135,7 +131,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
dispatch(setActiveTab('img2img'));
dispatch(setAllImageToImageParameters(metadata));
toast({
title: 'Initial Image Set',
title: t('toast:initialImageSet'),
status: 'success',
duration: 2500,
isClosable: true,
@@ -144,8 +140,8 @@ const HoverableImage = memo((props: HoverableImageProps) => {
}
}
toast({
title: 'Initial Image Not Set',
description: 'Could not load initial image.',
title: t('toast:initialImageNotSet'),
description: t('toast:initialImageNotSetDesc'),
status: 'error',
duration: 2500,
isClosable: true,
@@ -202,18 +198,16 @@ const HoverableImage = memo((props: HoverableImageProps) => {
</div>
{isHovered && galleryImageMinimumWidth >= 64 && (
<div className="hoverable-image-delete-button">
<Tooltip label={'Delete image'} hasArrow>
<DeleteImageModal image={image}>
<IconButton
aria-label="Delete image"
icon={<FaTrashAlt />}
size="xs"
variant={'imageHoverIconButton'}
fontSize={14}
isDisabled={!mayDeleteImage}
/>
</DeleteImageModal>
</Tooltip>
<DeleteImageModal image={image}>
<IconButton
aria-label={t('options:deleteImage')}
icon={<FaTrashAlt />}
size="xs"
variant={'imageHoverIconButton'}
fontSize={14}
isDisabled={!mayDeleteImage}
/>
</DeleteImageModal>
</div>
)}
</Box>
@@ -226,20 +220,20 @@ const HoverableImage = memo((props: HoverableImageProps) => {
}}
>
<ContextMenu.Item onClickCapture={handleLightBox}>
Open In Viewer
{t('options:openInViewer')}
</ContextMenu.Item>
<ContextMenu.Item
onClickCapture={handleUsePrompt}
disabled={image?.metadata?.image?.prompt === undefined}
>
Use Prompt
{t('options:usePrompt')}
</ContextMenu.Item>
<ContextMenu.Item
onClickCapture={handleUseSeed}
disabled={image?.metadata?.image?.seed === undefined}
>
Use Seed
{t('options:useSeed')}
</ContextMenu.Item>
<ContextMenu.Item
onClickCapture={handleUseAllParameters}
@@ -247,25 +241,25 @@ const HoverableImage = memo((props: HoverableImageProps) => {
!['txt2img', 'img2img'].includes(image?.metadata?.image?.type)
}
>
Use All Parameters
{t('options:useAll')}
</ContextMenu.Item>
<ContextMenu.Item
onClickCapture={handleUseInitialImage}
disabled={image?.metadata?.image?.type !== 'img2img'}
>
{t('options:useInitImg')}
</ContextMenu.Item>
<Tooltip label="Load initial image used for this generation">
<ContextMenu.Item
onClickCapture={handleUseInitialImage}
disabled={image?.metadata?.image?.type !== 'img2img'}
>
Use Initial Image
</ContextMenu.Item>
</Tooltip>
<ContextMenu.Item onClickCapture={handleSendToImageToImage}>
Send to Image To Image
{t('options:sendToImg2Img')}
</ContextMenu.Item>
<ContextMenu.Item onClickCapture={handleSendToCanvas}>
Send to Unified Canvas
{t('options:sendToUnifiedCanvas')}
</ContextMenu.Item>
<ContextMenu.Item data-warning>
<DeleteImageModal image={image}>
<p>{t('options:deleteImage')}</p>
</DeleteImageModal>
</ContextMenu.Item>
<DeleteImageModal image={image}>
<ContextMenu.Item data-warning>Delete Image</ContextMenu.Item>
</DeleteImageModal>
</ContextMenu.Content>
</ContextMenu.Root>
);

View File

@@ -42,6 +42,7 @@ import { setDoesCanvasNeedScaling } from 'features/canvas/store/canvasSlice';
import _ from 'lodash';
import IAIButton from 'common/components/IAIButton';
import { InvokeTabName } from 'features/tabs/tabMap';
import { useTranslation } from 'react-i18next';
const GALLERY_SHOW_BUTTONS_MIN_WIDTH = 320;
const GALLERY_IMAGE_WIDTH_OFFSET = 40;
@@ -63,6 +64,8 @@ const LIGHTBOX_GALLERY_WIDTH = 400;
export default function ImageGallery() {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const {
images,
currentCategory,
@@ -395,28 +398,28 @@ export default function ImageGallery() {
data-selected={currentCategory === 'result'}
onClick={() => dispatch(setCurrentCategory('result'))}
>
Generations
{t('gallery:generations')}
</IAIButton>
<IAIButton
size={'sm'}
data-selected={currentCategory === 'user'}
onClick={() => dispatch(setCurrentCategory('user'))}
>
Uploads
{t('gallery:uploads')}
</IAIButton>
</>
) : (
<>
<IAIIconButton
aria-label="Show Generations"
tooltip="Show Generations"
aria-label={t('gallery:showGenerations')}
tooltip={t('gallery:showGenerations')}
data-selected={currentCategory === 'result'}
icon={<FaImage />}
onClick={() => dispatch(setCurrentCategory('result'))}
/>
<IAIIconButton
aria-label="Show Uploads"
tooltip="Show Uploads"
aria-label={t('gallery:showUploads')}
tooltip={t('gallery:showUploads')}
data-selected={currentCategory === 'user'}
icon={<FaUser />}
onClick={() => dispatch(setCurrentCategory('user'))}
@@ -433,7 +436,7 @@ export default function ImageGallery() {
triggerComponent={
<IAIIconButton
size={'sm'}
aria-label={'Gallery Settings'}
aria-label={t('gallery:gallerySettings')}
icon={<FaWrench />}
className="image-gallery-icon-btn"
cursor={'pointer'}
@@ -448,12 +451,12 @@ export default function ImageGallery() {
min={32}
max={256}
hideTooltip={true}
label={'Image Size'}
label={t('gallery:galleryImageSize')}
/>
<IAIIconButton
size={'sm'}
aria-label={'Reset'}
tooltip={'Reset Size'}
aria-label={t('gallery:galleryImageResetSize')}
tooltip={t('gallery:galleryImageResetSize')}
onClick={() => dispatch(setGalleryImageMinimumWidth(64))}
icon={<BiReset />}
data-selected={shouldPinGallery}
@@ -462,7 +465,7 @@ export default function ImageGallery() {
</div>
<div>
<IAICheckbox
label="Maintain Aspect Ratio"
label={t('gallery:maintainAspectRatio')}
isChecked={galleryImageObjectFit === 'contain'}
onChange={() =>
dispatch(
@@ -477,7 +480,7 @@ export default function ImageGallery() {
</div>
<div>
<IAICheckbox
label="Auto-Switch to New Images"
label={t('gallery:autoSwitchNewImages')}
isChecked={shouldAutoSwitchToNewImages}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
dispatch(
@@ -488,7 +491,7 @@ export default function ImageGallery() {
</div>
<div>
<IAICheckbox
label="Single Column Layout"
label={t('gallery:singleColumnLayout')}
isChecked={shouldUseSingleGalleryColumn}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
dispatch(
@@ -503,8 +506,8 @@ export default function ImageGallery() {
<IAIIconButton
size={'sm'}
className={'image-gallery-icon-btn'}
aria-label={'Pin Gallery'}
tooltip={'Pin Gallery (Shift+G)'}
aria-label={t('gallery:pinGallery')}
tooltip={`${t('gallery:pinGallery')} (Shift+G)`}
onClick={handleSetShouldPinGallery}
icon={shouldPinGallery ? <BsPinAngleFill /> : <BsPinAngle />}
/>
@@ -534,13 +537,15 @@ export default function ImageGallery() {
isDisabled={!areMoreImagesAvailable}
className="image-gallery-load-more-btn"
>
{areMoreImagesAvailable ? 'Load More' : 'All Images Loaded'}
{areMoreImagesAvailable
? t('gallery:loadMore')
: t('gallery:allImagesLoaded')}
</Button>
</>
) : (
<div className="image-gallery-container-placeholder">
<MdPhotoLibrary />
<p>No Images In Gallery</p>
<p>{t('gallery:noImagesInGallery')}</p>
</div>
)}
</div>

View File

@@ -5,6 +5,7 @@ import IAISlider from 'common/components/IAISlider';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
import _ from 'lodash';
import { useTranslation } from 'react-i18next';
const selector = createSelector(
canvasSelector,
@@ -27,6 +28,8 @@ const BoundingBoxSettings = () => {
const dispatch = useAppDispatch();
const { boundingBoxDimensions } = useAppSelector(selector);
const { t } = useTranslation();
const handleChangeWidth = (v: number) => {
dispatch(
setBoundingBoxDimensions({
@@ -66,7 +69,7 @@ const BoundingBoxSettings = () => {
return (
<Flex direction="column" gap="1rem">
<IAISlider
label={'Width'}
label={t('options:width')}
min={64}
max={1024}
step={64}
@@ -79,7 +82,7 @@ const BoundingBoxSettings = () => {
withReset
/>
<IAISlider
label={'Height'}
label={t('options:height')}
min={64}
max={1024}
step={64}
@@ -98,9 +101,10 @@ const BoundingBoxSettings = () => {
export default BoundingBoxSettings;
export const BoundingBoxSettingsHeader = () => {
const { t } = useTranslation();
return (
<Box flex="1" textAlign="left">
Bounding Box
{t('options:boundingBoxHeader')}
</Box>
);
};

View File

@@ -20,6 +20,7 @@ import {
import { systemSelector } from 'features/system/store/systemSelectors';
import _ from 'lodash';
import { ChangeEvent } from 'react';
import { useTranslation } from 'react-i18next';
import InpaintReplace from './InpaintReplace';
const selector = createSelector(
@@ -61,6 +62,8 @@ const InfillAndScalingOptions = () => {
scaledBoundingBoxDimensions,
} = useAppSelector(selector);
const { t } = useTranslation();
const handleChangeScaledWidth = (v: number) => {
dispatch(
setScaledBoundingBoxDimensions({
@@ -106,7 +109,7 @@ const InfillAndScalingOptions = () => {
return (
<Flex direction="column" gap="1rem">
<IAISelect
label={'Scale Before Processing'}
label={t('options:scaleBeforeProcessing')}
validValues={BOUNDING_BOX_SCALES_DICT}
value={boundingBoxScale}
onChange={handleChangeBoundingBoxScaleMethod}
@@ -115,7 +118,7 @@ const InfillAndScalingOptions = () => {
isInputDisabled={!isManual}
isResetDisabled={!isManual}
isSliderDisabled={!isManual}
label={'Scaled W'}
label={t('options:scaledWidth')}
min={64}
max={1024}
step={64}
@@ -131,7 +134,7 @@ const InfillAndScalingOptions = () => {
isInputDisabled={!isManual}
isResetDisabled={!isManual}
isSliderDisabled={!isManual}
label={'Scaled H'}
label={t('options:scaledHeight')}
min={64}
max={1024}
step={64}
@@ -145,7 +148,7 @@ const InfillAndScalingOptions = () => {
/>
<InpaintReplace />
<IAISelect
label="Infill Method"
label={t('options:infillMethod')}
value={infillMethod}
validValues={availableInfillMethods}
onChange={(e) => dispatch(setInfillMethod(e.target.value))}
@@ -155,7 +158,7 @@ const InfillAndScalingOptions = () => {
isResetDisabled={infillMethod !== 'tile'}
isSliderDisabled={infillMethod !== 'tile'}
sliderMarkRightOffset={-4}
label={'Tile Size'}
label={t('options:tileSize')}
min={16}
max={64}
sliderNumberInputProps={{ max: 256 }}

View File

@@ -10,6 +10,7 @@ import {
setShouldUseInpaintReplace,
} from 'features/canvas/store/canvasSlice';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { useTranslation } from 'react-i18next';
const selector = createSelector(
canvasSelector,
@@ -32,10 +33,12 @@ export default function InpaintReplace() {
const dispatch = useAppDispatch();
const { t } = useTranslation();
return (
<Flex alignItems={'center'} columnGap={'1rem'}>
<IAISlider
label="Inpaint Replace"
label={t('options:inpaintReplace')}
value={inpaintReplace}
onChange={(v: number) => {
dispatch(setInpaintReplace(v));
@@ -56,6 +59,7 @@ export default function InpaintReplace() {
onChange={(e: ChangeEvent<HTMLInputElement>) =>
dispatch(setShouldUseInpaintReplace(e.target.checked))
}
marginTop="1.25rem"
/>
</Flex>
);

View File

@@ -10,6 +10,7 @@ import {
setSeamStrength,
} from 'features/options/store/optionsSlice';
import _ from 'lodash';
import { useTranslation } from 'react-i18next';
const selector = createSelector(
[optionsSelector],
@@ -35,11 +36,13 @@ const SeamCorrectionOptions = () => {
const { seamSize, seamBlur, seamStrength, seamSteps } =
useAppSelector(selector);
const { t } = useTranslation();
return (
<Flex direction="column" gap="1rem">
<IAISlider
sliderMarkRightOffset={-6}
label={'Seam Size'}
label={t('options:seamSize')}
min={1}
max={256}
sliderNumberInputProps={{ max: 512 }}
@@ -54,7 +57,7 @@ const SeamCorrectionOptions = () => {
/>
<IAISlider
sliderMarkRightOffset={-4}
label={'Seam Blur'}
label={t('options:seamBlur')}
min={0}
max={64}
sliderNumberInputProps={{ max: 512 }}
@@ -70,8 +73,8 @@ const SeamCorrectionOptions = () => {
withReset
/>
<IAISlider
sliderMarkRightOffset={-2}
label={'Seam Strength'}
sliderMarkRightOffset={-7}
label={t('options:seamStrength')}
min={0.01}
max={0.99}
step={0.01}
@@ -88,7 +91,7 @@ const SeamCorrectionOptions = () => {
/>
<IAISlider
sliderMarkRightOffset={-4}
label={'Seam Steps'}
label={t('options:seamSteps')}
min={1}
max={32}
sliderNumberInputProps={{ max: 100 }}

View File

@@ -18,6 +18,7 @@ import IAINumberInput from 'common/components/IAINumberInput';
import IAISelect from 'common/components/IAISelect';
import { FACETOOL_TYPES } from 'app/constants';
import { ChangeEvent } from 'react';
import { useTranslation } from 'react-i18next';
const optionsSelector = createSelector(
(state: RootState) => state.options,
@@ -66,17 +67,19 @@ const FaceRestoreOptions = () => {
const handleChangeFacetoolType = (e: ChangeEvent<HTMLSelectElement>) =>
dispatch(setFacetoolType(e.target.value as FacetoolType));
const { t } = useTranslation();
return (
<Flex direction={'column'} gap={2}>
<IAISelect
label="Type"
label={t('options:type')}
validValues={FACETOOL_TYPES.concat()}
value={facetoolType}
onChange={handleChangeFacetoolType}
/>
<IAINumberInput
isDisabled={!isGFPGANAvailable}
label="Strength"
label={t('options:strength')}
step={0.05}
min={0}
max={1}
@@ -88,7 +91,7 @@ const FaceRestoreOptions = () => {
{facetoolType === 'codeformer' && (
<IAINumberInput
isDisabled={!isGFPGANAvailable}
label="Fidelity"
label={t('options:codeformerFidelity')}
step={0.05}
min={0}
max={1}

View File

@@ -3,6 +3,7 @@ import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISwitch from 'common/components/IAISwitch';
import { setShouldFitToWidthHeight } from 'features/options/store/optionsSlice';
import { useTranslation } from 'react-i18next';
export default function ImageFit() {
const dispatch = useAppDispatch();
@@ -14,9 +15,11 @@ export default function ImageFit() {
const handleChangeFit = (e: ChangeEvent<HTMLInputElement>) =>
dispatch(setShouldFitToWidthHeight(e.target.checked));
const { t } = useTranslation();
return (
<IAISwitch
label="Fit Initial Image To Output Size"
label={t('options:imageFit')}
isChecked={shouldFitToWidthHeight}
onChange={handleChangeFit}
/>

View File

@@ -3,6 +3,7 @@ import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { setImg2imgStrength } from 'features/options/store/optionsSlice';
import { useTranslation } from 'react-i18next';
interface ImageToImageStrengthProps {
label?: string;
@@ -10,7 +11,8 @@ interface ImageToImageStrengthProps {
}
export default function ImageToImageStrength(props: ImageToImageStrengthProps) {
const { label = 'Strength', styleClass } = props;
const { t } = useTranslation();
const { label = `${t('options:strength')}`, styleClass } = props;
const img2imgStrength = useAppSelector(
(state: RootState) => state.options.img2imgStrength
);

View File

@@ -4,6 +4,7 @@ import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISwitch from 'common/components/IAISwitch';
import { setHiresFix } from 'features/options/store/optionsSlice';
import { useTranslation } from 'react-i18next';
/**
* Hires Fix Toggle
@@ -13,13 +14,15 @@ const HiresOptions = () => {
const hiresFix = useAppSelector((state: RootState) => state.options.hiresFix);
const { t } = useTranslation();
const handleChangeHiresFix = (e: ChangeEvent<HTMLInputElement>) =>
dispatch(setHiresFix(e.target.checked));
return (
<Flex gap={2} direction={'column'}>
<IAISwitch
label="High Res Optimization"
label={t('options:hiresOptim')}
fontSize={'md'}
isChecked={hiresFix}
onChange={handleChangeHiresFix}

View File

@@ -4,6 +4,7 @@ import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISwitch from 'common/components/IAISwitch';
import { setSeamless } from 'features/options/store/optionsSlice';
import { useTranslation } from 'react-i18next';
/**
* Seamless tiling toggle
@@ -16,10 +17,12 @@ const SeamlessOptions = () => {
const handleChangeSeamless = (e: ChangeEvent<HTMLInputElement>) =>
dispatch(setSeamless(e.target.checked));
const { t } = useTranslation();
return (
<Flex gap={2} direction={'column'}>
<IAISwitch
label="Seamless tiling"
label={t('options:seamlessTiling')}
fontSize={'md'}
isChecked={seamless}
onChange={handleChangeSeamless}

View File

@@ -3,16 +3,18 @@ import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAINumberInput from 'common/components/IAINumberInput';
import { setPerlin } from 'features/options/store/optionsSlice';
import { useTranslation } from 'react-i18next';
export default function Perlin() {
const dispatch = useAppDispatch();
const perlin = useAppSelector((state: RootState) => state.options.perlin);
const { t } = useTranslation();
const handleChangePerlin = (v: number) => dispatch(setPerlin(v));
return (
<IAINumberInput
label="Perlin Noise"
label={t('options:perlinNoise')}
min={0}
max={1}
step={0.05}

View File

@@ -5,9 +5,11 @@ import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISwitch from 'common/components/IAISwitch';
import { setShouldRandomizeSeed } from 'features/options/store/optionsSlice';
import { useTranslation } from 'react-i18next';
export default function RandomizeSeed() {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const shouldRandomizeSeed = useAppSelector(
(state: RootState) => state.options.shouldRandomizeSeed
@@ -18,7 +20,7 @@ export default function RandomizeSeed() {
return (
<IAISwitch
label="Randomize Seed"
label={t('options:randomizeSeed')}
isChecked={shouldRandomizeSeed}
onChange={handleChangeShouldRandomizeSeed}
/>

View File

@@ -4,6 +4,7 @@ import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAINumberInput from 'common/components/IAINumberInput';
import { setSeed } from 'features/options/store/optionsSlice';
import { useTranslation } from 'react-i18next';
export default function Seed() {
const seed = useAppSelector((state: RootState) => state.options.seed);
@@ -14,13 +15,15 @@ export default function Seed() {
(state: RootState) => state.options.shouldGenerateVariations
);
const { t } = useTranslation();
const dispatch = useAppDispatch();
const handleChangeSeed = (v: number) => dispatch(setSeed(v));
return (
<IAINumberInput
label="Seed"
label={t('options:seed')}
step={1}
precision={0}
flexGrow={1}

View File

@@ -5,12 +5,14 @@ import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import randomInt from 'common/util/randomInt';
import { setSeed } from 'features/options/store/optionsSlice';
import { useTranslation } from 'react-i18next';
export default function ShuffleSeed() {
const dispatch = useAppDispatch();
const shouldRandomizeSeed = useAppSelector(
(state: RootState) => state.options.shouldRandomizeSeed
);
const { t } = useTranslation();
const handleClickRandomizeSeed = () =>
dispatch(setSeed(randomInt(NUMPY_RAND_MIN, NUMPY_RAND_MAX)));
@@ -21,7 +23,7 @@ export default function ShuffleSeed() {
isDisabled={shouldRandomizeSeed}
onClick={handleClickRandomizeSeed}
>
<p>Shuffle</p>
<p>{t('options:shuffle')}</p>
</Button>
);
}

View File

@@ -3,18 +3,20 @@ import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAINumberInput from 'common/components/IAINumberInput';
import { setThreshold } from 'features/options/store/optionsSlice';
import { useTranslation } from 'react-i18next';
export default function Threshold() {
const dispatch = useAppDispatch();
const threshold = useAppSelector(
(state: RootState) => state.options.threshold
);
const { t } = useTranslation();
const handleChangeThreshold = (v: number) => dispatch(setThreshold(v));
return (
<IAINumberInput
label="Noise Threshold"
label={t('options:noiseThreshold')}
min={0}
max={1000}
step={0.1}

View File

@@ -15,6 +15,7 @@ import { SystemState } from 'features/system/store/systemSlice';
import { ChangeEvent } from 'react';
import IAINumberInput from 'common/components/IAINumberInput';
import IAISelect from 'common/components/IAISelect';
import { useTranslation } from 'react-i18next';
const optionsSelector = createSelector(
(state: RootState) => state.options,
@@ -54,6 +55,8 @@ const UpscaleOptions = () => {
const { isESRGANAvailable } = useAppSelector(systemSelector);
const { t } = useTranslation();
const handleChangeLevel = (e: ChangeEvent<HTMLSelectElement>) =>
dispatch(setUpscalingLevel(Number(e.target.value) as UpscalingLevel));
@@ -63,14 +66,14 @@ const UpscaleOptions = () => {
<div className="upscale-options">
<IAISelect
isDisabled={!isESRGANAvailable}
label="Scale"
label={t('options:scale')}
value={upscalingLevel}
onChange={handleChangeLevel}
validValues={UPSCALING_LEVELS}
/>
<IAINumberInput
isDisabled={!isESRGANAvailable}
label="Strength"
label={t('options:strength')}
step={0.05}
min={0}
max={1}

View File

@@ -4,6 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAIInput from 'common/components/IAIInput';
import { validateSeedWeights } from 'common/util/seedWeightPairs';
import { setSeedWeights } from 'features/options/store/optionsSlice';
import { useTranslation } from 'react-i18next';
export default function SeedWeights() {
const seedWeights = useAppSelector(
@@ -14,6 +15,8 @@ export default function SeedWeights() {
(state: RootState) => state.options.shouldGenerateVariations
);
const { t } = useTranslation();
const dispatch = useAppDispatch();
const handleChangeSeedWeights = (e: ChangeEvent<HTMLInputElement>) =>
@@ -21,7 +24,7 @@ export default function SeedWeights() {
return (
<IAIInput
label={'Seed Weights'}
label={t('options:seedWeights')}
value={seedWeights}
isInvalid={
shouldGenerateVariations &&

View File

@@ -3,6 +3,7 @@ import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAINumberInput from 'common/components/IAINumberInput';
import { setVariationAmount } from 'features/options/store/optionsSlice';
import { useTranslation } from 'react-i18next';
export default function VariationAmount() {
const variationAmount = useAppSelector(
@@ -13,13 +14,15 @@ export default function VariationAmount() {
(state: RootState) => state.options.shouldGenerateVariations
);
const { t } = useTranslation();
const dispatch = useAppDispatch();
const handleChangevariationAmount = (v: number) =>
dispatch(setVariationAmount(v));
return (
<IAINumberInput
label="Variation Amount"
label={t('options:variationAmount')}
value={variationAmount}
step={0.01}
min={0}

View File

@@ -3,16 +3,18 @@ import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAINumberInput from 'common/components/IAINumberInput';
import { setCfgScale } from 'features/options/store/optionsSlice';
import { useTranslation } from 'react-i18next';
export default function MainCFGScale() {
const dispatch = useAppDispatch();
const cfgScale = useAppSelector((state: RootState) => state.options.cfgScale);
const { t } = useTranslation();
const handleChangeCfgScale = (v: number) => dispatch(setCfgScale(v));
return (
<IAINumberInput
label="CFG Scale"
label={t('options:cfgScale')}
step={0.5}
min={1.01}
max={200}

View File

@@ -6,11 +6,13 @@ import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISelect from 'common/components/IAISelect';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import { setHeight } from 'features/options/store/optionsSlice';
import { useTranslation } from 'react-i18next';
export default function MainHeight() {
const height = useAppSelector((state: RootState) => state.options.height);
const activeTabName = useAppSelector(activeTabNameSelector);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleChangeHeight = (e: ChangeEvent<HTMLSelectElement>) =>
dispatch(setHeight(Number(e.target.value)));
@@ -18,7 +20,7 @@ export default function MainHeight() {
return (
<IAISelect
isDisabled={activeTabName === 'unifiedCanvas'}
label="Height"
label={t('options:height')}
value={height}
flexGrow={1}
onChange={handleChangeHeight}

View File

@@ -8,6 +8,7 @@ import {
OptionsState,
setIterations,
} from 'features/options/store/optionsSlice';
import { useTranslation } from 'react-i18next';
const mainIterationsSelector = createSelector(
[(state: RootState) => state.options],
@@ -28,12 +29,13 @@ const mainIterationsSelector = createSelector(
export default function MainIterations() {
const dispatch = useAppDispatch();
const { iterations } = useAppSelector(mainIterationsSelector);
const { t } = useTranslation();
const handleChangeIterations = (v: number) => dispatch(setIterations(v));
return (
<IAINumberInput
label="Images"
label={t('options:images')}
step={1}
min={1}
max={9999}

View File

@@ -4,17 +4,19 @@ import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISelect from 'common/components/IAISelect';
import { setSampler } from 'features/options/store/optionsSlice';
import { useTranslation } from 'react-i18next';
export default function MainSampler() {
const sampler = useAppSelector((state: RootState) => state.options.sampler);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleChangeSampler = (e: ChangeEvent<HTMLSelectElement>) =>
dispatch(setSampler(e.target.value));
return (
<IAISelect
label="Sampler"
label={t('options:sampler')}
value={sampler}
onChange={handleChangeSampler}
validValues={SAMPLERS}

View File

@@ -3,16 +3,18 @@ import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAINumberInput from 'common/components/IAINumberInput';
import { setSteps } from 'features/options/store/optionsSlice';
import { useTranslation } from 'react-i18next';
export default function MainSteps() {
const dispatch = useAppDispatch();
const steps = useAppSelector((state: RootState) => state.options.steps);
const { t } = useTranslation();
const handleChangeSteps = (v: number) => dispatch(setSteps(v));
return (
<IAINumberInput
label="Steps"
label={t('options:steps')}
min={1}
max={9999}
step={1}

View File

@@ -5,10 +5,12 @@ import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISelect from 'common/components/IAISelect';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import { setWidth } from 'features/options/store/optionsSlice';
import { useTranslation } from 'react-i18next';
export default function MainWidth() {
const width = useAppSelector((state: RootState) => state.options.width);
const activeTabName = useAppSelector(activeTabNameSelector);
const { t } = useTranslation();
const dispatch = useAppDispatch();
@@ -18,7 +20,7 @@ export default function MainWidth() {
return (
<IAISelect
isDisabled={activeTabName === 'unifiedCanvas'}
label="Width"
label={t('options:width')}
value={width}
flexGrow={1}
onChange={handleChangeWidth}

View File

@@ -9,6 +9,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
import { createSelector } from '@reduxjs/toolkit';
import { SystemState } from 'features/system/store/systemSlice';
import _ from 'lodash';
import { useTranslation } from 'react-i18next';
const cancelButtonSelector = createSelector(
(state: RootState) => state.system,
@@ -35,6 +36,8 @@ export default function CancelButton(
useAppSelector(cancelButtonSelector);
const handleClickCancel = () => dispatch(cancelProcessing());
const { t } = useTranslation();
useHotkeys(
'shift+x',
() => {
@@ -48,8 +51,8 @@ export default function CancelButton(
return (
<IAIIconButton
icon={<MdCancel />}
tooltip="Cancel"
aria-label="Cancel"
tooltip={t('options:cancel')}
aria-label={t('options:cancel')}
isDisabled={!isConnected || !isProcessing || !isCancelable}
onClick={handleClickCancel}
styleClass="cancel-btn"

View File

@@ -8,6 +8,7 @@ import IAIIconButton, {
IAIIconButtonProps,
} from 'common/components/IAIIconButton';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import { useTranslation } from 'react-i18next';
interface InvokeButton
extends Omit<IAIButtonProps | IAIIconButtonProps, 'aria-label'> {
@@ -24,6 +25,8 @@ export default function InvokeButton(props: InvokeButton) {
dispatch(generateImage(activeTabName));
};
const { t } = useTranslation();
useHotkeys(
['ctrl+enter', 'meta+enter'],
() => {
@@ -41,19 +44,19 @@ export default function InvokeButton(props: InvokeButton) {
<div style={{ flexGrow: 4 }}>
{iconButton ? (
<IAIIconButton
aria-label="Invoke"
aria-label={t('options:invoke')}
type="submit"
icon={<FaPlay />}
isDisabled={!isReady}
onClick={handleClickGenerate}
className="invoke-btn"
tooltip="Invoke"
tooltip={t('options:invoke')}
tooltipProps={{ placement: 'bottom' }}
{...rest}
/>
) : (
<IAIButton
aria-label="Invoke"
aria-label={t('options:invoke')}
type="submit"
isDisabled={!isReady}
onClick={handleClickGenerate}

View File

@@ -7,6 +7,7 @@ import {
OptionsState,
setShouldLoopback,
} from 'features/options/store/optionsSlice';
import { useTranslation } from 'react-i18next';
const loopbackSelector = createSelector(
(state: RootState) => state.options,
@@ -17,10 +18,12 @@ const LoopbackButton = () => {
const dispatch = useAppDispatch();
const shouldLoopback = useAppSelector(loopbackSelector);
const { t } = useTranslation();
return (
<IAIIconButton
aria-label="Toggle Loopback"
tooltip="Toggle Loopback"
aria-label={t('options:toggleLoopback')}
tooltip={t('options:toggleLoopback')}
styleClass="loopback-btn"
asCheckbox={true}
isChecked={shouldLoopback}

View File

@@ -10,6 +10,7 @@ import _ from 'lodash';
import { useHotkeys } from 'react-hotkeys-hook';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import { readinessSelector } from 'app/selectors/readinessSelector';
import { useTranslation } from 'react-i18next';
const promptInputSelector = createSelector(
[(state: RootState) => state.options, activeTabNameSelector],
@@ -36,6 +37,8 @@ const PromptInput = () => {
const promptRef = useRef<HTMLTextAreaElement>(null);
const { t } = useTranslation();
const handleChangePrompt = (e: ChangeEvent<HTMLTextAreaElement>) => {
dispatch(setPrompt(e.target.value));
};
@@ -63,7 +66,7 @@ const PromptInput = () => {
<Textarea
id="prompt"
name="prompt"
placeholder="Type prompt here. [negative tokens], (upweight)++, (downweight)--, swap and blend are available (see docs)"
placeholder={t('options:promptPlaceholder')}
size={'lg'}
value={prompt}
onChange={handleChangePrompt}

View File

@@ -7,11 +7,13 @@ import {
clearCanvasHistory,
resetCanvas,
} from 'features/canvas/store/canvasSlice';
import { useTranslation } from 'react-i18next';
import { FaTrash } from 'react-icons/fa';
const EmptyTempFolderButtonModal = () => {
const isStaging = useAppSelector(isStagingSelector);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const acceptCallback = () => {
dispatch(emptyTempFolder());
@@ -21,22 +23,18 @@ const EmptyTempFolderButtonModal = () => {
return (
<IAIAlertDialog
title={'Empty Temp Image Folder'}
title={t('unifiedcanvas:emptyTempImageFolder')}
acceptCallback={acceptCallback}
acceptButtonText={'Empty Folder'}
acceptButtonText={t('unifiedcanvas:emptyFolder')}
triggerComponent={
<IAIButton leftIcon={<FaTrash />} size={'sm'} isDisabled={isStaging}>
Empty Temp Image Folder
{t('unifiedcanvas:emptyTempImageFolder')}
</IAIButton>
}
>
<p>
Emptying the temp image folder also fully resets the Unified Canvas.
This includes all undo/redo history, images in the staging area, and the
canvas base layer.
</p>
<p>{t('unifiedcanvas:emptyTempImagesFolderMessage')}</p>
<br />
<p>Are you sure you want to empty the temp folder?</p>
<p>{t('unifiedcanvas:emptyTempImagesFolderConfirm')}</p>
</IAIAlertDialog>
);
};

View File

@@ -11,6 +11,7 @@ import {
useDisclosure,
} from '@chakra-ui/react';
import React, { cloneElement, ReactElement } from 'react';
import { useTranslation } from 'react-i18next';
import HotkeysModalItem from './HotkeysModalItem';
type HotkeysModalProps = {
@@ -31,245 +32,267 @@ export default function HotkeysModal({ children }: HotkeysModalProps) {
onClose: onHotkeysModalClose,
} = useDisclosure();
const { t } = useTranslation();
const appHotkeys = [
{ title: 'Invoke', desc: 'Generate an image', hotkey: 'Ctrl+Enter' },
{ title: 'Cancel', desc: 'Cancel image generation', hotkey: 'Shift+X' },
{
title: 'Focus Prompt',
desc: 'Focus the prompt input area',
title: t('hotkeys:invoke.title'),
desc: t('hotkeys:invoke.desc'),
hotkey: 'Ctrl+Enter',
},
{
title: t('hotkeys:cancel.title'),
desc: t('hotkeys:cancel.desc'),
hotkey: 'Shift+X',
},
{
title: t('hotkeys:focusPrompt.title'),
desc: t('hotkeys:focusPrompt.desc'),
hotkey: 'Alt+A',
},
{
title: 'Toggle Options',
desc: 'Open and close the options panel',
title: t('hotkeys:toggleOptions.title'),
desc: t('hotkeys:toggleOptions.desc'),
hotkey: 'O',
},
{
title: 'Pin Options',
desc: 'Pin the options panel',
title: t('hotkeys:pinOptions.title'),
desc: t('hotkeys:pinOptions.desc'),
hotkey: 'Shift+O',
},
{
title: 'Toggle Viewer',
desc: 'Open and close Image Viewer',
title: t('hotkeys:toggleViewer.title'),
desc: t('hotkeys:toggleViewer.desc'),
hotkey: 'Z',
},
{
title: 'Toggle Gallery',
desc: 'Open and close the gallery drawer',
title: t('hotkeys:toggleGallery.title'),
desc: t('hotkeys:toggleGallery.desc'),
hotkey: 'G',
},
{
title: 'Maximize Workspace',
desc: 'Close panels and maximize work area',
title: t('hotkeys:maximizeWorkSpace.title'),
desc: t('hotkeys:maximizeWorkSpace.desc'),
hotkey: 'F',
},
{
title: 'Change Tabs',
desc: 'Switch to another workspace',
title: t('hotkeys:changeTabs.title'),
desc: t('hotkeys:changeTabs.desc'),
hotkey: '1-5',
},
{
title: 'Console Toggle',
desc: 'Open and close console',
title: t('hotkeys:consoleToggle.title'),
desc: t('hotkeys:consoleToggle.desc'),
hotkey: '`',
},
];
const generalHotkeys = [
{
title: 'Set Prompt',
desc: 'Use the prompt of the current image',
title: t('hotkeys:setPrompt.title'),
desc: t('hotkeys:setPrompt.desc'),
hotkey: 'P',
},
{
title: 'Set Seed',
desc: 'Use the seed of the current image',
title: t('hotkeys:setSeed.title'),
desc: t('hotkeys:setSeed.desc'),
hotkey: 'S',
},
{
title: 'Set Parameters',
desc: 'Use all parameters of the current image',
title: t('hotkeys:setParameters.title'),
desc: t('hotkeys:setParameters.desc'),
hotkey: 'A',
},
{
title: 'Restore Faces',
desc: 'Restore the current image',
title: t('hotkeys:restoreFaces.title'),
desc: t('hotkeys:restoreFaces.desc'),
hotkey: 'Shift+R',
},
{ title: 'Upscale', desc: 'Upscale the current image', hotkey: 'Shift+U' },
{
title: 'Show Info',
desc: 'Show metadata info of the current image',
title: t('hotkeys:upscale.title'),
desc: t('hotkeys:upscale.desc'),
hotkey: 'Shift+U',
},
{
title: t('hotkeys:showInfo.title'),
desc: t('hotkeys:showInfo.desc'),
hotkey: 'I',
},
{
title: 'Send To Image To Image',
desc: 'Send current image to Image to Image',
title: t('hotkeys:sendToImageToImage.title'),
desc: t('hotkeys:sendToImageToImage.desc'),
hotkey: 'Shift+I',
},
{ title: 'Delete Image', desc: 'Delete the current image', hotkey: 'Del' },
{ title: 'Close Panels', desc: 'Closes open panels', hotkey: 'Esc' },
{
title: t('hotkeys:deleteImage.title'),
desc: t('hotkeys:deleteImage.desc'),
hotkey: 'Del',
},
{
title: t('hotkeys:closePanels.title'),
desc: t('hotkeys:closePanels.desc'),
hotkey: 'Esc',
},
];
const galleryHotkeys = [
{
title: 'Previous Image',
desc: 'Display the previous image in gallery',
title: t('hotkeys:previousImage.title'),
desc: t('hotkeys:previousImage.desc'),
hotkey: 'Arrow Left',
},
{
title: 'Next Image',
desc: 'Display the next image in gallery',
title: t('hotkeys:nextImage.title'),
desc: t('hotkeys:nextImage.desc'),
hotkey: 'Arrow Right',
},
{
title: 'Toggle Gallery Pin',
desc: 'Pins and unpins the gallery to the UI',
title: t('hotkeys:toggleGalleryPin.title'),
desc: t('hotkeys:toggleGalleryPin.desc'),
hotkey: 'Shift+G',
},
{
title: 'Increase Gallery Image Size',
desc: 'Increases gallery thumbnails size',
title: t('hotkeys:increaseGalleryThumbSize.title'),
desc: t('hotkeys:increaseGalleryThumbSize.desc'),
hotkey: 'Shift+Up',
},
{
title: 'Decrease Gallery Image Size',
desc: 'Decreases gallery thumbnails size',
title: t('hotkeys:decreaseGalleryThumbSize.title'),
desc: t('hotkeys:decreaseGalleryThumbSize.desc'),
hotkey: 'Shift+Down',
},
];
const unifiedCanvasHotkeys = [
{
title: 'Select Brush',
desc: 'Selects the canvas brush',
title: t('hotkeys:selectBrush.title'),
desc: t('hotkeys:selectBrush.desc'),
hotkey: 'B',
},
{
title: 'Select Eraser',
desc: 'Selects the canvas eraser',
title: t('hotkeys:selectEraser.title'),
desc: t('hotkeys:selectEraser.desc'),
hotkey: 'E',
},
{
title: 'Decrease Brush Size',
desc: 'Decreases the size of the canvas brush/eraser',
title: t('hotkeys:decreaseBrushSize.title'),
desc: t('hotkeys:decreaseBrushSize.desc'),
hotkey: '[',
},
{
title: 'Increase Brush Size',
desc: 'Increases the size of the canvas brush/eraser',
title: t('hotkeys:increaseBrushSize.title'),
desc: t('hotkeys:increaseBrushSize.desc'),
hotkey: ']',
},
{
title: 'Decrease Brush Opacity',
desc: 'Decreases the opacity of the canvas brush',
title: t('hotkeys:decreaseBrushOpacity.title'),
desc: t('hotkeys:decreaseBrushOpacity.desc'),
hotkey: 'Shift + [',
},
{
title: 'Increase Brush Opacity',
desc: 'Increases the opacity of the canvas brush',
title: t('hotkeys:increaseBrushOpacity.title'),
desc: t('hotkeys:increaseBrushOpacity.desc'),
hotkey: 'Shift + ]',
},
{
title: 'Move Tool',
desc: 'Allows canvas navigation',
title: t('hotkeys:moveTool.title'),
desc: t('hotkeys:moveTool.desc'),
hotkey: 'V',
},
{
title: 'Fill Bounding Box',
desc: 'Fills the bounding box with brush color',
title: t('hotkeys:fillBoundingBox.title'),
desc: t('hotkeys:fillBoundingBox.desc'),
hotkey: 'Shift + F',
},
{
title: 'Erase Bounding Box',
desc: 'Erases the bounding box area',
title: t('hotkeys:eraseBoundingBox.title'),
desc: t('hotkeys:eraseBoundingBox.desc'),
hotkey: 'Delete / Backspace',
},
{
title: 'Select Color Picker',
desc: 'Selects the canvas color picker',
title: t('hotkeys:colorPicker.title'),
desc: t('hotkeys:colorPicker.desc'),
hotkey: 'C',
},
{
title: 'Toggle Snap',
desc: 'Toggles Snap to Grid',
title: t('hotkeys:toggleSnap.title'),
desc: t('hotkeys:toggleSnap.desc'),
hotkey: 'N',
},
{
title: 'Quick Toggle Move',
desc: 'Temporarily toggles Move mode',
title: t('hotkeys:quickToggleMove.title'),
desc: t('hotkeys:quickToggleMove.desc'),
hotkey: 'Hold Space',
},
{
title: 'Toggle Layer',
desc: 'Toggles mask/base layer selection',
title: t('hotkeys:toggleLayer.title'),
desc: t('hotkeys:toggleLayer.desc'),
hotkey: 'Q',
},
{
title: 'Clear Mask',
desc: 'Clear the entire mask',
title: t('hotkeys:clearMask.title'),
desc: t('hotkeys:clearMask.desc'),
hotkey: 'Shift+C',
},
{
title: 'Hide Mask',
desc: 'Hide and unhide mask',
title: t('hotkeys:hideMask.title'),
desc: t('hotkeys:hideMask.desc'),
hotkey: 'H',
},
{
title: 'Show/Hide Bounding Box',
desc: 'Toggle visibility of bounding box',
title: t('hotkeys:showHideBoundingBox.title'),
desc: t('hotkeys:showHideBoundingBox.desc'),
hotkey: 'Shift+H',
},
{
title: 'Merge Visible',
desc: 'Merge all visible layers of canvas',
title: t('hotkeys:mergeVisible.title'),
desc: t('hotkeys:mergeVisible.desc'),
hotkey: 'Shift+M',
},
{
title: 'Save To Gallery',
desc: 'Save current canvas to gallery',
title: t('hotkeys:saveToGallery.title'),
desc: t('hotkeys:saveToGallery.desc'),
hotkey: 'Shift+S',
},
{
title: 'Copy to Clipboard',
desc: 'Copy current canvas to clipboard',
title: t('hotkeys:copyToClipboard.title'),
desc: t('hotkeys:copyToClipboard.desc'),
hotkey: 'Ctrl+C',
},
{
title: 'Download Image',
desc: 'Download current canvas',
title: t('hotkeys:downloadImage.title'),
desc: t('hotkeys:downloadImage.desc'),
hotkey: 'Shift+D',
},
{
title: 'Undo Stroke',
desc: 'Undo a brush stroke',
title: t('hotkeys:undoStroke.title'),
desc: t('hotkeys:undoStroke.desc'),
hotkey: 'Ctrl+Z',
},
{
title: 'Redo Stroke',
desc: 'Redo a brush stroke',
title: t('hotkeys:redoStroke.title'),
desc: t('hotkeys:redoStroke.desc'),
hotkey: 'Ctrl+Shift+Z, Ctrl+Y',
},
{
title: 'Reset View',
desc: 'Reset Canvas View',
title: t('hotkeys:resetView.title'),
desc: t('hotkeys:resetView.desc'),
hotkey: 'R',
},
{
title: 'Previous Staging Image',
desc: 'Previous Staging Area Image',
title: t('hotkeys:previousStagingImage.title'),
desc: t('hotkeys:previousStagingImage.desc'),
hotkey: 'Arrow Left',
},
{
title: 'Next Staging Image',
desc: 'Next Staging Area Image',
title: t('hotkeys:nextStagingImage.title'),
desc: t('hotkeys:nextStagingImage.desc'),
hotkey: 'Arrow Right',
},
{
title: 'Accept Staging Image',
desc: 'Accept Current Staging Area Image',
title: t('hotkeys:acceptStagingImage.title'),
desc: t('hotkeys:acceptStagingImage.desc'),
hotkey: 'Enter',
},
];
@@ -308,7 +331,7 @@ export default function HotkeysModal({ children }: HotkeysModalProps) {
<Accordion allowMultiple>
<AccordionItem>
<AccordionButton className="hotkeys-modal-button">
<h2>App Hotkeys</h2>
<h2>{t('hotkeys:appHotkeys')}</h2>
<AccordionIcon />
</AccordionButton>
<AccordionPanel>
@@ -318,7 +341,7 @@ export default function HotkeysModal({ children }: HotkeysModalProps) {
<AccordionItem>
<AccordionButton className="hotkeys-modal-button">
<h2>General Hotkeys</h2>
<h2>{t('hotkeys:generalHotkeys')}</h2>
<AccordionIcon />
</AccordionButton>
<AccordionPanel>
@@ -328,7 +351,7 @@ export default function HotkeysModal({ children }: HotkeysModalProps) {
<AccordionItem>
<AccordionButton className="hotkeys-modal-button">
<h2>Gallery Hotkeys</h2>
<h2>{t('hotkeys:galleryHotkeys')}</h2>
<AccordionIcon />
</AccordionButton>
<AccordionPanel>
@@ -338,7 +361,7 @@ export default function HotkeysModal({ children }: HotkeysModalProps) {
<AccordionItem>
<AccordionButton className="hotkeys-modal-button">
<h2>Unified Canvas Hotkeys</h2>
<h2>{t('hotkeys:unifiedCanvasHotkeys')}</h2>
<AccordionIcon />
</AccordionButton>
<AccordionPanel>

View File

@@ -0,0 +1,63 @@
import type { ReactNode } from 'react';
import { VStack } from '@chakra-ui/react';
import IAIPopover from 'common/components/IAIPopover';
import React from 'react';
import IAIButton from 'common/components/IAIButton';
import { useTranslation } from 'react-i18next';
import IAIIconButton from 'common/components/IAIIconButton';
import { FaLanguage } from 'react-icons/fa';
export default function LanguagePicker() {
const { t, i18n } = useTranslation();
const LANGUAGES = {
en: t('common:langEnglish'),
ru: t('common:langRussian'),
it: t('common:langItalian'),
pt_br: t('common:langBrPortuguese'),
de: t('common:langGerman'),
pl: t('common:langPolish'),
};
const renderLanguagePicker = () => {
const languagesToRender: ReactNode[] = [];
Object.keys(LANGUAGES).forEach((lang) => {
languagesToRender.push(
<IAIButton
key={lang}
data-selected={localStorage.getItem('i18nextLng') === lang}
onClick={() => i18n.changeLanguage(lang)}
className="modal-close-btn lang-select-btn"
aria-label={LANGUAGES[lang as keyof typeof LANGUAGES]}
tooltip={LANGUAGES[lang as keyof typeof LANGUAGES]}
size="sm"
minWidth="200px"
>
{LANGUAGES[lang as keyof typeof LANGUAGES]}
</IAIButton>
);
});
return languagesToRender;
};
return (
<IAIPopover
trigger="hover"
triggerComponent={
<IAIIconButton
aria-label={t('common:languagePickerLabel')}
tooltip={t('common:languagePickerLabel')}
icon={<FaLanguage />}
size={'sm'}
variant="link"
data-variant="link"
fontSize={26}
/>
}
>
<VStack>{renderLanguagePicker()}</VStack>
</IAIPopover>
);
}

View File

@@ -7,6 +7,7 @@ import {
AccordionButton,
AccordionPanel,
AccordionIcon,
Text,
} from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import _ from 'lodash';
@@ -15,6 +16,7 @@ import { requestModelChange } from 'app/socketio/actions';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { SystemState } from 'features/system/store/systemSlice';
import { useTranslation } from 'react-i18next';
type ModelListItemProps = {
name: string;
@@ -32,6 +34,7 @@ const ModelListItem = (props: ModelListItemProps) => {
const handleChangeModel = () => {
dispatch(requestModelChange(name));
};
const { t } = useTranslation();
return (
<div className="model-list-item">
<Tooltip label={description} hasArrow placement="bottom">
@@ -47,7 +50,7 @@ const ModelListItem = (props: ModelListItemProps) => {
onClick={handleChangeModel}
isDisabled={status === 'active' || isProcessing || !isConnected}
>
Load
{t('common:load')}
</Button>
</div>
</div>
@@ -72,6 +75,7 @@ const modelListSelector = createSelector(
const ModelList = () => {
const { models } = useAppSelector(modelListSelector);
const { t } = useTranslation();
return (
<Accordion
@@ -82,7 +86,13 @@ const ModelList = () => {
<AccordionItem>
<AccordionButton>
<div className="model-list-button">
<h2>Models</h2>
<Text
fontSize="sm"
fontWeight="bold"
color="var(--text-color-secondary)"
>
{t('settings:models')}
</Text>
<AccordionIcon />
</div>
</AccordionButton>

View File

@@ -34,6 +34,7 @@ import IAINumberInput from 'common/components/IAINumberInput';
import { systemSelector } from 'features/system/store/systemSelectors';
import { optionsSelector } from 'features/options/store/optionsSelectors';
import { setShouldUseCanvasBetaLayout } from 'features/options/store/optionsSlice';
import { useTranslation } from 'react-i18next';
const selector = createSelector(
[systemSelector, optionsSelector],
@@ -77,6 +78,7 @@ type SettingsModalProps = {
*/
const SettingsModal = ({ children }: SettingsModalProps) => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const steps = useAppSelector((state: RootState) => state.options.steps);
@@ -124,10 +126,16 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
onClick: onSettingsModalOpen,
})}
<Modal isOpen={isSettingsModalOpen} onClose={onSettingsModalClose}>
<Modal
isOpen={isSettingsModalOpen}
onClose={onSettingsModalClose}
size="lg"
>
<ModalOverlay />
<ModalContent className="modal settings-modal">
<ModalHeader className="settings-modal-header">Settings</ModalHeader>
<ModalHeader className="settings-modal-header">
{t('common:settingsLabel')}
</ModalHeader>
<ModalCloseButton className="modal-close-btn" />
<ModalBody className="settings-modal-content">
<div className="settings-modal-items">
@@ -139,7 +147,7 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
style={{ gridAutoFlow: 'row', rowGap: '0.5rem' }}
>
<IAISelect
label={'Display In-Progress Images'}
label={t('settings:displayInProgress')}
validValues={IN_PROGRESS_IMAGE_TYPES}
value={shouldDisplayInProgressType}
onChange={(e: ChangeEvent<HTMLSelectElement>) =>
@@ -152,7 +160,7 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
/>
{shouldDisplayInProgressType === 'full-res' && (
<IAINumberInput
label="Save images every n steps"
label={t('settings:saveSteps')}
min={1}
max={steps}
step={1}
@@ -165,7 +173,7 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
</div>
<IAISwitch
styleClass="settings-modal-item"
label={'Confirm on Delete'}
label={t('settings:confirmOnDelete')}
isChecked={shouldConfirmOnDelete}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
dispatch(setShouldConfirmOnDelete(e.target.checked))
@@ -173,7 +181,7 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
/>
<IAISwitch
styleClass="settings-modal-item"
label={'Display Help Icons'}
label={t('settings:displayHelpIcons')}
isChecked={shouldDisplayGuides}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
dispatch(setShouldDisplayGuides(e.target.checked))
@@ -181,7 +189,7 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
/>
<IAISwitch
styleClass="settings-modal-item"
label={'Use Canvas Beta Layout'}
label={t('settings:useCanvasBeta')}
isChecked={shouldUseCanvasBetaLayout}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
dispatch(setShouldUseCanvasBetaLayout(e.target.checked))
@@ -193,7 +201,7 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
<h2 style={{ fontWeight: 'bold' }}>Developer</h2>
<IAISwitch
styleClass="settings-modal-item"
label={'Enable Image Debugging'}
label={t('settings:enableImageDebugging')}
isChecked={enableImageDebugging}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
dispatch(setEnableImageDebugging(e.target.checked))
@@ -202,26 +210,18 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
</div>
<div className="settings-modal-reset">
<Heading size={'md'}>Reset Web UI</Heading>
<Heading size={'md'}>{t('settings:resetWebUI')}</Heading>
<Button colorScheme="red" onClick={handleClickResetWebUI}>
Reset Web UI
{t('settings:resetWebUI')}
</Button>
<Text>
Resetting the web UI only resets the browser's local cache of
your images and remembered settings. It does not delete any
images from disk.
</Text>
<Text>
If images aren't showing up in the gallery or something else
isn't working, please try resetting before submitting an issue
on GitHub.
</Text>
<Text>{t('settings:resetWebUIDesc1')}</Text>
<Text>{t('settings:resetWebUIDesc2')}</Text>
</div>
</ModalBody>
<ModalFooter>
<Button onClick={onSettingsModalClose} className="modal-close-btn">
Close
{t('common:close')}
</Button>
</ModalFooter>
</ModalContent>
@@ -238,7 +238,7 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
<ModalBody pb={6} pt={6}>
<Flex justifyContent={'center'}>
<Text fontSize={'lg'}>
Web UI has been reset. Refresh the page to reload.
<Text>{t('settings:resetComplete')}</Text>
</Text>
</Flex>
</ModalBody>

View File

@@ -23,4 +23,13 @@
display: flex;
align-items: center;
column-gap: 0.5rem;
.lang-select-btn {
&[data-selected='true'] {
background-color: var(--accent-color);
&:hover {
background-color: var(--accent-color);
}
}
}
}

View File

@@ -17,11 +17,16 @@ import SettingsModal from './SettingsModal/SettingsModal';
import StatusIndicator from './StatusIndicator';
import ThemeChanger from './ThemeChanger';
import ModelSelect from './ModelSelect';
import LanguagePicker from './LanguagePicker';
import { useTranslation } from 'react-i18next';
/**
* Header, includes color mode toggle, settings button, status message.
*/
const SiteHeader = () => {
const { t } = useTranslation();
return (
<div className="site-header">
<div className="site-header-left-side">
@@ -38,8 +43,8 @@ const SiteHeader = () => {
<HotkeysModal>
<IAIIconButton
aria-label="Hotkeys"
tooltip="Hotkeys"
aria-label={t('common:hotkeysLabel')}
tooltip={t('common:hotkeysLabel')}
size={'sm'}
variant="link"
data-variant="link"
@@ -50,9 +55,11 @@ const SiteHeader = () => {
<ThemeChanger />
<LanguagePicker />
<IAIIconButton
aria-label="Report Bug"
tooltip="Report Bug"
aria-label={t('common:reportBugLabel')}
tooltip={t('common:reportBugLabel')}
variant="link"
data-variant="link"
fontSize={20}
@@ -65,8 +72,8 @@ const SiteHeader = () => {
/>
<IAIIconButton
aria-label="Link to Github Repo"
tooltip="Github"
aria-label={t('common:githubLabel')}
tooltip={t('common:githubLabel')}
variant="link"
data-variant="link"
fontSize={20}
@@ -79,8 +86,8 @@ const SiteHeader = () => {
/>
<IAIIconButton
aria-label="Link to Discord Server"
tooltip="Discord"
aria-label={t('common:discordLabel')}
tooltip={t('common:discordLabel')}
variant="link"
data-variant="link"
fontSize={20}
@@ -94,8 +101,8 @@ const SiteHeader = () => {
<SettingsModal>
<IAIIconButton
aria-label="Settings"
tooltip="Settings"
aria-label={t('common:settingsLabel')}
tooltip={t('common:settingsLabel')}
variant="link"
data-variant="link"
fontSize={20}

View File

@@ -4,6 +4,7 @@ import { isEqual } from 'lodash';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { errorSeen, SystemState } from 'features/system/store/systemSlice';
import { useTranslation } from 'react-i18next';
const systemSelector = createSelector(
(state: RootState) => state.system,
@@ -34,6 +35,7 @@ const StatusIndicator = () => {
wasErrorSeen,
} = useAppSelector(systemSelector);
const dispatch = useAppDispatch();
const { t } = useTranslation();
let statusStyle;
if (isConnected && !hasError) {
@@ -45,14 +47,14 @@ const StatusIndicator = () => {
let statusMessage = currentStatus;
const intermediateStatuses = [
'generating',
'preparing',
'saving image',
'restoring faces',
'upscaling',
t('common:statusGenerating'),
t('common:statusPreparing'),
t('common:statusSavingImage'),
t('common:statusRestoringFaces'),
t('common:statusUpscaling'),
];
if (intermediateStatuses.includes(statusMessage.toLowerCase())) {
if (intermediateStatuses.includes(statusMessage)) {
statusStyle = 'status-working';
}
@@ -84,7 +86,7 @@ const StatusIndicator = () => {
onClick={handleClickStatusIndicator}
className={`status ${statusStyle}`}
>
{statusMessage}
{t(statusMessage as keyof typeof t)}
</Text>
</Tooltip>
);

View File

@@ -7,16 +7,23 @@ import IAIPopover from 'common/components/IAIPopover';
import IAIIconButton from 'common/components/IAIIconButton';
import { FaCheck, FaPalette } from 'react-icons/fa';
import IAIButton from 'common/components/IAIButton';
const THEMES = ['dark', 'light', 'green'];
import { useTranslation } from 'react-i18next';
import type { ReactNode } from 'react';
export default function ThemeChanger() {
const { t } = useTranslation();
const { setColorMode, colorMode } = useColorMode();
const dispatch = useAppDispatch();
const currentTheme = useAppSelector(
(state: RootState) => state.options.currentTheme
);
const THEMES = {
dark: t('common:darkTheme'),
light: t('common:lightTheme'),
green: t('common:greenTheme'),
};
useEffect(() => {
// syncs the redux store theme to the chakra's theme on startup and when
// setCurrentTheme is dispatched
@@ -29,12 +36,34 @@ export default function ThemeChanger() {
dispatch(setCurrentTheme(theme));
};
const renderThemeOptions = () => {
const themesToRender: ReactNode[] = [];
Object.keys(THEMES).forEach((theme) => {
themesToRender.push(
<IAIButton
style={{
width: '6rem',
}}
leftIcon={currentTheme === theme ? <FaCheck /> : undefined}
size={'sm'}
onClick={() => handleChangeTheme(theme)}
key={theme}
>
{THEMES[theme as keyof typeof THEMES]}
</IAIButton>
);
});
return themesToRender;
};
return (
<IAIPopover
trigger="hover"
triggerComponent={
<IAIIconButton
aria-label="Theme"
aria-label={t('common:themeLabel')}
size={'sm'}
variant="link"
data-variant="link"
@@ -43,21 +72,7 @@ export default function ThemeChanger() {
/>
}
>
<VStack align={'stretch'}>
{THEMES.map((theme) => (
<IAIButton
style={{
width: '6rem',
}}
leftIcon={currentTheme === theme ? <FaCheck /> : undefined}
size={'sm'}
onClick={() => handleChangeTheme(theme)}
key={theme}
>
{theme.charAt(0).toUpperCase() + theme.slice(1)}
</IAIButton>
))}
</VStack>
<VStack align={'stretch'}>{renderThemeOptions()}</VStack>
</IAIPopover>
);
}

View File

@@ -2,6 +2,7 @@ import { createSlice } from '@reduxjs/toolkit';
import type { PayloadAction } from '@reduxjs/toolkit';
import { ExpandedIndex, UseToastOptions } from '@chakra-ui/react';
import * as InvokeAI from 'app/invokeai';
import i18n from 'i18n';
export type LogLevel = 'info' | 'warning' | 'error';
@@ -64,7 +65,9 @@ const initialSystemState: SystemState = {
totalSteps: 0,
currentIteration: 0,
totalIterations: 0,
currentStatus: 'Disconnected',
currentStatus: i18n.isInitialized
? i18n.t('common:statusDisconnected')
: 'Disconnected',
currentStatusHasSteps: false,
model: '',
model_id: '',
@@ -109,13 +112,15 @@ export const systemSlice = createSlice({
state.currentIteration = 0;
state.totalIterations = 0;
state.currentStatusHasSteps = false;
state.currentStatus = 'Error';
state.currentStatus = i18n.t('common:statusError');
state.wasErrorSeen = false;
},
errorSeen: (state) => {
state.hasError = false;
state.wasErrorSeen = true;
state.currentStatus = state.isConnected ? 'Connected' : 'Disconnected';
state.currentStatus = state.isConnected
? i18n.t('common:statusConnected')
: i18n.t('common:statusDisconnected');
},
addLogEntry: (
state,
@@ -176,7 +181,7 @@ export const systemSlice = createSlice({
state.currentIteration = 0;
state.totalIterations = 0;
state.currentStatusHasSteps = false;
state.currentStatus = 'Processing canceled';
state.currentStatus = i18n.t('common:statusProcessingCanceled');
},
generationRequested: (state) => {
state.isProcessing = true;
@@ -186,7 +191,7 @@ export const systemSlice = createSlice({
state.currentIteration = 0;
state.totalIterations = 0;
state.currentStatusHasSteps = false;
state.currentStatus = 'Preparing';
state.currentStatus = i18n.t('common:statusPreparing');
},
setModelList: (
state,
@@ -198,7 +203,7 @@ export const systemSlice = createSlice({
state.isCancelable = action.payload;
},
modelChangeRequested: (state) => {
state.currentStatus = 'Loading Model';
state.currentStatus = i18n.t('common:statusLoadingModel');
state.isCancelable = false;
state.isProcessing = true;
state.currentStatusHasSteps = false;

View File

@@ -18,34 +18,37 @@ import { setHiresFix } from 'features/options/store/optionsSlice';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import InvokeOptionsPanel from 'features/tabs/components/InvokeOptionsPanel';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import { useTranslation } from 'react-i18next';
export default function ImageToImagePanel() {
const { t } = useTranslation();
const imageToImageAccordions = {
seed: {
header: 'Seed',
header: `${t('options:seed')}`,
feature: Feature.SEED,
content: <SeedOptions />,
},
variations: {
header: 'Variations',
header: `${t('options:variations')}`,
feature: Feature.VARIATIONS,
content: <VariationsOptions />,
additionalHeaderComponents: <GenerateVariationsToggle />,
},
face_restore: {
header: 'Face Restoration',
header: `${t('options:faceRestoration')}`,
feature: Feature.FACE_CORRECTION,
content: <FaceRestoreOptions />,
additionalHeaderComponents: <FaceRestoreToggle />,
},
upscale: {
header: 'Upscaling',
header: `${t('options:upscaling')}`,
feature: Feature.UPSCALE,
content: <UpscaleOptions />,
additionalHeaderComponents: <UpscaleToggle />,
},
other: {
header: 'Other Options',
header: `${t('options:otherOptions')}`,
feature: Feature.OTHER,
content: <ImageToImageOutputOptions />,
},
@@ -68,7 +71,7 @@ export default function ImageToImagePanel() {
<ProcessButtons />
<MainOptions />
<ImageToImageStrength
label="Image To Image Strength"
label={t('options:img2imgStrength')}
styleClass="main-option-block image-to-image-strength-main-option"
/>
<ImageFit />

View File

@@ -3,25 +3,23 @@ import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import ImageUploaderIconButton from 'common/components/ImageUploaderIconButton';
import { clearInitialImage } from 'features/options/store/optionsSlice';
import { useTranslation } from 'react-i18next';
export default function InitImagePreview() {
const initialImage = useAppSelector(
(state: RootState) => state.options.initialImage
);
const { t } = useTranslation();
const dispatch = useAppDispatch();
const toast = useToast();
// const handleClickResetInitialImage = (e: SyntheticEvent) => {
// e.stopPropagation();
// dispatch(clearInitialImage());
// };
const alertMissingInitImage = () => {
toast({
title: 'Problem loading parameters',
description: 'Unable to load init image.',
title: t('toast:parametersFailed'),
description: t('toast:parametersFailedDesc'),
status: 'error',
isClosable: true,
});
@@ -31,14 +29,7 @@ export default function InitImagePreview() {
return (
<>
<div className="init-image-preview-header">
{/* <div className="init-image-preview-header"> */}
<h2>Initial Image</h2>
{/* <IconButton
isDisabled={!initialImage}
aria-label={'Reset Initial Image'}
onClick={handleClickResetInitialImage}
icon={<MdClear />}
/> */}
<h2>{t('options:initialImage')}</h2>
<ImageUploaderIconButton />
</div>
{initialImage && (

View File

@@ -22,6 +22,8 @@ import UnifiedCanvasIcon from 'common/icons/UnifiedCanvasIcon';
import TrainingWIP from 'common/components/WorkInProgress/Training';
import TrainingIcon from 'common/icons/TrainingIcon';
import { InvokeTabName } from 'features/tabs/tabMap';
import i18n from 'i18n';
import useUpdateTranslations from 'common/hooks/useUpdateTranslations';
export interface InvokeTabInfo {
title: ReactElement;
@@ -62,6 +64,15 @@ export const tabDict: Record<InvokeTabName, InvokeTabInfo> = {
},
};
function updateTabTranslations() {
tabDict.txt2img.tooltip = i18n.t('common:text2img');
tabDict.img2img.tooltip = i18n.t('common:img2img');
tabDict.unifiedCanvas.tooltip = i18n.t('common:unifiedCanvas');
tabDict.nodes.tooltip = i18n.t('common:nodes');
tabDict.postprocess.tooltip = i18n.t('common:postProcessing');
tabDict.training.tooltip = i18n.t('common:training');
}
export default function InvokeTabs() {
const activeTab = useAppSelector(
(state: RootState) => state.options.activeTab
@@ -70,6 +81,8 @@ export default function InvokeTabs() {
(state: RootState) => state.options.isLightBoxOpen
);
useUpdateTranslations(updateTabTranslations);
const dispatch = useAppDispatch();
useHotkeys('1', () => {

View File

@@ -12,34 +12,37 @@ import OptionsAccordion from 'features/options/components/OptionsAccordion';
import ProcessButtons from 'features/options/components/ProcessButtons/ProcessButtons';
import PromptInput from 'features/options/components/PromptInput/PromptInput';
import InvokeOptionsPanel from 'features/tabs/components/InvokeOptionsPanel';
import { useTranslation } from 'react-i18next';
export default function TextToImagePanel() {
const { t } = useTranslation();
const textToImageAccordions = {
seed: {
header: 'Seed',
header: `${t('options:seed')}`,
feature: Feature.SEED,
content: <SeedOptions />,
},
variations: {
header: 'Variations',
header: `${t('options:variations')}`,
feature: Feature.VARIATIONS,
content: <VariationsOptions />,
additionalHeaderComponents: <GenerateVariationsToggle />,
},
face_restore: {
header: 'Face Restoration',
header: `${t('options:faceRestoration')}`,
feature: Feature.FACE_CORRECTION,
content: <FaceRestoreOptions />,
additionalHeaderComponents: <FaceRestoreToggle />,
},
upscale: {
header: 'Upscaling',
header: `${t('options:upscaling')}`,
feature: Feature.UPSCALE,
content: <UpscaleOptions />,
additionalHeaderComponents: <UpscaleToggle />,
},
other: {
header: 'Other Options',
header: `${t('options:otherOptions')}`,
feature: Feature.OTHER,
content: <OutputOptions />,
},

View File

@@ -5,6 +5,7 @@ import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { setBrushSize } from 'features/canvas/store/canvasSlice';
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
export default function UnifiedCanvasBrushSize() {
const dispatch = useAppDispatch();
@@ -13,6 +14,8 @@ export default function UnifiedCanvasBrushSize() {
(state: RootState) => state.canvas.brushSize
);
const { t } = useTranslation();
const isStaging = useAppSelector(isStagingSelector);
useHotkeys(
@@ -41,13 +44,14 @@ export default function UnifiedCanvasBrushSize() {
return (
<IAISlider
label="Size"
label={t('unifiedcanvas:brushSize')}
value={brushSize}
withInput
onChange={(newSize) => dispatch(setBrushSize(newSize))}
sliderNumberInputProps={{ max: 500 }}
inputReadOnly={false}
width={'100px'}
isCompact
/>
);
}

View File

@@ -3,11 +3,13 @@ import IAIButton from 'common/components/IAIButton';
import { clearMask } from 'features/canvas/store/canvasSlice';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { FaTrash } from 'react-icons/fa';
export default function UnifiedCanvasClearMask() {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleClearMask = () => dispatch(clearMask());
@@ -16,9 +18,9 @@ export default function UnifiedCanvasClearMask() {
size={'sm'}
leftIcon={<FaTrash />}
onClick={handleClearMask}
tooltip="Clear Mask (Shift+C)"
tooltip={`${t('unifiedcanvas:clearMask')} (Shift+C)`}
>
Clear
{t('unifiedcanvas:betaClear')}
</IAIButton>
);
}

View File

@@ -3,6 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAICheckbox from 'common/components/IAICheckbox';
import { setShouldDarkenOutsideBoundingBox } from 'features/canvas/store/canvasSlice';
import React from 'react';
import { useTranslation } from 'react-i18next';
export default function UnifiedCanvasDarkenOutsideSelection() {
const shouldDarkenOutsideBoundingBox = useAppSelector(
@@ -11,9 +12,11 @@ export default function UnifiedCanvasDarkenOutsideSelection() {
const dispatch = useAppDispatch();
const { t } = useTranslation();
return (
<IAICheckbox
label="Darken Outside"
label={t('unifiedcanvas:betaDarkenOutside')}
isChecked={shouldDarkenOutsideBoundingBox}
onChange={(e) =>
dispatch(setShouldDarkenOutsideBoundingBox(e.target.checked))

View File

@@ -3,6 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAICheckbox from 'common/components/IAICheckbox';
import { setIsMaskEnabled } from 'features/canvas/store/canvasSlice';
import React from 'react';
import { useTranslation } from 'react-i18next';
export default function UnifiedCanvasEnableMask() {
const isMaskEnabled = useAppSelector(
@@ -10,13 +11,14 @@ export default function UnifiedCanvasEnableMask() {
);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleToggleEnableMask = () =>
dispatch(setIsMaskEnabled(!isMaskEnabled));
return (
<IAICheckbox
label="Enable Mask (H)"
label={`${t('unifiedcanvas:enableMask')} (H)`}
isChecked={isMaskEnabled}
onChange={handleToggleEnableMask}
/>

View File

@@ -3,6 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAICheckbox from 'common/components/IAICheckbox';
import { setShouldRestrictStrokesToBox } from 'features/canvas/store/canvasSlice';
import React from 'react';
import { useTranslation } from 'react-i18next';
export default function UnifiedCanvasLimitStrokesToBox() {
const dispatch = useAppDispatch();
@@ -11,9 +12,11 @@ export default function UnifiedCanvasLimitStrokesToBox() {
(state: RootState) => state.canvas.shouldRestrictStrokesToBox
);
const { t } = useTranslation();
return (
<IAICheckbox
label="Limit To Box"
label={t('unifiedcanvas:betaLimitToBox')}
isChecked={shouldRestrictStrokesToBox}
onChange={(e) =>
dispatch(setShouldRestrictStrokesToBox(e.target.checked))

View File

@@ -3,9 +3,11 @@ import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAICheckbox from 'common/components/IAICheckbox';
import { setShouldPreserveMaskedArea } from 'features/canvas/store/canvasSlice';
import React from 'react';
import { useTranslation } from 'react-i18next';
export default function UnifiedCanvasPreserveMask() {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const shouldPreserveMaskedArea = useAppSelector(
(state: RootState) => state.canvas.shouldPreserveMaskedArea
@@ -13,7 +15,7 @@ export default function UnifiedCanvasPreserveMask() {
return (
<IAICheckbox
label="Preserve Masked"
label={t('unifiedcanvas:betaPreserveMasked')}
isChecked={shouldPreserveMaskedArea}
onChange={(e) => dispatch(setShouldPreserveMaskedArea(e.target.checked))}
/>

View File

@@ -16,6 +16,7 @@ import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import EmptyTempFolderButtonModal from 'features/system/components/ClearTempFolderButtonModal';
import ClearCanvasHistoryButtonModal from 'features/canvas/components/ClearCanvasHistoryButtonModal';
import { useTranslation } from 'react-i18next';
export const canvasControlsSelector = createSelector(
[canvasSelector],
@@ -43,6 +44,8 @@ export const canvasControlsSelector = createSelector(
const UnifiedCanvasSettings = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const {
shouldAutoSave,
shouldCropToBoundingBoxOnSave,
@@ -55,37 +58,37 @@ const UnifiedCanvasSettings = () => {
trigger="hover"
triggerComponent={
<IAIIconButton
tooltip="Canvas Settings"
tooltip={t('unifiedcanvas:canvasSettings')}
tooltipProps={{
placement: 'bottom',
}}
aria-label="Canvas Settings"
aria-label={t('unifiedcanvas:canvasSettings')}
icon={<FaWrench />}
/>
}
>
<Flex direction={'column'} gap={'0.5rem'}>
<IAICheckbox
label="Show Intermediates"
label={t('unifiedcanvas:showIntermediates')}
isChecked={shouldShowIntermediates}
onChange={(e) =>
dispatch(setShouldShowIntermediates(e.target.checked))
}
/>
<IAICheckbox
label="Auto Save to Gallery"
label={t('unifiedcanvas:autoSaveToGallery')}
isChecked={shouldAutoSave}
onChange={(e) => dispatch(setShouldAutoSave(e.target.checked))}
/>
<IAICheckbox
label="Save Box Region Only"
label={t('unifiedcanvas:saveBoxRegionOnly')}
isChecked={shouldCropToBoundingBoxOnSave}
onChange={(e) =>
dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked))
}
/>
<IAICheckbox
label="Show Canvas Debug Info"
label={t('unifiedcanvas:showCanvasDebugInfo')}
isChecked={shouldShowCanvasDebugInfo}
onChange={(e) =>
dispatch(setShouldShowCanvasDebugInfo(e.target.checked))

View File

@@ -3,6 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAICheckbox from 'common/components/IAICheckbox';
import { setShouldShowGrid } from 'features/canvas/store/canvasSlice';
import React from 'react';
import { useTranslation } from 'react-i18next';
export default function UnifiedCanvasShowGrid() {
const shouldShowGrid = useAppSelector(
@@ -10,10 +11,11 @@ export default function UnifiedCanvasShowGrid() {
);
const dispatch = useAppDispatch();
const { t } = useTranslation();
return (
<IAICheckbox
label="Show Grid"
label={t('unifiedcanvas:showGrid')}
isChecked={shouldShowGrid}
onChange={(e) => dispatch(setShouldShowGrid(e.target.checked))}
/>

View File

@@ -3,6 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAICheckbox from 'common/components/IAICheckbox';
import { setShouldSnapToGrid } from 'features/canvas/store/canvasSlice';
import React, { ChangeEvent } from 'react';
import { useTranslation } from 'react-i18next';
export default function UnifiedCanvasSnapToGrid() {
const shouldSnapToGrid = useAppSelector(
@@ -10,13 +11,14 @@ export default function UnifiedCanvasSnapToGrid() {
);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleChangeShouldSnapToGrid = (e: ChangeEvent<HTMLInputElement>) =>
dispatch(setShouldSnapToGrid(e.target.checked));
return (
<IAICheckbox
label="Snap to Grid (N)"
label={`${t('unifiedcanvas:snapToGrid')} (N)`}
isChecked={shouldSnapToGrid}
onChange={handleChangeShouldSnapToGrid}
/>

View File

@@ -6,6 +6,7 @@ import { mergeAndUploadCanvas } from 'features/canvas/store/thunks/mergeAndUploa
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { FaCopy } from 'react-icons/fa';
export default function UnifiedCanvasCopyToClipboard() {
@@ -21,6 +22,7 @@ export default function UnifiedCanvasCopyToClipboard() {
);
const dispatch = useAppDispatch();
const { t } = useTranslation();
useHotkeys(
['meta+c', 'ctrl+c'],
@@ -46,8 +48,8 @@ export default function UnifiedCanvasCopyToClipboard() {
return (
<IAIIconButton
aria-label="Copy to Clipboard (Cmd/Ctrl+C)"
tooltip="Copy to Clipboard (Cmd/Ctrl+C)"
aria-label={`${t('unifiedcanvas:copyToClipboard')} (Cmd/Ctrl+C)`}
tooltip={`${t('unifiedcanvas:copyToClipboard')} (Cmd/Ctrl+C)`}
icon={<FaCopy />}
onClick={handleCopyImageToClipboard}
isDisabled={isStaging}

View File

@@ -6,10 +6,13 @@ import { mergeAndUploadCanvas } from 'features/canvas/store/thunks/mergeAndUploa
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { FaDownload } from 'react-icons/fa';
export default function UnifiedCanvasDownloadImage() {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const canvasBaseLayer = getCanvasBaseLayer();
const isStaging = useAppSelector(isStagingSelector);
@@ -45,8 +48,8 @@ export default function UnifiedCanvasDownloadImage() {
};
return (
<IAIIconButton
aria-label="Download as Image (Shift+D)"
tooltip="Download as Image (Shift+D)"
aria-label={`${t('unifiedcanvas:downloadAsImage')} (Shift+D)`}
tooltip={`${t('unifiedcanvas:downloadAsImage')} (Shift+D)`}
icon={<FaDownload />}
onClick={handleDownloadAsImage}
isDisabled={isStaging}

View File

@@ -3,16 +3,18 @@ import IAIIconButton from 'common/components/IAIIconButton';
import useImageUploader from 'common/hooks/useImageUploader';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { FaUpload } from 'react-icons/fa';
export default function UnifiedCanvasFileUploader() {
const isStaging = useAppSelector(isStagingSelector);
const { openUploader } = useImageUploader();
const { t } = useTranslation();
return (
<IAIIconButton
aria-label="Upload"
tooltip="Upload"
aria-label={t('common:upload')}
tooltip={t('common:upload')}
icon={<FaUpload />}
onClick={openUploader}
isDisabled={isStaging}

View File

@@ -13,6 +13,7 @@ import {
LAYER_NAMES_DICT,
} from 'features/canvas/store/canvasTypes';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
const selector = createSelector(
[canvasSelector, isStagingSelector],
@@ -29,6 +30,7 @@ const selector = createSelector(
export default function UnifiedCanvasLayerSelect() {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const { layer, isMaskEnabled, isStaging } = useAppSelector(selector);
@@ -57,7 +59,7 @@ export default function UnifiedCanvasLayerSelect() {
};
return (
<IAISelect
tooltip={'Layer (Q)'}
tooltip={`${t('unifiedcanvas:layer')} (Q)`}
tooltipProps={{ hasArrow: true, placement: 'top' }}
value={layer}
validValues={LAYER_NAMES_DICT}

View File

@@ -6,10 +6,12 @@ import { mergeAndUploadCanvas } from 'features/canvas/store/thunks/mergeAndUploa
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { FaLayerGroup } from 'react-icons/fa';
export default function UnifiedCanvasMergeVisible() {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const canvasBaseLayer = getCanvasBaseLayer();
const isStaging = useAppSelector(isStagingSelector);
const isProcessing = useAppSelector(
@@ -38,8 +40,8 @@ export default function UnifiedCanvasMergeVisible() {
};
return (
<IAIIconButton
aria-label="Merge Visible (Shift+M)"
tooltip="Merge Visible (Shift+M)"
aria-label={`${t('unifiedcanvas:mergeVisible')} (Shift+M)`}
tooltip={`${t('unifiedcanvas:mergeVisible')} (Shift+M)`}
icon={<FaLayerGroup />}
onClick={handleMergeVisible}
isDisabled={isStaging}

View File

@@ -5,12 +5,14 @@ import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { setTool } from 'features/canvas/store/canvasSlice';
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { FaArrowsAlt } from 'react-icons/fa';
export default function UnifiedCanvasMoveTool() {
const tool = useAppSelector((state: RootState) => state.canvas.tool);
const isStaging = useAppSelector(isStagingSelector);
const dispatch = useAppDispatch();
const { t } = useTranslation();
useHotkeys(
['v'],
@@ -28,8 +30,8 @@ export default function UnifiedCanvasMoveTool() {
return (
<IAIIconButton
aria-label="Move Tool (V)"
tooltip="Move Tool (V)"
aria-label={`${t('unifiedcanvas:move')} (V)`}
tooltip={`${t('unifiedcanvas:move')} (V)`}
icon={<FaArrowsAlt />}
data-selected={tool === 'move' || isStaging}
onClick={handleSelectMoveTool}

View File

@@ -7,6 +7,7 @@ import CancelButton from 'features/options/components/ProcessButtons/CancelButto
import InvokeButton from 'features/options/components/ProcessButtons/InvokeButton';
import { setShouldShowOptionsPanel } from 'features/options/store/optionsSlice';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { FaSlidersH } from 'react-icons/fa';
export default function UnifiedCanvasProcessingButtons() {
@@ -15,6 +16,7 @@ export default function UnifiedCanvasProcessingButtons() {
);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleShowOptionsPanel = () => {
dispatch(setShouldShowOptionsPanel(true));
@@ -26,9 +28,9 @@ export default function UnifiedCanvasProcessingButtons() {
return (
<Flex flexDirection={'column'} gap="0.5rem">
<IAIIconButton
tooltip="Show Options Panel (O)"
tooltip={`${t('options:showOptionsPanel')} (O)`}
tooltipProps={{ placement: 'top' }}
aria-label="Show Options Panel"
aria-label={t('options:showOptionsPanel')}
onClick={handleShowOptionsPanel}
>
<FaSlidersH />

View File

@@ -6,10 +6,12 @@ import {
resizeAndScaleCanvas,
} from 'features/canvas/store/canvasSlice';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { FaTrash } from 'react-icons/fa';
export default function UnifiedCanvasResetCanvas() {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const isStaging = useAppSelector(isStagingSelector);
const handleResetCanvas = () => {
@@ -18,8 +20,8 @@ export default function UnifiedCanvasResetCanvas() {
};
return (
<IAIIconButton
aria-label="Clear Canvas"
tooltip="Clear Canvas"
aria-label={t('unifiedcanvas:clearCanvas')}
tooltip={t('unifiedcanvas:clearCanvas')}
icon={<FaTrash />}
onClick={handleResetCanvas}
style={{ backgroundColor: 'var(--btn-delete-image)' }}

View File

@@ -4,11 +4,13 @@ import { resetCanvasView } from 'features/canvas/store/canvasSlice';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { FaCrosshairs } from 'react-icons/fa';
export default function UnifiedCanvasResetView() {
const canvasBaseLayer = getCanvasBaseLayer();
const dispatch = useAppDispatch();
const { t } = useTranslation();
useHotkeys(
['r'],
@@ -36,8 +38,8 @@ export default function UnifiedCanvasResetView() {
};
return (
<IAIIconButton
aria-label="Reset View (R)"
tooltip="Reset View (R)"
aria-label={`${t('unifiedcanvas:resetView')} (R)`}
tooltip={`${t('unifiedcanvas:resetView')} (R)`}
icon={<FaCrosshairs />}
onClick={handleResetCanvasView}
/>

View File

@@ -6,6 +6,7 @@ import { mergeAndUploadCanvas } from 'features/canvas/store/thunks/mergeAndUploa
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { FaSave } from 'react-icons/fa';
export default function UnifiedCanvasSaveToGallery() {
@@ -19,6 +20,7 @@ export default function UnifiedCanvasSaveToGallery() {
);
const dispatch = useAppDispatch();
const { t } = useTranslation();
useHotkeys(
['shift+s'],
@@ -43,8 +45,8 @@ export default function UnifiedCanvasSaveToGallery() {
};
return (
<IAIIconButton
aria-label="Save to Gallery (Shift+S)"
tooltip="Save to Gallery (Shift+S)"
aria-label={`${t('unifiedcanvas:saveToGallery')} (Shift+S)`}
tooltip={`${t('unifiedcanvas:saveToGallery')} (Shift+S)`}
icon={<FaSave />}
onClick={handleSaveToGallery}
isDisabled={isStaging}

View File

@@ -21,6 +21,7 @@ import {
} from 'features/canvas/store/canvasSelectors';
import { systemSelector } from 'features/system/store/systemSelectors';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
export const selector = createSelector(
[canvasSelector, isStagingSelector, systemSelector],
@@ -43,6 +44,7 @@ export const selector = createSelector(
const UnifiedCanvasToolSelect = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const { tool, isStaging } = useAppSelector(selector);
useHotkeys(
@@ -113,16 +115,16 @@ const UnifiedCanvasToolSelect = () => {
<Flex flexDirection={'column'} gap={'0.5rem'}>
<ButtonGroup>
<IAIIconButton
aria-label="Brush Tool (B)"
tooltip="Brush Tool (B)"
aria-label={`${t('unifiedcanvas:brush')} (B)`}
tooltip={`${t('unifiedcanvas:brush')} (B)`}
icon={<FaPaintBrush />}
data-selected={tool === 'brush' && !isStaging}
onClick={handleSelectBrushTool}
isDisabled={isStaging}
/>
<IAIIconButton
aria-label="Eraser Tool (E)"
tooltip="Eraser Tool (E)"
aria-label={`${t('unifiedcanvas:eraser')} (E)`}
tooltip={`${t('unifiedcanvas:eraser')} (B)`}
icon={<FaEraser />}
data-selected={tool === 'eraser' && !isStaging}
isDisabled={isStaging}
@@ -131,23 +133,23 @@ const UnifiedCanvasToolSelect = () => {
</ButtonGroup>
<ButtonGroup>
<IAIIconButton
aria-label="Fill Bounding Box (Shift+F)"
tooltip="Fill Bounding Box (Shift+F)"
aria-label={`${t('unifiedcanvas:fillBoundingBox')} (Shift+F)`}
tooltip={`${t('unifiedcanvas:fillBoundingBox')} (Shift+F)`}
icon={<FaFillDrip />}
isDisabled={isStaging}
onClick={handleFillRect}
/>
<IAIIconButton
aria-label="Erase Bounding Box Area (Delete/Backspace)"
tooltip="Erase Bounding Box Area (Delete/Backspace)"
aria-label={`${t('unifiedcanvas:eraseBoundingBox')} (Del/Backspace)`}
tooltip={`${t('unifiedcanvas:eraseBoundingBox')} (Del/Backspace)`}
icon={<FaPlus style={{ transform: 'rotate(45deg)' }} />}
isDisabled={isStaging}
onClick={handleEraseBoundingBox}
/>
</ButtonGroup>
<IAIIconButton
aria-label="Color Picker (C)"
tooltip="Color Picker (C)"
aria-label={`${t('unifiedcanvas:colorPicker')} (C)`}
tooltip={`${t('unifiedcanvas:colorPicker')} (C)`}
icon={<FaEyeDropper />}
data-selected={tool === 'colorPicker' && !isStaging}
isDisabled={isStaging}

View File

@@ -12,31 +12,34 @@ import PromptInput from 'features/options/components/PromptInput/PromptInput';
import InvokeOptionsPanel from 'features/tabs/components/InvokeOptionsPanel';
import BoundingBoxSettings from 'features/options/components/AdvancedOptions/Canvas/BoundingBoxSettings/BoundingBoxSettings';
import InfillAndScalingOptions from 'features/options/components/AdvancedOptions/Canvas/InfillAndScalingOptions';
import { useTranslation } from 'react-i18next';
export default function UnifiedCanvasPanel() {
const { t } = useTranslation();
const unifiedCanvasAccordions = {
boundingBox: {
header: 'Bounding Box',
header: `${t('options:boundingBoxHeader')}`,
feature: Feature.BOUNDING_BOX,
content: <BoundingBoxSettings />,
},
seamCorrection: {
header: 'Seam Correction',
header: `${t('options:seamCorrectionHeader')}`,
feature: Feature.SEAM_CORRECTION,
content: <SeamCorrectionOptions />,
},
infillAndScaling: {
header: 'Infill and Scaling',
header: `${t('options:infillScalingHeader')}`,
feature: Feature.INFILL_AND_SCALING,
content: <InfillAndScalingOptions />,
},
seed: {
header: 'Seed',
header: `${t('options:seed')}`,
feature: Feature.SEED,
content: <SeedOptions />,
},
variations: {
header: 'Variations',
header: `${t('options:variations')}`,
feature: Feature.VARIATIONS,
content: <VariationsOptions />,
additionalHeaderComponents: <GenerateVariationsToggle />,
@@ -49,7 +52,7 @@ export default function UnifiedCanvasPanel() {
<ProcessButtons />
<MainOptions />
<ImageToImageStrength
label="Image To Image Strength"
label={t('options:img2imgStrength')}
styleClass="main-option-block image-to-image-strength-main-option"
/>
<OptionsAccordion accordionInfo={unifiedCanvasAccordions} />