add label to pictures
All checks were successful
Deploy Sokko G / deploy (push) Successful in 6s

This commit is contained in:
Shinuwa 2026-07-25 13:39:35 +02:00
parent ef5f977f8a
commit d7871736c8
10 changed files with 566 additions and 8 deletions

View file

@ -224,6 +224,8 @@
"pastePlaceholder": "Coller une image ici",
"pasteAriaLabel": "Coller une image depuis le presse-papiers",
"imageAlt": "Image",
"labelPlaceholder": "Libellé de l'image",
"labelAriaLabel": "Libellé de l'image",
"previewAriaLabel": "Agrandir l'image",
"annotateAriaLabel": "Annoter l'image",
"annotateTitle": "Annoter",

View file

@ -13,6 +13,12 @@ export function ScreenshotsModule({ toolboxId, moduleId, context, editing }) {
}
}
function updateShot(shotId, patch) {
context.setModuleData(toolboxId, moduleId, {
shots: data.shots.map((shot) => shot.id === shotId ? { ...shot, ...patch } : shot)
});
}
return (
<>
{editing && (
@ -66,6 +72,17 @@ export function ScreenshotsModule({ toolboxId, moduleId, context, editing }) {
<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"

View file

@ -241,6 +241,11 @@ function normalizeCalculatorValue(value) {
return Number.isFinite(parsed) ? parsed : 0;
}
function labelFromFileName(name) {
if (!name || name === "image.png") return "";
return name.replace(/\.[^.]+$/, "").trim();
}
function normalizeCalculatorData(data) {
const entries = (data?.entries || [])
.map((entry) => ({
@ -313,7 +318,11 @@ function compactModuleDataForStorage(type, value) {
if (type === "screenshots") {
const shots = (Array.isArray(value?.shots) ? value.shots : [])
.filter((shot) => shot?.dataUrl)
.map((shot) => ({ id: shot.id || uid("shot"), dataUrl: shot.dataUrl }));
.map((shot) => {
const compact = { id: shot.id || uid("shot"), dataUrl: shot.dataUrl };
if (shot.label) compact.label = shot.label;
return compact;
});
return shots.length ? { shots } : null;
}
if (type === "imageAnnotation") {
@ -798,7 +807,10 @@ function App() {
if (!imageFiles.length) return false;
const data = store.getModuleData(toolboxId, moduleId, { shots: [] });
for (const file of imageFiles) {
data.shots.unshift({ id: uid("shot"), dataUrl: await compressImage(file) });
const shot = { id: uid("shot"), dataUrl: await compressImage(file) };
const label = labelFromFileName(file.name);
if (label) shot.label = label;
data.shots.unshift(shot);
}
return store.updateModuleData(toolboxId, moduleId, data);
}
@ -1812,10 +1824,13 @@ function ScreenshotViewer({ shot, onClose }) {
<div className="screenshot-viewer-backdrop" onClick={onClose} />
<section ref={viewerRef} className="screenshot-viewer" role="dialog" aria-modal="true" aria-label="Screenshot">
<header>
{!canAnnotate && !hasMarkers && (
<button className="screenshot-viewer-button" onClick={() => openImageInNewTab(shot.dataUrl)} aria-label="Ouvrir l'image en taille réelle" title="Taille réelle"><Icon name="zoom" /></button>
)}
<button className="screenshot-viewer-button" onClick={onClose} aria-label="Fermer" title="Fermer"><Icon name="close" /></button>
{shot.label && <strong className="screenshot-viewer-title">{shot.label}</strong>}
<div className="screenshot-viewer-actions">
{!canAnnotate && !hasMarkers && (
<button className="screenshot-viewer-button" onClick={() => openImageInNewTab(shot.dataUrl)} aria-label="Ouvrir l'image en taille réelle" title="Taille réelle"><Icon name="zoom" /></button>
)}
<button className="screenshot-viewer-button" onClick={onClose} aria-label="Fermer" title="Fermer"><Icon name="close" /></button>
</div>
</header>
<div className={`screenshot-viewer-body ${canAnnotate ? "has-annotation-side" : ""}`}>
<div

View file

@ -31,8 +31,22 @@
}
.screenshot-viewer header {
display: flex;
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
gap: var(--space-2);
}
.screenshot-viewer-title {
overflow: hidden;
color: var(--color-text);
font-size: var(--font-size-md);
text-overflow: ellipsis;
white-space: nowrap;
}
.screenshot-viewer-actions {
display: flex;
justify-content: flex-end;
gap: var(--space-2);
}

View file

@ -1710,6 +1710,8 @@ textarea:focus {
.shots figure {
position: relative;
display: grid;
gap: 8px;
margin: 0;
padding: 8px;
border: 1px solid var(--color-border);
@ -1732,6 +1734,40 @@ textarea:focus {
box-shadow: 0 0 0 2px rgba(246, 196, 83, 0.28);
}
.shot-label,
.shot-label-input {
min-width: 0;
font-size: var(--font-size-sm);
}
.shot-label {
overflow: hidden;
color: var(--color-text-secondary);
font-weight: 700;
text-overflow: ellipsis;
white-space: nowrap;
}
.shot-label-input {
width: 100%;
min-height: 36px;
padding: 0 10px;
border: 1px solid rgba(138, 150, 197, 0.2);
border-radius: var(--radius-md);
background: rgba(8, 13, 30, 0.62);
color: var(--color-text);
}
.shot-label-input::placeholder {
color: rgba(185, 194, 215, 0.48);
}
.shot-label-input:focus {
border-color: rgba(246, 196, 83, 0.72);
outline: none;
box-shadow: 0 0 0 3px rgba(246, 196, 83, 0.14);
}
.shots img {
display: block;
width: 100%;
@ -1744,7 +1780,6 @@ textarea:focus {
.shot-actions {
display: flex;
gap: var(--space-2);
margin-top: 8px;
}
.shot-actions .shot-annotate-button,