mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
136 lines
4.3 KiB
TypeScript
136 lines
4.3 KiB
TypeScript
import type { HTMLChakraProps } from '@chakra-ui/react';
|
|
import { AspectRatio, Box } from '@chakra-ui/react';
|
|
import React from 'react';
|
|
import { useInView } from 'react-intersection-observer';
|
|
|
|
import type { TokenInstance } from 'types/api/token';
|
|
|
|
import { Skeleton } from 'toolkit/chakra/skeleton';
|
|
import { useDisclosure } from 'toolkit/hooks/useDisclosure';
|
|
|
|
import NftFallback from './NftFallback';
|
|
import NftHtml from './NftHtml';
|
|
import NftImage from './NftImage';
|
|
import NftMediaFullscreenModal from './NftMediaFullscreenModal';
|
|
import NftVideo from './NftVideo';
|
|
import useNftMediaInfo from './useNftMediaInfo';
|
|
import type { MediaType, Size } from './utils';
|
|
import { mediaStyleProps } from './utils';
|
|
|
|
interface Props extends Omit<HTMLChakraProps<'div'>, 'size'> {
|
|
data: TokenInstance;
|
|
size?: Size;
|
|
allowedTypes?: Array<MediaType>;
|
|
isLoading?: boolean;
|
|
withFullscreen?: boolean;
|
|
autoplayVideo?: boolean;
|
|
fallback?: React.ReactNode;
|
|
}
|
|
|
|
const NftMedia = ({ data, size = 'original', allowedTypes, isLoading, withFullscreen, autoplayVideo, fallback, ...rest }: Props) => {
|
|
const [ isMediaLoading, setIsMediaLoading ] = React.useState(true);
|
|
const [ isMediaLoadingError, setIsMediaLoadingError ] = React.useState(false);
|
|
const [ mediaInfoIndex, setMediaInfoIndex ] = React.useState(0);
|
|
const [ mediaInfoField, setMediaInfoField ] = React.useState<'animation_url' | 'image_url'>('animation_url');
|
|
|
|
const { ref, inView } = useInView({ triggerOnce: true });
|
|
|
|
const mediaInfoQuery = useNftMediaInfo({ data, size, allowedTypes, field: mediaInfoField, isEnabled: !isLoading && inView });
|
|
|
|
React.useEffect(() => {
|
|
if (!mediaInfoQuery.isPending && !mediaInfoQuery.data) {
|
|
if (mediaInfoField === 'animation_url') {
|
|
setMediaInfoField('image_url');
|
|
} else {
|
|
setIsMediaLoadingError(true);
|
|
setIsMediaLoading(false);
|
|
}
|
|
}
|
|
}, [ mediaInfoQuery.isPending, mediaInfoQuery.data, mediaInfoField ]);
|
|
|
|
const handleMediaLoaded = React.useCallback(() => {
|
|
setIsMediaLoading(false);
|
|
}, []);
|
|
|
|
const handleMediaLoadError = React.useCallback(() => {
|
|
if (mediaInfoQuery.data) {
|
|
if (mediaInfoIndex < mediaInfoQuery.data.length - 1) {
|
|
setMediaInfoIndex(mediaInfoIndex + 1);
|
|
return;
|
|
} else if (mediaInfoField === 'animation_url') {
|
|
setMediaInfoField('image_url');
|
|
setMediaInfoIndex(0);
|
|
return;
|
|
}
|
|
}
|
|
|
|
setIsMediaLoading(false);
|
|
setIsMediaLoadingError(true);
|
|
}, [ mediaInfoField, mediaInfoIndex, mediaInfoQuery.data ]);
|
|
|
|
const { open, onOpen, onOpenChange } = useDisclosure();
|
|
|
|
const content = (() => {
|
|
if (isLoading) {
|
|
return null;
|
|
}
|
|
|
|
if (isMediaLoadingError) {
|
|
const styleProps = withFullscreen ? {} : mediaStyleProps;
|
|
return fallback ?? <NftFallback { ...styleProps }/>;
|
|
}
|
|
|
|
const mediaInfo = mediaInfoQuery.data?.[mediaInfoIndex];
|
|
const props = {
|
|
onLoad: handleMediaLoaded,
|
|
onError: handleMediaLoadError,
|
|
...(withFullscreen ? { onClick: onOpen } : {}),
|
|
...(size !== 'sm' ? mediaStyleProps : {}),
|
|
};
|
|
|
|
switch (mediaInfo?.mediaType) {
|
|
case 'video': {
|
|
return <NftVideo { ...props } src={ mediaInfo.src } transport={ mediaInfo.transport } autoPlay={ autoplayVideo } instance={ data }/>;
|
|
}
|
|
case 'html':
|
|
return <NftHtml { ...props } src={ mediaInfo.src } transport={ mediaInfo.transport }/>;
|
|
case 'image': {
|
|
return <NftImage { ...props } src={ mediaInfo.src } srcSet={ mediaInfo.srcSet } transport={ mediaInfo.transport }/>;
|
|
}
|
|
default:
|
|
return null;
|
|
}
|
|
})();
|
|
|
|
return (
|
|
<>
|
|
<AspectRatio
|
|
ref={ ref }
|
|
ratio={ 1 / 1 }
|
|
overflow="hidden"
|
|
borderRadius="md"
|
|
isolation="isolate"
|
|
{ ...rest }
|
|
>
|
|
<>
|
|
<Box
|
|
css={{
|
|
'& > img, & > video': {
|
|
objectFit: 'contain',
|
|
},
|
|
}}
|
|
>
|
|
{ content }
|
|
</Box>
|
|
{ isMediaLoading && <Skeleton loading position="absolute" left={ 0 } top={ 0 } w="100%" h="100%" zIndex="1"/> }
|
|
</>
|
|
</AspectRatio>
|
|
{ open && (
|
|
<NftMediaFullscreenModal open={ open } onOpenChange={ onOpenChange } data={ data } allowedTypes={ allowedTypes } field={ mediaInfoField }/>
|
|
) }
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default React.memo(NftMedia);
|