allaw-ui 5.1.1 → 5.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -14,6 +14,7 @@ export interface TextAreaProps {
|
|
|
14
14
|
error?: string;
|
|
15
15
|
maxHeight?: number;
|
|
16
16
|
minHeight?: number;
|
|
17
|
+
maxLength?: number;
|
|
17
18
|
variant?: "bold" | "semiBold" | "medium";
|
|
18
19
|
color?: "bleu-allaw" | "mid-grey" | "dark-grey" | "noir" | "pure-white" | "grey-venom" | "venom-grey-dark";
|
|
19
20
|
dataTestId?: string;
|
|
@@ -6,7 +6,7 @@ import Paragraph from "../typography/Paragraph";
|
|
|
6
6
|
import TinyInfo from "../typography/TinyInfo";
|
|
7
7
|
import "./TextArea.css";
|
|
8
8
|
var TextArea = forwardRef(function (_a, ref) {
|
|
9
|
-
var title = _a.title, _b = _a.style, style = _b === void 0 ? "default" : _b, placeholder = _a.placeholder, _c = _a.isRequired, isRequired = _c === void 0 ? false : _c, validate = _a.validate, onError = _a.onError, onChange = _a.onChange, propValue = _a.value, propError = _a.error, _d = _a.maxHeight, maxHeight = _d === void 0 ? 400 : _d, minHeight = _a.minHeight, _e = _a.variant, variant = _e === void 0 ? "medium" : _e, _f = _a.color, color = _f === void 0 ? "noir" : _f, dataTestId = _a.dataTestId, _g = _a.isPrefilled, isPrefilled = _g === void 0 ? false : _g;
|
|
9
|
+
var title = _a.title, _b = _a.style, style = _b === void 0 ? "default" : _b, placeholder = _a.placeholder, _c = _a.isRequired, isRequired = _c === void 0 ? false : _c, validate = _a.validate, onError = _a.onError, onChange = _a.onChange, propValue = _a.value, propError = _a.error, _d = _a.maxHeight, maxHeight = _d === void 0 ? 400 : _d, minHeight = _a.minHeight, maxLength = _a.maxLength, _e = _a.variant, variant = _e === void 0 ? "medium" : _e, _f = _a.color, color = _f === void 0 ? "noir" : _f, dataTestId = _a.dataTestId, _g = _a.isPrefilled, isPrefilled = _g === void 0 ? false : _g;
|
|
10
10
|
var _h = useState(propValue || ""), value = _h[0], setValue = _h[1];
|
|
11
11
|
var _j = useState(propError || ""), error = _j[0], setError = _j[1];
|
|
12
12
|
var _k = useState(false), isTouched = _k[0], setIsTouched = _k[1];
|
|
@@ -86,7 +86,7 @@ var TextArea = forwardRef(function (_a, ref) {
|
|
|
86
86
|
: ""), "data-testid": dataTestId || "text-area" },
|
|
87
87
|
React.createElement("textarea", { ref: textareaRef, placeholder: placeholder, className: minHeight !== undefined
|
|
88
88
|
? "text-area-placeholder no-transition"
|
|
89
|
-
: "text-area-placeholder", value: value, onChange: handleChange, onBlur: handleBlur, style: textareaStyle, "data-testid": dataTestId || "text-area" })),
|
|
89
|
+
: "text-area-placeholder", value: value, onChange: handleChange, onBlur: handleBlur, style: textareaStyle, maxLength: maxLength, "data-testid": dataTestId || "text-area" })),
|
|
90
90
|
error && isTouched && (React.createElement("div", { className: "error-message" },
|
|
91
91
|
React.createElement(TinyInfo, { variant: "medium12", color: "actions-error", text: error }))))));
|
|
92
92
|
});
|
|
@@ -6,6 +6,21 @@ export interface FileUploaderProps {
|
|
|
6
6
|
enableDragAndDrop?: boolean;
|
|
7
7
|
enableCropping?: boolean;
|
|
8
8
|
cropShape?: "circle" | "square" | "banner";
|
|
9
|
+
clientSideCrop?: boolean;
|
|
10
|
+
cropOutputDimensions?: {
|
|
11
|
+
square?: {
|
|
12
|
+
width: number;
|
|
13
|
+
height: number;
|
|
14
|
+
};
|
|
15
|
+
circle?: {
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
};
|
|
19
|
+
banner?: {
|
|
20
|
+
width: number;
|
|
21
|
+
height: number;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
9
24
|
iconUrl?: string;
|
|
10
25
|
descriptionParts?: {
|
|
11
26
|
beforeLink: string;
|
|
@@ -41,17 +41,21 @@ import ImageCropperModal from "./ImageCropperModal";
|
|
|
41
41
|
import IconButton from "../../atoms/buttons/IconButton";
|
|
42
42
|
import TertiaryButton from "../../atoms/buttons/TertiaryButton";
|
|
43
43
|
var FileUploader = function (_a) {
|
|
44
|
-
var acceptedExtensions = _a.acceptedExtensions, maxFileSizeMB = _a.maxFileSizeMB, _b = _a.enableDragAndDrop, enableDragAndDrop = _b === void 0 ? true : _b, _c = _a.enableCropping, enableCropping = _c === void 0 ? false : _c, _d = _a.cropShape, cropShape = _d === void 0 ? "square" : _d,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
var
|
|
50
|
-
var
|
|
51
|
-
var
|
|
44
|
+
var acceptedExtensions = _a.acceptedExtensions, maxFileSizeMB = _a.maxFileSizeMB, _b = _a.enableDragAndDrop, enableDragAndDrop = _b === void 0 ? true : _b, _c = _a.enableCropping, enableCropping = _c === void 0 ? false : _c, _d = _a.cropShape, cropShape = _d === void 0 ? "square" : _d, _e = _a.clientSideCrop, clientSideCrop = _e === void 0 ? false : _e, _f = _a.cropOutputDimensions, cropOutputDimensions = _f === void 0 ? {
|
|
45
|
+
square: { width: 200, height: 200 },
|
|
46
|
+
circle: { width: 200, height: 200 },
|
|
47
|
+
banner: { width: 1200, height: 400 },
|
|
48
|
+
} : _f, iconUrl = _a.iconUrl, descriptionParts = _a.descriptionParts, onFileRead = _a.onFileRead, onFileRemove = _a.onFileRemove, _g = _a.uploadProgress, uploadProgress = _g === void 0 ? 0 : _g, _h = _a.isLoading, isLoading = _h === void 0 ? false : _h, _j = _a.autoManageProgress, autoManageProgress = _j === void 0 ? false : _j, _k = _a.progressSteps, progressSteps = _k === void 0 ? 10 : _k, _l = _a.progressInterval, progressInterval = _l === void 0 ? 500 : _l, _m = _a.errorMessage, errorMessage = _m === void 0 ? null : _m, _o = _a.buttonLabel, buttonLabel = _o === void 0 ? "Choisir un fichier" : _o, _p = _a.acceptedLabel, acceptedLabel = _p === void 0 ? "Format accepté :" : _p, _q = _a.maxSizeLabel, maxSizeLabel = _q === void 0 ? "Taille maximale :" : _q, fileName = _a.fileName, fileSize = _a.fileSize, _r = _a.filePresentationLabel, filePresentationLabel = _r === void 0 ? "Voici votre fichier." : _r, initialFile = _a.initialFile, initialPreviewUrl = _a.initialPreviewUrl, initialCropMetadata = _a.initialCropMetadata, originalFileName = _a.originalFileName;
|
|
49
|
+
var _s = useState(initialFile || null), selectedFile = _s[0], setSelectedFile = _s[1];
|
|
50
|
+
var _t = useState(null), fileContent = _t[0], setFileContent = _t[1];
|
|
51
|
+
var _u = useState(false), isHovering = _u[0], setIsHovering = _u[1];
|
|
52
|
+
var _v = useState(false), showCropper = _v[0], setShowCropper = _v[1];
|
|
53
|
+
var _w = useState(initialPreviewUrl || null), previewUrl = _w[0], setPreviewUrl = _w[1];
|
|
54
|
+
var _x = useState(initialCropMetadata || null), cropMetadata = _x[0], setCropMetadata = _x[1];
|
|
55
|
+
var _y = useState(0), internalProgress = _y[0], setInternalProgress = _y[1];
|
|
52
56
|
var progressIntervalRef = useRef(null);
|
|
53
57
|
var fileInputRef = useRef(null);
|
|
54
|
-
var
|
|
58
|
+
var _z = useState(originalFileName), currentFileName = _z[0], setCurrentFileName = _z[1];
|
|
55
59
|
useEffect(function () {
|
|
56
60
|
if (initialFile) {
|
|
57
61
|
setSelectedFile(initialFile);
|
|
@@ -207,19 +211,161 @@ var FileUploader = function (_a) {
|
|
|
207
211
|
setShowCropper(true);
|
|
208
212
|
}
|
|
209
213
|
};
|
|
214
|
+
/**
|
|
215
|
+
* Crop l'image côté client et retourne un Blob
|
|
216
|
+
* Utilise la même logique que ImageCropperModal pour calculer le crop
|
|
217
|
+
*/
|
|
218
|
+
var cropImageToBlob = function (imageUrl, cropMetadata, outputWidth, outputHeight) { return __awaiter(void 0, void 0, void 0, function () {
|
|
219
|
+
return __generator(this, function (_a) {
|
|
220
|
+
return [2 /*return*/, new Promise(function (resolve, reject) {
|
|
221
|
+
var img = new window.Image();
|
|
222
|
+
img.crossOrigin = "anonymous";
|
|
223
|
+
img.onload = function () {
|
|
224
|
+
try {
|
|
225
|
+
// Créer un canvas aux dimensions de sortie
|
|
226
|
+
var canvas = document.createElement("canvas");
|
|
227
|
+
canvas.width = outputWidth;
|
|
228
|
+
canvas.height = outputHeight;
|
|
229
|
+
var ctx = canvas.getContext("2d");
|
|
230
|
+
if (!ctx) {
|
|
231
|
+
reject(new Error("Impossible de créer le contexte canvas"));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
// Dimensions de la zone de crop dans le modal (selon le CSS)
|
|
235
|
+
// Ces dimensions correspondent exactement à celles utilisées dans ImageCropperModal.module.css
|
|
236
|
+
var cropAreaWidth = 280; // Dimensions réelles du cadre de crop pour square/circle
|
|
237
|
+
var cropAreaHeight = 280;
|
|
238
|
+
if (cropMetadata.shape === "banner") {
|
|
239
|
+
cropAreaWidth = 320;
|
|
240
|
+
cropAreaHeight = 60;
|
|
241
|
+
}
|
|
242
|
+
// Le zoom dans cropMetadata est déjà le zoom total (baseZoom * zoom relatif)
|
|
243
|
+
// Comme calculé dans ImageCropperModal ligne 318: zoom: baseZoom * zoom
|
|
244
|
+
var totalZoom = cropMetadata.zoom;
|
|
245
|
+
// Dimensions de l'image après zoom
|
|
246
|
+
var scaledImgWidth = img.width * totalZoom;
|
|
247
|
+
var scaledImgHeight = img.height * totalZoom;
|
|
248
|
+
// Dans le modal, l'image est centrée dans le container
|
|
249
|
+
// Le container a les mêmes dimensions que le viewport du modal
|
|
250
|
+
// La cropArea est centrée dans ce container
|
|
251
|
+
// Les offsets (offsetX, offsetY) représentent le déplacement de l'image depuis le centre
|
|
252
|
+
// Position du centre de l'image zoomée (qui correspond au centre du container)
|
|
253
|
+
var imgCenterX = scaledImgWidth / 2;
|
|
254
|
+
var imgCenterY = scaledImgHeight / 2;
|
|
255
|
+
// Position du centre de la cropArea dans l'image zoomée
|
|
256
|
+
// Les offsets sont la position de l'image par rapport au centre du container
|
|
257
|
+
// Donc le centre de la cropArea dans l'image zoomée est :
|
|
258
|
+
var cropCenterX = imgCenterX - cropMetadata.offsetX;
|
|
259
|
+
var cropCenterY = imgCenterY - cropMetadata.offsetY;
|
|
260
|
+
// Coordonnées du coin supérieur gauche de la zone à cropper dans l'image zoomée
|
|
261
|
+
var cropXInScaledImg = cropCenterX - cropAreaWidth / 2;
|
|
262
|
+
var cropYInScaledImg = cropCenterY - cropAreaHeight / 2;
|
|
263
|
+
// Convertir ces coordonnées en coordonnées de l'image originale (diviser par le zoom)
|
|
264
|
+
var sourceX = cropXInScaledImg / totalZoom;
|
|
265
|
+
var sourceY = cropYInScaledImg / totalZoom;
|
|
266
|
+
var sourceWidth = cropAreaWidth / totalZoom;
|
|
267
|
+
var sourceHeight = cropAreaHeight / totalZoom;
|
|
268
|
+
// S'assurer que les coordonnées sont valides
|
|
269
|
+
var clampedSourceX = Math.max(0, Math.min(sourceX, img.width - sourceWidth));
|
|
270
|
+
var clampedSourceY = Math.max(0, Math.min(sourceY, img.height - sourceHeight));
|
|
271
|
+
var clampedSourceWidth = Math.min(sourceWidth, img.width - clampedSourceX);
|
|
272
|
+
var clampedSourceHeight = Math.min(sourceHeight, img.height - clampedSourceY);
|
|
273
|
+
// Dessiner l'image croppée sur le canvas
|
|
274
|
+
ctx.drawImage(img, clampedSourceX, clampedSourceY, clampedSourceWidth, clampedSourceHeight, 0, 0, outputWidth, outputHeight);
|
|
275
|
+
// Si c'est un cercle, appliquer un masque
|
|
276
|
+
if (cropMetadata.shape === "circle") {
|
|
277
|
+
var imageData = ctx.getImageData(0, 0, outputWidth, outputHeight);
|
|
278
|
+
var pixels = imageData.data;
|
|
279
|
+
var centerX = outputWidth / 2;
|
|
280
|
+
var centerY = outputHeight / 2;
|
|
281
|
+
var radius = Math.min(outputWidth, outputHeight) / 2;
|
|
282
|
+
for (var y = 0; y < outputHeight; y++) {
|
|
283
|
+
for (var x = 0; x < outputWidth; x++) {
|
|
284
|
+
var distance = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2));
|
|
285
|
+
if (distance > radius) {
|
|
286
|
+
var index = (y * outputWidth + x) * 4;
|
|
287
|
+
pixels[index + 3] = 0; // Rendre transparent
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
ctx.putImageData(imageData, 0, 0);
|
|
292
|
+
}
|
|
293
|
+
// Convertir en Blob PNG
|
|
294
|
+
canvas.toBlob(function (blob) {
|
|
295
|
+
if (blob) {
|
|
296
|
+
resolve(blob);
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
reject(new Error("Échec de la conversion canvas vers Blob"));
|
|
300
|
+
}
|
|
301
|
+
}, "image/png", 0.95);
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
reject(error);
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
img.onerror = function () {
|
|
308
|
+
reject(new Error("Échec du chargement de l'image"));
|
|
309
|
+
};
|
|
310
|
+
img.src = imageUrl;
|
|
311
|
+
})];
|
|
312
|
+
});
|
|
313
|
+
}); };
|
|
210
314
|
var handleCropCancel = function () {
|
|
211
315
|
setShowCropper(false);
|
|
212
316
|
};
|
|
213
|
-
var handleCropConfirm = function (cropMetadata) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
317
|
+
var handleCropConfirm = function (cropMetadata) { return __awaiter(void 0, void 0, void 0, function () {
|
|
318
|
+
var dimensions, croppedBlob, croppedFile, croppedContent, croppedUrl, error_2;
|
|
319
|
+
return __generator(this, function (_a) {
|
|
320
|
+
switch (_a.label) {
|
|
321
|
+
case 0:
|
|
322
|
+
setShowCropper(false);
|
|
323
|
+
setCropMetadata(cropMetadata);
|
|
324
|
+
if (autoManageProgress) {
|
|
325
|
+
startProgressSimulation();
|
|
326
|
+
}
|
|
327
|
+
if (!(selectedFile && fileContent && previewUrl)) return [3 /*break*/, 7];
|
|
328
|
+
if (!clientSideCrop) return [3 /*break*/, 6];
|
|
329
|
+
_a.label = 1;
|
|
330
|
+
case 1:
|
|
331
|
+
_a.trys.push([1, 4, , 5]);
|
|
332
|
+
dimensions = cropOutputDimensions[cropMetadata.shape] ||
|
|
333
|
+
cropOutputDimensions.square ||
|
|
334
|
+
{ width: 200, height: 200 };
|
|
335
|
+
return [4 /*yield*/, cropImageToBlob(previewUrl, cropMetadata, dimensions.width, dimensions.height)];
|
|
336
|
+
case 2:
|
|
337
|
+
croppedBlob = _a.sent();
|
|
338
|
+
croppedFile = new File([croppedBlob], selectedFile.name.replace(/\.[^/.]+$/, ".png"), // Remplacer l'extension par .png
|
|
339
|
+
{ type: "image/png" });
|
|
340
|
+
return [4 /*yield*/, croppedFile.arrayBuffer()];
|
|
341
|
+
case 3:
|
|
342
|
+
croppedContent = _a.sent();
|
|
343
|
+
croppedUrl = URL.createObjectURL(croppedBlob);
|
|
344
|
+
if (previewUrl && !initialPreviewUrl) {
|
|
345
|
+
URL.revokeObjectURL(previewUrl);
|
|
346
|
+
}
|
|
347
|
+
setPreviewUrl(croppedUrl);
|
|
348
|
+
setSelectedFile(croppedFile);
|
|
349
|
+
setFileContent(croppedContent);
|
|
350
|
+
setCurrentFileName(croppedFile.name);
|
|
351
|
+
// Appeler onFileRead avec le fichier croppé (pas de cropMetadata car déjà croppé)
|
|
352
|
+
onFileRead === null || onFileRead === void 0 ? void 0 : onFileRead(croppedFile, croppedContent);
|
|
353
|
+
return [3 /*break*/, 5];
|
|
354
|
+
case 4:
|
|
355
|
+
error_2 = _a.sent();
|
|
356
|
+
console.error("Erreur lors du crop de l'image:", error_2);
|
|
357
|
+
// En cas d'erreur, fallback sur l'image originale avec métadonnées
|
|
358
|
+
onFileRead === null || onFileRead === void 0 ? void 0 : onFileRead(selectedFile, fileContent, cropMetadata);
|
|
359
|
+
return [3 /*break*/, 5];
|
|
360
|
+
case 5: return [3 /*break*/, 7];
|
|
361
|
+
case 6:
|
|
362
|
+
// Passer l'image originale avec les métadonnées au serveur
|
|
363
|
+
onFileRead === null || onFileRead === void 0 ? void 0 : onFileRead(selectedFile, fileContent, cropMetadata);
|
|
364
|
+
_a.label = 7;
|
|
365
|
+
case 7: return [2 /*return*/];
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
}); };
|
|
223
369
|
var handleFileDelete = function () {
|
|
224
370
|
if (previewUrl && !initialPreviewUrl) {
|
|
225
371
|
URL.revokeObjectURL(previewUrl);
|