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