feat(ui): use stable objects for animation/native element styles

This commit is contained in:
psychedelicious 2023-12-29 13:27:39 +11:00 committed by Kent Keirsey
parent f5194f9e2d
commit ca4b8e65c1
33 changed files with 292 additions and 183 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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