Merge branch 'main' into dev/diffusers

This commit is contained in:
Kevin Turner 2022-12-12 23:09:04 -08:00 committed by GitHub
commit 07ac88baed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 2158 additions and 771 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" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>InvokeAI - A Stable Diffusion Toolkit</title> <title>InvokeAI - A Stable Diffusion Toolkit</title>
<link rel="shortcut icon" type="icon" href="./assets/favicon.0d253ced.ico" /> <link rel="shortcut icon" type="icon" href="./assets/favicon.0d253ced.ico" />
<script type="module" crossorigin src="./assets/index.d864890e.js"></script> <script type="module" crossorigin src="./assets/index.d8f54146.js"></script>
<link rel="stylesheet" href="./assets/index.81f1c71c.css"> <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">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> <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> </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>!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-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> </body>
</html> </html>

View File

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

View File

@ -3,8 +3,11 @@ import _ from 'lodash';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { activeTabNameSelector } from 'features/options/store/optionsSelectors'; import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
import { import {
clearMask,
resetCanvasInteractionState, resetCanvasInteractionState,
setIsMaskEnabled,
setShouldShowBoundingBox, setShouldShowBoundingBox,
setShouldSnapToGrid,
setTool, setTool,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import { useAppDispatch, useAppSelector } from 'app/store'; import { useAppDispatch, useAppSelector } from 'app/store';
@ -24,6 +27,8 @@ const selector = createSelector(
shouldLockBoundingBox, shouldLockBoundingBox,
shouldShowBoundingBox, shouldShowBoundingBox,
tool, tool,
isMaskEnabled,
shouldSnapToGrid,
} = canvas; } = canvas;
return { return {
@ -33,6 +38,8 @@ const selector = createSelector(
shouldShowBoundingBox, shouldShowBoundingBox,
tool, tool,
isStaging, isStaging,
isMaskEnabled,
shouldSnapToGrid,
}; };
}, },
{ {
@ -44,13 +51,62 @@ const selector = createSelector(
const useInpaintingCanvasHotkeys = () => { const useInpaintingCanvasHotkeys = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { activeTabName, shouldShowBoundingBox, tool, isStaging } = const {
useAppSelector(selector); activeTabName,
shouldShowBoundingBox,
tool,
isStaging,
isMaskEnabled,
shouldSnapToGrid,
} = useAppSelector(selector);
const previousToolRef = useRef<CanvasTool | null>(null); const previousToolRef = useRef<CanvasTool | null>(null);
const canvasStage = getCanvasStage(); 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( useHotkeys(
'esc', 'esc',
() => { () => {

View File

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

View File

@ -55,6 +55,7 @@ export interface OptionsState {
upscalingStrength: number; upscalingStrength: number;
variationAmount: number; variationAmount: number;
width: number; width: number;
shouldUseCanvasBetaLayout: boolean;
} }
const initialOptionsState: OptionsState = { const initialOptionsState: OptionsState = {
@ -101,6 +102,7 @@ const initialOptionsState: OptionsState = {
upscalingStrength: 0.75, upscalingStrength: 0.75,
variationAmount: 0.1, variationAmount: 0.1,
width: 512, width: 512,
shouldUseCanvasBetaLayout: false,
}; };
const initialState: OptionsState = initialOptionsState; const initialState: OptionsState = initialOptionsState;
@ -396,6 +398,9 @@ export const optionsSlice = createSlice({
setInfillMethod: (state, action: PayloadAction<string>) => { setInfillMethod: (state, action: PayloadAction<string>) => {
state.infillMethod = action.payload; state.infillMethod = action.payload;
}, },
setShouldUseCanvasBetaLayout: (state, action: PayloadAction<boolean>) => {
state.shouldUseCanvasBetaLayout = action.payload;
},
}, },
}); });
@ -451,6 +456,7 @@ export const {
setUpscalingStrength, setUpscalingStrength,
setVariationAmount, setVariationAmount,
setWidth, setWidth,
setShouldUseCanvasBetaLayout,
} = optionsSlice.actions; } = optionsSlice.actions;
export default optionsSlice.reducer; 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: { memoizeOptions: {
@ -35,7 +36,8 @@ const selector = createSelector(
const ModelSelect = () => { const ModelSelect = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { models, activeModel, isProcessing } = useAppSelector(selector); const { models, activeModel, isProcessing, activeDesc } =
useAppSelector(selector);
const handleChangeModel = (e: ChangeEvent<HTMLSelectElement>) => { const handleChangeModel = (e: ChangeEvent<HTMLSelectElement>) => {
dispatch(requestModelChange(e.target.value)); dispatch(requestModelChange(e.target.value));
}; };
@ -48,6 +50,7 @@ const ModelSelect = () => {
> >
<IAISelect <IAISelect
style={{ fontSize: '0.8rem' }} style={{ fontSize: '0.8rem' }}
tooltip={activeDesc}
isDisabled={isProcessing} isDisabled={isProcessing}
value={activeModel} value={activeModel}
validValues={models} validValues={models}

View File

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

View File

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

View File

@ -39,6 +39,7 @@
column-gap: 1rem; column-gap: 1rem;
} }
} }
}
.inpainting-canvas-area { .inpainting-canvas-area {
display: flex; display: flex;
@ -59,11 +60,11 @@
.inpainting-canvas-container { .inpainting-canvas-container {
display: flex; display: flex;
align-items: center; position: relative;
justify-content: center;
height: 100%; height: 100%;
width: 100%; width: 100%;
border-radius: 0.5rem; border-radius: 0.5rem;
}
.inpainting-canvas-wrapper { .inpainting-canvas-wrapper {
position: relative; position: relative;
@ -80,8 +81,6 @@
border-radius: 0.5rem; border-radius: 0.5rem;
} }
} }
}
}
.inpainting-options-btn { .inpainting-options-btn {
min-height: 2rem; min-height: 2rem;

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

View File

@ -3,6 +3,7 @@
cd "$(dirname "$0")" cd "$(dirname "$0")"
VERSION=$(grep ^VERSION ../setup.py | awk '{ print $3 }' | sed "s/'//g" ) VERSION=$(grep ^VERSION ../setup.py | awk '{ print $3 }' | sed "s/'//g" )
VERSION="$VERSION-p1"
echo "Be certain that you're in the 'installer' directory before continuing." echo "Be certain that you're in the 'installer' directory before continuing."
read -p "Press any key to continue, or CTRL-C to exit..." read -p "Press any key to continue, or CTRL-C to exit..."

View File

@ -22,7 +22,6 @@ set PYTHON_URL=https://www.python.org/downloads/windows/
set MINIMUM_PYTHON_VERSION=3.9.0 set MINIMUM_PYTHON_VERSION=3.9.0
set PYTHON_URL=https://www.python.org/downloads/release/python-3109/ set PYTHON_URL=https://www.python.org/downloads/release/python-3109/
set err_msg=An error has occurred and the script could not continue. set err_msg=An error has occurred and the script could not continue.
@rem --------------------------- Intro ------------------------------- @rem --------------------------- Intro -------------------------------
@ -65,23 +64,21 @@ if %errorlevel% == 1 (
@rem Cleanup @rem Cleanup
del /q .tmp1 .tmp2 del /q .tmp1 .tmp2
echo Updating PIP...
call python -m pip install --no-warn-script-location -q --upgrade pip
@rem --------------------- Get the requirements file ------------ @rem --------------------- Get the requirements file ------------
echo. echo.
echo Setting up requirements file for your system. echo Setting up requirements file for your system.
copy /y environments-and-requirements\requirements-win-colab-cuda.txt .\requirements.txt copy /y environments-and-requirements\requirements-win-colab-cuda.txt .\requirements.txt
@rem --------------------- Get the root directory for installation ------------ @rem --------------------- Get the root directory for installation ------------
set rootdir="" set "rootdir="
set response="" set "response="
set selection="" set "selection="
:pick_rootdir :pick_rootdir
if %rootdir% neq "" goto :done if defined rootdir goto :done
set /p selection=Select the path to install InvokeAI's directory into [%UserProfile%]: set /p selection=Select the path to install InvokeAI's directory into [%UserProfile%]:
if %selection% == "" set selection=%UserProfile% if not defined selection set selection=%UserProfile%
set dest=%selection%\invokeai set selection=%selection:"=%
set dest="%selection%\invokeai"
if exist %dest% ( if exist %dest% (
set response=y set response=y
set /p response=The directory %dest% exists. Do you wish to resume install from a previous attempt? [Y/n]: set /p response=The directory %dest% exists. Do you wish to resume install from a previous attempt? [Y/n]:
@ -93,16 +90,18 @@ set selection=""
set response=y set response=y
set /p response="You have chosen to install InvokeAI into %rootdir%. OK? [Y/n]: " set /p response="You have chosen to install InvokeAI into %rootdir%. OK? [Y/n]: "
if !response! == "" set response=y if !response! == "" set response=y
if /I !response! neq y set rootdir="" if /I !response! neq y set "rootdir="
goto :pick_rootdir goto :pick_rootdir
:done :done
set rootdir=%rootdir:"=%
@rem ---------------------- Initialize the runtime directory --------------------- @rem ---------------------- Initialize the runtime directory ---------------------
echo. echo.
echo *** Creating Runtime Directory %rootdir% *** echo *** Creating Runtime Directory %rootdir% ***
if not exist %rootdir% mkdir %rootdir% if not exist "%rootdir%" mkdir "%rootdir%"
@rem for unknown reasons the mkdir works but returns an error code @rem for unknown reasons the mkdir works but returns an error code
if not exist %rootdir% ( if not exist "%rootdir%" (
set err_msg=Could not create the directory %rootdir%. Please check the directory's permissions and try again. set err_msg=Could not create the directory %rootdir%. Please check the directory's permissions and try again.
goto :err_exit goto :err_exit
) )
@ -111,7 +110,7 @@ echo Successful.
@rem --------------------------- Create and populate .venv --------------------------- @rem --------------------------- Create and populate .venv ---------------------------
echo. echo.
echo ** Creating Virtual Environment for InvokeAI ** echo ** Creating Virtual Environment for InvokeAI **
call python -mvenv %rootdir%\.venv call python -mvenv "%rootdir%\.venv"
if %errorlevel% neq 0 ( if %errorlevel% neq 0 (
set err_msg=Could not create virtual environment %rootdir%\.venv. Please check the directory's permissions and try again. set err_msg=Could not create virtual environment %rootdir%\.venv. Please check the directory's permissions and try again.
goto :err_exit goto :err_exit
@ -120,18 +119,24 @@ echo Successful.
echo. echo.
echo *** Installing InvokeAI Requirements *** echo *** Installing InvokeAI Requirements ***
call %rootdir%\.venv\Scripts\activate.bat echo Activating environment
call "%rootdir%\.venv\Scripts\activate.bat"
set PYTHON=%rootdir%\.venv\Scripts\python
echo updating pip with "%PYTHON%"
call "%PYTHON%" -mensurepip --upgrade
copy environments-and-requirements\requirements-win-colab-cuda.txt .\requirements.txt copy environments-and-requirements\requirements-win-colab-cuda.txt .\requirements.txt
call python -mpip install -r requirements.txt call "%PYTHON%" -mpip install --prefer-binary -r requirements.txt
if %errorlevel% neq 0 ( if %errorlevel% neq 0 (
set err_msg=Installation of requirements failed. See above for errors and check %TROUBLESHOOTING% for potential solutions. set err_msg=Requirements installation failed. See above for errors and check %TROUBLESHOOTING% for potential solutions.
goto :err_exit goto :err_exit
) )
echo Installation successful. echo Installation successful.
echo. echo.
echo *** Installing InvokeAI Modules and Executables *** echo *** Installing InvokeAI Modules and Executables ***
call python -mpip install %INVOKE_AI_SRC% call "%PYTHON%" -mpip install %INVOKE_AI_SRC%
if %errorlevel% neq 0 ( if %errorlevel% neq 0 (
set err_msg=Installation of InvokeAI failed. See above for errors and check %TROUBLESHOOTING% for potential solutions. set err_msg=Installation of InvokeAI failed. See above for errors and check %TROUBLESHOOTING% for potential solutions.
goto :err_exit goto :err_exit
@ -139,21 +144,21 @@ if %errorlevel% neq 0 (
echo Installation successful. echo Installation successful.
@rem --------------------------- Set up the root directory --------------------------- @rem --------------------------- Set up the root directory ---------------------------
xcopy /E /Y .\templates\rootdir %rootdir% xcopy /E /Y .\templates\rootdir "%rootdir%"
PUSHD "%rootdir%" PUSHD "%rootdir%"
call .venv\Scripts\python .venv\Scripts\configure_invokeai.py --root="%rootdir%" call "%PYTHON%" .venv\Scripts\configure_invokeai.py "--root=%rootdir%"
if %errorlevel% neq 0 ( if %errorlevel% neq 0 (
set err_msg=Configuration failed. See above for error messages and check %TROUBLESHOOTING% for potential solutions. set err_msg=Configuration failed. See above for error messages and check %TROUBLESHOOTING% for potential solutions.
goto :err_exit goto :err_exit
) )
POPD POPD
copy .\templates\invoke.bat.in %rootdir%\invoke.bat copy .\templates\invoke.bat.in "%rootdir%\invoke.bat"
copy .\templates\update.bat.in %rootdir%\update.bat copy .\templates\update.bat.in "%rootdir%\update.bat"
@rem so that update.bat works @rem so that update.bat works
mkdir %rootdir%\environments-and-requirements mkdir "%rootdir%\environments-and-requirements"
xcopy /I /Y .\environments-and-requirements %rootdir%\environments-and-requirements xcopy /I /Y .\environments-and-requirements "%rootdir%\environments-and-requirements"
copy .\requirements.txt %rootdir%\requirements.txt copy .\requirements.txt "%rootdir%\requirements.txt"
echo. echo.
@ -213,3 +218,4 @@ echo The installer will exit now.
pause pause
exit /b exit /b
pause

View File

@ -126,17 +126,17 @@ do
read -e -p "InvokeAI will be installed into $ROOTDIR. OK? [y]: " input read -e -p "InvokeAI will be installed into $ROOTDIR. OK? [y]: " input
RESPONSE=${input:='y'} RESPONSE=${input:='y'}
if [ "$RESPONSE" == 'y' ]; then if [ "$RESPONSE" == 'y' ]; then
if [ -e $ROOTDIR ]; then if [ -e "$ROOTDIR" ]; then
echo echo
read -e -p "Directory $ROOTDIR already exists. Do you want to resume an interrupted install? [y]: " input read -e -p "Directory "$ROOTDIR" already exists. Do you want to resume an interrupted install? [y]: " input
RESPONSE=${input:='y'} RESPONSE=${input:='y'}
if [ "$RESPONSE" != 'y' ]; then if [ "$RESPONSE" != 'y' ]; then
ROOTDIR="" ROOTDIR=""
fi fi
else else
mkdir -p $ROOTDIR mkdir -p "$ROOTDIR"
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "Could not create $ROOTDIR. Try again with a different install location." echo "Could not create "$ROOTDIR". Try again with a different install location."
ROOTDIR="" ROOTDIR=""
fi fi
fi fi
@ -149,18 +149,19 @@ done
echo echo
echo "** Creating Virtual Environment for InvokeAI **" echo "** Creating Virtual Environment for InvokeAI **"
$PYTHON -mpip install --upgrade pip $PYTHON -mvenv "$ROOTDIR"/.venv
$PYTHON -mvenv $ROOTDIR/.venv _err_exit $? "Python failed to create virtual environment "$ROOTDIR"/.venv. Please see $TROUBLESHOOTING for help."
_err_exit $? "Python failed to create virtual environment $ROOTDIR/.venv. Please see $TROUBLESHOOTING for help."
#-------------------------------------------------------------------------------- #--------------------------------------------------------------------------------
echo echo
echo "** Activating Virtual Environment for InvokeAI **" echo "** Activating Virtual Environment for InvokeAI **"
source $ROOTDIR/.venv/bin/activate source "$ROOTDIR"/.venv/bin/activate
_err_exit $? "Failed to activate virtual evironment $ROOTDIR/.venv. Please see $TROUBLESHOOTING for help." _err_exit $? "Failed to activate virtual evironment "$ROOTDIR"/.venv. Please see $TROUBLESHOOTING for help."
PYTHON=$ROOTDIR/.venv/bin/python PYTHON="$ROOTDIR"/.venv/bin/python
$PYTHON -mensurepip --upgrade
$PYTHON -mpip install --upgrade pip
#-------------------------------------------------------------------------------- #--------------------------------------------------------------------------------
echo echo
@ -179,7 +180,7 @@ else
fi fi
fi fi
$PYTHON -mpip install -r requirements.txt $PYTHON -mpip install --prefer-binary -r requirements.txt
_err_exit $? "Failed to install InvokeAI's dependencies." _err_exit $? "Failed to install InvokeAI's dependencies."
#-------------------------------------------------------------------------------- #--------------------------------------------------------------------------------
@ -189,29 +190,29 @@ $PYTHON -mpip install $INVOKE_AI_SRC
_err_exit $? "Installation of InvokeAI failed." _err_exit $? "Installation of InvokeAI failed."
#-------------------------------------------------------------------------------- #--------------------------------------------------------------------------------
echo " *** Setting Up Root Directory $ROOTDIR *** " echo " *** Setting Up Root Directory "$ROOTDIR" *** "
cp -pr templates/rootdir/* $ROOTDIR/ cp -pr templates/rootdir/* "$ROOTDIR"/
cp templates/invoke.sh.in $ROOTDIR/invoke.sh cp templates/invoke.sh.in "$ROOTDIR"/invoke.sh
chmod a+rx $ROOTDIR/invoke.sh chmod a+rx "$ROOTDIR"/invoke.sh
cp templates/update.sh.in $ROOTDIR/update.sh cp templates/update.sh.in "$ROOTDIR"/update.sh
chmod a+rx $ROOTDIR/update.sh chmod a+rx "$ROOTDIR"/update.sh
# This allows the updater to work! # This allows the updater to work!
cp -pr environments-and-requirements requirements.txt $ROOTDIR/ cp -pr environments-and-requirements requirements.txt "$ROOTDIR/"
#-------------------------------------------------------------------------------- #--------------------------------------------------------------------------------
echo echo
echo "*** Confguring InvokeAI ***" echo "*** Confguring InvokeAI ***"
pushd $ROOTDIR pushd "$ROOTDIR" >/dev/null
./.venv/bin/configure_invokeai.py --root=$ROOTDIR $PYTHON ./.venv/bin/configure_invokeai.py --root="$ROOTDIR"
_err_exit $? "Initial configuration failed. Please see above error messages and $TROUBLESHOOTING for help." _err_exit $? "Initial configuration failed. Please see above error messages and $TROUBLESHOOTING for help."
#-------------------------------------------------------------------------------- #--------------------------------------------------------------------------------
popd popd
cp templates/invoke.sh.in $ROOTDIR/invoke.sh cp templates/invoke.sh.in "$ROOTDIR"/invoke.sh
chmod a+rx $ROOTDIR/invoke.sh chmod a+rx "$ROOTDIR"/invoke.sh
cp templates/update.sh.in $ROOTDIR/update.sh cp templates/update.sh.in "$ROOTDIR"/update.sh
chmod a+rx $ROOTDIR/update.sh chmod a+rx "$ROOTDIR"/update.sh
echo "You may now run InvokeAI by entering the directory $ROOTDIR and running invoke.sh" echo "You may now run InvokeAI by entering the directory $ROOTDIR and running invoke.sh"

View File

@ -84,6 +84,8 @@ class Concepts(object):
better to store the concept name (unique) than the concept trigger better to store the concept name (unique) than the concept trigger
(not necessarily unique!) (not necessarily unique!)
''' '''
if not prompt:
return prompt
triggers = self.match_trigger.findall(prompt) triggers = self.match_trigger.findall(prompt)
if not triggers: if not triggers:
return prompt return prompt

View File

@ -18,7 +18,12 @@ from argparse import Namespace
Globals = Namespace() Globals = Namespace()
# This is usually overwritten by the command line and/or environment variables # This is usually overwritten by the command line and/or environment variables
Globals.root = osp.abspath(os.environ.get('INVOKEAI_ROOT') or osp.abspath(osp.join(os.environ.get('VIRTUAL_ENV'),'..')) or osp.expanduser('~/invokeai')) if os.environ.get('INVOKEAI_ROOT'):
Globals.root = osp.abspath(os.environ.get('INVOKEAI_ROOT'))
elif os.environ.get('VIRTUAL_ENV'):
Globals.root = osp.abspath(osp.join(os.environ.get('VIRTUAL_ENV'), '..'))
else:
Globals.root = osp.abspath(osp.expanduser('~/invokeai'))
# Where to look for the initialization file # Where to look for the initialization file
Globals.initfile = 'invokeai.init' Globals.initfile = 'invokeai.init'