feat(ui): wip img2img ui

This commit is contained in:
psychedelicious 2023-04-24 20:34:24 +10:00
parent 568f0aad71
commit d40d5276dd
30 changed files with 453 additions and 100 deletions

View File

@ -98,7 +98,8 @@
"pinOptionsPanel": "Pin Options Panel",
"loading": "Loading",
"loadingInvokeAI": "Loading Invoke AI",
"random": "Random"
"random": "Random",
"generate": "Generate"
},
"gallery": {
"generations": "Generations",

View File

@ -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(

View File

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

View File

@ -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) => {

View File

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

View File

@ -59,9 +59,6 @@ const CurrentImageDisplay = () => {
justifyContent: 'center',
}}
>
<Box sx={{ position: 'absolute', top: 0 }}>
<CurrentImageButtons />
</Box>
<Flex
sx={{
w: 'full',
@ -83,6 +80,9 @@ const CurrentImageDisplay = () => {
/>
)}
</Flex>
<Box sx={{ position: 'absolute', top: 0 }}>
<CurrentImageButtons />
</Box>
</Flex>
);
};

View File

@ -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 },

View File

@ -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<HTMLInputElement>) =>
// dispatch(setShouldRandomizeSeed(e.target.checked));
// return (
// <Switch
// aria-label={t('parameters.randomizeSeed')}
// isChecked={shouldRandomizeSeed}
// onChange={handleChangeShouldRandomizeSeed}
// />
// );
// }
const SeedToggle = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
@ -16,13 +36,15 @@ export default function RandomizeSeed() {
);
const handleChangeShouldRandomizeSeed = (e: ChangeEvent<HTMLInputElement>) =>
dispatch(setShouldRandomizeSeed(e.target.checked));
dispatch(setShouldRandomizeSeed(!e.target.checked));
return (
<Switch
aria-label={t('parameters.randomizeSeed')}
isChecked={shouldRandomizeSeed}
isChecked={!shouldRandomizeSeed}
onChange={handleChangeShouldRandomizeSeed}
/>
);
}
};
export default memo(SeedToggle);

View File

@ -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 (
<Flex
sx={{
alignItems: 'center',
justifyContent: 'center',
w: size,
h: size,
}}
>
<Flex
sx={{
alignItems: 'center',
justifyContent: 'center',
bg: 'base.700',
color: 'base.400',
borderRadius: 'base',
aspectRatio: ratioCSSString,
objectFit: 'contain',
...(orientation === 'landscape' ? { h: 'full' } : { w: 'full' }),
}}
>
<Text sx={{ size: 'xs', userSelect: 'none' }}>
{ratioDisplayString}
</Text>
</Flex>
</Flex>
);
};
export default memo(AspectRatioPreview);

View File

@ -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<Orientation>('portrait');
return (
<Flex gap={3}>
<Box flexShrink={0}>
<AspectRatioPreview
ratio={RATIOS[ratioIndex]}
orientation={orientation}
size="4rem"
/>
</Box>
<FormControl>
<FormLabel>Aspect Ratio</FormLabel>
<Select
onChange={(e) => {
setRatioIndex(Number(e.target.value));
}}
>
{RATIOS.map((r, i) => (
<option key={r.join()} value={i}>{`${r[0]}:${r[1]}`}</option>
))}
</Select>
</FormControl>
<IAISlider
label="Size"
value={width}
min={64}
max={2048}
step={8}
onChange={(v) => {
dispatch(setWidth(v));
}}
/>
</Flex>
);
};
export default memo(DimensionsSettings);

View File

@ -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 (
<Box {...props}>
<IAISlider
isDisabled={activeTabName === 'unifiedCanvas'}
label={t('parameters.height')}
value={height}
min={64}
step={shift ? 8 : 64}
max={2048}
onChange={(v) => dispatch(setHeight(v))}
handleReset={() => dispatch(setHeight(512))}
withInput
withReset
withSliderMarks
sliderNumberInputProps={{ max: 15360 }}
/>
</Box>
);
};
export default memo(HeightSlider);

View File

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

View File

@ -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 (
<IAISelect
label={t('parameters.sampler')}
value={sampler}
onChange={handleChangeSampler}
validValues={
activeModel.format === 'diffusers' ? DIFFUSERS_SAMPLERS : SAMPLERS
}
minWidth={36}
/>
<Box {...props}>
<IAISelect
label={t('parameters.sampler')}
value={sampler}
onChange={handleChangeSampler}
validValues={
activeModel.format === 'diffusers' ? DIFFUSERS_SAMPLERS : SAMPLERS
}
minWidth={36}
/>
</Box>
);
}

View File

@ -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() {
<MainSampler />
</VStack>
) : (
<Flex rowGap={2} flexDirection="column">
<Flex columnGap={1}>
<Flex gap={3} flexDirection="column">
<Flex gap={3}>
<MainIterations />
<MainSteps />
<MainCFGScale />
</Flex>
<Flex columnGap={1}>
<MainWidth />
<MainHeight />
<MainSampler />
<Flex gap={3}>
<MainSampler flexGrow={2} />
<ModelSelect flexGrow={3} />
</Flex>
<WidthSlider />
<HeightSlider />
</Flex>
);
}

View File

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

View File

@ -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 (
<Box {...props}>
<IAISlider
isDisabled={activeTabName === 'unifiedCanvas'}
label={t('parameters.width')}
value={width}
min={64}
step={shift ? 8 : 64}
max={2048}
onChange={(v) => dispatch(setWidth(v))}
handleReset={() => dispatch(setWidth(512))}
withInput
withReset
withSliderMarks
sliderNumberInputProps={{ max: 15360 }}
/>
</Box>
);
};
export default memo(WidthSlider);

