[WebUI 2.2.5] Unified Canvas Alternate UI Beta (#1951)

* Fix Prompt Placeholder Text Color

* Display Model Desc as tooltip in SiteHeader

This'll allow the user to quickly access info like activation token for that model if they set it in the description.

* Unified Canvas UI Beta

* Initial Test Build

* Make Snap Grid Hotkey Accessible Always
This commit is contained in:
blessedcoolant 2022-12-13 13:36:05 +13:00 committed by GitHub
parent 1a1625406c
commit 4402ca10b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 2093 additions and 721 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

623
frontend/dist/assets/index.d8f54146.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -8,8 +8,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>InvokeAI - A Stable Diffusion Toolkit</title>
<link rel="shortcut icon" type="icon" href="./assets/favicon.0d253ced.ico" />
<script type="module" crossorigin src="./assets/index.d864890e.js"></script>
<link rel="stylesheet" href="./assets/index.81f1c71c.css">
<script type="module" crossorigin src="./assets/index.d8f54146.js"></script>
<link rel="stylesheet" href="./assets/index.8ee30fa0.css">
<script type="module">try{import.meta.url;import("_").catch(()=>1);}catch(e){}window.__vite_is_modern_browser=true;</script>
<script type="module">!function(){if(window.__vite_is_modern_browser)return;console.warn("vite: loading legacy build because dynamic import or import.meta.url is unsupported, syntax error above should be ignored");var e=document.getElementById("vite-legacy-polyfill"),n=document.createElement("script");n.src=e.src,n.onload=function(){System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))},document.body.appendChild(n)}();</script>
</head>
@ -19,7 +19,7 @@
<script nomodule>!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",(function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()}),!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();</script>
<script nomodule crossorigin id="vite-legacy-polyfill" src="./assets/polyfills-legacy-dde3a68a.js"></script>
<script nomodule crossorigin id="vite-legacy-entry" data-src="./assets/index-legacy-8e84772c.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
<script nomodule crossorigin id="vite-legacy-entry" data-src="./assets/index-legacy-f27062cd.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
</body>
</html>

View File

@ -40,6 +40,7 @@ export type IAIFullSliderProps = {
sliderMarkRightOffset?: number;
withInput?: boolean;
isInteger?: boolean;
width?: string | number;
inputWidth?: string | number;
inputReadOnly?: boolean;
withReset?: boolean;
@ -71,6 +72,7 @@ export default function IAISlider(props: IAIFullSliderProps) {
max = 100,
step = 1,
onChange,
width = '100%',
tooltipSuffix = '',
withSliderMarks = false,
sliderMarkLeftOffset = 0,
@ -161,6 +163,7 @@ export default function IAISlider(props: IAIFullSliderProps) {
onMouseLeave={() => setShowTooltip(false)}
focusThumbOnChange={false}
isDisabled={isSliderDisabled}
width={width}
{...rest}
>
{withSliderMarks && (

View File

@ -3,8 +3,11 @@ import _ from 'lodash';
import { useHotkeys } from 'react-hotkeys-hook';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import {
clearMask,
resetCanvasInteractionState,
setIsMaskEnabled,
setShouldShowBoundingBox,
setShouldSnapToGrid,
setTool,
} from 'features/canvas/store/canvasSlice';
import { useAppDispatch, useAppSelector } from 'app/store';
@ -24,6 +27,8 @@ const selector = createSelector(
shouldLockBoundingBox,
shouldShowBoundingBox,
tool,
isMaskEnabled,
shouldSnapToGrid,
} = canvas;
return {
@ -33,6 +38,8 @@ const selector = createSelector(
shouldShowBoundingBox,
tool,
isStaging,
isMaskEnabled,
shouldSnapToGrid,
};
},
{
@ -44,13 +51,62 @@ const selector = createSelector(
const useInpaintingCanvasHotkeys = () => {
const dispatch = useAppDispatch();
const { activeTabName, shouldShowBoundingBox, tool, isStaging } =
useAppSelector(selector);
const {
activeTabName,
shouldShowBoundingBox,
tool,
isStaging,
isMaskEnabled,
shouldSnapToGrid,
} = useAppSelector(selector);
const previousToolRef = useRef<CanvasTool | null>(null);
const canvasStage = getCanvasStage();
// Beta Keys
const handleClearMask = () => dispatch(clearMask());
useHotkeys(
['shift+c'],
() => {
handleClearMask();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[]
);
const handleToggleEnableMask = () =>
dispatch(setIsMaskEnabled(!isMaskEnabled));
useHotkeys(
['h'],
() => {
handleToggleEnableMask();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[isMaskEnabled]
);
useHotkeys(
['n'],
() => {
dispatch(setShouldSnapToGrid(!shouldSnapToGrid));
},
{
enabled: true,
preventDefault: true,
},
[shouldSnapToGrid]
);
//
useHotkeys(
'esc',
() => {

View File

@ -70,6 +70,9 @@ const PromptInput = () => {
resize="vertical"
height={30}
ref={promptRef}
_placeholder={{
color: 'var(--text-color-secondary)',
}}
/>
</FormControl>
</div>

View File

@ -55,6 +55,7 @@ export interface OptionsState {
upscalingStrength: number;
variationAmount: number;
width: number;
shouldUseCanvasBetaLayout: boolean;
}
const initialOptionsState: OptionsState = {
@ -101,6 +102,7 @@ const initialOptionsState: OptionsState = {
upscalingStrength: 0.75,
variationAmount: 0.1,
width: 512,
shouldUseCanvasBetaLayout: false,
};
const initialState: OptionsState = initialOptionsState;
@ -396,6 +398,9 @@ export const optionsSlice = createSlice({
setInfillMethod: (state, action: PayloadAction<string>) => {
state.infillMethod = action.payload;
},
setShouldUseCanvasBetaLayout: (state, action: PayloadAction<boolean>) => {
state.shouldUseCanvasBetaLayout = action.payload;
},
},
});
@ -451,6 +456,7 @@ export const {
setUpscalingStrength,
setVariationAmount,
setWidth,
setShouldUseCanvasBetaLayout,
} = optionsSlice.actions;
export default optionsSlice.reducer;

View File

@ -23,8 +23,9 @@ const selector = createSelector(
},
''
);
const activeDesc = model_list[activeModel].description;
return { models, activeModel, isProcessing };
return { models, activeModel, isProcessing, activeDesc };
},
{
memoizeOptions: {
@ -35,7 +36,8 @@ const selector = createSelector(
const ModelSelect = () => {
const dispatch = useAppDispatch();
const { models, activeModel, isProcessing } = useAppSelector(selector);
const { models, activeModel, isProcessing, activeDesc } =
useAppSelector(selector);
const handleChangeModel = (e: ChangeEvent<HTMLSelectElement>) => {
dispatch(requestModelChange(e.target.value));
};
@ -48,6 +50,7 @@ const ModelSelect = () => {
>
<IAISelect
style={{ fontSize: '0.8rem' }}
tooltip={activeDesc}
isDisabled={isProcessing}
value={activeModel}
validValues={models}

View File

@ -32,10 +32,11 @@ import IAISelect from 'common/components/IAISelect';
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';
const selector = createSelector(
[systemSelector, optionsSelector],
(system) => {
(system, options) => {
const {
shouldDisplayInProgressType,
shouldConfirmOnDelete,
@ -45,6 +46,8 @@ const selector = createSelector(
enableImageDebugging,
} = system;
const { shouldUseCanvasBetaLayout } = options;
return {
shouldDisplayInProgressType,
shouldConfirmOnDelete,
@ -52,6 +55,7 @@ const selector = createSelector(
models: _.map(model_list, (_model, key) => key),
saveIntermediatesInterval,
enableImageDebugging,
shouldUseCanvasBetaLayout,
};
},
{
@ -93,6 +97,7 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
shouldDisplayGuides,
saveIntermediatesInterval,
enableImageDebugging,
shouldUseCanvasBetaLayout,
} = useAppSelector(selector);
/**
@ -173,6 +178,14 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
dispatch(setShouldDisplayGuides(e.target.checked))
}
/>
<IAISwitch
styleClass="settings-modal-item"
label={'Use Canvas Beta Layout'}
isChecked={shouldUseCanvasBetaLayout}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
dispatch(setShouldUseCanvasBetaLayout(e.target.checked))
}
/>
</div>
<div className="settings-modal-items">

View File

@ -28,25 +28,34 @@ export const floatingSelector = createSelector(
shouldPinOptionsPanel,
shouldShowOptionsPanel,
shouldHoldOptionsPanelOpen,
shouldUseCanvasBetaLayout,
} = options;
const { shouldShowGallery, shouldPinGallery, shouldHoldGalleryOpen } =
gallery;
const canvasBetaLayoutCheck =
shouldUseCanvasBetaLayout && activeTabName === 'unifiedCanvas';
const shouldShowOptionsPanelButton =
!canvasBetaLayoutCheck &&
!(
shouldShowOptionsPanel ||
(shouldHoldOptionsPanelOpen && !shouldPinOptionsPanel)
) && ['txt2img', 'img2img', 'unifiedCanvas'].includes(activeTabName);
) &&
['txt2img', 'img2img', 'unifiedCanvas'].includes(activeTabName);
const shouldShowGalleryButton =
!(shouldShowGallery || (shouldHoldGalleryOpen && !shouldPinGallery)) &&
['txt2img', 'img2img', 'unifiedCanvas'].includes(activeTabName);
const shouldShowProcessButtons =
!canvasBetaLayoutCheck &&
(!shouldPinOptionsPanel || !shouldShowOptionsPanel);
return {
shouldPinOptionsPanel,
shouldShowProcessButtons:
!shouldPinOptionsPanel || !shouldShowOptionsPanel,
shouldShowProcessButtons,
shouldShowOptionsPanelButton,
shouldShowOptionsPanel,
shouldShowGallery,

View File

@ -39,47 +39,46 @@
column-gap: 1rem;
}
}
}
.inpainting-canvas-area {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
row-gap: 1rem;
width: 100%;
height: 100%;
}
.inpainting-canvas-area {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
row-gap: 1rem;
width: 100%;
height: 100%;
}
.inpainting-canvas-spiner {
display: flex;
align-items: center;
width: 100%;
height: 100%;
}
.inpainting-canvas-spiner {
display: flex;
align-items: center;
width: 100%;
height: 100%;
}
.inpainting-canvas-container {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
width: 100%;
.inpainting-canvas-container {
display: flex;
position: relative;
height: 100%;
width: 100%;
border-radius: 0.5rem;
}
.inpainting-canvas-wrapper {
position: relative;
}
.inpainting-canvas-stage {
outline: none;
border-radius: 0.5rem;
box-shadow: 0px 0px 0px 1px var(--border-color-light);
overflow: hidden;
canvas {
outline: none;
border-radius: 0.5rem;
.inpainting-canvas-wrapper {
position: relative;
}
.inpainting-canvas-stage {
outline: none;
border-radius: 0.5rem;
box-shadow: 0px 0px 0px 1px var(--border-color-light);
overflow: hidden;
canvas {
outline: none;
border-radius: 0.5rem;
}
}
}
}

View File

@ -0,0 +1,71 @@
import { createSelector } from '@reduxjs/toolkit';
// import IAICanvas from 'features/canvas/components/IAICanvas';
import IAICanvasResizer from 'features/canvas/components/IAICanvasResizer';
import _ from 'lodash';
import { useLayoutEffect } from 'react';
import { useAppDispatch, useAppSelector } from 'app/store';
import { setDoesCanvasNeedScaling } from 'features/canvas/store/canvasSlice';
import IAICanvas from 'features/canvas/components/IAICanvas';
import IAICanvasOutpaintingControls from 'features/canvas/components/IAICanvasToolbar/IAICanvasToolbar';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { Flex } from '@chakra-ui/react';
import UnifiedCanvasToolbarBeta from './UnifiedCanvasToolbarBeta';
import UnifiedCanvasToolSettingsBeta from './UnifiedCanvasToolSettingsBeta';
const selector = createSelector(
[canvasSelector],
(canvas) => {
const { doesCanvasNeedScaling } = canvas;
return {
doesCanvasNeedScaling,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
const UnifiedCanvasDisplayBeta = () => {
const dispatch = useAppDispatch();
const { doesCanvasNeedScaling } = useAppSelector(selector);
useLayoutEffect(() => {
dispatch(setDoesCanvasNeedScaling(true));
const resizeCallback = _.debounce(() => {
dispatch(setDoesCanvasNeedScaling(true));
}, 250);
window.addEventListener('resize', resizeCallback);
return () => window.removeEventListener('resize', resizeCallback);
}, [dispatch]);
return (
<div className={'workarea-single-view'}>
<Flex
flexDirection={'row'}
width="100%"
height="100%"
columnGap={'1rem'}
padding="1rem"
>
<UnifiedCanvasToolbarBeta />
<Flex
width="100%"
height="100%"
flexDirection={'column'}
rowGap={'1rem'}
>
<UnifiedCanvasToolSettingsBeta />
{doesCanvasNeedScaling ? <IAICanvasResizer /> : <IAICanvas />}
</Flex>
</Flex>
</div>
);
};
export default UnifiedCanvasDisplayBeta;

View File

@ -0,0 +1,13 @@
import { Flex } from '@chakra-ui/react';
import React from 'react';
import UnifiedCanvasBrushSettings from './UnifiedCanvasBrushSettings';
import UnifiedCanvasLimitStrokesToBox from './UnifiedCanvasLimitStrokesToBox';
export default function UnifiedCanvasBaseBrushSettings() {
return (
<Flex gap={'1rem'} alignItems="center">
<UnifiedCanvasBrushSettings />
<UnifiedCanvasLimitStrokesToBox />
</Flex>
);
}

View File

@ -0,0 +1,13 @@
import { Flex } from '@chakra-ui/react';
import React from 'react';
import UnifiedCanvasBrushSize from './UnifiedCanvasBrushSize';
import UnifiedCanvasColorPicker from './UnifiedCanvasColorPicker';
export default function UnifiedCanvasBrushSettings() {
return (
<Flex columnGap={'1rem'} alignItems="center">
<UnifiedCanvasBrushSize />
<UnifiedCanvasColorPicker />
</Flex>
);
}

View File

@ -0,0 +1,52 @@
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAISlider from 'common/components/IAISlider';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { setBrushSize } from 'features/canvas/store/canvasSlice';
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
export default function UnifiedCanvasBrushSize() {
const dispatch = useAppDispatch();
const brushSize = useAppSelector(
(state: RootState) => state.canvas.brushSize
);
const isStaging = useAppSelector(isStagingSelector);
useHotkeys(
['BracketLeft'],
() => {
dispatch(setBrushSize(Math.max(brushSize - 5, 5)));
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[brushSize]
);
useHotkeys(
['BracketRight'],
() => {
dispatch(setBrushSize(Math.min(brushSize + 5, 500)));
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[brushSize]
);
return (
<IAISlider
label="Size"
value={brushSize}
withInput
onChange={(newSize) => dispatch(setBrushSize(newSize))}
sliderNumberInputProps={{ max: 500 }}
inputReadOnly={false}
width={'100px'}
/>
);
}

View File

@ -0,0 +1,24 @@
import { useAppDispatch } from 'app/store';
import IAIButton from 'common/components/IAIButton';
import { clearMask } from 'features/canvas/store/canvasSlice';
import React from 'react';
import { FaTrash } from 'react-icons/fa';
export default function UnifiedCanvasClearMask() {
const dispatch = useAppDispatch();
const handleClearMask = () => dispatch(clearMask());
return (
<IAIButton
size={'sm'}
leftIcon={<FaTrash />}
onClick={handleClearMask}
tooltip="Clear Mask (Shift+C)"
>
Clear
</IAIButton>
);
}

View File

@ -0,0 +1,121 @@
import { Box, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store';
import IAIColorPicker from 'common/components/IAIColorPicker';
import IAIPopover from 'common/components/IAIPopover';
import {
canvasSelector,
isStagingSelector,
} from 'features/canvas/store/canvasSelectors';
import { setBrushColor, setMaskColor } from 'features/canvas/store/canvasSlice';
import React from 'react';
import _ from 'lodash';
import { useHotkeys } from 'react-hotkeys-hook';
const selector = createSelector(
[canvasSelector, isStagingSelector],
(canvas, isStaging) => {
const { brushColor, maskColor, layer } = canvas;
return {
brushColor,
maskColor,
layer,
isStaging,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
export default function UnifiedCanvasColorPicker() {
const dispatch = useAppDispatch();
const { brushColor, maskColor, layer, isStaging } = useAppSelector(selector);
const currentColorDisplay = () => {
if (layer === 'base')
return `rgba(${brushColor.r},${brushColor.g},${brushColor.b},${brushColor.a})`;
if (layer === 'mask')
return `rgba(${maskColor.r},${maskColor.g},${maskColor.b},${maskColor.a})`;
};
useHotkeys(
['shift+BracketLeft'],
() => {
dispatch(
setBrushColor({
...brushColor,
a: _.clamp(brushColor.a - 0.05, 0.05, 1),
})
);
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[brushColor]
);
useHotkeys(
['shift+BracketRight'],
() => {
dispatch(
setBrushColor({
...brushColor,
a: _.clamp(brushColor.a + 0.05, 0.05, 1),
})
);
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[brushColor]
);
return (
<IAIPopover
trigger="hover"
triggerComponent={
<Box
style={{
width: '30px',
height: '30px',
minWidth: '30px',
minHeight: '30px',
borderRadius: '99999999px',
backgroundColor: currentColorDisplay(),
cursor: 'pointer',
}}
/>
}
>
<Flex minWidth={'15rem'} direction={'column'} gap={'1rem'} width={'100%'}>
{layer === 'base' && (
<IAIColorPicker
style={{
width: '100%',
paddingTop: '0.5rem',
paddingBottom: '0.5rem',
}}
color={brushColor}
onChange={(newColor) => dispatch(setBrushColor(newColor))}
/>
)}
{layer === 'mask' && (
<IAIColorPicker
style={{
width: '100%',
paddingTop: '0.5rem',
paddingBottom: '0.5rem',
}}
color={maskColor}
onChange={(newColor) => dispatch(setMaskColor(newColor))}
/>
)}
</Flex>
</IAIPopover>
);
}

View File

@ -0,0 +1,22 @@
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAICheckbox from 'common/components/IAICheckbox';
import { setShouldDarkenOutsideBoundingBox } from 'features/canvas/store/canvasSlice';
import React from 'react';
export default function UnifiedCanvasDarkenOutsideSelection() {
const shouldDarkenOutsideBoundingBox = useAppSelector(
(state: RootState) => state.canvas.shouldDarkenOutsideBoundingBox
);
const dispatch = useAppDispatch();
return (
<IAICheckbox
label="Darken Outside"
isChecked={shouldDarkenOutsideBoundingBox}
onChange={(e) =>
dispatch(setShouldDarkenOutsideBoundingBox(e.target.checked))
}
/>
);
}

View File

@ -0,0 +1,23 @@
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAICheckbox from 'common/components/IAICheckbox';
import { setIsMaskEnabled } from 'features/canvas/store/canvasSlice';
import React from 'react';
export default function UnifiedCanvasEnableMask() {
const isMaskEnabled = useAppSelector(
(state: RootState) => state.canvas.isMaskEnabled
);
const dispatch = useAppDispatch();
const handleToggleEnableMask = () =>
dispatch(setIsMaskEnabled(!isMaskEnabled));
return (
<IAICheckbox
label="Enable Mask (H)"
isChecked={isMaskEnabled}
onChange={handleToggleEnableMask}
/>
);
}

View File

@ -0,0 +1,22 @@
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAICheckbox from 'common/components/IAICheckbox';
import { setShouldRestrictStrokesToBox } from 'features/canvas/store/canvasSlice';
import React from 'react';
export default function UnifiedCanvasLimitStrokesToBox() {
const dispatch = useAppDispatch();
const shouldRestrictStrokesToBox = useAppSelector(
(state: RootState) => state.canvas.shouldRestrictStrokesToBox
);
return (
<IAICheckbox
label="Limit To Box"
isChecked={shouldRestrictStrokesToBox}
onChange={(e) =>
dispatch(setShouldRestrictStrokesToBox(e.target.checked))
}
/>
);
}

View File

@ -0,0 +1,17 @@
import { Flex } from '@chakra-ui/react';
import React from 'react';
import UnifiedCanvasBrushSettings from './UnifiedCanvasBrushSettings';
import UnifiedCanvasClearMask from './UnifiedCanvasClearMask';
import UnifiedCanvasEnableMask from './UnifiedCanvasEnableMask';
import UnifiedCanvasPreserveMask from './UnifiedCanvasPreserveMask';
export default function UnifiedCanvasMaskBrushSettings() {
return (
<Flex gap={'1rem'} alignItems="center">
<UnifiedCanvasBrushSettings />
<UnifiedCanvasEnableMask />
<UnifiedCanvasPreserveMask />
<UnifiedCanvasClearMask />
</Flex>
);
}

View File

@ -0,0 +1,15 @@
import { Flex } from '@chakra-ui/layout';
import React from 'react';
import UnifiedCanvasDarkenOutsideSelection from './UnifiedCanvasDarkenOutsideSelection';
import UnifiedCanvasShowGrid from './UnifiedCanvasShowGrid';
import UnifiedCanvasSnapToGrid from './UnifiedCanvasSnapToGrid';
export default function UnifiedCanvasMoveSettings() {
return (
<Flex alignItems={'center'} gap="1rem">
<UnifiedCanvasShowGrid />
<UnifiedCanvasSnapToGrid />
<UnifiedCanvasDarkenOutsideSelection />
</Flex>
);
}

View File

@ -0,0 +1,20 @@
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAICheckbox from 'common/components/IAICheckbox';
import { setShouldPreserveMaskedArea } from 'features/canvas/store/canvasSlice';
import React from 'react';
export default function UnifiedCanvasPreserveMask() {
const dispatch = useAppDispatch();
const shouldPreserveMaskedArea = useAppSelector(
(state: RootState) => state.canvas.shouldPreserveMaskedArea
);
return (
<IAICheckbox
label="Preserve Masked"
isChecked={shouldPreserveMaskedArea}
onChange={(e) => dispatch(setShouldPreserveMaskedArea(e.target.checked))}
/>
);
}

View File

@ -0,0 +1,101 @@
import { Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import {
setShouldAutoSave,
setShouldCropToBoundingBoxOnSave,
setShouldShowCanvasDebugInfo,
setShouldShowIntermediates,
} from 'features/canvas/store/canvasSlice';
import { useAppDispatch, useAppSelector } from 'app/store';
import _ from 'lodash';
import IAIIconButton from 'common/components/IAIIconButton';
import { FaWrench } from 'react-icons/fa';
import IAIPopover from 'common/components/IAIPopover';
import IAICheckbox from 'common/components/IAICheckbox';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import EmptyTempFolderButtonModal from 'features/system/components/ClearTempFolderButtonModal';
import ClearCanvasHistoryButtonModal from 'features/canvas/components/ClearCanvasHistoryButtonModal';
export const canvasControlsSelector = createSelector(
[canvasSelector],
(canvas) => {
const {
shouldAutoSave,
shouldCropToBoundingBoxOnSave,
shouldShowCanvasDebugInfo,
shouldShowIntermediates,
} = canvas;
return {
shouldAutoSave,
shouldCropToBoundingBoxOnSave,
shouldShowCanvasDebugInfo,
shouldShowIntermediates,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
const UnifiedCanvasSettings = () => {
const dispatch = useAppDispatch();
const {
shouldAutoSave,
shouldCropToBoundingBoxOnSave,
shouldShowCanvasDebugInfo,
shouldShowIntermediates,
} = useAppSelector(canvasControlsSelector);
return (
<IAIPopover
trigger="hover"
triggerComponent={
<IAIIconButton
tooltip="Canvas Settings"
tooltipProps={{
placement: 'bottom',
}}
aria-label="Canvas Settings"
icon={<FaWrench />}
/>
}
>
<Flex direction={'column'} gap={'0.5rem'}>
<IAICheckbox
label="Show Intermediates"
isChecked={shouldShowIntermediates}
onChange={(e) =>
dispatch(setShouldShowIntermediates(e.target.checked))
}
/>
<IAICheckbox
label="Auto Save to Gallery"
isChecked={shouldAutoSave}
onChange={(e) => dispatch(setShouldAutoSave(e.target.checked))}
/>
<IAICheckbox
label="Save Box Region Only"
isChecked={shouldCropToBoundingBoxOnSave}
onChange={(e) =>
dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked))
}
/>
<IAICheckbox
label="Show Canvas Debug Info"
isChecked={shouldShowCanvasDebugInfo}
onChange={(e) =>
dispatch(setShouldShowCanvasDebugInfo(e.target.checked))
}
/>
<ClearCanvasHistoryButtonModal />
<EmptyTempFolderButtonModal />
</Flex>
</IAIPopover>
);
};
export default UnifiedCanvasSettings;

View File

@ -0,0 +1,20 @@
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAICheckbox from 'common/components/IAICheckbox';
import { setShouldShowGrid } from 'features/canvas/store/canvasSlice';
import React from 'react';
export default function UnifiedCanvasShowGrid() {
const shouldShowGrid = useAppSelector(
(state: RootState) => state.canvas.shouldShowGrid
);
const dispatch = useAppDispatch();
return (
<IAICheckbox
label="Show Grid"
isChecked={shouldShowGrid}
onChange={(e) => dispatch(setShouldShowGrid(e.target.checked))}
/>
);
}

View File

@ -0,0 +1,23 @@
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAICheckbox from 'common/components/IAICheckbox';
import { setShouldSnapToGrid } from 'features/canvas/store/canvasSlice';
import React, { ChangeEvent } from 'react';
export default function UnifiedCanvasSnapToGrid() {
const shouldSnapToGrid = useAppSelector(
(state: RootState) => state.canvas.shouldSnapToGrid
);
const dispatch = useAppDispatch();
const handleChangeShouldSnapToGrid = (e: ChangeEvent<HTMLInputElement>) =>
dispatch(setShouldSnapToGrid(e.target.checked));
return (
<IAICheckbox
label="Snap to Grid (N)"
isChecked={shouldSnapToGrid}
onChange={handleChangeShouldSnapToGrid}
/>
);
}

View File

@ -0,0 +1,42 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import React from 'react';
import _ from 'lodash';
import UnifiedCanvasBaseBrushSettings from './UnifiedCanvasToolSettings/UnifiedCanvasBaseBrushSettings';
import UnifiedCanvasMaskBrushSettings from './UnifiedCanvasToolSettings/UnifiedCanvasMaskBrushSettings';
import { Flex } from '@chakra-ui/react';
import UnifiedCanvasMoveSettings from './UnifiedCanvasToolSettings/UnifiedCanvasMoveSettings';
const selector = createSelector(
[canvasSelector],
(canvas) => {
const { tool, layer } = canvas;
return {
tool,
layer,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
export default function UnifiedCanvasToolSettingsBeta() {
const { tool, layer } = useAppSelector(selector);
return (
<Flex height="2rem" minHeight="2rem" maxHeight="2rem" alignItems={'center'}>
{layer == 'base' && ['brush', 'eraser', 'colorPicker'].includes(tool) && (
<UnifiedCanvasBaseBrushSettings />
)}
{layer == 'mask' && ['brush', 'eraser', 'colorPicker'].includes(tool) && (
<UnifiedCanvasMaskBrushSettings />
)}
{tool == 'move' && <UnifiedCanvasMoveSettings />}
</Flex>
);
}

View File

@ -0,0 +1,55 @@
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { mergeAndUploadCanvas } from 'features/canvas/store/thunks/mergeAndUploadCanvas';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { FaCopy } from 'react-icons/fa';
export default function UnifiedCanvasCopyToClipboard() {
const isStaging = useAppSelector(isStagingSelector);
const canvasBaseLayer = getCanvasBaseLayer();
const isProcessing = useAppSelector(
(state: RootState) => state.system.isProcessing
);
const shouldCropToBoundingBoxOnSave = useAppSelector(
(state: RootState) => state.canvas.shouldCropToBoundingBoxOnSave
);
const dispatch = useAppDispatch();
useHotkeys(
['meta+c', 'ctrl+c'],
() => {
handleCopyImageToClipboard();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[canvasBaseLayer, isProcessing]
);
const handleCopyImageToClipboard = () => {
dispatch(
mergeAndUploadCanvas({
cropVisible: shouldCropToBoundingBoxOnSave ? false : true,
cropToBoundingBox: shouldCropToBoundingBoxOnSave,
shouldCopy: true,
})
);
};
return (
<IAIIconButton
aria-label="Copy to Clipboard (Cmd/Ctrl+C)"
tooltip="Copy to Clipboard (Cmd/Ctrl+C)"
icon={<FaCopy />}
onClick={handleCopyImageToClipboard}
isDisabled={isStaging}
/>
);
}

View File

@ -0,0 +1,54 @@
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { mergeAndUploadCanvas } from 'features/canvas/store/thunks/mergeAndUploadCanvas';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { FaDownload } from 'react-icons/fa';
export default function UnifiedCanvasDownloadImage() {
const dispatch = useAppDispatch();
const canvasBaseLayer = getCanvasBaseLayer();
const isStaging = useAppSelector(isStagingSelector);
const isProcessing = useAppSelector(
(state: RootState) => state.system.isProcessing
);
const shouldCropToBoundingBoxOnSave = useAppSelector(
(state: RootState) => state.canvas.shouldCropToBoundingBoxOnSave
);
useHotkeys(
['shift+d'],
() => {
handleDownloadAsImage();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[canvasBaseLayer, isProcessing]
);
const handleDownloadAsImage = () => {
dispatch(
mergeAndUploadCanvas({
cropVisible: shouldCropToBoundingBoxOnSave ? false : true,
cropToBoundingBox: shouldCropToBoundingBoxOnSave,
shouldDownload: true,
})
);
};
return (
<IAIIconButton
aria-label="Download as Image (Shift+D)"
tooltip="Download as Image (Shift+D)"
icon={<FaDownload />}
onClick={handleDownloadAsImage}
isDisabled={isStaging}
/>
);
}

View File

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

View File

@ -0,0 +1,68 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store';
import IAISelect from 'common/components/IAISelect';
import {
canvasSelector,
isStagingSelector,
} from 'features/canvas/store/canvasSelectors';
import { setIsMaskEnabled, setLayer } from 'features/canvas/store/canvasSlice';
import React, { ChangeEvent } from 'react';
import _ from 'lodash';
import {
CanvasLayer,
LAYER_NAMES_DICT,
} from 'features/canvas/store/canvasTypes';
import { useHotkeys } from 'react-hotkeys-hook';
const selector = createSelector(
[canvasSelector, isStagingSelector],
(canvas, isStaging) => {
const { layer, isMaskEnabled } = canvas;
return { layer, isMaskEnabled, isStaging };
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
export default function UnifiedCanvasLayerSelect() {
const dispatch = useAppDispatch();
const { layer, isMaskEnabled, isStaging } = useAppSelector(selector);
const handleToggleMaskLayer = () => {
dispatch(setLayer(layer === 'mask' ? 'base' : 'mask'));
};
useHotkeys(
['q'],
() => {
handleToggleMaskLayer();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[layer]
);
const handleChangeLayer = (e: ChangeEvent<HTMLSelectElement>) => {
const newLayer = e.target.value as CanvasLayer;
dispatch(setLayer(newLayer));
if (newLayer === 'mask' && !isMaskEnabled) {
dispatch(setIsMaskEnabled(true));
}
};
return (
<IAISelect
tooltip={'Layer (Q)'}
tooltipProps={{ hasArrow: true, placement: 'top' }}
value={layer}
validValues={LAYER_NAMES_DICT}
onChange={handleChangeLayer}
isDisabled={isStaging}
/>
);
}

View File

@ -0,0 +1,47 @@
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { mergeAndUploadCanvas } from 'features/canvas/store/thunks/mergeAndUploadCanvas';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { FaLayerGroup } from 'react-icons/fa';
export default function UnifiedCanvasMergeVisible() {
const dispatch = useAppDispatch();
const canvasBaseLayer = getCanvasBaseLayer();
const isStaging = useAppSelector(isStagingSelector);
const isProcessing = useAppSelector(
(state: RootState) => state.system.isProcessing
);
useHotkeys(
['shift+m'],
() => {
handleMergeVisible();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[canvasBaseLayer, isProcessing]
);
const handleMergeVisible = () => {
dispatch(
mergeAndUploadCanvas({
cropVisible: false,
shouldSetAsInitialImage: true,
})
);
};
return (
<IAIIconButton
aria-label="Merge Visible (Shift+M)"
tooltip="Merge Visible (Shift+M)"
icon={<FaLayerGroup />}
onClick={handleMergeVisible}
isDisabled={isStaging}
/>
);
}

View File

@ -0,0 +1,37 @@
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
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 { FaArrowsAlt } from 'react-icons/fa';
export default function UnifiedCanvasMoveTool() {
const tool = useAppSelector((state: RootState) => state.canvas.tool);
const isStaging = useAppSelector(isStagingSelector);
const dispatch = useAppDispatch();
useHotkeys(
['v'],
() => {
handleSelectMoveTool();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[]
);
const handleSelectMoveTool = () => dispatch(setTool('move'));
return (
<IAIIconButton
aria-label="Move Tool (V)"
tooltip="Move Tool (V)"
icon={<FaArrowsAlt />}
data-selected={tool === 'move' || isStaging}
onClick={handleSelectMoveTool}
/>
);
}

View File

@ -0,0 +1,43 @@
import { Flex } from '@chakra-ui/layout';
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import { setDoesCanvasNeedScaling } from 'features/canvas/store/canvasSlice';
import CancelButton from 'features/options/components/ProcessButtons/CancelButton';
import InvokeButton from 'features/options/components/ProcessButtons/InvokeButton';
import { setShouldShowOptionsPanel } from 'features/options/store/optionsSlice';
import React from 'react';
import { FaSlidersH } from 'react-icons/fa';
export default function UnifiedCanvasProcessingButtons() {
const shouldPinOptionsPanel = useAppSelector(
(state: RootState) => state.options.shouldPinOptionsPanel
);
const dispatch = useAppDispatch();
const handleShowOptionsPanel = () => {
dispatch(setShouldShowOptionsPanel(true));
if (shouldPinOptionsPanel) {
setTimeout(() => dispatch(setDoesCanvasNeedScaling(true)), 400);
}
};
return (
<Flex flexDirection={'column'} gap="0.5rem">
<IAIIconButton
tooltip="Show Options Panel (O)"
tooltipProps={{ placement: 'top' }}
aria-label="Show Options Panel"
onClick={handleShowOptionsPanel}
>
<FaSlidersH />
</IAIIconButton>
<Flex>
<InvokeButton iconButton />
</Flex>
<Flex>
<CancelButton width={'100%'} height={'40px'} />
</Flex>
</Flex>
);
}

View File

@ -0,0 +1,29 @@
import { useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import {
resetCanvas,
resizeAndScaleCanvas,
} from 'features/canvas/store/canvasSlice';
import React from 'react';
import { FaTrash } from 'react-icons/fa';
export default function UnifiedCanvasResetCanvas() {
const dispatch = useAppDispatch();
const isStaging = useAppSelector(isStagingSelector);
const handleResetCanvas = () => {
dispatch(resetCanvas());
dispatch(resizeAndScaleCanvas());
};
return (
<IAIIconButton
aria-label="Clear Canvas"
tooltip="Clear Canvas"
icon={<FaTrash />}
onClick={handleResetCanvas}
style={{ backgroundColor: 'var(--btn-delete-image)' }}
isDisabled={isStaging}
/>
);
}

View File

@ -0,0 +1,45 @@
import { useAppDispatch } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
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 { FaCrosshairs } from 'react-icons/fa';
export default function UnifiedCanvasResetView() {
const canvasBaseLayer = getCanvasBaseLayer();
const dispatch = useAppDispatch();
useHotkeys(
['r'],
() => {
handleResetCanvasView();
},
{
enabled: () => true,
preventDefault: true,
},
[canvasBaseLayer]
);
const handleResetCanvasView = () => {
const canvasBaseLayer = getCanvasBaseLayer();
if (!canvasBaseLayer) return;
const clientRect = canvasBaseLayer.getClientRect({
skipTransform: true,
});
dispatch(
resetCanvasView({
contentRect: clientRect,
})
);
};
return (
<IAIIconButton
aria-label="Reset View (R)"
tooltip="Reset View (R)"
icon={<FaCrosshairs />}
onClick={handleResetCanvasView}
/>
);
}

View File

@ -0,0 +1,52 @@
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { mergeAndUploadCanvas } from 'features/canvas/store/thunks/mergeAndUploadCanvas';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { FaSave } from 'react-icons/fa';
export default function UnifiedCanvasSaveToGallery() {
const isStaging = useAppSelector(isStagingSelector);
const canvasBaseLayer = getCanvasBaseLayer();
const isProcessing = useAppSelector(
(state: RootState) => state.system.isProcessing
);
const shouldCropToBoundingBoxOnSave = useAppSelector(
(state: RootState) => state.canvas.shouldCropToBoundingBoxOnSave
);
const dispatch = useAppDispatch();
useHotkeys(
['shift+s'],
() => {
handleSaveToGallery();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[canvasBaseLayer, isProcessing]
);
const handleSaveToGallery = () => {
dispatch(
mergeAndUploadCanvas({
cropVisible: shouldCropToBoundingBoxOnSave ? false : true,
cropToBoundingBox: shouldCropToBoundingBoxOnSave,
shouldSaveToGallery: true,
})
);
};
return (
<IAIIconButton
aria-label="Save to Gallery (Shift+S)"
tooltip="Save to Gallery (Shift+S)"
icon={<FaSave />}
onClick={handleSaveToGallery}
isDisabled={isStaging}
/>
);
}

View File

@ -0,0 +1,162 @@
import { ButtonGroup, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import {
addEraseRect,
addFillRect,
setBrushColor,
setTool,
} from 'features/canvas/store/canvasSlice';
import { useAppDispatch, useAppSelector } from 'app/store';
import _ from 'lodash';
import IAIIconButton from 'common/components/IAIIconButton';
import {
FaEraser,
FaEyeDropper,
FaFillDrip,
FaPaintBrush,
FaPlus,
} from 'react-icons/fa';
import {
canvasSelector,
isStagingSelector,
} from 'features/canvas/store/canvasSelectors';
import { systemSelector } from 'features/system/store/systemSelectors';
import { useHotkeys } from 'react-hotkeys-hook';
export const selector = createSelector(
[canvasSelector, isStagingSelector, systemSelector],
(canvas, isStaging, system) => {
const { isProcessing } = system;
const { tool } = canvas;
return {
tool,
isStaging,
isProcessing,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
const UnifiedCanvasToolSelect = () => {
const dispatch = useAppDispatch();
const { tool, isStaging } = useAppSelector(selector);
useHotkeys(
['b'],
() => {
handleSelectBrushTool();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[]
);
useHotkeys(
['e'],
() => {
handleSelectEraserTool();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[tool]
);
useHotkeys(
['c'],
() => {
handleSelectColorPickerTool();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[tool]
);
useHotkeys(
['shift+f'],
() => {
handleFillRect();
},
{
enabled: () => !isStaging,
preventDefault: true,
}
);
useHotkeys(
['delete', 'backspace'],
() => {
handleEraseBoundingBox();
},
{
enabled: () => !isStaging,
preventDefault: true,
}
);
const handleSelectBrushTool = () => dispatch(setTool('brush'));
const handleSelectEraserTool = () => dispatch(setTool('eraser'));
const handleSelectColorPickerTool = () => dispatch(setTool('colorPicker'));
const handleFillRect = () => dispatch(addFillRect());
const handleEraseBoundingBox = () => dispatch(addEraseRect());
return (
<Flex flexDirection={'column'} gap={'0.5rem'}>
<ButtonGroup>
<IAIIconButton
aria-label="Brush Tool (B)"
tooltip="Brush Tool (B)"
icon={<FaPaintBrush />}
data-selected={tool === 'brush' && !isStaging}
onClick={handleSelectBrushTool}
isDisabled={isStaging}
/>
<IAIIconButton
aria-label="Eraser Tool (E)"
tooltip="Eraser Tool (E)"
icon={<FaEraser />}
data-selected={tool === 'eraser' && !isStaging}
isDisabled={isStaging}
onClick={handleSelectEraserTool}
/>
</ButtonGroup>
<ButtonGroup>
<IAIIconButton
aria-label="Fill Bounding Box (Shift+F)"
tooltip="Fill Bounding Box (Shift+F)"
icon={<FaFillDrip />}
isDisabled={isStaging}
onClick={handleFillRect}
/>
<IAIIconButton
aria-label="Erase Bounding Box Area (Delete/Backspace)"
tooltip="Erase Bounding Box Area (Delete/Backspace)"
icon={<FaPlus style={{ transform: 'rotate(45deg)' }} />}
isDisabled={isStaging}
onClick={handleEraseBoundingBox}
/>
</ButtonGroup>
<IAIIconButton
aria-label="Color Picker (C)"
tooltip="Color Picker (C)"
icon={<FaEyeDropper />}
data-selected={tool === 'colorPicker' && !isStaging}
isDisabled={isStaging}
onClick={handleSelectColorPickerTool}
width={'max-content'}
/>
</Flex>
);
};
export default UnifiedCanvasToolSelect;

View File

@ -0,0 +1,59 @@
import { Flex } from '@chakra-ui/react';
import IAICanvasUndoButton from 'features/canvas/components/IAICanvasToolbar/IAICanvasUndoButton';
import IAICanvasRedoButton from 'features/canvas/components/IAICanvasToolbar/IAICanvasRedoButton';
import UnifiedCanvasLayerSelect from './UnifiedCanvasToolbar/UnifiedCanvasLayerSelect';
import UnifiedCanvasToolSelect from './UnifiedCanvasToolbar/UnifiedCanvasToolSelect';
import UnifiedCanvasSettings from './UnifiedCanvasToolSettings/UnifiedCanvasSettings';
import UnifiedCanvasMoveTool from './UnifiedCanvasToolbar/UnifiedCanvasMoveTool';
import UnifiedCanvasResetView from './UnifiedCanvasToolbar/UnifiedCanvasResetView';
import UnifiedCanvasMergeVisible from './UnifiedCanvasToolbar/UnifiedCanvasMergeVisible';
import UnifiedCanvasSaveToGallery from './UnifiedCanvasToolbar/UnifiedCanvasSaveToGallery';
import UnifiedCanvasCopyToClipboard from './UnifiedCanvasToolbar/UnifiedCanvasCopyToClipboard';
import UnifiedCanvasDownloadImage from './UnifiedCanvasToolbar/UnifiedCanvasDownloadImage';
import UnifiedCanvasFileUploader from './UnifiedCanvasToolbar/UnifiedCanvasFileUploader';
import UnifiedCanvasResetCanvas from './UnifiedCanvasToolbar/UnifiedCanvasResetCanvas';
import UnifiedCanvasProcessingButtons from './UnifiedCanvasToolbar/UnifiedCanvasProcessingButtons';
import { RootState, useAppSelector } from 'app/store';
const UnifiedCanvasToolbarBeta = () => {
const shouldShowOptionsPanel = useAppSelector(
(state: RootState) => state.options.shouldShowOptionsPanel
);
return (
<Flex flexDirection={'column'} rowGap="0.5rem" width="6rem">
<UnifiedCanvasLayerSelect />
<UnifiedCanvasToolSelect />
<Flex gap={'0.5rem'}>
<UnifiedCanvasMoveTool />
<UnifiedCanvasResetView />
</Flex>
<Flex columnGap={'0.5rem'}>
<UnifiedCanvasMergeVisible />
<UnifiedCanvasSaveToGallery />
</Flex>
<Flex columnGap={'0.5rem'}>
<UnifiedCanvasCopyToClipboard />
<UnifiedCanvasDownloadImage />
</Flex>
<Flex gap={'0.5rem'}>
<IAICanvasUndoButton />
<IAICanvasRedoButton />
</Flex>
<Flex gap={'0.5rem'}>
<UnifiedCanvasFileUploader />
<UnifiedCanvasResetCanvas />
</Flex>
<UnifiedCanvasSettings />
{!shouldShowOptionsPanel && <UnifiedCanvasProcessingButtons />}
</Flex>
);
};
export default UnifiedCanvasToolbarBeta;

View File

@ -1,14 +1,23 @@
import UnifiedCanvasPanel from './UnifiedCanvasPanel';
import UnifiedCanvasDisplay from './UnifiedCanvasDisplay';
import InvokeWorkarea from 'features/tabs/components/InvokeWorkarea';
import { RootState, useAppSelector } from 'app/store';
import UnifiedCanvasDisplayBeta from './UnifiedCanvasBeta/UnifiedCanvasDisplayBeta';
export default function UnifiedCanvasWorkarea() {
const shouldUseCanvasBetaLayout = useAppSelector(
(state: RootState) => state.options.shouldUseCanvasBetaLayout
);
return (
<InvokeWorkarea
optionsPanel={<UnifiedCanvasPanel />}
styleClass="inpainting-workarea-overrides"
>
<UnifiedCanvasDisplay />
{shouldUseCanvasBetaLayout ? (
<UnifiedCanvasDisplayBeta />
) : (
<UnifiedCanvasDisplay />
)}
</InvokeWorkarea>
);
}