From d40d5276ddb3c26088fc7dcaa20432e23d6f2655 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Mon, 24 Apr 2023 20:34:24 +1000
Subject: [PATCH] feat(ui): wip img2img ui
---
invokeai/frontend/web/public/locales/en.json | 3 +-
invokeai/frontend/web/src/app/App.tsx | 2 +
invokeai/frontend/web/src/app/store.ts | 3 +
.../web/src/common/components/IAISlider.tsx | 5 +-
.../web/src/common/hooks/useGlobalHotkeys.ts | 37 +++++++++
.../components/CurrentImageDisplay.tsx | 6 +-
.../gallery/components/ImageGalleryPanel.tsx | 2 +-
.../AdvancedParameters/Seed/RandomizeSeed.tsx | 32 ++++++--
.../ImageDimensions/AspectRatioPreview.tsx | 75 ++++++++++++++++++
.../ImageDimensions/DimensionsSettings.tsx | 76 +++++++++++++++++++
.../MainParameters/HeightSlider.tsx | 38 ++++++++++
.../components/MainParameters/MainHeight.tsx | 2 +-
.../components/MainParameters/MainSampler.tsx | 23 +++---
.../MainParameters/MainSettings.tsx | 18 +++--
.../components/MainParameters/MainWidth.tsx | 2 +-
.../components/MainParameters/WidthSlider.tsx | 37 +++++++++
.../components/ParametersAccordion.tsx | 7 +-
.../ProcessButtons/InvokeButton.tsx | 4 +-
.../system/components/ModelSelect.tsx | 13 ++--
.../features/system/components/SiteHeader.tsx | 2 -
.../FloatingParametersPanelButtons.tsx | 2 +-
.../src/features/ui/components/InvokeTabs.tsx | 70 +++++++++--------
.../GenerateContent.tsx} | 4 +-
.../GenerateParameters.tsx} | 28 +++++--
.../GenerateWorkspace.tsx} | 14 ++--
.../web/src/features/ui/store/hotkeysSlice.ts | 26 +++++++
.../web/src/features/ui/store/tabMap.ts | 2 +-
.../web/src/features/ui/store/uiSlice.ts | 7 +-
.../web/src/features/ui/store/uiTypes.ts | 3 +
.../web/src/services/thunks/session.ts | 10 +--
30 files changed, 453 insertions(+), 100 deletions(-)
create mode 100644 invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts
create mode 100644 invokeai/frontend/web/src/features/parameters/components/ImageDimensions/AspectRatioPreview.tsx
create mode 100644 invokeai/frontend/web/src/features/parameters/components/ImageDimensions/DimensionsSettings.tsx
create mode 100644 invokeai/frontend/web/src/features/parameters/components/MainParameters/HeightSlider.tsx
create mode 100644 invokeai/frontend/web/src/features/parameters/components/MainParameters/WidthSlider.tsx
rename invokeai/frontend/web/src/features/ui/components/tabs/{Linear/LinearContent.tsx => Generate/GenerateContent.tsx} (86%)
rename invokeai/frontend/web/src/features/ui/components/tabs/{Linear/LinearParameters.tsx => Generate/GenerateParameters.tsx} (83%)
rename invokeai/frontend/web/src/features/ui/components/tabs/{Linear/LinearWorkspace.tsx => Generate/GenerateWorkspace.tsx} (82%)
create mode 100644 invokeai/frontend/web/src/features/ui/store/hotkeysSlice.ts
diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json
index 6996119de3..68978baf3b 100644
--- a/invokeai/frontend/web/public/locales/en.json
+++ b/invokeai/frontend/web/public/locales/en.json
@@ -98,7 +98,8 @@
"pinOptionsPanel": "Pin Options Panel",
"loading": "Loading",
"loadingInvokeAI": "Loading Invoke AI",
- "random": "Random"
+ "random": "Random",
+ "generate": "Generate"
},
"gallery": {
"generations": "Generations",
diff --git a/invokeai/frontend/web/src/app/App.tsx b/invokeai/frontend/web/src/app/App.tsx
index d2f96b0746..e1078ac411 100644
--- a/invokeai/frontend/web/src/app/App.tsx
+++ b/invokeai/frontend/web/src/app/App.tsx
@@ -26,6 +26,7 @@ import {
} from 'features/system/store/systemSlice';
import { useIsApplicationReady } from 'features/system/hooks/useIsApplicationReady';
import { ApplicationFeature } from './invokeai';
+import { useGlobalHotkeys } from 'common/hooks/useGlobalHotkeys';
keepGUIAlive();
@@ -40,6 +41,7 @@ interface Props extends PropsWithChildren {
const App = (props: Props) => {
useToastWatcher();
+ useGlobalHotkeys();
const currentTheme = useAppSelector((state) => state.ui.currentTheme);
const disabledFeatures = useAppSelector(
diff --git a/invokeai/frontend/web/src/app/store.ts b/invokeai/frontend/web/src/app/store.ts
index 3e046d8ed9..05154c6987 100644
--- a/invokeai/frontend/web/src/app/store.ts
+++ b/invokeai/frontend/web/src/app/store.ts
@@ -14,6 +14,7 @@ import generationReducer from 'features/parameters/store/generationSlice';
import postprocessingReducer from 'features/parameters/store/postprocessingSlice';
import systemReducer from 'features/system/store/systemSlice';
import uiReducer from 'features/ui/store/uiSlice';
+import hotkeysReducer from 'features/ui/store/hotkeysSlice';
import modelsReducer from 'features/system/store/modelSlice';
import nodesReducer from 'features/nodes/store/nodesSlice';
@@ -55,6 +56,7 @@ const rootReducer = combineReducers({
system: systemReducer,
ui: uiReducer,
uploads: uploadsReducer,
+ hotkeys: hotkeysReducer,
});
const rootPersistConfig = getPersistConfig({
@@ -75,6 +77,7 @@ const rootPersistConfig = getPersistConfig({
...uiBlacklist,
// ...uploadsBlacklist,
'uploads',
+ 'hotkeys',
],
debounce: 300,
});
diff --git a/invokeai/frontend/web/src/common/components/IAISlider.tsx b/invokeai/frontend/web/src/common/components/IAISlider.tsx
index 189ef4f5ad..614bebe807 100644
--- a/invokeai/frontend/web/src/common/components/IAISlider.tsx
+++ b/invokeai/frontend/web/src/common/components/IAISlider.tsx
@@ -29,6 +29,7 @@ import { useTranslation } from 'react-i18next';
import { FocusEvent, memo, useEffect, useMemo, useState } from 'react';
import { BiReset } from 'react-icons/bi';
import IAIIconButton, { IAIIconButtonProps } from './IAIIconButton';
+import { roundDownToMultiple } from 'common/util/roundDownToMultiple';
export type IAIFullSliderProps = {
label: string;
@@ -119,7 +120,9 @@ const IAISlider = (props: IAIFullSliderProps) => {
min,
numberInputMax
);
- onChange(clamped);
+ const quantized = roundDownToMultiple(clamped, step);
+ onChange(quantized);
+ setLocalInputValue(quantized);
};
const handleInputChange = (v: number | string) => {
diff --git a/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts b/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts
new file mode 100644
index 0000000000..79fcf6446a
--- /dev/null
+++ b/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts
@@ -0,0 +1,37 @@
+import { createSelector } from '@reduxjs/toolkit';
+import { RootState } from 'app/store';
+import { useAppDispatch, useAppSelector } from 'app/storeHooks';
+import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice';
+import { isEqual } from 'lodash';
+import { isHotkeyPressed, useHotkeys } from 'react-hotkeys-hook';
+
+const globalHotkeysSelector = createSelector(
+ (state: RootState) => state.hotkeys,
+ (hotkeys) => {
+ const { shift } = hotkeys;
+ return { shift };
+ },
+ {
+ memoizeOptions: {
+ resultEqualityCheck: isEqual,
+ },
+ }
+);
+
+export const useGlobalHotkeys = () => {
+ const dispatch = useAppDispatch();
+ const { shift } = useAppSelector(globalHotkeysSelector);
+
+ useHotkeys(
+ '*',
+ () => {
+ if (isHotkeyPressed('shift')) {
+ !shift && dispatch(shiftKeyPressed(true));
+ } else {
+ shift && dispatch(shiftKeyPressed(false));
+ }
+ },
+ { keyup: true, keydown: true },
+ [shift]
+ );
+};
diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImageDisplay.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImageDisplay.tsx
index 0364f879cc..1730b41095 100644
--- a/invokeai/frontend/web/src/features/gallery/components/CurrentImageDisplay.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImageDisplay.tsx
@@ -59,9 +59,6 @@ const CurrentImageDisplay = () => {
justifyContent: 'center',
}}
>
-
-
-
{
/>
)}
+
+
+
);
};
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryPanel.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryPanel.tsx
index ea19408c65..2a557240ef 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryPanel.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryPanel.tsx
@@ -35,7 +35,7 @@ const GALLERY_TAB_WIDTHS: Record<
> = {
// txt2img: { galleryMinWidth: 200, galleryMaxWidth: 500 },
// img2img: { galleryMinWidth: 200, galleryMaxWidth: 500 },
- linear: { galleryMinWidth: 200, galleryMaxWidth: 500 },
+ generate: { galleryMinWidth: 200, galleryMaxWidth: 500 },
unifiedCanvas: { galleryMinWidth: 200, galleryMaxWidth: 200 },
nodes: { galleryMinWidth: 200, galleryMaxWidth: 500 },
// postprocessing: { galleryMinWidth: 200, galleryMaxWidth: 500 },
diff --git a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Seed/RandomizeSeed.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Seed/RandomizeSeed.tsx
index ebb6196c23..576ac61aba 100644
--- a/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Seed/RandomizeSeed.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/Seed/RandomizeSeed.tsx
@@ -1,4 +1,4 @@
-import { ChangeEvent } from 'react';
+import { ChangeEvent, memo } from 'react';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
@@ -7,7 +7,27 @@ import { setShouldRandomizeSeed } from 'features/parameters/store/generationSlic
import { useTranslation } from 'react-i18next';
import { Switch } from '@chakra-ui/react';
-export default function RandomizeSeed() {
+// export default function RandomizeSeed() {
+// const dispatch = useAppDispatch();
+// const { t } = useTranslation();
+
+// const shouldRandomizeSeed = useAppSelector(
+// (state: RootState) => state.generation.shouldRandomizeSeed
+// );
+
+// const handleChangeShouldRandomizeSeed = (e: ChangeEvent) =>
+// dispatch(setShouldRandomizeSeed(e.target.checked));
+
+// return (
+//
+// );
+// }
+
+const SeedToggle = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
@@ -16,13 +36,15 @@ export default function RandomizeSeed() {
);
const handleChangeShouldRandomizeSeed = (e: ChangeEvent) =>
- dispatch(setShouldRandomizeSeed(e.target.checked));
+ dispatch(setShouldRandomizeSeed(!e.target.checked));
return (
);
-}
+};
+
+export default memo(SeedToggle);
diff --git a/invokeai/frontend/web/src/features/parameters/components/ImageDimensions/AspectRatioPreview.tsx b/invokeai/frontend/web/src/features/parameters/components/ImageDimensions/AspectRatioPreview.tsx
new file mode 100644
index 0000000000..ecf4a6713e
--- /dev/null
+++ b/invokeai/frontend/web/src/features/parameters/components/ImageDimensions/AspectRatioPreview.tsx
@@ -0,0 +1,75 @@
+import { Flex, Text } from '@chakra-ui/react';
+import { memo, useMemo } from 'react';
+
+export const ratioToCSSString = (
+ ratio: AspectRatio,
+ orientation: Orientation
+) => {
+ if (orientation === 'portrait') {
+ return `${ratio[0]}/${ratio[1]}`;
+ }
+ return `${ratio[1]}/${ratio[0]}`;
+};
+
+export const ratioToDisplayString = (
+ ratio: AspectRatio,
+ orientation: Orientation
+) => {
+ if (orientation === 'portrait') {
+ return `${ratio[0]}:${ratio[1]}`;
+ }
+ return `${ratio[1]}:${ratio[0]}`;
+};
+
+type AspectRatioPreviewProps = {
+ ratio: AspectRatio;
+ orientation: Orientation;
+ size: string;
+};
+
+export type AspectRatio = [number, number];
+
+export type Orientation = 'portrait' | 'landscape';
+
+const AspectRatioPreview = (props: AspectRatioPreviewProps) => {
+ const { ratio, size, orientation } = props;
+
+ const ratioCSSString = useMemo(() => {
+ if (orientation === 'portrait') {
+ return `${ratio[0]}/${ratio[1]}`;
+ }
+ return `${ratio[1]}/${ratio[0]}`;
+ }, [ratio, orientation]);
+
+ const ratioDisplayString = useMemo(() => `${ratio[0]}:${ratio[1]}`, [ratio]);
+
+ return (
+
+
+
+ {ratioDisplayString}
+
+
+
+ );
+};
+
+export default memo(AspectRatioPreview);
diff --git a/invokeai/frontend/web/src/features/parameters/components/ImageDimensions/DimensionsSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/ImageDimensions/DimensionsSettings.tsx
new file mode 100644
index 0000000000..b6b1e206b6
--- /dev/null
+++ b/invokeai/frontend/web/src/features/parameters/components/ImageDimensions/DimensionsSettings.tsx
@@ -0,0 +1,76 @@
+import { Box, Flex, FormControl, FormLabel, Select } from '@chakra-ui/react';
+import { createSelector } from '@reduxjs/toolkit';
+import { RootState } from 'app/store';
+import { useAppDispatch, useAppSelector } from 'app/storeHooks';
+import IAISlider from 'common/components/IAISlider';
+import { setWidth } from 'features/parameters/store/generationSlice';
+import { memo, useState } from 'react';
+import AspectRatioPreview, {
+ AspectRatio,
+ Orientation,
+} from './AspectRatioPreview';
+
+const RATIOS: AspectRatio[] = [
+ [1, 1],
+ [5, 4],
+ [3, 2],
+ [16, 10],
+ [16, 9],
+];
+
+RATIOS.forEach((r) => {
+ const float = r[0] / r[1];
+ console.log((512 * float) / 8);
+});
+
+const dimensionsSettingsSelector = createSelector(
+ (state: RootState) => state.generation,
+ (generation) => {
+ const { width, height } = generation;
+
+ return { width, height };
+ }
+);
+
+const DimensionsSettings = () => {
+ const { width, height } = useAppSelector(dimensionsSettingsSelector);
+ const dispatch = useAppDispatch();
+ const [ratioIndex, setRatioIndex] = useState(4);
+ const [orientation, setOrientation] = useState('portrait');
+
+ return (
+
+
+
+
+
+ Aspect Ratio
+
+
+ {
+ dispatch(setWidth(v));
+ }}
+ />
+
+ );
+};
+
+export default memo(DimensionsSettings);
diff --git a/invokeai/frontend/web/src/features/parameters/components/MainParameters/HeightSlider.tsx b/invokeai/frontend/web/src/features/parameters/components/MainParameters/HeightSlider.tsx
new file mode 100644
index 0000000000..87f1842f78
--- /dev/null
+++ b/invokeai/frontend/web/src/features/parameters/components/MainParameters/HeightSlider.tsx
@@ -0,0 +1,38 @@
+import { Box, BoxProps } from '@chakra-ui/react';
+import { RootState } from 'app/store';
+import { useAppDispatch, useAppSelector } from 'app/storeHooks';
+import IAISlider from 'common/components/IAISlider';
+import { setHeight } from 'features/parameters/store/generationSlice';
+import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
+import { memo } from 'react';
+
+import { useTranslation } from 'react-i18next';
+
+const HeightSlider = (props: BoxProps) => {
+ const height = useAppSelector((state: RootState) => state.generation.height);
+ const shift = useAppSelector((state: RootState) => state.hotkeys.shift);
+ const activeTabName = useAppSelector(activeTabNameSelector);
+ const dispatch = useAppDispatch();
+ const { t } = useTranslation();
+
+ return (
+
+ dispatch(setHeight(v))}
+ handleReset={() => dispatch(setHeight(512))}
+ withInput
+ withReset
+ withSliderMarks
+ sliderNumberInputProps={{ max: 15360 }}
+ />
+
+ );
+};
+
+export default memo(HeightSlider);
diff --git a/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainHeight.tsx
index 8dbf70eab5..744e3a0967 100644
--- a/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainHeight.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainHeight.tsx
@@ -23,7 +23,7 @@ export default function MainHeight() {
label={t('parameters.height')}
value={height}
min={64}
- step={64}
+ step={8}
max={2048}
onChange={(v) => dispatch(setHeight(v))}
handleReset={() => dispatch(setHeight(512))}
diff --git a/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainSampler.tsx b/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainSampler.tsx
index 0f55cca12a..5e010d7912 100644
--- a/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainSampler.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainSampler.tsx
@@ -1,3 +1,4 @@
+import { Box, BoxProps } from '@chakra-ui/react';
import { DIFFUSERS_SAMPLERS, SAMPLERS } from 'app/constants';
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
@@ -7,7 +8,7 @@ import { activeModelSelector } from 'features/system/store/systemSelectors';
import { ChangeEvent } from 'react';
import { useTranslation } from 'react-i18next';
-export default function MainSampler() {
+export default function MainSampler(props: BoxProps) {
const sampler = useAppSelector(
(state: RootState) => state.generation.sampler
);
@@ -19,14 +20,16 @@ export default function MainSampler() {
dispatch(setSampler(e.target.value));
return (
-
+
+
+
);
}
diff --git a/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainSettings.tsx
index cad30e58e6..5ed0076153 100644
--- a/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainSettings.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainSettings.tsx
@@ -1,12 +1,15 @@
-import { Flex, VStack } from '@chakra-ui/react';
+import { Divider, Flex, VStack } from '@chakra-ui/react';
import { RootState } from 'app/store';
import { useAppSelector } from 'app/storeHooks';
+import { ModelSelect } from 'exports';
+import HeightSlider from './HeightSlider';
import MainCFGScale from './MainCFGScale';
import MainHeight from './MainHeight';
import MainIterations from './MainIterations';
import MainSampler from './MainSampler';
import MainSteps from './MainSteps';
import MainWidth from './MainWidth';
+import WidthSlider from './WidthSlider';
export default function MainSettings() {
const shouldUseSliders = useAppSelector(
@@ -23,17 +26,18 @@ export default function MainSettings() {
) : (
-
-
+
+
-
-
-
-
+
+
+
+
+
);
}
diff --git a/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainWidth.tsx b/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainWidth.tsx
index f6045be4e4..81942b83f9 100644
--- a/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainWidth.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/MainParameters/MainWidth.tsx
@@ -22,7 +22,7 @@ export default function MainWidth() {
isDisabled={activeTabName === 'unifiedCanvas'}
label={t('parameters.width')}
value={width}
- min={64}
+ min={8}
step={64}
max={2048}
onChange={(v) => dispatch(setWidth(v))}
diff --git a/invokeai/frontend/web/src/features/parameters/components/MainParameters/WidthSlider.tsx b/invokeai/frontend/web/src/features/parameters/components/MainParameters/WidthSlider.tsx
new file mode 100644
index 0000000000..d450b217f9
--- /dev/null
+++ b/invokeai/frontend/web/src/features/parameters/components/MainParameters/WidthSlider.tsx
@@ -0,0 +1,37 @@
+import { Box, BoxProps } from '@chakra-ui/react';
+import { RootState } from 'app/store';
+import { useAppDispatch, useAppSelector } from 'app/storeHooks';
+import IAISlider from 'common/components/IAISlider';
+import { setWidth } from 'features/parameters/store/generationSlice';
+import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
+import { memo } from 'react';
+import { useTranslation } from 'react-i18next';
+
+const WidthSlider = (props: BoxProps) => {
+ const width = useAppSelector((state: RootState) => state.generation.width);
+ const shift = useAppSelector((state: RootState) => state.hotkeys.shift);
+ const activeTabName = useAppSelector(activeTabNameSelector);
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+
+ return (
+
+ dispatch(setWidth(v))}
+ handleReset={() => dispatch(setWidth(512))}
+ withInput
+ withReset
+ withSliderMarks
+ sliderNumberInputProps={{ max: 15360 }}
+ />
+
+ );
+};
+
+export default memo(WidthSlider);
diff --git a/invokeai/frontend/web/src/features/parameters/components/ParametersAccordion.tsx b/invokeai/frontend/web/src/features/parameters/components/ParametersAccordion.tsx
index a2d12f96ff..726e3fb1fc 100644
--- a/invokeai/frontend/web/src/features/parameters/components/ParametersAccordion.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/ParametersAccordion.tsx
@@ -4,7 +4,10 @@ import { Feature } from 'app/features';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { systemSelector } from 'features/system/store/systemSelectors';
import { tabMap } from 'features/ui/store/tabMap';
-import { uiSelector } from 'features/ui/store/uiSelectors';
+import {
+ activeTabNameSelector,
+ uiSelector,
+} from 'features/ui/store/uiSelectors';
import { openAccordionItemsChanged } from 'features/ui/store/uiSlice';
import { filter, map } from 'lodash';
import { ReactNode, useCallback } from 'react';
@@ -23,7 +26,7 @@ const parametersAccordionSelector = createSelector(
let openAccordions: number[] = [];
- if (tabMap[activeTab] === 'linear') {
+ if (tabMap[activeTab] === 'generate') {
openAccordions = openLinearAccordionItems;
}
diff --git a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx
index d4293c0938..ab1953dcc6 100644
--- a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx
@@ -11,7 +11,7 @@ import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { FaPlay } from 'react-icons/fa';
-import { linearGraphBuilt, sessionCreated } from 'services/thunks/session';
+import { generateGraphBuilt, sessionCreated } from 'services/thunks/session';
interface InvokeButton
extends Omit {
@@ -26,7 +26,7 @@ export default function InvokeButton(props: InvokeButton) {
const handleClickGenerate = () => {
// dispatch(generateImage(activeTabName));
- dispatch(linearGraphBuilt());
+ dispatch(generateGraphBuilt());
};
const { t } = useTranslation();
diff --git a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx
index 31d228df35..20fe231fec 100644
--- a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx
+++ b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx
@@ -1,4 +1,4 @@
-import { Flex } from '@chakra-ui/react';
+import { Box, BoxProps, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { ChangeEvent } from 'react';
import { isEqual } from 'lodash';
@@ -30,7 +30,7 @@ const selector = createSelector(
}
);
-const ModelSelect = () => {
+const ModelSelect = (props: BoxProps) => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const { allModelNames, selectedModel } = useAppSelector(selector);
@@ -39,12 +39,9 @@ const ModelSelect = () => {
};
return (
-
+
{
validValues={allModelNames}
onChange={handleChangeModel}
/>
-
+
);
};
diff --git a/invokeai/frontend/web/src/features/system/components/SiteHeader.tsx b/invokeai/frontend/web/src/features/system/components/SiteHeader.tsx
index f407206b67..af7a4ce33f 100644
--- a/invokeai/frontend/web/src/features/system/components/SiteHeader.tsx
+++ b/invokeai/frontend/web/src/features/system/components/SiteHeader.tsx
@@ -34,8 +34,6 @@ const SiteHeader = () => {
>
-
-
{resolution === 'desktop' ? (
) : (
diff --git a/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx b/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx
index 06ac904bb1..77855cd05f 100644
--- a/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx
@@ -39,7 +39,7 @@ export const floatingParametersPanelButtonSelector = createSelector(
const shouldShowParametersPanelButton =
!canvasBetaLayoutCheck &&
(!shouldPinParametersPanel || !shouldShowParametersPanel) &&
- ['linear', 'unifiedCanvas'].includes(activeTabName);
+ ['generate', 'unifiedCanvas'].includes(activeTabName);
return {
shouldPinParametersPanel,
diff --git a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx
index 11dddc48bf..96545c2b9b 100644
--- a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx
@@ -23,8 +23,10 @@ import { useTranslation } from 'react-i18next';
import { ResourceKey } from 'i18next';
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
import NodeEditor from 'features/nodes/components/NodeEditor';
-import LinearWorkspace from './tabs/Linear/LinearWorkspace';
+import GenerateWorkspace from './tabs/Generate/GenerateWorkspace';
import { FaImage } from 'react-icons/fa';
+import { createSelector } from '@reduxjs/toolkit';
+import { BsLightningChargeFill, BsLightningFill } from 'react-icons/bs';
export interface InvokeTabInfo {
id: InvokeTabName;
@@ -36,30 +38,36 @@ const tabIconStyles: ChakraProps['sx'] = {
boxSize: 6,
};
-const buildTabs = (disabledTabs: InvokeTabName[]): InvokeTabInfo[] => {
- const tabs: InvokeTabInfo[] = [
- {
- id: 'linear',
- icon: ,
- workarea: ,
- },
- {
- id: 'unifiedCanvas',
- icon: ,
- workarea: ,
- },
- {
- id: 'nodes',
- icon: ,
- workarea: ,
- },
- ];
- return tabs.filter((tab) => !disabledTabs.includes(tab.id));
-};
+const tabs: InvokeTabInfo[] = [
+ {
+ id: 'generate',
+ icon: ,
+ workarea: ,
+ },
+ {
+ id: 'unifiedCanvas',
+ icon: ,
+ workarea: ,
+ },
+ {
+ id: 'nodes',
+ icon: ,
+ workarea: ,
+ },
+];
+
+const enabledTabsSelector = createSelector(
+ (state: RootState) => state.ui,
+ (ui) => {
+ const { disabledTabs } = ui;
+
+ return tabs.filter((tab) => !disabledTabs.includes(tab.id));
+ }
+);
export default function InvokeTabs() {
const activeTab = useAppSelector(activeTabIndexSelector);
-
+ const enabledTabs = useAppSelector(enabledTabsSelector);
const isLightBoxOpen = useAppSelector(
(state: RootState) => state.lightbox.isLightboxOpen
);
@@ -72,22 +80,20 @@ export default function InvokeTabs() {
(state: RootState) => state.system.disabledTabs
);
- const activeTabs = buildTabs(disabledTabs);
-
const { t } = useTranslation();
const dispatch = useAppDispatch();
useHotkeys('1', () => {
- dispatch(setActiveTab(0));
+ dispatch(setActiveTab('generate'));
});
useHotkeys('2', () => {
- dispatch(setActiveTab(1));
+ dispatch(setActiveTab('unifiedCanvas'));
});
useHotkeys('3', () => {
- dispatch(setActiveTab(2));
+ dispatch(setActiveTab('nodes'));
});
// Lightbox Hotkey
@@ -111,7 +117,7 @@ export default function InvokeTabs() {
const tabs = useMemo(
() =>
- activeTabs.map((tab) => (
+ enabledTabs.map((tab) => (
)),
- [t, activeTabs]
+ [t, enabledTabs]
);
const tabPanels = useMemo(
() =>
- activeTabs.map((tab) => {tab.workarea}),
- [activeTabs]
+ enabledTabs.map((tab) => (
+ {tab.workarea}
+ )),
+ [enabledTabs]
);
return (
diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Linear/LinearContent.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateContent.tsx
similarity index 86%
rename from invokeai/frontend/web/src/features/ui/components/tabs/Linear/LinearContent.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateContent.tsx
index 8860956aeb..53fdcb4a49 100644
--- a/invokeai/frontend/web/src/features/ui/components/tabs/Linear/LinearContent.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateContent.tsx
@@ -1,7 +1,7 @@
import { Box, Flex } from '@chakra-ui/react';
import CurrentImageDisplay from 'features/gallery/components/CurrentImageDisplay';
-const LinearContent = () => {
+const GenerateContent = () => {
return (
{
);
};
-export default LinearContent;
+export default GenerateContent;
diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Linear/LinearParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateParameters.tsx
similarity index 83%
rename from invokeai/frontend/web/src/features/ui/components/tabs/Linear/LinearParameters.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateParameters.tsx
index 5e6c9f82f3..46cf07d088 100644
--- a/invokeai/frontend/web/src/features/ui/components/tabs/Linear/LinearParameters.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateParameters.tsx
@@ -1,5 +1,16 @@
-import { Flex } from '@chakra-ui/react';
+import {
+ AspectRatio,
+ Box,
+ Flex,
+ Select,
+ Slider,
+ SliderFilledTrack,
+ SliderThumb,
+ SliderTrack,
+ Text,
+} from '@chakra-ui/react';
import { Feature } from 'app/features';
+import IAISlider from 'common/components/IAISlider';
import IAISwitch from 'common/components/IAISwitch';
import ImageToImageSettings from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings';
import ImageToImageToggle from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageToggle';
@@ -10,6 +21,7 @@ import RandomizeSeed from 'features/parameters/components/AdvancedParameters/See
import SeedSettings from 'features/parameters/components/AdvancedParameters/Seed/SeedSettings';
import GenerateVariationsToggle from 'features/parameters/components/AdvancedParameters/Variations/GenerateVariations';
import VariationsSettings from 'features/parameters/components/AdvancedParameters/Variations/VariationsSettings';
+import DimensionsSettings from 'features/parameters/components/ImageDimensions/DimensionsSettings';
import MainSettings from 'features/parameters/components/MainParameters/MainSettings';
import ParametersAccordion, {
ParametersAccordionItems,
@@ -17,14 +29,15 @@ import ParametersAccordion, {
import ProcessButtons from 'features/parameters/components/ProcessButtons/ProcessButtons';
import NegativePromptInput from 'features/parameters/components/PromptInput/NegativePromptInput';
import PromptInput from 'features/parameters/components/PromptInput/PromptInput';
-import { memo, useMemo } from 'react';
+import { findIndex } from 'lodash';
+import { memo, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants';
-const LinearParameters = () => {
+const GenerateParameters = () => {
const { t } = useTranslation();
- const linearAccordions: ParametersAccordionItems = useMemo(
+ const generateAccordionItems: ParametersAccordionItems = useMemo(
() => ({
// general: {
// name: 'general',
@@ -80,15 +93,16 @@ const LinearParameters = () => {
gap: 2,
bg: 'base.800',
p: 4,
+ pb: 6,
borderRadius: 'base',
}}
>
-
-
+
+
);
};
-export default memo(LinearParameters);
+export default memo(GenerateParameters);
diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Linear/LinearWorkspace.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateWorkspace.tsx
similarity index 82%
rename from invokeai/frontend/web/src/features/ui/components/tabs/Linear/LinearWorkspace.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateWorkspace.tsx
index 706cee8247..e6c0c71ae1 100644
--- a/invokeai/frontend/web/src/features/ui/components/tabs/Linear/LinearWorkspace.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateWorkspace.tsx
@@ -1,15 +1,15 @@
import { Box, Flex } from '@chakra-ui/react';
import { useAppSelector } from 'app/storeHooks';
import { memo } from 'react';
-import LinearContent from './LinearContent';
-import LinearParameters from './LinearParameters';
+import GenerateContent from './GenerateContent';
+import GenerateParameters from './GenerateParameters';
import PinParametersPanelButton from '../../PinParametersPanelButton';
import { RootState } from 'app/store';
import Scrollable from '../../common/Scrollable';
import ParametersSlide from '../../common/ParametersSlide';
import AnimatedImageToImagePanel from 'features/parameters/components/AnimatedImageToImagePanel';
-const LinearWorkspace = () => {
+const GenerateWorkspace = () => {
const shouldPinParametersPanel = useAppSelector(
(state: RootState) => state.ui.shouldPinParametersPanel
);
@@ -33,7 +33,7 @@ const LinearWorkspace = () => {
}}
>
-
+
{
) : (
-
+
)}
-
+
);
};
-export default memo(LinearWorkspace);
+export default memo(GenerateWorkspace);
diff --git a/invokeai/frontend/web/src/features/ui/store/hotkeysSlice.ts b/invokeai/frontend/web/src/features/ui/store/hotkeysSlice.ts
new file mode 100644
index 0000000000..65f2011841
--- /dev/null
+++ b/invokeai/frontend/web/src/features/ui/store/hotkeysSlice.ts
@@ -0,0 +1,26 @@
+import type { PayloadAction } from '@reduxjs/toolkit';
+import { createSlice } from '@reduxjs/toolkit';
+
+type HotkeysState = {
+ shift: boolean;
+};
+
+const initialHotkeysState: HotkeysState = {
+ shift: false,
+};
+
+const initialState: HotkeysState = initialHotkeysState;
+
+export const hotkeysSlice = createSlice({
+ name: 'hotkeys',
+ initialState,
+ reducers: {
+ shiftKeyPressed: (state, action: PayloadAction) => {
+ state.shift = action.payload;
+ },
+ },
+});
+
+export const { shiftKeyPressed } = hotkeysSlice.actions;
+
+export default hotkeysSlice.reducer;
diff --git a/invokeai/frontend/web/src/features/ui/store/tabMap.ts b/invokeai/frontend/web/src/features/ui/store/tabMap.ts
index 7584878e02..fe6e2d033a 100644
--- a/invokeai/frontend/web/src/features/ui/store/tabMap.ts
+++ b/invokeai/frontend/web/src/features/ui/store/tabMap.ts
@@ -1,7 +1,7 @@
export const tabMap = [
// 'txt2img',
// 'img2img',
- 'linear',
+ 'generate',
'unifiedCanvas',
'nodes',
// 'postprocessing',
diff --git a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts
index fd8ba78f01..c2795ed648 100644
--- a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts
+++ b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts
@@ -19,6 +19,9 @@ const initialUIState: UIState = {
shouldShowGallery: true,
shouldHidePreview: false,
openLinearAccordionItems: [],
+ disabledParameterPanels: [],
+ disabledTabs: [],
+ openGenerateAccordionItems: [],
openUnifiedCanvasAccordionItems: [],
};
@@ -96,8 +99,8 @@ export const uiSlice = createSlice({
}
},
openAccordionItemsChanged: (state, action: PayloadAction) => {
- if (tabMap[state.activeTab] === 'linear') {
- state.openLinearAccordionItems = action.payload;
+ if (tabMap[state.activeTab] === 'generate') {
+ state.openGenerateAccordionItems = action.payload;
}
if (tabMap[state.activeTab] === 'unifiedCanvas') {
diff --git a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts
index a222aaee88..f519eaa6ec 100644
--- a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts
+++ b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts
@@ -15,5 +15,8 @@ export interface UIState {
shouldPinGallery: boolean;
shouldShowGallery: boolean;
openLinearAccordionItems: number[];
+ disabledParameterPanels: string[];
+ disabledTabs: InvokeTabName[];
+ openGenerateAccordionItems: number[];
openUnifiedCanvasAccordionItems: number[];
}
diff --git a/invokeai/frontend/web/src/services/thunks/session.ts b/invokeai/frontend/web/src/services/thunks/session.ts
index a1213ffcc2..6267f66ac2 100644
--- a/invokeai/frontend/web/src/services/thunks/session.ts
+++ b/invokeai/frontend/web/src/services/thunks/session.ts
@@ -1,13 +1,13 @@
import { createAppAsyncThunk } from 'app/storeUtils';
import { SessionsService } from 'services/api';
-import { buildLinearGraph } from 'features/nodes/util/linearGraphBuilder/buildLinearGraph';
+import { buildLinearGraph as buildGenerateGraph } from 'features/nodes/util/linearGraphBuilder/buildLinearGraph';
import { isAnyOf, isFulfilled } from '@reduxjs/toolkit';
import { buildNodesGraph } from 'features/nodes/util/nodesGraphBuilder/buildNodesGraph';
-export const linearGraphBuilt = createAppAsyncThunk(
- 'api/linearGraphBuilt',
+export const generateGraphBuilt = createAppAsyncThunk(
+ 'api/generateGraphBuilt',
async (_, { dispatch, getState }) => {
- const graph = buildLinearGraph(getState());
+ const graph = buildGenerateGraph(getState());
dispatch(sessionCreated({ graph }));
@@ -27,7 +27,7 @@ export const nodesGraphBuilt = createAppAsyncThunk(
);
export const isFulfilledAnyGraphBuilt = isAnyOf(
- linearGraphBuilt.fulfilled,
+ generateGraphBuilt.fulfilled,
nodesGraphBuilt.fulfilled
);