11import { AvatarImage as FoldsAvatarImage } from 'folds' ;
2- import { ReactEventHandler , useState } from 'react' ;
2+ import { ReactEventHandler , useState , useEffect } from 'react' ;
33import bgColorImg from '$utils/bgColorImg' ;
44import { settingsAtom } from '$state/settings' ;
55import { useSetting } from '$state/hooks/settings' ;
@@ -15,20 +15,67 @@ type AvatarImageProps = {
1515export function AvatarImage ( { src, alt, uniformIcons, onError } : AvatarImageProps ) {
1616 const [ uniformIconsSetting ] = useSetting ( settingsAtom , 'uniformIcons' ) ;
1717 const [ image , setImage ] = useState < HTMLImageElement | undefined > ( undefined ) ;
18- const normalizedBg = image ? bgColorImg ( image ) : undefined ;
18+ const [ processedSrc , setProcessedSrc ] = useState < string > ( src ) ;
19+
1920 const useUniformIcons = uniformIconsSetting && uniformIcons === true ;
21+ const normalizedBg = useUniformIcons && image ? bgColorImg ( image ) : undefined ;
22+
23+ useEffect ( ( ) => {
24+ let isMounted = true ;
25+ let objectUrl : string | null = null ;
26+
27+ const processImage = async ( ) => {
28+ try {
29+ const res = await fetch ( src , { mode : 'cors' } ) ;
30+ const contentType = res . headers . get ( 'content-type' ) ;
31+
32+ if ( contentType && contentType . includes ( 'image/svg+xml' ) ) {
33+ const text = await res . text ( ) ;
34+ const parser = new DOMParser ( ) ;
35+ const doc = parser . parseFromString ( text , 'image/svg+xml' ) ;
36+
37+ const animations = doc . querySelectorAll ( 'animate, animateTransform, animateMotion' ) ;
38+ animations . forEach ( ( anim ) => anim . setAttribute ( 'repeatCount' , 'indefinite' ) ) ;
39+
40+ const style = doc . createElementNS ( 'http://www.w3.org/2000/svg' , 'style' ) ;
41+ style . textContent = '* { animation-iteration-count: infinite !important; }' ;
42+ doc . documentElement . appendChild ( style ) ;
43+
44+ const serializer = new XMLSerializer ( ) ;
45+ const newSvgString = serializer . serializeToString ( doc ) ;
46+ const blob = new Blob ( [ newSvgString ] , { type : 'image/svg+xml' } ) ;
47+
48+ objectUrl = URL . createObjectURL ( blob ) ;
49+ if ( isMounted ) setProcessedSrc ( objectUrl ) ;
50+ } else if ( isMounted ) setProcessedSrc ( src ) ;
51+ } catch {
52+ if ( isMounted ) setProcessedSrc ( src ) ;
53+ }
54+ } ;
55+
56+ processImage ( ) ;
57+
58+ return ( ) => {
59+ isMounted = false ;
60+ if ( objectUrl ) {
61+ URL . revokeObjectURL ( objectUrl ) ;
62+ }
63+ } ;
64+ } , [ src ] ) ;
2065
2166 const handleLoad : ReactEventHandler < HTMLImageElement > = ( evt ) => {
2267 evt . currentTarget . setAttribute ( 'data-image-loaded' , 'true' ) ;
2368 setImage ( evt . currentTarget ) ;
2469 } ;
2570
71+ const isBlobUrl = processedSrc . startsWith ( 'blob:' ) ;
72+
2673 return (
2774 < FoldsAvatarImage
2875 className = { css . RoomAvatar }
2976 style = { { backgroundColor : useUniformIcons ? normalizedBg : undefined } }
30- src = { src }
31- crossOrigin = " anonymous"
77+ src = { processedSrc }
78+ crossOrigin = { isBlobUrl ? undefined : ' anonymous' }
3279 alt = { alt }
3380 onError = { ( ) => {
3481 setImage ( undefined ) ;
0 commit comments