fix(ui): merge refs when forwardingin DndImage

This commit is contained in:
psychedelicious
2025-05-30 21:31:43 +10:00
parent 1f7d6c46e6
commit f7dca00a58
2 changed files with 63 additions and 67 deletions

View File

@@ -9,7 +9,7 @@ import { createSingleImageDragPreview, setSingleImageDragPreview } from 'feature
import { firefoxDndFix } from 'features/dnd/util';
import { useImageContextMenu } from 'features/gallery/components/ImageContextMenu/ImageContextMenu';
import { $imageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
import { memo, useEffect, useRef, useState } from 'react';
import { forwardRef, memo, useEffect, useImperativeHandle, useRef, useState } from 'react';
import type { ImageDTO } from 'services/api/types';
const sx = {
@@ -23,69 +23,68 @@ const sx = {
},
} satisfies SystemStyleObject;
/* eslint-disable-next-line @typescript-eslint/no-namespace */
export namespace DndImage {
export interface Props extends ImageProps {
imageDTO: ImageDTO;
asThumbnail?: boolean;
}
}
type Props = {
imageDTO: ImageDTO;
asThumbnail?: boolean;
} & ImageProps;
export const DndImage = memo(({ imageDTO, asThumbnail, ...rest }: DndImage.Props) => {
const store = useAppStore();
const [isDragging, setIsDragging] = useState(false);
const ref = useRef<HTMLImageElement>(null);
const [dragPreviewState, setDragPreviewState] = useState<DndDragPreviewSingleImageState | null>(null);
export const DndImage = memo(
forwardRef(({ imageDTO, asThumbnail, ...rest }: Props, forwardedRef) => {
const store = useAppStore();
const [isDragging, setIsDragging] = useState(false);
const ref = useRef<HTMLImageElement>(null);
useImperativeHandle(forwardedRef, () => ref.current!, []);
const [dragPreviewState, setDragPreviewState] = useState<DndDragPreviewSingleImageState | null>(null);
useEffect(() => {
const element = ref.current;
if (!element) {
return;
}
return combine(
firefoxDndFix(element),
draggable({
element,
getInitialData: () => singleImageDndSource.getData({ imageDTO }, imageDTO.image_name),
onDragStart: () => {
setIsDragging(true);
if ($imageViewer.get()) {
$imageViewer.set(false);
}
},
onDrop: () => {
setIsDragging(false);
},
onGenerateDragPreview: (args) => {
if (singleImageDndSource.typeGuard(args.source.data)) {
setSingleImageDragPreview({
singleImageDndData: args.source.data,
onGenerateDragPreviewArgs: args,
setDragPreviewState,
});
}
},
})
useEffect(() => {
const element = ref.current;
if (!element) {
return;
}
return combine(
firefoxDndFix(element),
draggable({
element,
getInitialData: () => singleImageDndSource.getData({ imageDTO }, imageDTO.image_name),
onDragStart: () => {
setIsDragging(true);
if ($imageViewer.get()) {
$imageViewer.set(false);
}
},
onDrop: () => {
setIsDragging(false);
},
onGenerateDragPreview: (args) => {
if (singleImageDndSource.typeGuard(args.source.data)) {
setSingleImageDragPreview({
singleImageDndData: args.source.data,
onGenerateDragPreviewArgs: args,
setDragPreviewState,
});
}
},
})
);
}, [forwardedRef, imageDTO, store]);
useImageContextMenu(imageDTO, ref);
return (
<>
<Image
role="button"
ref={ref}
src={asThumbnail ? imageDTO.thumbnail_url : imageDTO.image_url}
fallbackSrc={asThumbnail ? undefined : imageDTO.thumbnail_url}
w={imageDTO.width}
sx={sx}
data-is-dragging={isDragging}
{...rest}
/>
{dragPreviewState?.type === 'single-image' ? createSingleImageDragPreview(dragPreviewState) : null}
</>
);
}, [imageDTO, store]);
useImageContextMenu(imageDTO, ref);
return (
<>
<Image
role="button"
ref={ref}
src={asThumbnail ? imageDTO.thumbnail_url : imageDTO.image_url}
fallbackSrc={asThumbnail ? undefined : imageDTO.thumbnail_url}
w={imageDTO.width}
sx={sx}
data-is-dragging={isDragging}
{...rest}
/>
{dragPreviewState?.type === 'single-image' ? createSingleImageDragPreview(dragPreviewState) : null}
</>
);
});
})
);
DndImage.displayName = 'DndImage';

View File

@@ -60,11 +60,8 @@ const getImageDTOFromMap = (target: Node): ImageDTO | undefined => {
* @param imageDTO The image DTO to register the context menu for.
* @param targetRef The ref of the target element that should trigger the context menu.
*/
export const useImageContextMenu = (imageDTO: ImageDTO | undefined, ref: RefObject<HTMLElement>) => {
export const useImageContextMenu = (imageDTO: ImageDTO, ref: RefObject<HTMLElement>) => {
useEffect(() => {
if (!imageDTO) {
return;
}
const el = ref.current;
if (!el) {
return;