[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
43 changed files with 2093 additions and 721 deletions

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>
);
}