sokko-g/website/src/features/toolboxes/modules/ScreenshotsModule.jsx
Shinuwa d7871736c8
All checks were successful
Deploy Sokko G / deploy (push) Successful in 6s
add label to pictures
2026-07-25 13:39:35 +02:00

107 lines
4.3 KiB
JavaScript

import { useState } from "react";
import { Icon } from "../../../components/Icon.jsx";
export function ScreenshotsModule({ toolboxId, moduleId, context, editing }) {
const data = context.getModuleData(toolboxId, moduleId, { shots: [] });
const textContent = context.moduleText?.screenshots || {};
const [dragOver, setDragOver] = useState(false);
const pastePlaceholder = textContent.pastePlaceholder || "Coller une image ici";
async function addFiles(files) {
if (await context.addScreenshotFiles(toolboxId, moduleId, files)) {
setDragOver(false);
}
}
function updateShot(shotId, patch) {
context.setModuleData(toolboxId, moduleId, {
shots: data.shots.map((shot) => shot.id === shotId ? { ...shot, ...patch } : shot)
});
}
return (
<>
{editing && (
<div className="module-add-panel">
<label
className={`dropzone ${dragOver ? "is-drag-over" : ""}`}
onDragOver={(event) => {
event.preventDefault();
event.dataTransfer.dropEffect = "copy";
setDragOver(true);
}}
onDragLeave={() => setDragOver(false)}
onDrop={(event) => {
event.preventDefault();
addFiles(event.dataTransfer.files);
}}
>
<input type="file" accept="image/*" multiple hidden onChange={(event) => addFiles(event.target.files)} />
{textContent.addImages || "Ajouter des images"}
</label>
<div
className="paste-target"
contentEditable
suppressContentEditableWarning
role="textbox"
aria-label={textContent.pasteAriaLabel || "Coller une image depuis le presse-papiers"}
onFocus={(event) => {
if (event.currentTarget.textContent.trim() === pastePlaceholder) event.currentTarget.textContent = "";
}}
onBlur={(event) => {
if (!event.currentTarget.textContent.trim()) event.currentTarget.textContent = pastePlaceholder;
}}
onPaste={(event) => {
const files = [...(event.clipboardData?.items || [])]
.filter((item) => item.type.startsWith("image/"))
.map((item) => item.getAsFile())
.filter(Boolean);
if (!files.length) return;
event.preventDefault();
event.currentTarget.textContent = pastePlaceholder;
addFiles(files);
}}
>
{pastePlaceholder}
</div>
</div>
)}
<div className="shots">
{data.shots.map((shot) => (
<figure key={shot.id}>
<button className="shot-preview" onClick={() => context.setScreenshot(shot)} aria-label={textContent.previewAriaLabel || "Agrandir l'image"}>
<img src={shot.dataUrl} alt={textContent.imageAlt || "Image"} />
</button>
{editing ? (
<input
className="shot-label-input"
value={shot.label || ""}
placeholder={textContent.labelPlaceholder || "Libellé de l'image"}
onChange={(event) => updateShot(shot.id, { label: event.target.value })}
aria-label={textContent.labelAriaLabel || "Libellé de l'image"}
/>
) : shot.label ? (
<figcaption className="shot-label">{shot.label}</figcaption>
) : null}
<div className="shot-actions">
<button
className="shot-annotate-button"
type="button"
onClick={() => context.createImageAnnotationModule?.(shot.dataUrl)}
aria-label={textContent.annotateAriaLabel || "Annoter l'image"}
title={textContent.annotateTitle || "Annoter"}
>
<Icon name="map" />
</button>
<button className="shot-delete-button danger" type="button" onClick={() => {
context.setModuleData(toolboxId, moduleId, { shots: data.shots.filter((item) => item.id !== shot.id) });
}} aria-label={textContent.deleteAriaLabel || "Supprimer l'image"} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" />
</button>
</div>
</figure>
))}
</div>
</>
);
}