View File

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

View File

@ -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<IAIButtonProps | IAIIconButtonProps, 'aria-label'> {
@ -26,7 +26,7 @@ export default function InvokeButton(props: InvokeButton) {
const handleClickGenerate = () => {
// dispatch(generateImage(activeTabName));
dispatch(linearGraphBuilt());
dispatch(generateGraphBuilt());
};
const { t } = useTranslation();

View File

@ -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 (
<Flex
style={{
paddingInlineStart: 1.5,
}}
>
<Box {...props}>
<IAISelect
label={t('modelManager.model')}
style={{ fontSize: 'sm' }}
aria-label={t('accessibility.modelSelect')}
tooltip={selectedModel?.description || ''}
@ -52,7 +49,7 @@ const ModelSelect = () => {
validValues={allModelNames}
onChange={handleChangeModel}
/>
</Flex>
</Box>
);
};

View File

@ -34,8 +34,6 @@ const SiteHeader = () => {
>
<StatusIndicator />
<ModelSelect />
{resolution === 'desktop' ? (
<SiteHeaderMenu />
) : (

View File

@ -39,7 +39,7 @@ export const floatingParametersPanelButtonSelector = createSelector(
const shouldShowParametersPanelButton =
!canvasBetaLayoutCheck &&
(!shouldPinParametersPanel || !shouldShowParametersPanel) &&
['linear', 'unifiedCanvas'].includes(activeTabName);
['generate', 'unifiedCanvas'].includes(activeTabName);
return {
shouldPinParametersPanel,

View File

@ -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: <Icon as={FaImage} sx={tabIconStyles} />,
workarea: <LinearWorkspace />,
},
{
id: 'unifiedCanvas',
icon: <Icon as={MdGridOn} sx={tabIconStyles} />,
workarea: <UnifiedCanvasWorkarea />,
},
{
id: 'nodes',
icon: <Icon as={MdDeviceHub} sx={tabIconStyles} />,
workarea: <NodeEditor />,
},
];
return tabs.filter((tab) => !disabledTabs.includes(tab.id));
};
const tabs: InvokeTabInfo[] = [
{
id: 'generate',
icon: <Icon as={BsLightningChargeFill} sx={{ boxSize: 5 }} />,
workarea: <GenerateWorkspace />,
},
{
id: 'unifiedCanvas',
icon: <Icon as={MdGridOn} sx={{ boxSize: 6 }} />,
workarea: <UnifiedCanvasWorkarea />,
},
{
id: 'nodes',
icon: <Icon as={MdDeviceHub} sx={{ boxSize: 6 }} />,
workarea: <NodeEditor />,
},
];
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) => (
<Tooltip
key={tab.id}
hasArrow
@ -126,13 +132,15 @@ export default function InvokeTabs() {
</Tab>
</Tooltip>
)),
[t, activeTabs]
[t, enabledTabs]
);
const tabPanels = useMemo(
() =>
activeTabs.map((tab) => <TabPanel key={tab.id}>{tab.workarea}</TabPanel>),
[activeTabs]
enabledTabs.map((tab) => (
<TabPanel key={tab.id}>{tab.workarea}</TabPanel>
)),
[enabledTabs]
);
return (

View File

@ -1,7 +1,7 @@
import { Box, Flex } from '@chakra-ui/react';
import CurrentImageDisplay from 'features/gallery/components/CurrentImageDisplay';
const LinearContent = () => {
const GenerateContent = () => {
return (
<Box
sx={{
@ -18,4 +18,4 @@ const LinearContent = () => {
);
};
export default LinearContent;
export default GenerateContent;

View File

@ -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',
}}
>
<MainSettings />
<ImageToImageToggle />
</Flex>
<ParametersAccordion accordionItems={linearAccordions} />
<ImageToImageToggle />
<ParametersAccordion accordionItems={generateAccordionItems} />
</Flex>
);
};
export default memo(LinearParameters);
export default memo(GenerateParameters);

View File

@ -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 = () => {
}}
>
<Scrollable>
<LinearParameters />
<GenerateParameters />
</Scrollable>
<PinParametersPanelButton
sx={{ position: 'absolute', top: 0, insetInlineEnd: 0 }}
@ -42,12 +42,12 @@ const LinearWorkspace = () => {
</Flex>
) : (
<ParametersSlide>
<LinearParameters />
<GenerateParameters />
</ParametersSlide>
)}
<LinearContent />
<GenerateContent />
</Flex>
);
};
export default memo(LinearWorkspace);
export default memo(GenerateWorkspace);

View File

@ -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<boolean>) => {
state.shift = action.payload;
},
},
});
export const { shiftKeyPressed } = hotkeysSlice.actions;
export default hotkeysSlice.reducer;

View File

@ -1,7 +1,7 @@
export const tabMap = [
// 'txt2img',
// 'img2img',
'linear',
'generate',
'unifiedCanvas',
'nodes',
// 'postprocessing',

View File

@ -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<number[]>) => {
if (tabMap[state.activeTab] === 'linear') {
state.openLinearAccordionItems = action.payload;
if (tabMap[state.activeTab] === 'generate') {
state.openGenerateAccordionItems = action.payload;
}
if (tabMap[state.activeTab] === 'unifiedCanvas') {

View File

@ -15,5 +15,8 @@ export interface UIState {
shouldPinGallery: boolean;
shouldShowGallery: boolean;
openLinearAccordionItems: number[];
disabledParameterPanels: string[];
disabledTabs: InvokeTabName[];
openGenerateAccordionItems: number[];
openUnifiedCanvasAccordionItems: number[];
}

View File

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