mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): use stable objects for animation/native element styles
This commit is contained in:
parent
f5194f9e2d
commit
ca4b8e65c1
@ -1,5 +1,6 @@
|
|||||||
import type { ChakraProps } from '@chakra-ui/react';
|
import type { ChakraProps } from '@chakra-ui/react';
|
||||||
import { Flex } from '@chakra-ui/react';
|
import { Flex } from '@chakra-ui/react';
|
||||||
|
import type { CSSProperties } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { RgbaColorPicker } from 'react-colorful';
|
import { RgbaColorPicker } from 'react-colorful';
|
||||||
import type {
|
import type {
|
||||||
@ -14,20 +15,22 @@ type IAIColorPickerProps = ColorPickerBaseProps<RgbaColor> & {
|
|||||||
withNumberInput?: boolean;
|
withNumberInput?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const colorPickerStyles: NonNullable<ChakraProps['sx']> = {
|
const colorPickerPointerStyles: NonNullable<ChakraProps['sx']> = {
|
||||||
width: 6,
|
width: 6,
|
||||||
height: 6,
|
height: 6,
|
||||||
borderColor: 'base.100',
|
borderColor: 'base.100',
|
||||||
};
|
};
|
||||||
|
|
||||||
const sx: ChakraProps['sx'] = {
|
const sx: ChakraProps['sx'] = {
|
||||||
'.react-colorful__hue-pointer': colorPickerStyles,
|
'.react-colorful__hue-pointer': colorPickerPointerStyles,
|
||||||
'.react-colorful__saturation-pointer': colorPickerStyles,
|
'.react-colorful__saturation-pointer': colorPickerPointerStyles,
|
||||||
'.react-colorful__alpha-pointer': colorPickerStyles,
|
'.react-colorful__alpha-pointer': colorPickerPointerStyles,
|
||||||
gap: 2,
|
gap: 2,
|
||||||
flexDir: 'column',
|
flexDir: 'column',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const colorPickerStyles: CSSProperties = { width: '100%' };
|
||||||
|
|
||||||
const numberInputWidth: ChakraProps['w'] = '4.2rem';
|
const numberInputWidth: ChakraProps['w'] = '4.2rem';
|
||||||
|
|
||||||
const IAIColorPicker = (props: IAIColorPickerProps) => {
|
const IAIColorPicker = (props: IAIColorPickerProps) => {
|
||||||
@ -53,7 +56,7 @@ const IAIColorPicker = (props: IAIColorPickerProps) => {
|
|||||||
<RgbaColorPicker
|
<RgbaColorPicker
|
||||||
color={color}
|
color={color}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
style={{ width: '100%' }}
|
style={colorPickerStyles}
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
{withNumberInput && (
|
{withNumberInput && (
|
||||||
|
@ -112,7 +112,7 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
|||||||
isDisabled: isUploadDisabled,
|
isDisabled: isUploadDisabled,
|
||||||
});
|
});
|
||||||
|
|
||||||
const uploadButtonStyles = useMemo(() => {
|
const uploadButtonStyles = useMemo<SystemStyleObject>(() => {
|
||||||
const styles: SystemStyleObject = {
|
const styles: SystemStyleObject = {
|
||||||
minH: minSize,
|
minH: minSize,
|
||||||
w: 'full',
|
w: 'full',
|
||||||
@ -134,6 +134,7 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
return styles;
|
||||||
}, [isUploadDisabled, minSize]);
|
}, [isUploadDisabled, minSize]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Box, Flex } from '@chakra-ui/react';
|
import { Box, Flex } from '@chakra-ui/react';
|
||||||
|
import type { AnimationProps } from 'framer-motion';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { memo, useRef } from 'react';
|
import { memo, useRef } from 'react';
|
||||||
@ -9,23 +10,27 @@ type Props = {
|
|||||||
label?: ReactNode;
|
label?: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const initial: AnimationProps['initial'] = {
|
||||||
|
opacity: 0,
|
||||||
|
};
|
||||||
|
const animate: AnimationProps['animate'] = {
|
||||||
|
opacity: 1,
|
||||||
|
transition: { duration: 0.1 },
|
||||||
|
};
|
||||||
|
const exit: AnimationProps['exit'] = {
|
||||||
|
opacity: 0,
|
||||||
|
transition: { duration: 0.1 },
|
||||||
|
};
|
||||||
|
|
||||||
export const IAIDropOverlay = (props: Props) => {
|
export const IAIDropOverlay = (props: Props) => {
|
||||||
const { isOver, label = 'Drop' } = props;
|
const { isOver, label = 'Drop' } = props;
|
||||||
const motionId = useRef(uuidv4());
|
const motionId = useRef(uuidv4());
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={motionId.current}
|
key={motionId.current}
|
||||||
initial={{
|
initial={initial}
|
||||||
opacity: 0,
|
animate={animate}
|
||||||
}}
|
exit={exit}
|
||||||
animate={{
|
|
||||||
opacity: 1,
|
|
||||||
transition: { duration: 0.1 },
|
|
||||||
}}
|
|
||||||
exit={{
|
|
||||||
opacity: 0,
|
|
||||||
transition: { duration: 0.1 },
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Flex position="absolute" top={0} insetInlineStart={0} w="full" h="full">
|
<Flex position="absolute" top={0} insetInlineStart={0} w="full" h="full">
|
||||||
<Flex
|
<Flex
|
||||||
|
@ -4,6 +4,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
|||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||||
|
import type { AnimationProps } from 'framer-motion';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import type { KeyboardEvent, ReactNode } from 'react';
|
import type { KeyboardEvent, ReactNode } from 'react';
|
||||||
import { memo, useCallback, useEffect, useState } from 'react';
|
import { memo, useCallback, useEffect, useState } from 'react';
|
||||||
@ -155,17 +156,9 @@ const ImageUploader = (props: ImageUploaderProps) => {
|
|||||||
{isDragActive && isHandlingUpload && (
|
{isDragActive && isHandlingUpload && (
|
||||||
<motion.div
|
<motion.div
|
||||||
key="image-upload-overlay"
|
key="image-upload-overlay"
|
||||||
initial={{
|
initial={initial}
|
||||||
opacity: 0,
|
animate={animate}
|
||||||
}}
|
exit={exit}
|
||||||
animate={{
|
|
||||||
opacity: 1,
|
|
||||||
transition: { duration: 0.1 },
|
|
||||||
}}
|
|
||||||
exit={{
|
|
||||||
opacity: 0,
|
|
||||||
transition: { duration: 0.1 },
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<ImageUploadOverlay
|
<ImageUploadOverlay
|
||||||
isDragAccept={isDragAccept}
|
isDragAccept={isDragAccept}
|
||||||
@ -180,3 +173,15 @@ const ImageUploader = (props: ImageUploaderProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default memo(ImageUploader);
|
export default memo(ImageUploader);
|
||||||
|
|
||||||
|
const initial: AnimationProps['initial'] = {
|
||||||
|
opacity: 0,
|
||||||
|
};
|
||||||
|
const animate: AnimationProps['animate'] = {
|
||||||
|
opacity: 1,
|
||||||
|
transition: { duration: 0.1 },
|
||||||
|
};
|
||||||
|
const exit: AnimationProps['exit'] = {
|
||||||
|
opacity: 0,
|
||||||
|
transition: { duration: 0.1 },
|
||||||
|
};
|
||||||
|
@ -31,8 +31,8 @@ import {
|
|||||||
FaEyeDropper,
|
FaEyeDropper,
|
||||||
FaFillDrip,
|
FaFillDrip,
|
||||||
FaPaintBrush,
|
FaPaintBrush,
|
||||||
FaPlus,
|
|
||||||
FaSlidersH,
|
FaSlidersH,
|
||||||
|
FaTimes,
|
||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa';
|
||||||
|
|
||||||
export const selector = createMemoizedSelector(
|
export const selector = createMemoizedSelector(
|
||||||
@ -230,7 +230,7 @@ const IAICanvasToolChooserOptions = () => {
|
|||||||
<InvIconButton
|
<InvIconButton
|
||||||
aria-label={`${t('unifiedCanvas.eraseBoundingBox')} (Del/Backspace)`}
|
aria-label={`${t('unifiedCanvas.eraseBoundingBox')} (Del/Backspace)`}
|
||||||
tooltip={`${t('unifiedCanvas.eraseBoundingBox')} (Del/Backspace)`}
|
tooltip={`${t('unifiedCanvas.eraseBoundingBox')} (Del/Backspace)`}
|
||||||
icon={<FaPlus style={{ transform: 'rotate(45deg)' }} />}
|
icon={<FaTimes />}
|
||||||
isDisabled={isStaging}
|
isDisabled={isStaging}
|
||||||
onClick={handleEraseBoundingBox}
|
onClick={handleEraseBoundingBox}
|
||||||
/>
|
/>
|
||||||
|
@ -16,8 +16,9 @@ import type {
|
|||||||
TypesafeDraggableData,
|
TypesafeDraggableData,
|
||||||
} from 'features/dnd/types';
|
} from 'features/dnd/types';
|
||||||
import { customPointerWithin } from 'features/dnd/util/customPointerWithin';
|
import { customPointerWithin } from 'features/dnd/util/customPointerWithin';
|
||||||
|
import type { AnimationProps } from 'framer-motion';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import type { PropsWithChildren } from 'react';
|
import type { CSSProperties, PropsWithChildren } from 'react';
|
||||||
import { memo, useCallback, useState } from 'react';
|
import { memo, useCallback, useState } from 'react';
|
||||||
|
|
||||||
import { DndContextTypesafe } from './DndContextTypesafe';
|
import { DndContextTypesafe } from './DndContextTypesafe';
|
||||||
@ -90,29 +91,15 @@ const AppDndContext = (props: PropsWithChildren) => {
|
|||||||
<DragOverlay
|
<DragOverlay
|
||||||
dropAnimation={null}
|
dropAnimation={null}
|
||||||
modifiers={[scaledModifier]}
|
modifiers={[scaledModifier]}
|
||||||
style={{
|
style={dragOverlayStyles}
|
||||||
width: 'min-content',
|
|
||||||
height: 'min-content',
|
|
||||||
cursor: 'grabbing',
|
|
||||||
userSelect: 'none',
|
|
||||||
// expand overlay to prevent cursor from going outside it and displaying
|
|
||||||
padding: '10rem',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{activeDragData && (
|
{activeDragData && (
|
||||||
<motion.div
|
<motion.div
|
||||||
layout
|
layout
|
||||||
key="overlay-drag-image"
|
key="overlay-drag-image"
|
||||||
initial={{
|
initial={initial}
|
||||||
opacity: 0,
|
animate={animate}
|
||||||
scale: 0.7,
|
|
||||||
}}
|
|
||||||
animate={{
|
|
||||||
opacity: 1,
|
|
||||||
scale: 1,
|
|
||||||
transition: { duration: 0.1 },
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<DragPreview dragData={activeDragData} />
|
<DragPreview dragData={activeDragData} />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -124,3 +111,22 @@ const AppDndContext = (props: PropsWithChildren) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default memo(AppDndContext);
|
export default memo(AppDndContext);
|
||||||
|
|
||||||
|
const dragOverlayStyles: CSSProperties = {
|
||||||
|
width: 'min-content',
|
||||||
|
height: 'min-content',
|
||||||
|
cursor: 'grabbing',
|
||||||
|
userSelect: 'none',
|
||||||
|
// expand overlay to prevent cursor from going outside it and displaying
|
||||||
|
padding: '10rem',
|
||||||
|
};
|
||||||
|
|
||||||
|
const initial: AnimationProps['initial'] = {
|
||||||
|
opacity: 0,
|
||||||
|
scale: 0.7,
|
||||||
|
};
|
||||||
|
const animate: AnimationProps['animate'] = {
|
||||||
|
opacity: 1,
|
||||||
|
scale: 1,
|
||||||
|
transition: { duration: 0.1 },
|
||||||
|
};
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import type { ChakraProps } from '@chakra-ui/react';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { InvControl } from 'common/components/InvControl/InvControl';
|
import { InvControl } from 'common/components/InvControl/InvControl';
|
||||||
import { InvSelect } from 'common/components/InvSelect/InvSelect';
|
import { InvSelect } from 'common/components/InvSelect/InvSelect';
|
||||||
@ -68,8 +69,12 @@ export const EmbeddingSelect = ({
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onMenuClose={onClose}
|
onMenuClose={onClose}
|
||||||
data-testid="add-embedding"
|
data-testid="add-embedding"
|
||||||
w="full"
|
sx={selectStyles}
|
||||||
/>
|
/>
|
||||||
</InvControl>
|
</InvControl>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectStyles: ChakraProps['sx'] = {
|
||||||
|
w: 'full',
|
||||||
|
};
|
||||||
|
@ -93,7 +93,7 @@ const BoardContextMenu = ({
|
|||||||
|
|
||||||
const renderMenuFunc = useCallback(
|
const renderMenuFunc = useCallback(
|
||||||
() => (
|
() => (
|
||||||
<InvMenuList visibility="visible !important" onContextMenu={skipEvent}>
|
<InvMenuList visibility="visible" onContextMenu={skipEvent}>
|
||||||
<InvMenuGroup title={boardName}>
|
<InvMenuGroup title={boardName}>
|
||||||
<InvMenuItem
|
<InvMenuItem
|
||||||
icon={<FaPlus />}
|
icon={<FaPlus />}
|
||||||
|
@ -5,7 +5,7 @@ import { useAppSelector } from 'app/store/storeHooks';
|
|||||||
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
|
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
|
||||||
import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModal';
|
import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModal';
|
||||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||||
import type { CSSProperties} from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { memo, useState } from 'react';
|
import { memo, useState } from 'react';
|
||||||
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
||||||
import type { BoardDTO } from 'services/api/types';
|
import type { BoardDTO } from 'services/api/types';
|
||||||
|
@ -14,17 +14,10 @@ import ImageMetadataViewer from 'features/gallery/components/ImageMetadataViewer
|
|||||||
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
|
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
|
||||||
import { useNextPrevImage } from 'features/gallery/hooks/useNextPrevImage';
|
import { useNextPrevImage } from 'features/gallery/hooks/useNextPrevImage';
|
||||||
import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors';
|
import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors';
|
||||||
import type { AnimationProps} from 'framer-motion';
|
import type { AnimationProps } from 'framer-motion';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import type {
|
import type { CSSProperties } from 'react';
|
||||||
CSSProperties} from 'react';
|
import { memo, useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import {
|
|
||||||
memo,
|
|
||||||
useCallback,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaImage } from 'react-icons/fa';
|
import { FaImage } from 'react-icons/fa';
|
||||||
|
@ -4,7 +4,7 @@ import { InvTooltip } from 'common/components/InvTooltip/InvTooltip';
|
|||||||
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
|
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
|
||||||
import { isString } from 'lodash-es';
|
import { isString } from 'lodash-es';
|
||||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||||
import type { CSSProperties} from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaCopy, FaDownload } from 'react-icons/fa';
|
import { FaCopy, FaDownload } from 'react-icons/fa';
|
||||||
|
@ -43,7 +43,9 @@ export const LoRACard = memo((props: LoRACardProps) => {
|
|||||||
return (
|
return (
|
||||||
<InvCard variant="lora">
|
<InvCard variant="lora">
|
||||||
<InvCardHeader>
|
<InvCardHeader>
|
||||||
<InvText noOfLines={1} wordBreak='break-all'>{lora.model_name}</InvText>
|
<InvText noOfLines={1} wordBreak="break-all">
|
||||||
|
{lora.model_name}
|
||||||
|
</InvText>
|
||||||
<InvIconButton
|
<InvIconButton
|
||||||
aria-label="Remove LoRA"
|
aria-label="Remove LoRA"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import type { ChakraProps } from '@chakra-ui/react';
|
||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
@ -69,10 +70,14 @@ const LoRASelect = () => {
|
|||||||
noOptionsMessage={noOptionsMessage}
|
noOptionsMessage={noOptionsMessage}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
data-testid="add-lora"
|
data-testid="add-lora"
|
||||||
w="full"
|
sx={selectStyles}
|
||||||
/>
|
/>
|
||||||
</InvControl>
|
</InvControl>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(LoRASelect);
|
export default memo(LoRASelect);
|
||||||
|
|
||||||
|
const selectStyles: ChakraProps['sx'] = {
|
||||||
|
w: 'full',
|
||||||
|
};
|
||||||
|
@ -11,7 +11,7 @@ import CheckpointConfigsSelect from 'features/modelManager/subpanels/shared/Chec
|
|||||||
import ModelVariantSelect from 'features/modelManager/subpanels/shared/ModelVariantSelect';
|
import ModelVariantSelect from 'features/modelManager/subpanels/shared/ModelVariantSelect';
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { makeToast } from 'features/system/util/makeToast';
|
import { makeToast } from 'features/system/util/makeToast';
|
||||||
import type { FocusEventHandler } from 'react';
|
import type { CSSProperties, FocusEventHandler } from 'react';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useAddMainModelsMutation } from 'services/api/endpoints/models';
|
import { useAddMainModelsMutation } from 'services/api/endpoints/models';
|
||||||
@ -112,7 +112,7 @@ export default function AdvancedAddCheckpoint(
|
|||||||
onSubmit={advancedAddCheckpointForm.onSubmit((v) =>
|
onSubmit={advancedAddCheckpointForm.onSubmit((v) =>
|
||||||
advancedAddCheckpointFormHandler(v)
|
advancedAddCheckpointFormHandler(v)
|
||||||
)}
|
)}
|
||||||
style={{ width: '100%' }}
|
style={formStyles}
|
||||||
>
|
>
|
||||||
<Flex flexDirection="column" gap={2}>
|
<Flex flexDirection="column" gap={2}>
|
||||||
<InvControl label={t('modelManager.model')} isRequired>
|
<InvControl label={t('modelManager.model')} isRequired>
|
||||||
@ -148,7 +148,6 @@ export default function AdvancedAddCheckpoint(
|
|||||||
{!useCustomConfig ? (
|
{!useCustomConfig ? (
|
||||||
<CheckpointConfigsSelect
|
<CheckpointConfigsSelect
|
||||||
required
|
required
|
||||||
w="full"
|
|
||||||
{...advancedAddCheckpointForm.getInputProps('config')}
|
{...advancedAddCheckpointForm.getInputProps('config')}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@ -175,3 +174,7 @@ export default function AdvancedAddCheckpoint(
|
|||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formStyles: CSSProperties = {
|
||||||
|
width: '100%',
|
||||||
|
};
|
||||||
|
@ -9,7 +9,7 @@ import BaseModelSelect from 'features/modelManager/subpanels/shared/BaseModelSel
|
|||||||
import ModelVariantSelect from 'features/modelManager/subpanels/shared/ModelVariantSelect';
|
import ModelVariantSelect from 'features/modelManager/subpanels/shared/ModelVariantSelect';
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { makeToast } from 'features/system/util/makeToast';
|
import { makeToast } from 'features/system/util/makeToast';
|
||||||
import type { FocusEventHandler } from 'react';
|
import type { CSSProperties, FocusEventHandler } from 'react';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useAddMainModelsMutation } from 'services/api/endpoints/models';
|
import { useAddMainModelsMutation } from 'services/api/endpoints/models';
|
||||||
@ -99,7 +99,7 @@ export default function AdvancedAddDiffusers(props: AdvancedAddDiffusersProps) {
|
|||||||
onSubmit={advancedAddDiffusersForm.onSubmit((v) =>
|
onSubmit={advancedAddDiffusersForm.onSubmit((v) =>
|
||||||
advancedAddDiffusersFormHandler(v)
|
advancedAddDiffusersFormHandler(v)
|
||||||
)}
|
)}
|
||||||
style={{ width: '100%' }}
|
style={formStyles}
|
||||||
>
|
>
|
||||||
<Flex flexDirection="column" gap={2}>
|
<Flex flexDirection="column" gap={2}>
|
||||||
<InvControl isRequired label={t('modelManager.model')}>
|
<InvControl isRequired label={t('modelManager.model')}>
|
||||||
@ -138,3 +138,7 @@ export default function AdvancedAddDiffusers(props: AdvancedAddDiffusersProps) {
|
|||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formStyles: CSSProperties = {
|
||||||
|
width: '100%',
|
||||||
|
};
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
setAdvancedAddScanModel,
|
setAdvancedAddScanModel,
|
||||||
setSearchFolder,
|
setSearchFolder,
|
||||||
} from 'features/modelManager/store/modelManagerSlice';
|
} from 'features/modelManager/store/modelManagerSlice';
|
||||||
|
import type { CSSProperties } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaSearch, FaSync, FaTrash } from 'react-icons/fa';
|
import { FaSearch, FaSync, FaTrash } from 'react-icons/fa';
|
||||||
@ -57,7 +58,7 @@ function SearchFolderForm() {
|
|||||||
onSubmit={searchFolderForm.onSubmit((values) =>
|
onSubmit={searchFolderForm.onSubmit((values) =>
|
||||||
searchFolderFormSubmitHandler(values)
|
searchFolderFormSubmitHandler(values)
|
||||||
)}
|
)}
|
||||||
style={{ width: '100%' }}
|
style={formStyles}
|
||||||
>
|
>
|
||||||
<Flex w="100%" gap={2} borderRadius={4} alignItems="center">
|
<Flex w="100%" gap={2} borderRadius={4} alignItems="center">
|
||||||
<Flex w="100%" alignItems="center" gap={4} minH={12}>
|
<Flex w="100%" alignItems="center" gap={4} minH={12}>
|
||||||
@ -127,3 +128,7 @@ function SearchFolderForm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default memo(SearchFolderForm);
|
export default memo(SearchFolderForm);
|
||||||
|
|
||||||
|
const formStyles: CSSProperties = {
|
||||||
|
width: '100%',
|
||||||
|
};
|
||||||
|
@ -8,6 +8,7 @@ import { InvSelect } from 'common/components/InvSelect/InvSelect';
|
|||||||
import type { InvSelectOption } from 'common/components/InvSelect/types';
|
import type { InvSelectOption } from 'common/components/InvSelect/types';
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { makeToast } from 'features/system/util/makeToast';
|
import { makeToast } from 'features/system/util/makeToast';
|
||||||
|
import type { CSSProperties } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useImportMainModelsMutation } from 'services/api/endpoints/models';
|
import { useImportMainModelsMutation } from 'services/api/endpoints/models';
|
||||||
|
|
||||||
@ -73,7 +74,7 @@ export default function SimpleAddModels() {
|
|||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={addModelForm.onSubmit((v) => handleAddModelSubmit(v))}
|
onSubmit={addModelForm.onSubmit((v) => handleAddModelSubmit(v))}
|
||||||
style={{ width: '100%' }}
|
style={formStyles}
|
||||||
>
|
>
|
||||||
<Flex flexDirection="column" width="100%" gap={4}>
|
<Flex flexDirection="column" width="100%" gap={4}>
|
||||||
<InvControl label={t('modelManager.modelLocation')}>
|
<InvControl label={t('modelManager.modelLocation')}>
|
||||||
@ -97,3 +98,7 @@ export default function SimpleAddModels() {
|
|||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formStyles: CSSProperties = {
|
||||||
|
width: '100%',
|
||||||
|
};
|
||||||
|
@ -123,8 +123,8 @@ export default function ModelListItem(props: ModelListItemProps) {
|
|||||||
acceptButtonText={t('modelManager.delete')}
|
acceptButtonText={t('modelManager.delete')}
|
||||||
>
|
>
|
||||||
<Flex rowGap={4} flexDirection="column">
|
<Flex rowGap={4} flexDirection="column">
|
||||||
<p style={{ fontWeight: 'bold' }}>{t('modelManager.deleteMsg1')}</p>
|
<InvText fontWeight="bold">{t('modelManager.deleteMsg1')}</InvText>
|
||||||
<p>{t('modelManager.deleteMsg2')}</p>
|
<InvText>{t('modelManager.deleteMsg2')}</InvText>
|
||||||
</Flex>
|
</Flex>
|
||||||
</InvConfirmationAlertDialog>
|
</InvConfirmationAlertDialog>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import type { ChakraProps } from '@chakra-ui/react';
|
||||||
import { InvControl } from 'common/components/InvControl/InvControl';
|
import { InvControl } from 'common/components/InvControl/InvControl';
|
||||||
import { InvSelect } from 'common/components/InvSelect/InvSelect';
|
import { InvSelect } from 'common/components/InvSelect/InvSelect';
|
||||||
import type {
|
import type {
|
||||||
@ -9,6 +10,8 @@ import { useGetCheckpointConfigsQuery } from 'services/api/endpoints/models';
|
|||||||
|
|
||||||
type CheckpointConfigSelectProps = Omit<InvSelectProps, 'options'>;
|
type CheckpointConfigSelectProps = Omit<InvSelectProps, 'options'>;
|
||||||
|
|
||||||
|
const sx: ChakraProps['sx'] = { w: 'full' };
|
||||||
|
|
||||||
export default function CheckpointConfigsSelect(
|
export default function CheckpointConfigsSelect(
|
||||||
props: CheckpointConfigSelectProps
|
props: CheckpointConfigSelectProps
|
||||||
) {
|
) {
|
||||||
@ -22,6 +25,7 @@ export default function CheckpointConfigsSelect(
|
|||||||
<InvSelect
|
<InvSelect
|
||||||
placeholder="Select A Config File"
|
placeholder="Select A Config File"
|
||||||
options={options}
|
options={options}
|
||||||
|
sx={sx}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</InvControl>
|
</InvControl>
|
||||||
|
@ -4,7 +4,9 @@ import { Flex } from '@chakra-ui/react';
|
|||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||||
import TopPanel from 'features/nodes/components/flow/panels/TopPanel/TopPanel';
|
import TopPanel from 'features/nodes/components/flow/panels/TopPanel/TopPanel';
|
||||||
|
import type { AnimationProps } from 'framer-motion';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
import type { CSSProperties } from 'react';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { MdDeviceHub } from 'react-icons/md';
|
import { MdDeviceHub } from 'react-icons/md';
|
||||||
@ -14,6 +16,28 @@ import { Flow } from './flow/Flow';
|
|||||||
import BottomLeftPanel from './flow/panels/BottomLeftPanel/BottomLeftPanel';
|
import BottomLeftPanel from './flow/panels/BottomLeftPanel/BottomLeftPanel';
|
||||||
import MinimapPanel from './flow/panels/MinimapPanel/MinimapPanel';
|
import MinimapPanel from './flow/panels/MinimapPanel/MinimapPanel';
|
||||||
|
|
||||||
|
const isReadyMotionStyles: CSSProperties = {
|
||||||
|
position: 'relative',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
};
|
||||||
|
const notIsReadyMotionStyles: CSSProperties = {
|
||||||
|
position: 'absolute',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
};
|
||||||
|
const initial: AnimationProps['initial'] = {
|
||||||
|
opacity: 0,
|
||||||
|
};
|
||||||
|
const animate: AnimationProps['animate'] = {
|
||||||
|
opacity: 1,
|
||||||
|
transition: { duration: 0.2 },
|
||||||
|
};
|
||||||
|
const exit: AnimationProps['exit'] = {
|
||||||
|
opacity: 0,
|
||||||
|
transition: { duration: 0.2 },
|
||||||
|
};
|
||||||
|
|
||||||
const NodeEditor = () => {
|
const NodeEditor = () => {
|
||||||
const isReady = useAppSelector((state) => state.nodes.isReady);
|
const isReady = useAppSelector((state) => state.nodes.isReady);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -30,18 +54,10 @@ const NodeEditor = () => {
|
|||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{isReady && (
|
{isReady && (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{
|
initial={initial}
|
||||||
opacity: 0,
|
animate={animate}
|
||||||
}}
|
exit={exit}
|
||||||
animate={{
|
style={isReadyMotionStyles}
|
||||||
opacity: 1,
|
|
||||||
transition: { duration: 0.2 },
|
|
||||||
}}
|
|
||||||
exit={{
|
|
||||||
opacity: 0,
|
|
||||||
transition: { duration: 0.2 },
|
|
||||||
}}
|
|
||||||
style={{ position: 'relative', width: '100%', height: '100%' }}
|
|
||||||
>
|
>
|
||||||
<Flow />
|
<Flow />
|
||||||
<AddNodePopover />
|
<AddNodePopover />
|
||||||
@ -54,18 +70,10 @@ const NodeEditor = () => {
|
|||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{!isReady && (
|
{!isReady && (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{
|
initial={initial}
|
||||||
opacity: 0,
|
animate={animate}
|
||||||
}}
|
exit={exit}
|
||||||
animate={{
|
style={notIsReadyMotionStyles}
|
||||||
opacity: 1,
|
|
||||||
transition: { duration: 0.2 },
|
|
||||||
}}
|
|
||||||
exit={{
|
|
||||||
opacity: 0,
|
|
||||||
transition: { duration: 0.2 },
|
|
||||||
}}
|
|
||||||
style={{ position: 'absolute', width: '100%', height: '100%' }}
|
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
layerStyle="first"
|
layerStyle="first"
|
||||||
|
@ -23,8 +23,8 @@ import {
|
|||||||
} from 'features/nodes/store/nodesSlice';
|
} from 'features/nodes/store/nodesSlice';
|
||||||
import { $flow } from 'features/nodes/store/reactFlowInstance';
|
import { $flow } from 'features/nodes/store/reactFlowInstance';
|
||||||
import { bumpGlobalMenuCloseTrigger } from 'features/ui/store/uiSlice';
|
import { bumpGlobalMenuCloseTrigger } from 'features/ui/store/uiSlice';
|
||||||
import type { MouseEvent } from 'react';
|
import type { CSSProperties, MouseEvent } from 'react';
|
||||||
import { useCallback, useRef } from 'react';
|
import { useCallback, useMemo, useRef } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import type {
|
import type {
|
||||||
OnConnect,
|
OnConnect,
|
||||||
@ -75,6 +75,8 @@ const selector = createMemoizedSelector(stateSelector, ({ nodes }) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const snapGrid: [number, number] = [25, 25];
|
||||||
|
|
||||||
export const Flow = () => {
|
export const Flow = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const nodes = useAppSelector((state) => state.nodes.nodes);
|
const nodes = useAppSelector((state) => state.nodes.nodes);
|
||||||
@ -87,6 +89,13 @@ export const Flow = () => {
|
|||||||
|
|
||||||
const [borderRadius] = useToken('radii', ['base']);
|
const [borderRadius] = useToken('radii', ['base']);
|
||||||
|
|
||||||
|
const flowStyles = useMemo<CSSProperties>(
|
||||||
|
() => ({
|
||||||
|
borderRadius,
|
||||||
|
}),
|
||||||
|
[borderRadius]
|
||||||
|
);
|
||||||
|
|
||||||
const onNodesChange: OnNodesChange = useCallback(
|
const onNodesChange: OnNodesChange = useCallback(
|
||||||
(changes) => {
|
(changes) => {
|
||||||
dispatch(nodesChanged(changes));
|
dispatch(nodesChanged(changes));
|
||||||
@ -266,10 +275,10 @@ export const Flow = () => {
|
|||||||
isValidConnection={isValidConnection}
|
isValidConnection={isValidConnection}
|
||||||
minZoom={0.1}
|
minZoom={0.1}
|
||||||
snapToGrid={shouldSnapToGrid}
|
snapToGrid={shouldSnapToGrid}
|
||||||
snapGrid={[25, 25]}
|
snapGrid={snapGrid}
|
||||||
connectionRadius={30}
|
connectionRadius={30}
|
||||||
proOptions={proOptions}
|
proOptions={proOptions}
|
||||||
style={{ borderRadius }}
|
style={flowStyles}
|
||||||
onPaneClick={handlePaneClick}
|
onPaneClick={handlePaneClick}
|
||||||
deleteKeyCode={DELETE_KEYS}
|
deleteKeyCode={DELETE_KEYS}
|
||||||
selectionMode={selectionMode}
|
selectionMode={selectionMode}
|
||||||
|
@ -3,6 +3,7 @@ import { stateSelector } from 'app/store/store';
|
|||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
|
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
|
||||||
import { getFieldColor } from 'features/nodes/components/flow/edges/util/getEdgeColor';
|
import { getFieldColor } from 'features/nodes/components/flow/edges/util/getEdgeColor';
|
||||||
|
import type { CSSProperties } from 'react';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import type { ConnectionLineComponentProps } from 'reactflow';
|
import type { ConnectionLineComponentProps } from 'reactflow';
|
||||||
import { getBezierPath } from 'reactflow';
|
import { getBezierPath } from 'reactflow';
|
||||||
@ -27,6 +28,8 @@ const selector = createMemoizedSelector(stateSelector, ({ nodes }) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const pathStyles: CSSProperties = { opacity: 0.8 };
|
||||||
|
|
||||||
const CustomConnectionLine = ({
|
const CustomConnectionLine = ({
|
||||||
fromX,
|
fromX,
|
||||||
fromY,
|
fromY,
|
||||||
@ -56,7 +59,7 @@ const CustomConnectionLine = ({
|
|||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
className={className}
|
className={className}
|
||||||
d={dAttr}
|
d={dAttr}
|
||||||
style={{ opacity: 0.8 }}
|
style={pathStyles}
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
|
@ -47,21 +47,20 @@ const InvocationCollapsedEdge = ({
|
|||||||
|
|
||||||
const { base500 } = useChakraThemeTokens();
|
const { base500 } = useChakraThemeTokens();
|
||||||
|
|
||||||
return (
|
const edgeStyles = useMemo(
|
||||||
<>
|
() => ({
|
||||||
<BaseEdge
|
|
||||||
path={edgePath}
|
|
||||||
markerEnd={markerEnd}
|
|
||||||
style={{
|
|
||||||
strokeWidth: isSelected ? 3 : 2,
|
strokeWidth: isSelected ? 3 : 2,
|
||||||
stroke: base500,
|
stroke: base500,
|
||||||
opacity: isSelected ? 0.8 : 0.5,
|
opacity: isSelected ? 0.8 : 0.5,
|
||||||
animation: shouldAnimate
|
animation: shouldAnimate ? 'dashdraw 0.5s linear infinite' : undefined,
|
||||||
? 'dashdraw 0.5s linear infinite'
|
|
||||||
: undefined,
|
|
||||||
strokeDasharray: shouldAnimate ? 5 : 'none',
|
strokeDasharray: shouldAnimate ? 5 : 'none',
|
||||||
}}
|
}),
|
||||||
/>
|
[base500, isSelected, shouldAnimate]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<BaseEdge path={edgePath} markerEnd={markerEnd} style={edgeStyles} />
|
||||||
{data?.count && data.count > 1 && (
|
{data?.count && data.count > 1 && (
|
||||||
<EdgeLabelRenderer>
|
<EdgeLabelRenderer>
|
||||||
<Flex
|
<Flex
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import type { CSSProperties } from 'react';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import type { EdgeProps } from 'reactflow';
|
import type { EdgeProps } from 'reactflow';
|
||||||
import { BaseEdge, getBezierPath } from 'reactflow';
|
import { BaseEdge, getBezierPath } from 'reactflow';
|
||||||
@ -42,19 +43,18 @@ const InvocationDefaultEdge = ({
|
|||||||
targetPosition,
|
targetPosition,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
const edgeStyles = useMemo<CSSProperties>(
|
||||||
<BaseEdge
|
() => ({
|
||||||
path={edgePath}
|
|
||||||
markerEnd={markerEnd}
|
|
||||||
style={{
|
|
||||||
strokeWidth: isSelected ? 3 : 2,
|
strokeWidth: isSelected ? 3 : 2,
|
||||||
stroke,
|
stroke,
|
||||||
opacity: isSelected ? 0.8 : 0.5,
|
opacity: isSelected ? 0.8 : 0.5,
|
||||||
animation: shouldAnimate ? 'dashdraw 0.5s linear infinite' : undefined,
|
animation: shouldAnimate ? 'dashdraw 0.5s linear infinite' : undefined,
|
||||||
strokeDasharray: shouldAnimate ? 5 : 'none',
|
strokeDasharray: shouldAnimate ? 5 : 'none',
|
||||||
}}
|
}),
|
||||||
/>
|
[isSelected, shouldAnimate, stroke]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return <BaseEdge path={edgePath} markerEnd={markerEnd} style={edgeStyles} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(InvocationDefaultEdge);
|
export default memo(InvocationDefaultEdge);
|
||||||
|
@ -7,8 +7,9 @@ import { InvText } from 'common/components/InvText/wrapper';
|
|||||||
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
|
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
|
||||||
import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper';
|
import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper';
|
||||||
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
|
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
|
||||||
|
import type { AnimationProps } from 'framer-motion';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import type { PropsWithChildren } from 'react';
|
import type { CSSProperties, PropsWithChildren } from 'react';
|
||||||
import { memo, useCallback, useState } from 'react';
|
import { memo, useCallback, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
@ -106,25 +107,10 @@ const Wrapper = (props: PropsWithChildren<{ nodeProps: NodeProps }>) => {
|
|||||||
{isHovering && (
|
{isHovering && (
|
||||||
<motion.div
|
<motion.div
|
||||||
key="nextPrevButtons"
|
key="nextPrevButtons"
|
||||||
initial={{
|
initial={initial}
|
||||||
opacity: 0,
|
animate={animate}
|
||||||
}}
|
exit={exit}
|
||||||
animate={{
|
style={styles}
|
||||||
opacity: 1,
|
|
||||||
transition: { duration: 0.1 },
|
|
||||||
}}
|
|
||||||
exit={{
|
|
||||||
opacity: 0,
|
|
||||||
transition: { duration: 0.1 },
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: 40,
|
|
||||||
left: -2,
|
|
||||||
right: -2,
|
|
||||||
bottom: 0,
|
|
||||||
pointerEvents: 'none',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<NextPrevImageButtons />
|
<NextPrevImageButtons />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -134,3 +120,23 @@ const Wrapper = (props: PropsWithChildren<{ nodeProps: NodeProps }>) => {
|
|||||||
</NodeWrapper>
|
</NodeWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const initial: AnimationProps['initial'] = {
|
||||||
|
opacity: 0,
|
||||||
|
};
|
||||||
|
const animate: AnimationProps['animate'] = {
|
||||||
|
opacity: 1,
|
||||||
|
transition: { duration: 0.1 },
|
||||||
|
};
|
||||||
|
const exit: AnimationProps['exit'] = {
|
||||||
|
opacity: 0,
|
||||||
|
transition: { duration: 0.1 },
|
||||||
|
};
|
||||||
|
const styles: CSSProperties = {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 40,
|
||||||
|
left: -2,
|
||||||
|
right: -2,
|
||||||
|
bottom: 0,
|
||||||
|
pointerEvents: 'none',
|
||||||
|
};
|
||||||
|
@ -10,6 +10,8 @@ interface Props {
|
|||||||
nodeId: string;
|
nodeId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hiddenHandleStyles: CSSProperties = { visibility: 'hidden' };
|
||||||
|
|
||||||
const InvocationNodeCollapsedHandles = ({ nodeId }: Props) => {
|
const InvocationNodeCollapsedHandles = ({ nodeId }: Props) => {
|
||||||
const data = useNodeData(nodeId);
|
const data = useNodeData(nodeId);
|
||||||
const { base600 } = useChakraThemeTokens();
|
const { base600 } = useChakraThemeTokens();
|
||||||
@ -26,6 +28,15 @@ const InvocationNodeCollapsedHandles = ({ nodeId }: Props) => {
|
|||||||
[base600]
|
[base600]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const collapsedTargetStyles: CSSProperties = useMemo(
|
||||||
|
() => ({ ...dummyHandleStyles, left: '-0.5rem' }),
|
||||||
|
[dummyHandleStyles]
|
||||||
|
);
|
||||||
|
const collapsedSourceStyles: CSSProperties = useMemo(
|
||||||
|
() => ({ ...dummyHandleStyles, right: '-0.5rem' }),
|
||||||
|
[dummyHandleStyles]
|
||||||
|
);
|
||||||
|
|
||||||
if (!isInvocationNodeData(data)) {
|
if (!isInvocationNodeData(data)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -37,7 +48,7 @@ const InvocationNodeCollapsedHandles = ({ nodeId }: Props) => {
|
|||||||
id={`${data.id}-collapsed-target`}
|
id={`${data.id}-collapsed-target`}
|
||||||
isConnectable={false}
|
isConnectable={false}
|
||||||
position={Position.Left}
|
position={Position.Left}
|
||||||
style={{ ...dummyHandleStyles, left: '-0.5rem' }}
|
style={collapsedTargetStyles}
|
||||||
/>
|
/>
|
||||||
{map(data.inputs, (input) => (
|
{map(data.inputs, (input) => (
|
||||||
<Handle
|
<Handle
|
||||||
@ -46,7 +57,7 @@ const InvocationNodeCollapsedHandles = ({ nodeId }: Props) => {
|
|||||||
id={input.name}
|
id={input.name}
|
||||||
isConnectable={false}
|
isConnectable={false}
|
||||||
position={Position.Left}
|
position={Position.Left}
|
||||||
style={{ visibility: 'hidden' }}
|
style={hiddenHandleStyles}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<Handle
|
<Handle
|
||||||
@ -54,7 +65,7 @@ const InvocationNodeCollapsedHandles = ({ nodeId }: Props) => {
|
|||||||
id={`${data.id}-collapsed-source`}
|
id={`${data.id}-collapsed-source`}
|
||||||
isConnectable={false}
|
isConnectable={false}
|
||||||
position={Position.Right}
|
position={Position.Right}
|
||||||
style={{ ...dummyHandleStyles, right: '-0.5rem' }}
|
style={collapsedSourceStyles}
|
||||||
/>
|
/>
|
||||||
{map(data.outputs, (output) => (
|
{map(data.outputs, (output) => (
|
||||||
<Handle
|
<Handle
|
||||||
@ -63,7 +74,7 @@ const InvocationNodeCollapsedHandles = ({ nodeId }: Props) => {
|
|||||||
id={output.name}
|
id={output.name}
|
||||||
isConnectable={false}
|
isConnectable={false}
|
||||||
position={Position.Right}
|
position={Position.Right}
|
||||||
style={{ visibility: 'hidden' }}
|
style={hiddenHandleStyles}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
@ -4,6 +4,7 @@ import { Flex } from '@chakra-ui/react';
|
|||||||
import QueueControls from 'features/queue/components/QueueControls';
|
import QueueControls from 'features/queue/components/QueueControls';
|
||||||
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
|
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
|
||||||
import { usePanelStorage } from 'features/ui/hooks/usePanelStorage';
|
import { usePanelStorage } from 'features/ui/hooks/usePanelStorage';
|
||||||
|
import type { CSSProperties } from 'react';
|
||||||
import { memo, useCallback, useRef, useState } from 'react';
|
import { memo, useCallback, useRef, useState } from 'react';
|
||||||
import type { ImperativePanelGroupHandle } from 'react-resizable-panels';
|
import type { ImperativePanelGroupHandle } from 'react-resizable-panels';
|
||||||
import { Panel, PanelGroup } from 'react-resizable-panels';
|
import { Panel, PanelGroup } from 'react-resizable-panels';
|
||||||
@ -11,6 +12,8 @@ import { Panel, PanelGroup } from 'react-resizable-panels';
|
|||||||
import InspectorPanel from './inspector/InspectorPanel';
|
import InspectorPanel from './inspector/InspectorPanel';
|
||||||
import WorkflowPanel from './workflow/WorkflowPanel';
|
import WorkflowPanel from './workflow/WorkflowPanel';
|
||||||
|
|
||||||
|
const panelGroupStyles: CSSProperties = { height: '100%', width: '100%' };
|
||||||
|
|
||||||
const NodeEditorPanelGroup = () => {
|
const NodeEditorPanelGroup = () => {
|
||||||
const [isTopPanelCollapsed, setIsTopPanelCollapsed] = useState(false);
|
const [isTopPanelCollapsed, setIsTopPanelCollapsed] = useState(false);
|
||||||
const [isBottomPanelCollapsed, setIsBottomPanelCollapsed] = useState(false);
|
const [isBottomPanelCollapsed, setIsBottomPanelCollapsed] = useState(false);
|
||||||
@ -31,7 +34,7 @@ const NodeEditorPanelGroup = () => {
|
|||||||
id="workflow-panel-group"
|
id="workflow-panel-group"
|
||||||
autoSaveId="workflow-panel-group"
|
autoSaveId="workflow-panel-group"
|
||||||
direction="vertical"
|
direction="vertical"
|
||||||
style={{ height: '100%', width: '100%' }}
|
style={panelGroupStyles}
|
||||||
storage={panelStorage}
|
storage={panelStorage}
|
||||||
>
|
>
|
||||||
<Panel
|
<Panel
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import { Flex, Image } from '@chakra-ui/react';
|
import { Flex, Image } from '@chakra-ui/react';
|
||||||
import InvokeAILogoImage from 'assets/images/logo.png';
|
import InvokeAILogoImage from 'assets/images/logo.png';
|
||||||
import { InvText } from 'common/components/InvText/wrapper';
|
import { InvText } from 'common/components/InvText/wrapper';
|
||||||
|
import type { AnimationProps } from 'framer-motion';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import { memo, useRef } from 'react';
|
import { memo, useRef } from 'react';
|
||||||
import { useHoverDirty } from 'react-use';
|
import { useHoverDirty } from 'react-use';
|
||||||
@ -35,17 +36,9 @@ const InvokeAILogoComponent = ({ showVersion = true }: Props) => {
|
|||||||
{showVersion && isHovered && appVersion && (
|
{showVersion && isHovered && appVersion && (
|
||||||
<motion.div
|
<motion.div
|
||||||
key="statusText"
|
key="statusText"
|
||||||
initial={{
|
initial={initial}
|
||||||
opacity: 0,
|
animate={animate}
|
||||||
}}
|
exit={exit}
|
||||||
animate={{
|
|
||||||
opacity: 1,
|
|
||||||
transition: { duration: 0.15 },
|
|
||||||
}}
|
|
||||||
exit={{
|
|
||||||
opacity: 0,
|
|
||||||
transition: { delay: 0.8 },
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<InvText
|
<InvText
|
||||||
fontWeight="semibold"
|
fontWeight="semibold"
|
||||||
@ -64,3 +57,15 @@ const InvokeAILogoComponent = ({ showVersion = true }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default memo(InvokeAILogoComponent);
|
export default memo(InvokeAILogoComponent);
|
||||||
|
|
||||||
|
const initial: AnimationProps['initial'] = {
|
||||||
|
opacity: 0,
|
||||||
|
};
|
||||||
|
const animate: AnimationProps['animate'] = {
|
||||||
|
opacity: 1,
|
||||||
|
transition: { duration: 0.1 },
|
||||||
|
};
|
||||||
|
const exit: AnimationProps['exit'] = {
|
||||||
|
opacity: 0,
|
||||||
|
transition: { delay: 0.8 },
|
||||||
|
};
|
||||||
|
@ -4,6 +4,7 @@ import { stateSelector } from 'app/store/store';
|
|||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { InvText } from 'common/components/InvText/wrapper';
|
import { InvText } from 'common/components/InvText/wrapper';
|
||||||
import { STATUS_TRANSLATION_KEYS } from 'features/system/store/types';
|
import { STATUS_TRANSLATION_KEYS } from 'features/system/store/types';
|
||||||
|
import type { AnimationProps } from 'framer-motion';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import type { ResourceKey } from 'i18next';
|
import type { ResourceKey } from 'i18next';
|
||||||
import { memo, useMemo, useRef } from 'react';
|
import { memo, useMemo, useRef } from 'react';
|
||||||
@ -58,17 +59,9 @@ const StatusIndicator = () => {
|
|||||||
{isHovered && (
|
{isHovered && (
|
||||||
<motion.div
|
<motion.div
|
||||||
key="statusText"
|
key="statusText"
|
||||||
initial={{
|
initial={initial}
|
||||||
opacity: 0,
|
animate={animate}
|
||||||
}}
|
exit={exit}
|
||||||
animate={{
|
|
||||||
opacity: 1,
|
|
||||||
transition: { duration: 0.15 },
|
|
||||||
}}
|
|
||||||
exit={{
|
|
||||||
opacity: 0,
|
|
||||||
transition: { delay: 0.8 },
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<InvText
|
<InvText
|
||||||
fontSize="sm"
|
fontSize="sm"
|
||||||
@ -88,3 +81,15 @@ const StatusIndicator = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default memo(StatusIndicator);
|
export default memo(StatusIndicator);
|
||||||
|
|
||||||
|
const initial: AnimationProps['initial'] = {
|
||||||
|
opacity: 0,
|
||||||
|
};
|
||||||
|
const animate: AnimationProps['animate'] = {
|
||||||
|
opacity: 1,
|
||||||
|
transition: { duration: 0.1 },
|
||||||
|
};
|
||||||
|
const exit: AnimationProps['exit'] = {
|
||||||
|
opacity: 0,
|
||||||
|
transition: { delay: 0.8 },
|
||||||
|
};
|
||||||
|
@ -21,7 +21,7 @@ import {
|
|||||||
activeTabNameSelector,
|
activeTabNameSelector,
|
||||||
} from 'features/ui/store/uiSelectors';
|
} from 'features/ui/store/uiSelectors';
|
||||||
import { setActiveTab } from 'features/ui/store/uiSlice';
|
import { setActiveTab } from 'features/ui/store/uiSlice';
|
||||||
import type { MouseEvent, ReactElement, ReactNode } from 'react';
|
import type { CSSProperties, MouseEvent, ReactElement, ReactNode } from 'react';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -101,6 +101,7 @@ const GALLERY_PANEL_MIN_SIZE_PX = 360;
|
|||||||
|
|
||||||
export const NO_GALLERY_TABS: InvokeTabName[] = ['modelManager', 'queue'];
|
export const NO_GALLERY_TABS: InvokeTabName[] = ['modelManager', 'queue'];
|
||||||
export const NO_SIDE_PANEL_TABS: InvokeTabName[] = ['modelManager', 'queue'];
|
export const NO_SIDE_PANEL_TABS: InvokeTabName[] = ['modelManager', 'queue'];
|
||||||
|
const panelStyles: CSSProperties = { height: '100%', width: '100%' };
|
||||||
|
|
||||||
const InvokeTabs = () => {
|
const InvokeTabs = () => {
|
||||||
const activeTabIndex = useAppSelector(activeTabIndexSelector);
|
const activeTabIndex = useAppSelector(activeTabIndexSelector);
|
||||||
@ -231,7 +232,7 @@ const InvokeTabs = () => {
|
|||||||
id="app"
|
id="app"
|
||||||
autoSaveId="app"
|
autoSaveId="app"
|
||||||
direction="horizontal"
|
direction="horizontal"
|
||||||
style={{ height: '100%', width: '100%' }}
|
style={panelStyles}
|
||||||
storage={panelStorage}
|
storage={panelStorage}
|
||||||
units="pixels"
|
units="pixels"
|
||||||
>
|
>
|
||||||
@ -263,9 +264,7 @@ const InvokeTabs = () => {
|
|||||||
</>
|
</>
|
||||||
)} */}
|
)} */}
|
||||||
<Panel id="main" order={1} minSize={MAIN_PANEL_MIN_SIZE_PX}>
|
<Panel id="main" order={1} minSize={MAIN_PANEL_MIN_SIZE_PX}>
|
||||||
<InvTabPanels style={{ height: '100%', width: '100%' }}>
|
<InvTabPanels style={panelStyles}>{tabPanels}</InvTabPanels>
|
||||||
{tabPanels}
|
|
||||||
</InvTabPanels>
|
|
||||||
</Panel>
|
</Panel>
|
||||||
{!NO_GALLERY_TABS.includes(activeTabName) && (
|
{!NO_GALLERY_TABS.includes(activeTabName) && (
|
||||||
<>
|
<>
|
||||||
|
@ -13,8 +13,14 @@ import { ImageSettingsAccordion } from 'features/settingsAccordions/ImageSetting
|
|||||||
import { RefinerSettingsAccordion } from 'features/settingsAccordions/RefinerSettingsAccordion/RefinerSettingsAccordion';
|
import { RefinerSettingsAccordion } from 'features/settingsAccordions/RefinerSettingsAccordion/RefinerSettingsAccordion';
|
||||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||||
|
import type { CSSProperties } from 'react';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
|
const overlayScrollbarsStyles: CSSProperties = {
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
};
|
||||||
|
|
||||||
const ParametersPanel = () => {
|
const ParametersPanel = () => {
|
||||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||||
const isSDXL = useAppSelector(
|
const isSDXL = useAppSelector(
|
||||||
@ -28,7 +34,7 @@ const ParametersPanel = () => {
|
|||||||
<Box position="absolute" top={0} left={0} right={0} bottom={0}>
|
<Box position="absolute" top={0} left={0} right={0} bottom={0}>
|
||||||
<OverlayScrollbarsComponent
|
<OverlayScrollbarsComponent
|
||||||
defer
|
defer
|
||||||
style={{ height: '100%', width: '100%' }}
|
style={overlayScrollbarsStyles}
|
||||||
options={overlayScrollbarsParams.options}
|
options={overlayScrollbarsParams.options}
|
||||||
>
|
>
|
||||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||||
|
@ -3,10 +3,19 @@ import InitialImageDisplay from 'features/parameters/components/ImageToImage/Ini
|
|||||||
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
|
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
|
||||||
import TextToImageTabMain from 'features/ui/components/tabs/TextToImageTab';
|
import TextToImageTabMain from 'features/ui/components/tabs/TextToImageTab';
|
||||||
import { usePanelStorage } from 'features/ui/hooks/usePanelStorage';
|
import { usePanelStorage } from 'features/ui/hooks/usePanelStorage';
|
||||||
|
import type { CSSProperties } from 'react';
|
||||||
import { memo, useCallback, useRef } from 'react';
|
import { memo, useCallback, useRef } from 'react';
|
||||||
import type { ImperativePanelGroupHandle } from 'react-resizable-panels';
|
import type { ImperativePanelGroupHandle } from 'react-resizable-panels';
|
||||||
import { Panel, PanelGroup } from 'react-resizable-panels';
|
import { Panel, PanelGroup } from 'react-resizable-panels';
|
||||||
|
|
||||||
|
const panelGroupStyles: CSSProperties = {
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
};
|
||||||
|
const panelStyles: CSSProperties = {
|
||||||
|
position: 'relative',
|
||||||
|
};
|
||||||
|
|
||||||
const ImageToImageTab = () => {
|
const ImageToImageTab = () => {
|
||||||
const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
|
const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
|
||||||
|
|
||||||
@ -25,7 +34,7 @@ const ImageToImageTab = () => {
|
|||||||
ref={panelGroupRef}
|
ref={panelGroupRef}
|
||||||
autoSaveId="imageTab.content"
|
autoSaveId="imageTab.content"
|
||||||
direction="horizontal"
|
direction="horizontal"
|
||||||
style={{ height: '100%', width: '100%' }}
|
style={panelGroupStyles}
|
||||||
storage={panelStorage}
|
storage={panelStorage}
|
||||||
units="percentages"
|
units="percentages"
|
||||||
>
|
>
|
||||||
@ -34,7 +43,7 @@ const ImageToImageTab = () => {
|
|||||||
order={0}
|
order={0}
|
||||||
defaultSize={50}
|
defaultSize={50}
|
||||||
minSize={25}
|
minSize={25}
|
||||||
style={{ position: 'relative' }}
|
style={panelStyles}
|
||||||
>
|
>
|
||||||
<InitialImageDisplay />
|
<InitialImageDisplay />
|
||||||
</Panel>
|
</Panel>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { FlexProps, SystemStyleObject } from '@chakra-ui/react';
|
import type { FlexProps, SystemStyleObject } from '@chakra-ui/react';
|
||||||
import { Box, Flex } from '@chakra-ui/react';
|
import { Box, Flex } from '@chakra-ui/react';
|
||||||
import type { CSSProperties} from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { PanelResizeHandle } from 'react-resizable-panels';
|
import { PanelResizeHandle } from 'react-resizable-panels';
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user