diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImage.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImage.tsx index 3a21e19bf8..2eeb2b908a 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImage.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RefImage/RefImage.tsx @@ -11,6 +11,7 @@ import { PopoverContent, Portal, Skeleton, + Text, } from '@invoke-ai/ui-library'; import { skipToken } from '@reduxjs/toolkit/query'; import { POPPER_MODIFIERS } from 'common/components/InformationalPopover/constants'; @@ -21,7 +22,9 @@ import { RefImageHeader } from 'features/controlLayers/components/RefImage/RefIm import { RefImageSettings } from 'features/controlLayers/components/RefImage/RefImageSettings'; import { useRefImageEntity } from 'features/controlLayers/components/RefImage/useRefImageEntity'; import { useRefImageIdContext } from 'features/controlLayers/contexts/RefImageIdContext'; -import { memo, useCallback, useRef } from 'react'; +import { isIPAdapterConfig } from 'features/controlLayers/store/types'; +import { round } from 'lodash-es'; +import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { PiImageBold } from 'react-icons/pi'; import { useGetImageDTOQuery } from 'services/api/endpoints/images'; @@ -80,10 +83,11 @@ export const RefImage = memo(() => { }); RefImage.displayName = 'RefImage'; -const imageSx: SystemStyleObject = { +const baseSx: SystemStyleObject = { opacity: 0.7, transitionProperty: 'opacity', transitionDuration: 'normal', + position: 'relative', _hover: { opacity: 1, }, @@ -92,11 +96,58 @@ const imageSx: SystemStyleObject = { }, }; +const weightDisplaySx: SystemStyleObject = { + pointerEvents: 'none', + transitionProperty: 'opacity', + transitionDuration: 'normal', + opacity: 0, + '&[data-visible="true"]': { + opacity: 1, + }, +}; + +const getImageSxWithWeight = (weight: number): SystemStyleObject => { + const fillPercentage = Math.max(0, Math.min(100, weight * 100)); + + return { + ...baseSx, + _after: { + content: '""', + position: 'absolute', + inset: 0, + background: `linear-gradient(to top, transparent ${fillPercentage}%, rgba(0, 0, 0, 0.8) ${fillPercentage}%)`, + pointerEvents: 'none', + borderRadius: 'base', + }, + }; +}; + const Thumbnail = memo(({ disclosure }: { disclosure: UseDisclosure }) => { const id = useRefImageIdContext(); const entity = useRefImageEntity(id); + const [showWeightDisplay, setShowWeightDisplay] = useState(false); const { data: imageDTO } = useGetImageDTOQuery(entity.config.image?.image_name ?? skipToken); + const sx = useMemo(() => { + if (!isIPAdapterConfig(entity.config)) { + return baseSx; + } + return getImageSxWithWeight(entity.config.weight); + }, [entity.config]); + + useEffect(() => { + if (!isIPAdapterConfig(entity.config)) { + return; + } + setShowWeightDisplay(true); + const timeout = window.setTimeout(() => { + setShowWeightDisplay(false); + }, 1000); + return () => { + window.clearTimeout(timeout); + }; + }, [entity.config]); + if (!entity.config.image) { return ( @@ -120,25 +171,47 @@ const Thumbnail = memo(({ disclosure }: { disclosure: UseDisclosure }) => { } return ( - } maxW="full" maxH="full" - borderRadius="base" - onClick={disclosure.toggle} flexShrink={0} - sx={imageSx} + sx={sx} data-is-open={disclosure.isOpen} - /> + id={getRefImagePopoverTriggerId(id)} + role="button" + onClick={disclosure.toggle} + cursor="pointer" + > + } + maxW="full" + maxH="full" + borderRadius="base" + /> + + + {`${round(entity.config.weight * 100, 2)}%`} + + + ); });