mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-08 02:04:58 -05:00
[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:
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))}
|
||||
|
||||
@@ -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)' }}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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));
|
||||
};
|
||||
|
||||
@@ -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)' }}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
63
frontend/src/features/system/components/LanguagePicker.tsx
Normal file
63
frontend/src/features/system/components/LanguagePicker.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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 />,
|
||||
},
|
||||
|
||||
@@ -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
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))}
|
||||
/>
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))}
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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)' }}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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} />
|
||||
|
||||
Reference in New Issue
Block a user