This commit is contained in:
parent
895fec2b40
commit
0c7ae46b4d
4 changed files with 129 additions and 29 deletions
|
|
@ -1,12 +1,33 @@
|
|||
// Rôle : fournit l'outil images avec import, collage, libellés et annotation rapide.
|
||||
import { useState } from "react";
|
||||
import { Icon } from "../../../components/Icon.jsx";
|
||||
import { usePointerReorder } from "../../../hooks/usePointerReorder.js";
|
||||
|
||||
export function ImagesModule({ toolboxId, moduleId, context, editing }) {
|
||||
const data = context.getModuleData(toolboxId, moduleId, { images: [] });
|
||||
const textContent = context.moduleText?.images || {};
|
||||
const [dragOver, setDragOver] = useState(false);
|
||||
const pastePlaceholder = textContent.pastePlaceholder || "Coller une image ici";
|
||||
const {
|
||||
draggingId: draggingImageId,
|
||||
dropTarget,
|
||||
startDrag
|
||||
} = usePointerReorder({
|
||||
targetSelector: "[data-image-id]",
|
||||
getTargetId: (target) => target.dataset.imageId,
|
||||
canDropOn: (target) => target.dataset.imageModuleId === moduleId,
|
||||
getPlacement: (event, target) => {
|
||||
const rect = target.getBoundingClientRect();
|
||||
return event.clientX > rect.left + rect.width / 2 ? "after" : "before";
|
||||
},
|
||||
onMove: (draggingId, targetId, placement) => {
|
||||
const nextImages = data.images.filter((image) => image.id !== draggingId);
|
||||
const targetIndex = nextImages.findIndex((image) => image.id === targetId);
|
||||
if (targetIndex === -1) return;
|
||||
nextImages.splice(placement === "after" ? targetIndex + 1 : targetIndex, 0, data.images.find((image) => image.id === draggingId));
|
||||
context.setModuleData(toolboxId, moduleId, { images: nextImages.filter(Boolean) });
|
||||
}
|
||||
});
|
||||
|
||||
async function addFiles(files) {
|
||||
if (await context.addImageFiles(toolboxId, moduleId, files)) {
|
||||
|
|
@ -68,40 +89,59 @@ export function ImagesModule({ toolboxId, moduleId, context, editing }) {
|
|||
</div>
|
||||
)}
|
||||
<div className="images">
|
||||
{data.images.map((image) => (
|
||||
<figure key={image.id}>
|
||||
<button className="image-preview" onClick={() => context.setImage(image)} aria-label={textContent.previewAriaLabel || "Agrandir l'image"}>
|
||||
<img src={image.dataUrl} alt={textContent.imageAlt || "Image"} />
|
||||
</button>
|
||||
{editing ? (
|
||||
<input
|
||||
className="image-label-input"
|
||||
value={image.label || ""}
|
||||
placeholder={textContent.labelPlaceholder || "Libellé de l'image"}
|
||||
onChange={(event) => updateImage(image.id, { label: event.target.value })}
|
||||
aria-label={textContent.labelAriaLabel || "Libellé de l'image"}
|
||||
/>
|
||||
) : image.label ? (
|
||||
<figcaption className="image-label">{image.label}</figcaption>
|
||||
) : null}
|
||||
<div className="image-actions">
|
||||
{data.images.map((image) => {
|
||||
const isDragging = draggingImageId === image.id;
|
||||
const isDropTarget = dropTarget.id === image.id;
|
||||
const className = [
|
||||
isDragging ? "is-dragging" : "",
|
||||
isDropTarget ? "is-drop-target" : "",
|
||||
isDropTarget && dropTarget.placement === "after" ? "drop-after" : ""
|
||||
].filter(Boolean).join(" ");
|
||||
|
||||
return (
|
||||
<figure key={image.id} className={className} data-image-id={image.id} data-image-module-id={moduleId}>
|
||||
<button
|
||||
className="image-annotate-button"
|
||||
className="image-drag-handle"
|
||||
type="button"
|
||||
onClick={() => context.createImageAnnotationModule?.(image.dataUrl)}
|
||||
aria-label={textContent.annotateAriaLabel || "Annoter l'image"}
|
||||
title={textContent.annotateTitle || "Annoter"}
|
||||
onPointerDown={(event) => startDrag(event, image.id)}
|
||||
aria-label={textContent.reorderAriaLabel || "Déplacer l'image"}
|
||||
title={textContent.reorderTitle || "Déplacer"}
|
||||
>
|
||||
<Icon name="map" />
|
||||
<Icon name="drag" />
|
||||
</button>
|
||||
<button className="image-delete-button danger" type="button" onClick={() => {
|
||||
context.setModuleData(toolboxId, moduleId, { images: data.images.filter((item) => item.id !== image.id) });
|
||||
}} aria-label={textContent.deleteAriaLabel || "Supprimer l'image"} title={textContent.deleteTitle || "Supprimer"}>
|
||||
<Icon name="trash" />
|
||||
<button className="image-preview" onClick={() => context.setImage(image)} aria-label={textContent.previewAriaLabel || "Agrandir l'image"}>
|
||||
<img src={image.dataUrl} alt={textContent.imageAlt || "Image"} />
|
||||
</button>
|
||||
</div>
|
||||
</figure>
|
||||
))}
|
||||
{editing ? (
|
||||
<input
|
||||
className="image-label-input"
|
||||
value={image.label || ""}
|
||||
placeholder={textContent.labelPlaceholder || "Libellé de l'image"}
|
||||
onChange={(event) => updateImage(image.id, { label: event.target.value })}
|
||||
aria-label={textContent.labelAriaLabel || "Libellé de l'image"}
|
||||
/>
|
||||
) : image.label ? (
|
||||
<figcaption className="image-label">{image.label}</figcaption>
|
||||
) : null}
|
||||
<div className="image-actions">
|
||||
<button
|
||||
className="image-annotate-button"
|
||||
type="button"
|
||||
onClick={() => context.createImageAnnotationModule?.(image.dataUrl)}
|
||||
aria-label={textContent.annotateAriaLabel || "Annoter l'image"}
|
||||
title={textContent.annotateTitle || "Annoter"}
|
||||
>
|
||||
<Icon name="map" />
|
||||
</button>
|
||||
<button className="image-delete-button danger" type="button" onClick={() => {
|
||||
context.setModuleData(toolboxId, moduleId, { images: data.images.filter((item) => item.id !== image.id) });
|
||||
}} aria-label={textContent.deleteAriaLabel || "Supprimer l'image"} title={textContent.deleteTitle || "Supprimer"}>
|
||||
<Icon name="trash" />
|
||||
</button>
|
||||
</div>
|
||||
</figure>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue