From ca4b8e65c1bc4df0a108a19abd7fe3012d8c453a Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 29 Dec 2023 13:27:39 +1100 Subject: [PATCH] feat(ui): use stable objects for animation/native element styles --- .../src/common/components/IAIColorPicker.tsx | 13 +++-- .../web/src/common/components/IAIDndImage.tsx | 3 +- .../src/common/components/IAIDropOverlay.tsx | 27 +++++---- .../src/common/components/ImageUploader.tsx | 27 +++++---- .../IAICanvasToolChooserOptions.tsx | 4 +- .../features/dnd/components/AppDndContext.tsx | 42 ++++++++------ .../features/embedding/EmbeddingSelect.tsx | 7 ++- .../components/Boards/BoardContextMenu.tsx | 2 +- .../Boards/BoardsList/BoardsList.tsx | 2 +- .../CurrentImage/CurrentImagePreview.tsx | 13 +---- .../ImageMetadataViewer/DataViewer.tsx | 2 +- .../src/features/lora/components/LoRACard.tsx | 4 +- .../features/lora/components/LoRASelect.tsx | 7 ++- .../AddModelsPanel/AdvancedAddCheckpoint.tsx | 9 ++- .../AddModelsPanel/AdvancedAddDiffusers.tsx | 8 ++- .../AddModelsPanel/SearchFolderForm.tsx | 7 ++- .../AddModelsPanel/SimpleAddModels.tsx | 7 ++- .../ModelManagerPanel/ModelListItem.tsx | 4 +- .../shared/CheckpointConfigsSelect.tsx | 4 ++ .../features/nodes/components/NodeEditor.tsx | 56 +++++++++++-------- .../features/nodes/components/flow/Flow.tsx | 17 ++++-- .../connectionLines/CustomConnectionLine.tsx | 5 +- .../flow/edges/InvocationCollapsedEdge.tsx | 25 ++++----- .../flow/edges/InvocationDefaultEdge.tsx | 24 ++++---- .../nodes/CurrentImage/CurrentImageNode.tsx | 46 ++++++++------- .../InvocationNodeCollapsedHandles.tsx | 19 +++++-- .../sidePanel/NodeEditorPanelGroup.tsx | 5 +- .../components/InvokeAILogoComponent.tsx | 27 +++++---- .../system/components/StatusIndicator.tsx | 27 +++++---- .../src/features/ui/components/InvokeTabs.tsx | 9 ++- .../ui/components/ParametersPanel.tsx | 8 ++- .../ui/components/tabs/ImageToImageTab.tsx | 13 ++++- .../ui/components/tabs/ResizeHandle.tsx | 2 +- 33 files changed, 292 insertions(+), 183 deletions(-) diff --git a/invokeai/frontend/web/src/common/components/IAIColorPicker.tsx b/invokeai/frontend/web/src/common/components/IAIColorPicker.tsx index 7be6537dfe..bf127709ea 100644 --- a/invokeai/frontend/web/src/common/components/IAIColorPicker.tsx +++ b/invokeai/frontend/web/src/common/components/IAIColorPicker.tsx @@ -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 & { withNumberInput?: boolean; }; -const colorPickerStyles: NonNullable = { +const colorPickerPointerStyles: NonNullable = { 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) => { {withNumberInput && ( diff --git a/invokeai/frontend/web/src/common/components/IAIDndImage.tsx b/invokeai/frontend/web/src/common/components/IAIDndImage.tsx index 8fd4d66935..2854fbb2e9 100644 --- a/invokeai/frontend/web/src/common/components/IAIDndImage.tsx +++ b/invokeai/frontend/web/src/common/components/IAIDndImage.tsx @@ -112,7 +112,7 @@ const IAIDndImage = (props: IAIDndImageProps) => { isDisabled: isUploadDisabled, }); - const uploadButtonStyles = useMemo(() => { + const uploadButtonStyles = useMemo(() => { const styles: SystemStyleObject = { minH: minSize, w: 'full', @@ -134,6 +134,7 @@ const IAIDndImage = (props: IAIDndImageProps) => { }, }); } + return styles; }, [isUploadDisabled, minSize]); return ( diff --git a/invokeai/frontend/web/src/common/components/IAIDropOverlay.tsx b/invokeai/frontend/web/src/common/components/IAIDropOverlay.tsx index 8fa305323b..3d2bf887aa 100644 --- a/invokeai/frontend/web/src/common/components/IAIDropOverlay.tsx +++ b/invokeai/frontend/web/src/common/components/IAIDropOverlay.tsx @@ -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 ( { {isDragActive && isHandlingUpload && ( { }; 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 }, +}; diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolChooserOptions.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolChooserOptions.tsx index 3279f62325..e23dfb389c 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolChooserOptions.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolChooserOptions.tsx @@ -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 = () => { } + icon={} isDisabled={isStaging} onClick={handleEraseBoundingBox} /> diff --git a/invokeai/frontend/web/src/features/dnd/components/AppDndContext.tsx b/invokeai/frontend/web/src/features/dnd/components/AppDndContext.tsx index 3948d5ee66..79514fdc92 100644 --- a/invokeai/frontend/web/src/features/dnd/components/AppDndContext.tsx +++ b/invokeai/frontend/web/src/features/dnd/components/AppDndContext.tsx @@ -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) => { {activeDragData && ( @@ -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 }, +}; diff --git a/invokeai/frontend/web/src/features/embedding/EmbeddingSelect.tsx b/invokeai/frontend/web/src/features/embedding/EmbeddingSelect.tsx index 46fbb6b0ca..2a4d3fb38f 100644 --- a/invokeai/frontend/web/src/features/embedding/EmbeddingSelect.tsx +++ b/invokeai/frontend/web/src/features/embedding/EmbeddingSelect.tsx @@ -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} /> ); }; + +const selectStyles: ChakraProps['sx'] = { + w: 'full', +}; diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx index d43666b795..cddb1b981b 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx @@ -93,7 +93,7 @@ const BoardContextMenu = ({ const renderMenuFunc = useCallback( () => ( - + } diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx index feef52f4c3..137d1bdf56 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx @@ -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'; diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImagePreview.tsx index 2599e9002c..bfe6017815 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImagePreview.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImagePreview.tsx @@ -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'; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/DataViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/DataViewer.tsx index 786c74cd04..981f7e6e59 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/DataViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/DataViewer.tsx @@ -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'; diff --git a/invokeai/frontend/web/src/features/lora/components/LoRACard.tsx b/invokeai/frontend/web/src/features/lora/components/LoRACard.tsx index 7ea4f819d0..39869fb556 100644 --- a/invokeai/frontend/web/src/features/lora/components/LoRACard.tsx +++ b/invokeai/frontend/web/src/features/lora/components/LoRACard.tsx @@ -43,7 +43,9 @@ export const LoRACard = memo((props: LoRACardProps) => { return ( - {lora.model_name} + + {lora.model_name} + { noOptionsMessage={noOptionsMessage} onChange={onChange} data-testid="add-lora" - w="full" + sx={selectStyles} /> ); }; export default memo(LoRASelect); + +const selectStyles: ChakraProps['sx'] = { + w: 'full', +}; diff --git a/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx index 167977c8e4..6d8b15aaae 100644 --- a/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx @@ -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} > @@ -148,7 +148,6 @@ export default function AdvancedAddCheckpoint( {!useCustomConfig ? ( ) : ( @@ -175,3 +174,7 @@ export default function AdvancedAddCheckpoint( ); } + +const formStyles: CSSProperties = { + width: '100%', +}; diff --git a/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx index f672e56475..561938587a 100644 --- a/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx @@ -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} > @@ -138,3 +138,7 @@ export default function AdvancedAddDiffusers(props: AdvancedAddDiffusersProps) { ); } + +const formStyles: CSSProperties = { + width: '100%', +}; diff --git a/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx index 9781c81808..2929f907a4 100644 --- a/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx @@ -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} > @@ -127,3 +128,7 @@ function SearchFolderForm() { } export default memo(SearchFolderForm); + +const formStyles: CSSProperties = { + width: '100%', +}; diff --git a/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/SimpleAddModels.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/SimpleAddModels.tsx index 1b97a0fe89..fc9d2630da 100644 --- a/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/SimpleAddModels.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/SimpleAddModels.tsx @@ -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 (
handleAddModelSubmit(v))} - style={{ width: '100%' }} + style={formStyles} > @@ -97,3 +98,7 @@ export default function SimpleAddModels() { ); } + +const formStyles: CSSProperties = { + width: '100%', +}; diff --git a/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/ModelListItem.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/ModelListItem.tsx index 7e23d0f7c6..84d401279d 100644 --- a/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/ModelListItem.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/ModelListItem.tsx @@ -123,8 +123,8 @@ export default function ModelListItem(props: ModelListItemProps) { acceptButtonText={t('modelManager.delete')} > -

{t('modelManager.deleteMsg1')}

-

{t('modelManager.deleteMsg2')}

+ {t('modelManager.deleteMsg1')} + {t('modelManager.deleteMsg2')}
diff --git a/invokeai/frontend/web/src/features/modelManager/subpanels/shared/CheckpointConfigsSelect.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/shared/CheckpointConfigsSelect.tsx index 05cf01b1c9..cf3958e244 100644 --- a/invokeai/frontend/web/src/features/modelManager/subpanels/shared/CheckpointConfigsSelect.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/shared/CheckpointConfigsSelect.tsx @@ -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; +const sx: ChakraProps['sx'] = { w: 'full' }; + export default function CheckpointConfigsSelect( props: CheckpointConfigSelectProps ) { @@ -22,6 +25,7 @@ export default function CheckpointConfigsSelect(
diff --git a/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx b/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx index 2a9682c7d2..3709ab0837 100644 --- a/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx @@ -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 = () => { {isReady && ( @@ -54,18 +70,10 @@ const NodeEditor = () => { {!isReady && ( { }; }); +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( + () => ({ + 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} diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/connectionLines/CustomConnectionLine.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/connectionLines/CustomConnectionLine.tsx index 7da2f96d42..fcff72b564 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/connectionLines/CustomConnectionLine.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/connectionLines/CustomConnectionLine.tsx @@ -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} /> ); diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/edges/InvocationCollapsedEdge.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/edges/InvocationCollapsedEdge.tsx index 2d2abf1cef..5e16d060d8 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/edges/InvocationCollapsedEdge.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/edges/InvocationCollapsedEdge.tsx @@ -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 ( <> - + {data?.count && data.count > 1 && ( + const edgeStyles = useMemo( + () => ({ + 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 ; }; export default memo(InvocationDefaultEdge); diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/CurrentImage/CurrentImageNode.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/CurrentImage/CurrentImageNode.tsx index e8a084f775..6dea78fafd 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/CurrentImage/CurrentImageNode.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/CurrentImage/CurrentImageNode.tsx @@ -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 && ( @@ -134,3 +120,23 @@ const Wrapper = (props: PropsWithChildren<{ nodeProps: NodeProps }>) => { ); }; + +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', +}; diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeCollapsedHandles.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeCollapsedHandles.tsx index 1057c40903..c287842f6e 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeCollapsedHandles.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeCollapsedHandles.tsx @@ -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) => ( { id={input.name} isConnectable={false} position={Position.Left} - style={{ visibility: 'hidden' }} + style={hiddenHandleStyles} /> ))} { id={`${data.id}-collapsed-source`} isConnectable={false} position={Position.Right} - style={{ ...dummyHandleStyles, right: '-0.5rem' }} + style={collapsedSourceStyles} /> {map(data.outputs, (output) => ( { id={output.name} isConnectable={false} position={Position.Right} - style={{ visibility: 'hidden' }} + style={hiddenHandleStyles} /> ))} diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/NodeEditorPanelGroup.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/NodeEditorPanelGroup.tsx index 92a8dd4208..d0554eb5cc 100644 --- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/NodeEditorPanelGroup.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/NodeEditorPanelGroup.tsx @@ -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} > { {showVersion && isHovered && appVersion && ( { }; 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 }, +}; diff --git a/invokeai/frontend/web/src/features/system/components/StatusIndicator.tsx b/invokeai/frontend/web/src/features/system/components/StatusIndicator.tsx index 9a940ba96d..b92392b9f0 100644 --- a/invokeai/frontend/web/src/features/system/components/StatusIndicator.tsx +++ b/invokeai/frontend/web/src/features/system/components/StatusIndicator.tsx @@ -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 && ( { }; 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 }, +}; diff --git a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx index 857154ea29..e4c7ad5f10 100644 --- a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx +++ b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx @@ -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 = () => { )} */} - - {tabPanels} - + {tabPanels} {!NO_GALLERY_TABS.includes(activeTabName) && ( <> diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersPanel.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersPanel.tsx index 66065fa137..db2d21fa34 100644 --- a/invokeai/frontend/web/src/features/ui/components/ParametersPanel.tsx +++ b/invokeai/frontend/web/src/features/ui/components/ParametersPanel.tsx @@ -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 = () => { diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImageTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImageTab.tsx index 100861ed67..0acd1e1034 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImageTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImageTab.tsx @@ -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(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} > diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx index 20f6d2db43..e9942e01b3 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx @@ -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';