electron
This commit is contained in:
@@ -8,13 +8,31 @@ type Modal500Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
export function Modal500({ requestClose, children }: Modal500Props) {
|
||||
const handleClickOutside = (e: MouseEvent | TouchEvent | FocusEvent) => {
|
||||
const target = e.target as HTMLElement;
|
||||
|
||||
// Don't close if clicking on a popout menu, overlay, or other portaled content
|
||||
// Check for common portal container elements and popout menus
|
||||
if (
|
||||
target.closest('[role="menu"]') ||
|
||||
target.closest('[role="dialog"]') ||
|
||||
target.closest('[data-floating-ui-portal]') ||
|
||||
target.closest('.folds-overlay') ||
|
||||
target.closest('.popout')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
return (
|
||||
<Overlay open backdrop={<OverlayBackdrop />}>
|
||||
<OverlayCenter>
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
initialFocus: false,
|
||||
clickOutsideDeactivates: true,
|
||||
clickOutsideDeactivates: handleClickOutside,
|
||||
onDeactivate: requestClose,
|
||||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AvatarFallback, AvatarImage, color } from 'folds';
|
||||
import React, { ReactEventHandler, ReactNode, useState } from 'react';
|
||||
import React, { ReactEventHandler, ReactNode, useEffect, useRef, useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import * as css from './UserAvatar.css';
|
||||
import colorMXID from '../../../util/colorMXID';
|
||||
@@ -7,6 +7,13 @@ import { useAuthenticatedMediaUrl } from '../../hooks/useAuthenticatedMediaUrl';
|
||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||
import { cacheAvatar, isAvatarCached, pruneAvatarCache } from '../../utils/avatarCache';
|
||||
|
||||
// Check if image is already in browser cache
|
||||
function isImageInBrowserCache(url: string): boolean {
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
return img.complete && img.naturalWidth > 0;
|
||||
}
|
||||
|
||||
type UserAvatarProps = {
|
||||
className?: string;
|
||||
userId: string;
|
||||
@@ -16,9 +23,28 @@ type UserAvatarProps = {
|
||||
};
|
||||
export function UserAvatar({ className, userId, src, alt, renderFallback }: UserAvatarProps) {
|
||||
const [error, setError] = useState(false);
|
||||
const [loaded, setLoaded] = useState(() => isAvatarCached(src));
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
const authenticatedSrc = useAuthenticatedMediaUrl(src, useAuthentication);
|
||||
|
||||
// Check both our cache and browser's native cache
|
||||
const [loaded, setLoaded] = useState(() => {
|
||||
if (isAvatarCached(src)) return true;
|
||||
if (authenticatedSrc && isImageInBrowserCache(authenticatedSrc)) {
|
||||
cacheAvatar(src);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const imgRef = useRef<HTMLImageElement>(null);
|
||||
|
||||
// Also check on mount if image is already complete (browser cached)
|
||||
useEffect(() => {
|
||||
if (!loaded && imgRef.current?.complete && imgRef.current?.naturalWidth > 0) {
|
||||
setLoaded(true);
|
||||
cacheAvatar(src);
|
||||
}
|
||||
}, [authenticatedSrc, loaded, src]);
|
||||
|
||||
const handleLoad: ReactEventHandler<HTMLImageElement> = (evt) => {
|
||||
evt.currentTarget.setAttribute('data-image-loaded', 'true');
|
||||
@@ -54,7 +80,8 @@ export function UserAvatar({ className, userId, src, alt, renderFallback }: User
|
||||
);
|
||||
}
|
||||
|
||||
// Loading state - render fallback with image overlay
|
||||
// Loading state - render image with hidden fallback behind it
|
||||
// Image loads invisibly, then we show it once complete
|
||||
return (
|
||||
<div style={{ position: 'relative', width: '100%', height: '100%' }}>
|
||||
<AvatarFallback
|
||||
@@ -69,6 +96,7 @@ export function UserAvatar({ className, userId, src, alt, renderFallback }: User
|
||||
{renderFallback()}
|
||||
</AvatarFallback>
|
||||
<AvatarImage
|
||||
ref={imgRef}
|
||||
className={classNames(css.UserAvatar, className)}
|
||||
style={{ position: 'relative', zIndex: 1 }}
|
||||
src={authenticatedSrc}
|
||||
|
||||
Reference in New Issue
Block a user