mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
fix path for style_preset_images, fix png type when converting blobs to files, built view mode components
This commit is contained in:
parent
0b0abfbe8f
commit
9a4d075074
@ -47,11 +47,11 @@ async def get_style_preset(
|
||||
},
|
||||
)
|
||||
async def update_style_preset(
|
||||
image: Optional[UploadFile] = File(description="The image file to upload", default=None),
|
||||
style_preset_id: str = Path(description="The id of the style preset to update"),
|
||||
name: str = Form(description="The name of the style preset to create"),
|
||||
positive_prompt: str = Form(description="The positive prompt of the style preset"),
|
||||
negative_prompt: str = Form(description="The negative prompt of the style preset"),
|
||||
image: Optional[UploadFile] = File(description="The image file to upload", default=None),
|
||||
) -> StylePresetRecordWithImage:
|
||||
"""Updates a style preset"""
|
||||
if image is not None:
|
||||
@ -73,8 +73,8 @@ async def update_style_preset(
|
||||
else:
|
||||
try:
|
||||
ApiDependencies.invoker.services.style_preset_images_service.delete(style_preset_id)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=409, detail=str(e))
|
||||
except StylePresetImageFileNotFoundException:
|
||||
pass
|
||||
|
||||
preset_data = PresetData(positive_prompt=positive_prompt, negative_prompt=negative_prompt)
|
||||
changes = StylePresetChanges(name=name, preset_data=preset_data)
|
||||
|
@ -153,7 +153,7 @@ class InvokeAIAppConfig(BaseSettings):
|
||||
db_dir: Path = Field(default=Path("databases"), description="Path to InvokeAI databases directory.")
|
||||
outputs_dir: Path = Field(default=Path("outputs"), description="Path to directory for outputs.")
|
||||
custom_nodes_dir: Path = Field(default=Path("nodes"), description="Path to directory for custom nodes.")
|
||||
style_preset_images_path: Path = Field(default=Path("style_preset_images"), description="Path to directory for style preset images.")
|
||||
style_preset_images_dir: Path = Field(default=Path("style_preset_images"), description="Path to directory for style preset images.")
|
||||
|
||||
# LOGGING
|
||||
log_handlers: list[str] = Field(default=["console"], description='Log handler. Valid options are "console", "file=<path>", "syslog=path|address:host:port", "http=<url>".')
|
||||
@ -301,6 +301,11 @@ class InvokeAIAppConfig(BaseSettings):
|
||||
"""Path to the models directory, resolved to an absolute path.."""
|
||||
return self._resolve(self.models_dir)
|
||||
|
||||
@property
|
||||
def style_preset_images_path(self) -> Path:
|
||||
"""Path to the style preset images directory, resolved to an absolute path.."""
|
||||
return self._resolve(self.style_preset_images_dir)
|
||||
|
||||
@property
|
||||
def convert_cache_path(self) -> Path:
|
||||
"""Path to the converted cache models directory, resolved to an absolute path.."""
|
||||
|
@ -13,12 +13,14 @@ export const useImageUrlToBlob = () => {
|
||||
new Promise<Blob | null>((resolve) => {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
console.log("on load")
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
|
||||
const context = canvas.getContext('2d');
|
||||
if (!context) {
|
||||
console.log("no context")
|
||||
return;
|
||||
}
|
||||
context.drawImage(img, 0, 0);
|
||||
|
@ -6,6 +6,7 @@ import { useCopyImageToClipboard } from 'common/hooks/useCopyImageToClipboard';
|
||||
import { useDownloadImage } from 'common/hooks/useDownloadImage';
|
||||
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
||||
import { imagesToChangeSelected, isModalOpenChanged } from 'features/changeBoardModal/store/slice';
|
||||
import { isModalOpenChanged as isStylePresetModalOpenChanged } from 'features/stylePresets/store/stylePresetModalSlice';
|
||||
import { iiLayerAdded } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
|
||||
import { useImageActions } from 'features/gallery/hooks/useImageActions';
|
||||
@ -58,8 +59,17 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
||||
const { downloadImage } = useDownloadImage();
|
||||
const templates = useStore($templates);
|
||||
|
||||
const { recallAll, remix, recallSeed, recallPrompts, hasMetadata, hasSeed, hasPrompts, isLoadingMetadata } =
|
||||
useImageActions(imageDTO?.image_name);
|
||||
const {
|
||||
recallAll,
|
||||
remix,
|
||||
recallSeed,
|
||||
recallPrompts,
|
||||
hasMetadata,
|
||||
hasSeed,
|
||||
hasPrompts,
|
||||
isLoadingMetadata,
|
||||
createAsPreset,
|
||||
} = useImageActions(imageDTO?.image_name);
|
||||
|
||||
const { getAndLoadEmbeddedWorkflow, getAndLoadEmbeddedWorkflowResult } = useGetAndLoadEmbeddedWorkflow({});
|
||||
|
||||
@ -133,11 +143,6 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
||||
dispatch(setActiveTab('upscaling'));
|
||||
}, [dispatch, imageDTO]);
|
||||
|
||||
const handleCreatePreset = useCallback(() => {
|
||||
dispatch(createPresetFromImageChanged(imageDTO));
|
||||
dispatch(isMenuOpenChanged(true));
|
||||
}, [dispatch, imageDTO]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuItem as="a" href={imageDTO.image_url} target="_blank" icon={<PiShareFatBold />}>
|
||||
@ -192,7 +197,7 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={isLoadingMetadata ? <SpinnerIcon /> : <PiPaintBrushBold />}
|
||||
onClickCapture={handleCreatePreset}
|
||||
onClickCapture={createAsPreset}
|
||||
isDisabled={isLoadingMetadata || !hasPrompts}
|
||||
>
|
||||
Create Preset
|
||||
|
@ -1,8 +1,12 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { handlers, parseAndRecallAllMetadata, parseAndRecallPrompts } from 'features/metadata/util/handlers';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useDebouncedMetadata } from 'services/api/hooks/useDebouncedMetadata';
|
||||
import { useImageUrlToBlob } from '../../../common/hooks/useImageUrlToBlob';
|
||||
import { prefilledFormDataChanged, isModalOpenChanged } from '../../stylePresets/store/stylePresetModalSlice';
|
||||
import { useGetImageDTOQuery } from '../../../services/api/endpoints/images';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
|
||||
export const useImageActions = (image_name?: string) => {
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
@ -10,6 +14,9 @@ export const useImageActions = (image_name?: string) => {
|
||||
const [hasMetadata, setHasMetadata] = useState(false);
|
||||
const [hasSeed, setHasSeed] = useState(false);
|
||||
const [hasPrompts, setHasPrompts] = useState(false);
|
||||
const imageUrlToBlob = useImageUrlToBlob();
|
||||
const dispatch = useAppDispatch()
|
||||
const { data: imageDTO } = useGetImageDTOQuery(image_name ?? skipToken)
|
||||
|
||||
useEffect(() => {
|
||||
const parseMetadata = async () => {
|
||||
@ -61,5 +68,17 @@ export const useImageActions = (image_name?: string) => {
|
||||
parseAndRecallPrompts(metadata);
|
||||
}, [metadata]);
|
||||
|
||||
return { recallAll, remix, recallSeed, recallPrompts, hasMetadata, hasSeed, hasPrompts, isLoadingMetadata };
|
||||
const createAsPreset = useCallback(async () => {
|
||||
if (image_name && metadata && imageDTO) {
|
||||
const positivePrompt = await handlers.positivePrompt.parse(metadata)
|
||||
const negativePrompt = await handlers.negativePrompt.parse(metadata)
|
||||
const imageBlob = await imageUrlToBlob(imageDTO.image_url)
|
||||
|
||||
dispatch(prefilledFormDataChanged({ name: "", positivePrompt, negativePrompt, image: imageBlob ? new File([imageBlob], "stylePreset.png", { type: 'image/png', }) : null }))
|
||||
dispatch(isModalOpenChanged(true))
|
||||
}
|
||||
|
||||
}, [image_name, metadata, dispatch, imageDTO])
|
||||
|
||||
return { recallAll, remix, recallSeed, recallPrompts, hasMetadata, hasSeed, hasPrompts, isLoadingMetadata, createAsPreset };
|
||||
};
|
||||
|
@ -125,7 +125,7 @@ const parseCreatedBy: MetadataParseFunc<string> = (metadata) => getProperty(meta
|
||||
|
||||
const parseGenerationMode: MetadataParseFunc<string> = (metadata) => getProperty(metadata, 'generation_mode', isString);
|
||||
|
||||
const parsePositivePrompt: MetadataParseFunc<ParameterPositivePrompt> = (metadata) =>
|
||||
export const parsePositivePrompt: MetadataParseFunc<ParameterPositivePrompt> = (metadata) =>
|
||||
getProperty(metadata, 'positive_prompt', isParameterPositivePrompt);
|
||||
|
||||
const parseNegativePrompt: MetadataParseFunc<ParameterNegativePrompt> = (metadata) =>
|
||||
|
@ -7,10 +7,15 @@ import { PromptPopover } from 'features/prompt/PromptPopover';
|
||||
import { usePrompt } from 'features/prompt/usePrompt';
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ViewModePrompt } from '../Prompts/ViewModePrompt';
|
||||
|
||||
const DEFAULT_HEIGHT = 20;
|
||||
|
||||
export const ParamNegativePrompt = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const prompt = useAppSelector((s) => s.controlLayers.present.negativePrompt);
|
||||
const viewMode = useAppSelector((s) => s.stylePreset.viewMode);
|
||||
const activeStylePreset = useAppSelector((s) => s.stylePreset.activeStylePreset);
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const { t } = useTranslation();
|
||||
const _onChange = useCallback(
|
||||
@ -25,6 +30,16 @@ export const ParamNegativePrompt = memo(() => {
|
||||
onChange: _onChange,
|
||||
});
|
||||
|
||||
if (viewMode) {
|
||||
return (
|
||||
<ViewModePrompt
|
||||
prompt={prompt}
|
||||
presetPrompt={activeStylePreset?.preset_data.negative_prompt || ''}
|
||||
height={DEFAULT_HEIGHT}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PromptPopover isOpen={isOpen} onClose={onClose} onSelect={onSelect} width={textareaRef.current?.clientWidth}>
|
||||
<Box pos="relative">
|
||||
@ -39,6 +54,7 @@ export const ParamNegativePrompt = memo(() => {
|
||||
fontSize="sm"
|
||||
variant="darkFilled"
|
||||
paddingRight={30}
|
||||
minH={DEFAULT_HEIGHT}
|
||||
/>
|
||||
<PromptOverlayButtonWrapper>
|
||||
<AddPromptTriggerButton isOpen={isOpen} onOpen={onOpen} />
|
||||
|
@ -11,11 +11,16 @@ import { memo, useCallback, useRef } from 'react';
|
||||
import type { HotkeyCallback } from 'react-hotkeys-hook';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ViewModePrompt } from '../Prompts/ViewModePrompt';
|
||||
|
||||
const DEFAULT_HEIGHT = 28;
|
||||
|
||||
export const ParamPositivePrompt = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const prompt = useAppSelector((s) => s.controlLayers.present.positivePrompt);
|
||||
const baseModel = useAppSelector((s) => s.generation.model)?.base;
|
||||
const viewMode = useAppSelector((s) => s.stylePreset.viewMode);
|
||||
const activeStylePreset = useAppSelector((s) => s.stylePreset.activeStylePreset);
|
||||
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const { t } = useTranslation();
|
||||
@ -41,6 +46,16 @@ export const ParamPositivePrompt = memo(() => {
|
||||
|
||||
useHotkeys('alt+a', focus, []);
|
||||
|
||||
if (viewMode) {
|
||||
return (
|
||||
<ViewModePrompt
|
||||
prompt={prompt}
|
||||
presetPrompt={activeStylePreset?.preset_data.positive_prompt || ''}
|
||||
height={DEFAULT_HEIGHT}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PromptPopover isOpen={isOpen} onClose={onClose} onSelect={onSelect} width={textareaRef.current?.clientWidth}>
|
||||
<Box pos="relative">
|
||||
@ -51,7 +66,7 @@ export const ParamPositivePrompt = memo(() => {
|
||||
value={prompt}
|
||||
placeholder={t('parameters.globalPositivePromptPlaceholder')}
|
||||
onChange={onChange}
|
||||
minH={28}
|
||||
minH={DEFAULT_HEIGHT}
|
||||
onKeyDown={onKeyDown}
|
||||
variant="darkFilled"
|
||||
paddingRight={30}
|
||||
|
@ -7,7 +7,6 @@ import { ParamPositivePrompt } from 'features/parameters/components/Core/ParamPo
|
||||
import { selectGenerationSlice } from 'features/parameters/store/generationSlice';
|
||||
import { ParamSDXLNegativeStylePrompt } from 'features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt';
|
||||
import { ParamSDXLPositiveStylePrompt } from 'features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt';
|
||||
import { usePresetModifiedPrompts } from 'features/stylePresets/hooks/usePresetModifiedPrompts';
|
||||
import { memo } from 'react';
|
||||
|
||||
const concatPromptsSelector = createSelector(
|
||||
@ -19,14 +18,11 @@ const concatPromptsSelector = createSelector(
|
||||
|
||||
export const Prompts = memo(() => {
|
||||
const shouldConcatPrompts = useAppSelector(concatPromptsSelector);
|
||||
const { presetModifiedPositivePrompt, presetModifiedNegativePrompt } = usePresetModifiedPrompts();
|
||||
return (
|
||||
<Flex flexDir="column" gap={2}>
|
||||
<ParamPositivePrompt />
|
||||
<Flex>{presetModifiedPositivePrompt}</Flex>
|
||||
{!shouldConcatPrompts && <ParamSDXLPositiveStylePrompt />}
|
||||
<ParamNegativePrompt />
|
||||
<Flex>{presetModifiedNegativePrompt}</Flex>
|
||||
{!shouldConcatPrompts && <ParamSDXLNegativeStylePrompt />}
|
||||
</Flex>
|
||||
);
|
||||
|
@ -0,0 +1,67 @@
|
||||
import { Flex, Icon, Text, Tooltip, Box } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from '../../../../app/store/storeHooks';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { PiEyeBold, PiQuestionBold } from 'react-icons/pi';
|
||||
import { viewModeChanged } from '../../../stylePresets/store/stylePresetSlice';
|
||||
import { getViewModeChunks } from '../../../stylePresets/util/getViewModeChunks';
|
||||
|
||||
export const ViewModePrompt = ({
|
||||
presetPrompt,
|
||||
prompt,
|
||||
height,
|
||||
}: {
|
||||
presetPrompt: string;
|
||||
prompt: string;
|
||||
height: number;
|
||||
}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const presetChunks = useMemo(() => {
|
||||
return getViewModeChunks(prompt, presetPrompt);
|
||||
}, [presetPrompt, prompt]);
|
||||
|
||||
const handleExitViewMode = useCallback(() => {
|
||||
dispatch(viewModeChanged(false));
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
flexDir="column"
|
||||
layerStyle="second"
|
||||
padding="8px 10px"
|
||||
borderRadius="base"
|
||||
height={height}
|
||||
onClick={handleExitViewMode}
|
||||
justifyContent="space-between"
|
||||
position="relative"
|
||||
>
|
||||
<Flex overflow="scroll">
|
||||
<Text fontSize="sm" lineHeight="1rem">
|
||||
{presetChunks.map((chunk, index) => {
|
||||
return (
|
||||
chunk && (
|
||||
<Text as="span" color={index === 1 ? 'white' : 'base.300'}>
|
||||
{chunk.trim()}{' '}
|
||||
</Text>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Box position="absolute" top={0} right={0} backgroundColor="rgba(0,0,0,0.75)" padding="2px 5px">
|
||||
<Flex alignItems="center" gap="1">
|
||||
<Tooltip
|
||||
label={
|
||||
'This is how your prompt will look with your currently selected preset. To edit your prompt, click anywhere in the text box.'
|
||||
}
|
||||
>
|
||||
<Flex>
|
||||
<Icon as={PiEyeBold} color="base.500" boxSize="12px" />
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
};
|
@ -0,0 +1,98 @@
|
||||
import { useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { isNil } from 'lodash-es';
|
||||
import type { ChangeEventHandler, FormEvent, FormEventHandler, KeyboardEventHandler, RefObject, SyntheticEvent } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
|
||||
type UseInsertTriggerArg = {
|
||||
prompt: string;
|
||||
paragraphRef: RefObject<HTMLParagraphElement>;
|
||||
onChange: (v: string) => void;
|
||||
};
|
||||
|
||||
export const usePromptContentEditable = ({ prompt, paragraphRef, onChange: _onChange }: UseInsertTriggerArg) => {
|
||||
const { isOpen, onClose, onOpen } = useDisclosure();
|
||||
|
||||
|
||||
const onChange = useCallback(
|
||||
(e: any) => {
|
||||
e.preventDefault();
|
||||
_onChange(e.data)
|
||||
},
|
||||
[_onChange]
|
||||
);
|
||||
|
||||
|
||||
const insertTrigger = useCallback(
|
||||
(v: string) => {
|
||||
const element = paragraphRef.current;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selection = window.getSelection();
|
||||
if (!selection || selection.rangeCount === 0) {
|
||||
// Insert at the end if no selection found
|
||||
const newPrompt = prompt + v;
|
||||
flushSync(() => {
|
||||
_onChange(newPrompt);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const range = selection.getRangeAt(0);
|
||||
const cursorPosition = range.startOffset;
|
||||
|
||||
|
||||
console.log({ cursorPosition })
|
||||
|
||||
|
||||
const updatedPrompt = prompt.slice(0, cursorPosition) + v + prompt.slice(cursorPosition);
|
||||
|
||||
console.log({ updatedPrompt })
|
||||
|
||||
flushSync(() => {
|
||||
_onChange(updatedPrompt);
|
||||
});
|
||||
|
||||
},
|
||||
[paragraphRef, _onChange, prompt]
|
||||
);
|
||||
|
||||
const onFocus = useCallback(() => {
|
||||
paragraphRef.current?.focus();
|
||||
}, [paragraphRef]);
|
||||
|
||||
const handleClosePopover = useCallback(() => {
|
||||
onClose();
|
||||
onFocus();
|
||||
}, [onFocus, onClose]);
|
||||
|
||||
const onSelect = useCallback(
|
||||
(v: string) => {
|
||||
insertTrigger(v)
|
||||
handleClosePopover();
|
||||
},
|
||||
[handleClosePopover, insertTrigger]
|
||||
);
|
||||
|
||||
const onKeyDown: KeyboardEventHandler<HTMLParagraphElement> = useCallback(
|
||||
(e) => {
|
||||
if (e.key === '<') {
|
||||
onOpen();
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
[onOpen]
|
||||
);
|
||||
|
||||
return {
|
||||
onChange,
|
||||
isOpen,
|
||||
onClose,
|
||||
onOpen,
|
||||
onSelect,
|
||||
onKeyDown,
|
||||
onFocus,
|
||||
};
|
||||
};
|
@ -2,14 +2,15 @@ import { Flex, IconButton, Text, Box, ButtonGroup } from '@invoke-ai/ui-library'
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { negativePromptChanged, positivePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { usePresetModifiedPrompts } from 'features/stylePresets/hooks/usePresetModifiedPrompts';
|
||||
import { activeStylePresetChanged } from 'features/stylePresets/store/stylePresetSlice';
|
||||
import { activeStylePresetChanged, viewModeChanged } from 'features/stylePresets/store/stylePresetSlice';
|
||||
import type { MouseEventHandler } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { PiStackSimpleBold, PiXBold } from 'react-icons/pi';
|
||||
import { PiEyeBold, PiStackSimpleBold, PiXBold } from 'react-icons/pi';
|
||||
import StylePresetImage from './StylePresetImage';
|
||||
|
||||
export const ActiveStylePreset = () => {
|
||||
const { activeStylePreset } = useAppSelector((s) => s.stylePreset);
|
||||
const { activeStylePreset, viewMode } = useAppSelector((s) => s.stylePreset);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { presetModifiedPositivePrompt, presetModifiedNegativePrompt } = usePresetModifiedPrompts();
|
||||
@ -17,6 +18,7 @@ export const ActiveStylePreset = () => {
|
||||
const handleClearActiveStylePreset = useCallback<MouseEventHandler<HTMLButtonElement>>(
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
dispatch(viewModeChanged(false));
|
||||
dispatch(activeStylePresetChanged(null));
|
||||
},
|
||||
[dispatch]
|
||||
@ -27,11 +29,20 @@ export const ActiveStylePreset = () => {
|
||||
e.stopPropagation();
|
||||
dispatch(positivePromptChanged(presetModifiedPositivePrompt));
|
||||
dispatch(negativePromptChanged(presetModifiedNegativePrompt));
|
||||
dispatch(viewModeChanged(false));
|
||||
dispatch(activeStylePresetChanged(null));
|
||||
},
|
||||
[dispatch, presetModifiedPositivePrompt, presetModifiedNegativePrompt]
|
||||
);
|
||||
|
||||
const handleToggleViewMode = useCallback<MouseEventHandler<HTMLButtonElement>>(
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
dispatch(viewModeChanged(!viewMode));
|
||||
},
|
||||
[dispatch, viewMode]
|
||||
);
|
||||
|
||||
if (!activeStylePreset) {
|
||||
return (
|
||||
<Flex h="25px" alignItems="center">
|
||||
@ -47,12 +58,20 @@ export const ActiveStylePreset = () => {
|
||||
<Flex gap="2" alignItems="center">
|
||||
<StylePresetImage imageWidth={25} presetImageUrl={activeStylePreset.image} />
|
||||
<Flex flexDir="column">
|
||||
<Text fontSize="sm" fontWeight="semibold" color="base.300">
|
||||
<Text fontSize="sm" fontWeight="semibold" color="base.300" noOfLines={1}>
|
||||
{activeStylePreset.name}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex gap="1">
|
||||
<IconButton
|
||||
onClick={handleToggleViewMode}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
aria-label="View"
|
||||
colorScheme={viewMode ? 'invokeBlue' : 'base'}
|
||||
icon={<PiEyeBold />}
|
||||
/>
|
||||
<IconButton
|
||||
onClick={handleFlattenPrompts}
|
||||
variant="outline"
|
||||
|
@ -1,13 +1,11 @@
|
||||
import { Button, Flex, FormControl, FormLabel, Icon, Input, Text } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useStylePresetFields } from 'features/stylePresets/hooks/useStylePresetFields';
|
||||
import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/stylePresetModalSlice';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { isModalOpenChanged, updatingStylePresetIdChanged } from 'features/stylePresets/store/stylePresetModalSlice';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { useCallback } from 'react';
|
||||
import type { SubmitHandler } from 'react-hook-form';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { PiBracketsCurlyBold } from 'react-icons/pi';
|
||||
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
|
||||
import { useCreateStylePresetMutation, useUpdateStylePresetMutation } from 'services/api/endpoints/stylePresets';
|
||||
|
||||
import { StylePresetPromptField } from './StylePresetPromptField';
|
||||
@ -20,15 +18,20 @@ export type StylePresetFormData = {
|
||||
image: File | null;
|
||||
};
|
||||
|
||||
export const StylePresetForm = ({ updatingPreset }: { updatingPreset: StylePresetRecordWithImage | null }) => {
|
||||
export const StylePresetForm = ({ updatingStylePresetId }: { updatingStylePresetId: string | null }) => {
|
||||
const [createStylePreset] = useCreateStylePresetMutation();
|
||||
const [updateStylePreset] = useUpdateStylePresetMutation();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const stylePresetFieldDefaults = useStylePresetFields(updatingPreset);
|
||||
const defaultValues = useAppSelector((s) => s.stylePresetModal.prefilledFormData);
|
||||
|
||||
const { handleSubmit, control, formState, reset, register } = useForm<StylePresetFormData>({
|
||||
defaultValues: stylePresetFieldDefaults,
|
||||
defaultValues: defaultValues || {
|
||||
name: '',
|
||||
positivePrompt: '',
|
||||
negativePrompt: '',
|
||||
image: null,
|
||||
},
|
||||
});
|
||||
|
||||
const handleClickSave = useCallback<SubmitHandler<StylePresetFormData>>(
|
||||
@ -41,9 +44,9 @@ export const StylePresetForm = ({ updatingPreset }: { updatingPreset: StylePrese
|
||||
};
|
||||
|
||||
try {
|
||||
if (updatingPreset) {
|
||||
if (updatingStylePresetId) {
|
||||
await updateStylePreset({
|
||||
id: updatingPreset.id,
|
||||
id: updatingStylePresetId,
|
||||
...payload,
|
||||
}).unwrap();
|
||||
} else {
|
||||
@ -56,10 +59,10 @@ export const StylePresetForm = ({ updatingPreset }: { updatingPreset: StylePrese
|
||||
});
|
||||
}
|
||||
|
||||
dispatch(updatingStylePresetChanged(null));
|
||||
dispatch(updatingStylePresetIdChanged(null));
|
||||
dispatch(isModalOpenChanged(false));
|
||||
},
|
||||
[dispatch, updatingPreset, updateStylePreset, createStylePreset]
|
||||
[dispatch, updatingStylePresetId, updateStylePreset, createStylePreset]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -1,31 +1,52 @@
|
||||
import { Badge, ConfirmationAlertDialog, Flex, IconButton, Text, useDisclosure } from '@invoke-ai/ui-library';
|
||||
import type { MouseEvent } from 'react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import ModelImage from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelImage';
|
||||
import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/stylePresetModalSlice';
|
||||
import {
|
||||
isModalOpenChanged,
|
||||
prefilledFormDataChanged,
|
||||
updatingStylePresetIdChanged,
|
||||
} from 'features/stylePresets/store/stylePresetModalSlice';
|
||||
import { activeStylePresetChanged, isMenuOpenChanged } from 'features/stylePresets/store/stylePresetSlice';
|
||||
import { useCallback } from 'react';
|
||||
import { PiPencilBold, PiTrashBold } from 'react-icons/pi';
|
||||
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
|
||||
import { useDeleteStylePresetMutation } from 'services/api/endpoints/stylePresets';
|
||||
import StylePresetImage from './StylePresetImage';
|
||||
import { useImageUrlToBlob } from 'common/hooks/useImageUrlToBlob';
|
||||
|
||||
export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordWithImage }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const [deleteStylePreset] = useDeleteStylePresetMutation();
|
||||
const activeStylePreset = useAppSelector((s) => s.stylePreset.activeStylePreset);
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const imageUrlToBlob = useImageUrlToBlob();
|
||||
|
||||
const handleClickEdit = useCallback(
|
||||
(e: MouseEvent<HTMLButtonElement>) => {
|
||||
async (e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
dispatch(updatingStylePresetChanged(preset));
|
||||
const { name, preset_data } = preset;
|
||||
const { positive_prompt, negative_prompt } = preset_data;
|
||||
let imageBlob = null;
|
||||
if (preset.image) {
|
||||
imageBlob = await imageUrlToBlob(preset.image);
|
||||
}
|
||||
|
||||
dispatch(
|
||||
prefilledFormDataChanged({
|
||||
name,
|
||||
positivePrompt: positive_prompt,
|
||||
negativePrompt: negative_prompt,
|
||||
image: imageBlob ? new File([imageBlob], `style_preset_${preset.id}.png`, { type: 'image/png' }) : null,
|
||||
})
|
||||
);
|
||||
|
||||
dispatch(updatingStylePresetIdChanged(preset.id));
|
||||
dispatch(isModalOpenChanged(true));
|
||||
},
|
||||
[dispatch, preset]
|
||||
);
|
||||
|
||||
const handleClickApply = useCallback(() => {
|
||||
const handleClickApply = useCallback(async () => {
|
||||
dispatch(activeStylePresetChanged(preset));
|
||||
dispatch(isMenuOpenChanged(false));
|
||||
}, [dispatch, preset]);
|
||||
@ -53,14 +74,16 @@ export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordWithI
|
||||
_hover={{ backgroundColor: 'base.750' }}
|
||||
padding="10px"
|
||||
borderRadius="base"
|
||||
alignItems="center"
|
||||
alignItems="flex-start"
|
||||
w="full"
|
||||
>
|
||||
<StylePresetImage presetImageUrl={preset.image} />
|
||||
<Flex flexDir="column" w="full">
|
||||
<Flex w="full" justifyContent="space-between">
|
||||
<Flex w="full" justifyContent="space-between" alignItems="flex-start">
|
||||
<Flex alignItems="center" gap="2">
|
||||
<Text fontSize="md">{preset.name}</Text>
|
||||
<Text fontSize="md" noOfLines={2}>
|
||||
{preset.name}
|
||||
</Text>
|
||||
{activeStylePreset && activeStylePreset.id === preset.id && (
|
||||
<Badge
|
||||
color="invokeBlue.400"
|
||||
@ -87,6 +110,7 @@ export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordWithI
|
||||
variant="ghost"
|
||||
aria-label="Delete"
|
||||
onClick={handleClickDelete}
|
||||
colorScheme="error"
|
||||
icon={<PiTrashBold />}
|
||||
/>
|
||||
</Flex>
|
||||
|
@ -1,7 +1,11 @@
|
||||
import { Flex, IconButton, Text } from '@invoke-ai/ui-library';
|
||||
import { EMPTY_ARRAY } from 'app/store/constants';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/stylePresetModalSlice';
|
||||
import {
|
||||
isModalOpenChanged,
|
||||
prefilledFormDataChanged,
|
||||
updatingStylePresetIdChanged,
|
||||
} from 'features/stylePresets/store/stylePresetModalSlice';
|
||||
import { useCallback } from 'react';
|
||||
import { PiPlusBold } from 'react-icons/pi';
|
||||
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
|
||||
@ -40,12 +44,13 @@ export const StylePresetMenu = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleClickAddNew = useCallback(() => {
|
||||
dispatch(updatingStylePresetChanged(null));
|
||||
dispatch(prefilledFormDataChanged(null));
|
||||
dispatch(updatingStylePresetIdChanged(null));
|
||||
dispatch(isModalOpenChanged(true));
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<Flex flexDir="column" gap="2" padding="10px">
|
||||
<Flex flexDir="column" gap="2" padding="10px" layerStyle="second">
|
||||
<Flex alignItems="center" gap="10" w="full" justifyContent="space-between">
|
||||
<StylePresetSearch />
|
||||
<IconButton
|
||||
|
@ -8,6 +8,7 @@ import { ActiveStylePreset } from './ActiveStylePreset';
|
||||
|
||||
export const StylePresetMenuTrigger = () => {
|
||||
const isMenuOpen = useAppSelector((s) => s.stylePreset.isMenuOpen);
|
||||
const activeStylePreset = useAppSelector((s) => s.stylePreset.activeStylePreset);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleToggle = useCallback(() => {
|
||||
@ -16,7 +17,7 @@ export const StylePresetMenuTrigger = () => {
|
||||
|
||||
return (
|
||||
<Flex
|
||||
as="button"
|
||||
// as="button"
|
||||
onClick={handleToggle}
|
||||
backgroundColor="base.800"
|
||||
justifyContent="space-between"
|
||||
@ -24,6 +25,8 @@ export const StylePresetMenuTrigger = () => {
|
||||
padding="5px 10px"
|
||||
borderRadius="base"
|
||||
gap="2"
|
||||
borderTop="2px solid transparent"
|
||||
borderColor={activeStylePreset ? 'invokeBlue.200' : 'transparent'}
|
||||
>
|
||||
<ActiveStylePreset />
|
||||
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
ModalOverlay,
|
||||
} from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/stylePresetModalSlice';
|
||||
import { isModalOpenChanged, updatingStylePresetIdChanged } from 'features/stylePresets/store/stylePresetModalSlice';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { StylePresetForm } from './StylePresetForm';
|
||||
@ -16,14 +16,14 @@ import { StylePresetForm } from './StylePresetForm';
|
||||
export const StylePresetModal = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const isModalOpen = useAppSelector((s) => s.stylePresetModal.isModalOpen);
|
||||
const updatingStylePreset = useAppSelector((s) => s.stylePresetModal.updatingStylePreset);
|
||||
const updatingStylePresetId = useAppSelector((s) => s.stylePresetModal.updatingStylePresetId);
|
||||
|
||||
const modalTitle = useMemo(() => {
|
||||
return updatingStylePreset ? `Update Style Preset` : `Create Style Preset`;
|
||||
}, [updatingStylePreset]);
|
||||
return updatingStylePresetId ? `Update Style Preset` : `Create Style Preset`;
|
||||
}, [updatingStylePresetId]);
|
||||
|
||||
const handleCloseModal = useCallback(() => {
|
||||
dispatch(updatingStylePresetChanged(null));
|
||||
dispatch(updatingStylePresetIdChanged(null));
|
||||
dispatch(isModalOpenChanged(false));
|
||||
}, [dispatch]);
|
||||
|
||||
@ -34,7 +34,7 @@ export const StylePresetModal = () => {
|
||||
<ModalHeader>{modalTitle}</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody display="flex" flexDir="column" gap={4}>
|
||||
<StylePresetForm updatingPreset={updatingStylePreset} />
|
||||
<StylePresetForm updatingStylePresetId={updatingStylePresetId} />
|
||||
</ModalBody>
|
||||
<ModalFooter />
|
||||
</ModalContent>
|
||||
|
@ -1,45 +0,0 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
|
||||
import { useAppSelector } from '../../../app/store/storeHooks';
|
||||
import { useDebouncedMetadata } from '../../../services/api/hooks/useDebouncedMetadata';
|
||||
import { handlers } from '../../metadata/util/handlers';
|
||||
import { useImageUrlToBlob } from '../../../common/hooks/useImageUrlToBlob';
|
||||
|
||||
|
||||
export const useStylePresetFields = (preset: StylePresetRecordWithImage | null) => {
|
||||
const createPresetFromImage = useAppSelector(s => s.stylePresetModal.createPresetFromImage)
|
||||
|
||||
const imageUrlToBlob = useImageUrlToBlob();
|
||||
|
||||
const getStylePresetFieldDefaults = useCallback(async () => {
|
||||
if (preset) {
|
||||
let file: File | null = null;
|
||||
if (preset.image) {
|
||||
const blob = await imageUrlToBlob(preset.image);
|
||||
if (blob) {
|
||||
file = new File([blob], "name");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
name: preset.name,
|
||||
positivePrompt: preset.preset_data.positive_prompt || "",
|
||||
negativePrompt: preset.preset_data.negative_prompt || "",
|
||||
image: file
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
name: "",
|
||||
positivePrompt: "",
|
||||
negativePrompt: "",
|
||||
image: null
|
||||
};
|
||||
}, [
|
||||
preset
|
||||
]);
|
||||
|
||||
return getStylePresetFieldDefaults;
|
||||
};
|
@ -1,16 +1,15 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import type { RootState } from 'app/store/store';
|
||||
import type { StylePresetRecordWithImage } from 'services/api/endpoints/stylePresets';
|
||||
|
||||
import type { StylePresetModalState, StylePresetPrefillOptions } from './types';
|
||||
import { ImageDTO } from '../../../services/api/types';
|
||||
import type { StylePresetModalState } from './types';
|
||||
import { StylePresetFormData } from '../components/StylePresetForm';
|
||||
|
||||
|
||||
export const initialState: StylePresetModalState = {
|
||||
isModalOpen: false,
|
||||
updatingStylePreset: null,
|
||||
createPresetFromImage: null
|
||||
updatingStylePresetId: null,
|
||||
prefilledFormData: null
|
||||
};
|
||||
|
||||
|
||||
@ -21,15 +20,15 @@ export const stylePresetModalSlice = createSlice({
|
||||
isModalOpenChanged: (state, action: PayloadAction<boolean>) => {
|
||||
state.isModalOpen = action.payload;
|
||||
},
|
||||
updatingStylePresetChanged: (state, action: PayloadAction<StylePresetRecordWithImage | null>) => {
|
||||
state.updatingStylePreset = action.payload;
|
||||
updatingStylePresetIdChanged: (state, action: PayloadAction<string | null>) => {
|
||||
state.updatingStylePresetId = action.payload;
|
||||
},
|
||||
createPresetFromImageChanged: (state, action: PayloadAction<ImageDTO | null>) => {
|
||||
state.createPresetFromImage = action.payload;
|
||||
prefilledFormDataChanged: (state, action: PayloadAction<StylePresetFormData | null>) => {
|
||||
state.prefilledFormData = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { isModalOpenChanged, updatingStylePresetChanged, createPresetFromImageChanged } = stylePresetModalSlice.actions;
|
||||
export const { isModalOpenChanged, updatingStylePresetIdChanged, prefilledFormDataChanged } = stylePresetModalSlice.actions;
|
||||
|
||||
export const selectStylePresetModalSlice = (state: RootState) => state.stylePresetModal;
|
||||
|
@ -9,7 +9,8 @@ import type { StylePresetState } from './types';
|
||||
export const initialState: StylePresetState = {
|
||||
isMenuOpen: false,
|
||||
activeStylePreset: null,
|
||||
searchTerm: ""
|
||||
searchTerm: "",
|
||||
viewMode: false
|
||||
};
|
||||
|
||||
|
||||
@ -26,9 +27,12 @@ export const stylePresetSlice = createSlice({
|
||||
searchTermChanged: (state, action: PayloadAction<string>) => {
|
||||
state.searchTerm = action.payload;
|
||||
},
|
||||
viewModeChanged: (state, action: PayloadAction<boolean>) => {
|
||||
state.viewMode = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { isMenuOpenChanged, activeStylePresetChanged, searchTermChanged } = stylePresetSlice.actions;
|
||||
export const { isMenuOpenChanged, activeStylePresetChanged, searchTermChanged, viewModeChanged } = stylePresetSlice.actions;
|
||||
|
||||
export const selectStylePresetSlice = (state: RootState) => state.stylePreset;
|
||||
|
@ -1,21 +1,17 @@
|
||||
import type { StylePresetRecordWithImage } from "services/api/endpoints/stylePresets";
|
||||
import { ImageDTO } from "../../../services/api/types";
|
||||
import { StylePresetFormData } from "../components/StylePresetForm";
|
||||
|
||||
export type StylePresetModalState = {
|
||||
isModalOpen: boolean;
|
||||
updatingStylePreset: StylePresetRecordWithImage | null;
|
||||
createPresetFromImage: ImageDTO | null
|
||||
updatingStylePresetId: string | null;
|
||||
prefilledFormData: StylePresetFormData | null
|
||||
};
|
||||
|
||||
export type StylePresetPrefillOptions = {
|
||||
positivePrompt: string;
|
||||
negativePrompt: string;
|
||||
image: File;
|
||||
}
|
||||
|
||||
export type StylePresetState = {
|
||||
isMenuOpen: boolean;
|
||||
activeStylePreset: StylePresetRecordWithImage | null;
|
||||
searchTerm: string
|
||||
searchTerm: string;
|
||||
viewMode: boolean;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,15 @@
|
||||
import { PRESET_PLACEHOLDER } from '../hooks/usePresetModifiedPrompts';
|
||||
|
||||
export const getViewModeChunks = (currentPrompt: string, presetPrompt?: string) => {
|
||||
if (!presetPrompt || !presetPrompt.length) {
|
||||
return ['', currentPrompt, ''];
|
||||
}
|
||||
|
||||
const chunks = presetPrompt.split(PRESET_PLACEHOLDER);
|
||||
|
||||
if (chunks.length === 1) {
|
||||
return ['', currentPrompt, chunks[0]];
|
||||
} else {
|
||||
return [chunks[0], currentPrompt, chunks[1]];
|
||||
}
|
||||
};
|
@ -12,6 +12,8 @@ import { RefinerSettingsAccordion } from 'features/settingsAccordions/components
|
||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { StylePresetMenu } from '../../../stylePresets/components/StylePresetMenu';
|
||||
import { StylePresetMenuTrigger } from '../../../stylePresets/components/StylePresetMenuTrigger';
|
||||
|
||||
const overlayScrollbarsStyles: CSSProperties = {
|
||||
height: '100%',
|
||||
@ -20,23 +22,33 @@ const overlayScrollbarsStyles: CSSProperties = {
|
||||
|
||||
const ParametersPanelCanvas = () => {
|
||||
const isSDXL = useAppSelector((s) => s.generation.model?.base === 'sdxl');
|
||||
const isMenuOpen = useAppSelector((s) => s.stylePreset.isMenuOpen);
|
||||
|
||||
return (
|
||||
<Flex w="full" h="full" flexDir="column" gap={2}>
|
||||
<QueueControls />
|
||||
<StylePresetMenuTrigger />
|
||||
<Flex w="full" h="full" position="relative">
|
||||
<Box position="absolute" top={0} left={0} right={0} bottom={0}>
|
||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||
<Prompts />
|
||||
<ImageSettingsAccordion />
|
||||
<GenerationSettingsAccordion />
|
||||
<ControlSettingsAccordion />
|
||||
<CompositingSettingsAccordion />
|
||||
{isSDXL && <RefinerSettingsAccordion />}
|
||||
<AdvancedSettingsAccordion />
|
||||
</Flex>
|
||||
</OverlayScrollbarsComponent>
|
||||
{isMenuOpen ? (
|
||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||
<StylePresetMenu />
|
||||
</Flex>
|
||||
</OverlayScrollbarsComponent>
|
||||
) : (
|
||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||
<Prompts />
|
||||
<ImageSettingsAccordion />
|
||||
<GenerationSettingsAccordion />
|
||||
<ControlSettingsAccordion />
|
||||
<CompositingSettingsAccordion />
|
||||
{isSDXL && <RefinerSettingsAccordion />}
|
||||
<AdvancedSettingsAccordion />
|
||||
</Flex>
|
||||
</OverlayScrollbarsComponent>
|
||||
)}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { ChakraProps } from '@invoke-ai/ui-library';
|
||||
import { Box, Flex, Portal, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library';
|
||||
import { Box, Flex, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
|
||||
import { ControlLayersPanelContent } from 'features/controlLayers/components/ControlLayersPanelContent';
|
||||
@ -69,22 +69,13 @@ const ParametersPanelTextToImage = () => {
|
||||
<StylePresetMenuTrigger />
|
||||
<Flex w="full" h="full" position="relative">
|
||||
<Box position="absolute" top={0} left={0} right={0} bottom={0} ref={ref}>
|
||||
<Portal containerRef={ref}>
|
||||
{isMenuOpen && (
|
||||
<Box position="absolute" top={0} left={0} right={0} bottom={0} layerStyle="second">
|
||||
<OverlayScrollbarsComponent
|
||||
defer
|
||||
style={overlayScrollbarsStyles}
|
||||
options={overlayScrollbarsParams.options}
|
||||
>
|
||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||
<StylePresetMenu />
|
||||
</Flex>
|
||||
</OverlayScrollbarsComponent>
|
||||
</Box>
|
||||
)}
|
||||
</Portal>
|
||||
{!isMenuOpen && (
|
||||
{isMenuOpen ? (
|
||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||
<StylePresetMenu />
|
||||
</Flex>
|
||||
</OverlayScrollbarsComponent>
|
||||
) : (
|
||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||
<Prompts />
|
||||
|
@ -8,6 +8,9 @@ import { UpscaleSettingsAccordion } from 'features/settingsAccordions/components
|
||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { useAppSelector } from '../../../../app/store/storeHooks';
|
||||
import { StylePresetMenu } from '../../../stylePresets/components/StylePresetMenu';
|
||||
import { StylePresetMenuTrigger } from '../../../stylePresets/components/StylePresetMenuTrigger';
|
||||
|
||||
const overlayScrollbarsStyles: CSSProperties = {
|
||||
height: '100%',
|
||||
@ -15,19 +18,29 @@ const overlayScrollbarsStyles: CSSProperties = {
|
||||
};
|
||||
|
||||
const ParametersPanelUpscale = () => {
|
||||
const isMenuOpen = useAppSelector((s) => s.stylePreset.isMenuOpen);
|
||||
return (
|
||||
<Flex w="full" h="full" flexDir="column" gap={2}>
|
||||
<QueueControls />
|
||||
<StylePresetMenuTrigger />
|
||||
<Flex w="full" h="full" position="relative">
|
||||
<Box position="absolute" top={0} left={0} right={0} bottom={0}>
|
||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||
<Prompts />
|
||||
<UpscaleSettingsAccordion />
|
||||
<GenerationSettingsAccordion />
|
||||
<AdvancedSettingsAccordion />
|
||||
</Flex>
|
||||
</OverlayScrollbarsComponent>
|
||||
{isMenuOpen ? (
|
||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||
<StylePresetMenu />
|
||||
</Flex>
|
||||
</OverlayScrollbarsComponent>
|
||||
) : (
|
||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||
<Prompts />
|
||||
<UpscaleSettingsAccordion />
|
||||
<GenerationSettingsAccordion />
|
||||
<AdvancedSettingsAccordion />
|
||||
</Flex>
|
||||
</OverlayScrollbarsComponent>
|
||||
)}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
Loading…
Reference in New Issue
Block a user