allaw-ui 3.0.1 → 3.0.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.
- package/dist/components/molecules/fileUploader/FileUploader.d.ts +5 -1
- package/dist/components/molecules/fileUploader/FileUploader.js +110 -18
- package/dist/components/molecules/fileUploader/FileUploader.module.css +127 -0
- package/dist/components/molecules/fileUploader/FileUploader.stories.d.ts +34 -15
- package/dist/components/molecules/fileUploader/FileUploader.stories.js +63 -9
- package/dist/components/molecules/fileUploader/ImageCropperModal.d.ts +15 -0
- package/dist/components/molecules/fileUploader/ImageCropperModal.js +212 -0
- package/dist/components/molecules/fileUploader/ImageCropperModal.module.css +232 -0
- package/package.json +1 -1
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { CropMetadata } from "./ImageCropperModal";
|
|
2
3
|
export interface FileUploaderProps {
|
|
3
4
|
acceptedExtensions: string[];
|
|
4
5
|
maxFileSizeMB: number;
|
|
5
6
|
enableDragAndDrop?: boolean;
|
|
7
|
+
enableCropping?: boolean;
|
|
8
|
+
cropShape?: "circle" | "square";
|
|
6
9
|
iconUrl?: string;
|
|
7
10
|
descriptionParts?: {
|
|
8
11
|
beforeLink: string;
|
|
@@ -10,7 +13,7 @@ export interface FileUploaderProps {
|
|
|
10
13
|
linkUrl: string;
|
|
11
14
|
afterLink: string;
|
|
12
15
|
};
|
|
13
|
-
onFileRead?: (file: File, fileContent: ArrayBuffer) => void;
|
|
16
|
+
onFileRead?: (file: File, fileContent: ArrayBuffer, cropMetadata?: CropMetadata) => void;
|
|
14
17
|
onFileRemove?: () => void;
|
|
15
18
|
uploadProgress?: number;
|
|
16
19
|
isLoading?: boolean;
|
|
@@ -20,6 +23,7 @@ export interface FileUploaderProps {
|
|
|
20
23
|
maxSizeLabel?: string;
|
|
21
24
|
fileName?: string;
|
|
22
25
|
fileSize?: number;
|
|
26
|
+
filePresentationLabel?: string;
|
|
23
27
|
}
|
|
24
28
|
declare const FileUploader: React.FC<FileUploaderProps>;
|
|
25
29
|
export default FileUploader;
|
|
@@ -35,11 +35,19 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
35
35
|
}
|
|
36
36
|
};
|
|
37
37
|
import React, { useState, useRef } from "react";
|
|
38
|
+
import Image from "next/image";
|
|
38
39
|
import styles from "./FileUploader.module.css";
|
|
40
|
+
import ImageCropperModal from "./ImageCropperModal";
|
|
41
|
+
import IconButton from "../../atoms/buttons/IconButton";
|
|
42
|
+
import { TertiaryButton } from "src/components/atoms/buttons";
|
|
39
43
|
var FileUploader = function (_a) {
|
|
40
|
-
var acceptedExtensions = _a.acceptedExtensions, maxFileSizeMB = _a.maxFileSizeMB, _b = _a.enableDragAndDrop, enableDragAndDrop = _b === void 0 ? true : _b, iconUrl = _a.iconUrl, descriptionParts = _a.descriptionParts, onFileRead = _a.onFileRead, onFileRemove = _a.onFileRemove,
|
|
41
|
-
var
|
|
42
|
-
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, iconUrl = _a.iconUrl, descriptionParts = _a.descriptionParts, onFileRead = _a.onFileRead, onFileRemove = _a.onFileRemove, _e = _a.uploadProgress, uploadProgress = _e === void 0 ? 0 : _e, _f = _a.isLoading, isLoading = _f === void 0 ? false : _f, _g = _a.errorMessage, errorMessage = _g === void 0 ? null : _g, _h = _a.buttonLabel, buttonLabel = _h === void 0 ? "Choisir un fichier" : _h, _j = _a.acceptedLabel, acceptedLabel = _j === void 0 ? "Format accepté :" : _j, _k = _a.maxSizeLabel, maxSizeLabel = _k === void 0 ? "Taille maximale :" : _k, fileName = _a.fileName, fileSize = _a.fileSize, _l = _a.filePresentationLabel, filePresentationLabel = _l === void 0 ? "Voici votre fichier." : _l;
|
|
45
|
+
var _m = useState(null), selectedFile = _m[0], setSelectedFile = _m[1];
|
|
46
|
+
var _o = useState(null), fileContent = _o[0], setFileContent = _o[1];
|
|
47
|
+
var _p = useState(false), isHovering = _p[0], setIsHovering = _p[1];
|
|
48
|
+
var _q = useState(false), showCropper = _q[0], setShowCropper = _q[1];
|
|
49
|
+
var _r = useState(null), previewUrl = _r[0], setPreviewUrl = _r[1];
|
|
50
|
+
var _s = useState(null), cropMetadata = _s[0], setCropMetadata = _s[1];
|
|
43
51
|
var fileInputRef = useRef(null);
|
|
44
52
|
var resetFileInput = function () {
|
|
45
53
|
if (fileInputRef.current) {
|
|
@@ -55,6 +63,12 @@ var FileUploader = function (_a) {
|
|
|
55
63
|
var isValidSize = file.size <= maxFileSizeMB * 1024 * 1024;
|
|
56
64
|
return isValidExtension && isValidSize;
|
|
57
65
|
};
|
|
66
|
+
var isImageFile = function (file) {
|
|
67
|
+
var _a;
|
|
68
|
+
var fileExtension = ".".concat((_a = file.name.split(".").pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase());
|
|
69
|
+
var imageExtensions = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"];
|
|
70
|
+
return imageExtensions.includes(fileExtension);
|
|
71
|
+
};
|
|
58
72
|
var handleFileSelect = function (event) { return __awaiter(void 0, void 0, void 0, function () {
|
|
59
73
|
var file;
|
|
60
74
|
return __generator(this, function (_a) {
|
|
@@ -85,37 +99,93 @@ var FileUploader = function (_a) {
|
|
|
85
99
|
});
|
|
86
100
|
}); };
|
|
87
101
|
var processFile = function (file) { return __awaiter(void 0, void 0, void 0, function () {
|
|
88
|
-
var
|
|
102
|
+
var content, url, error_1;
|
|
89
103
|
return __generator(this, function (_a) {
|
|
90
104
|
switch (_a.label) {
|
|
91
105
|
case 0:
|
|
92
106
|
if (!validateFile(file)) {
|
|
93
107
|
setSelectedFile(null);
|
|
108
|
+
setPreviewUrl(null);
|
|
109
|
+
setCropMetadata(null);
|
|
94
110
|
return [2 /*return*/];
|
|
95
111
|
}
|
|
96
112
|
_a.label = 1;
|
|
97
113
|
case 1:
|
|
98
114
|
_a.trys.push([1, 3, , 4]);
|
|
99
|
-
setSelectedFile(file);
|
|
100
115
|
return [4 /*yield*/, file.arrayBuffer()];
|
|
101
116
|
case 2:
|
|
102
|
-
|
|
103
|
-
|
|
117
|
+
content = _a.sent();
|
|
118
|
+
setFileContent(content);
|
|
119
|
+
// Créer un aperçu si c'est une image
|
|
120
|
+
if (isImageFile(file)) {
|
|
121
|
+
url = URL.createObjectURL(file);
|
|
122
|
+
setPreviewUrl(url);
|
|
123
|
+
// Si le cadrage n'est pas activé, on utilise une métadonnée par défaut
|
|
124
|
+
if (!enableCropping) {
|
|
125
|
+
setCropMetadata({
|
|
126
|
+
zoom: 1,
|
|
127
|
+
offsetX: 0,
|
|
128
|
+
offsetY: 0,
|
|
129
|
+
shape: cropShape,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
setPreviewUrl(null);
|
|
135
|
+
setCropMetadata(null);
|
|
136
|
+
}
|
|
137
|
+
// Si c'est une image et que le cadrage est activé, afficher le composant de cadrage
|
|
138
|
+
if (enableCropping && isImageFile(file)) {
|
|
139
|
+
setSelectedFile(file);
|
|
140
|
+
setShowCropper(true);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
// Sinon, traiter normalement
|
|
144
|
+
setSelectedFile(file);
|
|
145
|
+
onFileRead === null || onFileRead === void 0 ? void 0 : onFileRead(file, content);
|
|
146
|
+
}
|
|
104
147
|
return [3 /*break*/, 4];
|
|
105
148
|
case 3:
|
|
106
149
|
error_1 = _a.sent();
|
|
107
150
|
console.error("Error reading file:", error_1);
|
|
108
151
|
setSelectedFile(null);
|
|
152
|
+
setPreviewUrl(null);
|
|
153
|
+
setCropMetadata(null);
|
|
109
154
|
return [3 /*break*/, 4];
|
|
110
155
|
case 4: return [2 /*return*/];
|
|
111
156
|
}
|
|
112
157
|
});
|
|
113
158
|
}); };
|
|
159
|
+
var openCropModal = function () {
|
|
160
|
+
if (selectedFile && isImageFile(selectedFile)) {
|
|
161
|
+
setShowCropper(true);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
var handleCropCancel = function () {
|
|
165
|
+
setShowCropper(false);
|
|
166
|
+
};
|
|
167
|
+
var handleCropConfirm = function (cropMetadata) {
|
|
168
|
+
setShowCropper(false);
|
|
169
|
+
setCropMetadata(cropMetadata);
|
|
170
|
+
if (selectedFile && fileContent) {
|
|
171
|
+
onFileRead === null || onFileRead === void 0 ? void 0 : onFileRead(selectedFile, fileContent, cropMetadata);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
114
174
|
var handleFileDelete = function () {
|
|
175
|
+
if (previewUrl) {
|
|
176
|
+
URL.revokeObjectURL(previewUrl);
|
|
177
|
+
}
|
|
115
178
|
setSelectedFile(null);
|
|
179
|
+
setFileContent(null);
|
|
180
|
+
setPreviewUrl(null);
|
|
181
|
+
setCropMetadata(null);
|
|
116
182
|
resetFileInput();
|
|
117
183
|
onFileRemove === null || onFileRemove === void 0 ? void 0 : onFileRemove();
|
|
118
184
|
};
|
|
185
|
+
var handleNewFileClick = function () {
|
|
186
|
+
var _a;
|
|
187
|
+
(_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click();
|
|
188
|
+
};
|
|
119
189
|
var formatFileSize = function (size) {
|
|
120
190
|
if (size < 1024) {
|
|
121
191
|
return "".concat(size, " octets");
|
|
@@ -129,9 +199,28 @@ var FileUploader = function (_a) {
|
|
|
129
199
|
};
|
|
130
200
|
var displayFileSize = fileSize !== undefined ? fileSize : selectedFile ? selectedFile.size : 0;
|
|
131
201
|
var displayFileName = fileName || (selectedFile ? selectedFile.name : "");
|
|
202
|
+
var isSelectedImage = selectedFile && isImageFile(selectedFile);
|
|
132
203
|
return (React.createElement("div", { className: styles.upload_main_container },
|
|
133
|
-
enableDragAndDrop ? (React.createElement("div", { className: "".concat(styles.upload_container, " ").concat(isHovering ? styles.drag_over : ""), onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop },
|
|
134
|
-
React.createElement("div", { className: styles.
|
|
204
|
+
enableDragAndDrop ? (React.createElement("div", { className: "".concat(styles.upload_container, " ").concat(isHovering ? styles.drag_over : "", " ").concat(previewUrl ? styles.upload_container_with_preview : ""), onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, onClick: function () { var _a; return !previewUrl && ((_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click()); } },
|
|
205
|
+
previewUrl ? (React.createElement("div", { className: styles.preview_container, onClick: function (e) {
|
|
206
|
+
var _a;
|
|
207
|
+
// Ne pas déclencher si on clique sur le conteneur d'image
|
|
208
|
+
if (!e.target.closest(".".concat(styles.preview_image_container))) {
|
|
209
|
+
(_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click();
|
|
210
|
+
}
|
|
211
|
+
} },
|
|
212
|
+
React.createElement("div", { className: "".concat(styles.preview_image_container, " ").concat(styles["preview_image_".concat((cropMetadata === null || cropMetadata === void 0 ? void 0 : cropMetadata.shape) || cropShape)]), onClick: function () { return enableCropping && openCropModal(); } },
|
|
213
|
+
React.createElement("div", { className: styles.preview_image_wrapper, style: {
|
|
214
|
+
transform: cropMetadata
|
|
215
|
+
? "translate(".concat(cropMetadata.offsetX, "px, ").concat(cropMetadata.offsetY, "px) scale(").concat(cropMetadata.zoom, ")")
|
|
216
|
+
: "scale(1)",
|
|
217
|
+
} },
|
|
218
|
+
React.createElement("img", { src: previewUrl, alt: displayFileName, className: styles.preview_image, draggable: false }))),
|
|
219
|
+
enableCropping && (React.createElement("div", { className: styles.edit_button_container, onClick: function (e) {
|
|
220
|
+
e.stopPropagation();
|
|
221
|
+
openCropModal();
|
|
222
|
+
} },
|
|
223
|
+
React.createElement(IconButton, { style: "largeFilled", iconName: "allaw-icon-edit-2" }))))) : (React.createElement("div", { className: styles.upload_content },
|
|
135
224
|
React.createElement("div", { className: styles.upload_icons },
|
|
136
225
|
React.createElement("div", { className: styles.upload_file_icon },
|
|
137
226
|
React.createElement("i", { className: "allaw-icon-file" })),
|
|
@@ -142,13 +231,14 @@ var FileUploader = function (_a) {
|
|
|
142
231
|
React.createElement("span", null,
|
|
143
232
|
"Glissez-d\u00E9posez votre fichier, ou",
|
|
144
233
|
" ",
|
|
145
|
-
React.createElement("
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
React.createElement("label", { htmlFor: "file-upload", className: styles.file_upload_label },
|
|
234
|
+
React.createElement("span", { className: styles.btn_tertiary }, buttonLabel))))),
|
|
235
|
+
React.createElement("input", { id: "file-upload", ref: fileInputRef, type: "file", accept: acceptedExtensions.join(","), style: { display: "none" }, onChange: handleFileSelect }))) : (React.createElement("div", { className: styles.manual_upload_container },
|
|
236
|
+
React.createElement("button", { className: styles.file_upload_button, onClick: function () { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); } },
|
|
149
237
|
React.createElement("span", { className: styles.btn_tertiary }, buttonLabel),
|
|
150
238
|
React.createElement("input", { id: "file-upload", ref: fileInputRef, type: "file", accept: acceptedExtensions.join(","), style: { display: "none" }, onChange: handleFileSelect })))),
|
|
151
|
-
React.createElement("div", { className: styles.
|
|
239
|
+
previewUrl ? (React.createElement("div", { className: styles.file_info_container },
|
|
240
|
+
filePresentationLabel && (React.createElement("span", { className: styles.file_presentation_label }, filePresentationLabel)),
|
|
241
|
+
React.createElement(TertiaryButton, { label: "Charger ".concat(isSelectedImage ? "une autre image" : "un autre fichier"), color: "bleu-allaw", variant: true, onClick: handleNewFileClick }))) : (React.createElement("div", { className: styles.limits },
|
|
152
242
|
React.createElement("div", { className: styles.format },
|
|
153
243
|
React.createElement("span", { className: styles.limit_text },
|
|
154
244
|
acceptedLabel,
|
|
@@ -159,7 +249,7 @@ var FileUploader = function (_a) {
|
|
|
159
249
|
maxSizeLabel,
|
|
160
250
|
" ",
|
|
161
251
|
maxFileSizeMB,
|
|
162
|
-
" Mo"))),
|
|
252
|
+
" Mo")))),
|
|
163
253
|
descriptionParts && (React.createElement("p", { className: styles.description },
|
|
164
254
|
descriptionParts.beforeLink,
|
|
165
255
|
" ",
|
|
@@ -171,16 +261,18 @@ var FileUploader = function (_a) {
|
|
|
171
261
|
React.createElement("span", { className: styles.error_message }, errorMessage)))),
|
|
172
262
|
(isLoading || selectedFile || fileName) && (React.createElement("div", { className: styles.uploaded_file_container },
|
|
173
263
|
React.createElement("div", { className: styles.uploaded_file_content },
|
|
174
|
-
React.createElement("div", { className: styles.uploaded_file_icon }, iconUrl ? (React.createElement(
|
|
264
|
+
React.createElement("div", { className: styles.uploaded_file_icon }, iconUrl ? (React.createElement(Image, { src: iconUrl, alt: "File icon", className: styles.file_icon, width: 20, height: 20 })) : (React.createElement("i", { className: "allaw-icon-file" }))),
|
|
175
265
|
React.createElement("div", { className: styles.uploaded_file_name_size },
|
|
176
266
|
React.createElement("div", { className: styles.uploaded_file_name },
|
|
177
|
-
React.createElement("span", { className: styles.file_name }, displayFileName || "Analyse en cours...")
|
|
267
|
+
React.createElement("span", { className: styles.file_name }, displayFileName || "Analyse en cours..."),
|
|
268
|
+
selectedFile && (React.createElement("i", { className: "allaw-icon-check ".concat(styles.success_icon) }))),
|
|
178
269
|
React.createElement("div", { className: styles.uploaded_file_size },
|
|
179
270
|
React.createElement("span", { className: styles.file_size }, displayFileSize > 0 ? formatFileSize(displayFileSize) : ""))),
|
|
180
271
|
selectedFile && (React.createElement("div", { className: styles.uploaded_file_delete_container },
|
|
181
272
|
React.createElement("div", { className: styles.uploaded_file_delete, onClick: handleFileDelete },
|
|
182
273
|
React.createElement("i", { className: "allaw-icon-close" }))))),
|
|
183
274
|
React.createElement("div", { className: styles.uploaded_file_progress_bar }, uploadProgress > 0 && (React.createElement("div", { className: styles.progress_bar_container },
|
|
184
|
-
React.createElement("div", { className: styles.progress_bar_fill, style: { width: "".concat((uploadProgress / 10) * 100, "%") } }))))))
|
|
275
|
+
React.createElement("div", { className: styles.progress_bar_fill, style: { width: "".concat((uploadProgress / 10) * 100, "%") } })))))),
|
|
276
|
+
showCropper && selectedFile && (React.createElement(ImageCropperModal, { file: selectedFile, shape: cropShape, onCancel: handleCropCancel, onConfirm: handleCropConfirm }))));
|
|
185
277
|
};
|
|
186
278
|
export default FileUploader;
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
background-color: #ffffff;
|
|
20
20
|
width: 100%;
|
|
21
21
|
transition: all 0.2s ease-in-out;
|
|
22
|
+
cursor: pointer;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
.manual_upload_container {
|
|
@@ -265,6 +266,20 @@
|
|
|
265
266
|
font-weight: 600;
|
|
266
267
|
}
|
|
267
268
|
|
|
269
|
+
.file_upload_button {
|
|
270
|
+
background: none;
|
|
271
|
+
border: none;
|
|
272
|
+
padding: 0;
|
|
273
|
+
margin: 0;
|
|
274
|
+
cursor: pointer;
|
|
275
|
+
font-family: inherit;
|
|
276
|
+
font-size: inherit;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.format {
|
|
280
|
+
width: 61.8%;
|
|
281
|
+
}
|
|
282
|
+
|
|
268
283
|
@media (max-width: 768px) {
|
|
269
284
|
.upload_container {
|
|
270
285
|
padding: 2rem 1rem;
|
|
@@ -285,4 +300,116 @@
|
|
|
285
300
|
align-items: flex-start;
|
|
286
301
|
gap: 0.25rem;
|
|
287
302
|
}
|
|
303
|
+
|
|
304
|
+
.format {
|
|
305
|
+
width: 100%;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.upload_container_with_preview {
|
|
310
|
+
padding: 0;
|
|
311
|
+
cursor: default;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.preview_container {
|
|
315
|
+
width: 100%;
|
|
316
|
+
height: 300px;
|
|
317
|
+
position: relative;
|
|
318
|
+
display: flex;
|
|
319
|
+
justify-content: center;
|
|
320
|
+
align-items: center;
|
|
321
|
+
background-color: rgba(0, 0, 0, 0.03);
|
|
322
|
+
overflow: hidden;
|
|
323
|
+
cursor: pointer;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.preview_image_container {
|
|
327
|
+
position: relative;
|
|
328
|
+
width: 280px;
|
|
329
|
+
height: 280px;
|
|
330
|
+
display: flex;
|
|
331
|
+
justify-content: center;
|
|
332
|
+
align-items: center;
|
|
333
|
+
overflow: hidden;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.preview_image_circle {
|
|
337
|
+
border-radius: 50%;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.preview_image_square {
|
|
341
|
+
border-radius: 8px;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.preview_image_wrapper {
|
|
345
|
+
position: absolute;
|
|
346
|
+
width: auto;
|
|
347
|
+
height: auto;
|
|
348
|
+
max-width: none;
|
|
349
|
+
max-height: none;
|
|
350
|
+
transform-origin: center;
|
|
351
|
+
display: flex;
|
|
352
|
+
justify-content: center;
|
|
353
|
+
align-items: center;
|
|
354
|
+
will-change: transform;
|
|
355
|
+
pointer-events: none;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.preview_image {
|
|
359
|
+
width: auto;
|
|
360
|
+
height: auto;
|
|
361
|
+
object-fit: cover;
|
|
362
|
+
display: block;
|
|
363
|
+
max-width: none;
|
|
364
|
+
max-height: none;
|
|
365
|
+
user-select: none;
|
|
366
|
+
-webkit-user-drag: none;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.edit_button_container {
|
|
370
|
+
position: absolute;
|
|
371
|
+
bottom: 12px;
|
|
372
|
+
right: 12px;
|
|
373
|
+
z-index: 20;
|
|
374
|
+
pointer-events: auto;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.edit_button_container button {
|
|
378
|
+
pointer-events: auto;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.file_info_container {
|
|
382
|
+
display: flex;
|
|
383
|
+
align-items: center;
|
|
384
|
+
justify-content: center;
|
|
385
|
+
margin-top: 12px;
|
|
386
|
+
flex-wrap: wrap;
|
|
387
|
+
gap: 8px;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.file_info_container span {
|
|
391
|
+
font-family: var(--font-open-sans, sans-serif);
|
|
392
|
+
font-size: 14px;
|
|
393
|
+
color: var(----noir, #171e25);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.file_presentation_label {
|
|
397
|
+
font-size: 14px;
|
|
398
|
+
color: var(--gris-03);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.change_file_link {
|
|
402
|
+
cursor: pointer;
|
|
403
|
+
text-decoration: none;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.success_icon {
|
|
407
|
+
margin-left: 8px;
|
|
408
|
+
color: var(--actions-valid, #29a36a);
|
|
409
|
+
font-size: 14px;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.uploaded_file_name {
|
|
413
|
+
display: flex;
|
|
414
|
+
align-items: center;
|
|
288
415
|
}
|
|
@@ -29,53 +29,69 @@ declare namespace _default {
|
|
|
29
29
|
let description_2: string;
|
|
30
30
|
export { description_2 as description };
|
|
31
31
|
}
|
|
32
|
-
namespace
|
|
32
|
+
namespace enableCropping {
|
|
33
33
|
let control_3: string;
|
|
34
34
|
export { control_3 as control };
|
|
35
35
|
let description_3: string;
|
|
36
36
|
export { description_3 as description };
|
|
37
37
|
}
|
|
38
|
-
namespace
|
|
39
|
-
|
|
38
|
+
namespace cropShape {
|
|
39
|
+
export namespace control_4 {
|
|
40
|
+
let type: string;
|
|
41
|
+
let options: string[];
|
|
42
|
+
}
|
|
40
43
|
export { control_4 as control };
|
|
41
44
|
let description_4: string;
|
|
42
45
|
export { description_4 as description };
|
|
43
46
|
}
|
|
44
|
-
namespace
|
|
47
|
+
namespace iconUrl {
|
|
45
48
|
let control_5: string;
|
|
46
49
|
export { control_5 as control };
|
|
47
50
|
let description_5: string;
|
|
48
51
|
export { description_5 as description };
|
|
49
52
|
}
|
|
50
|
-
namespace
|
|
53
|
+
namespace buttonLabel {
|
|
51
54
|
let control_6: string;
|
|
52
55
|
export { control_6 as control };
|
|
53
56
|
let description_6: string;
|
|
54
57
|
export { description_6 as description };
|
|
55
58
|
}
|
|
56
|
-
namespace
|
|
57
|
-
|
|
58
|
-
let type: string;
|
|
59
|
-
let min: number;
|
|
60
|
-
let max: number;
|
|
61
|
-
let step: number;
|
|
62
|
-
}
|
|
59
|
+
namespace acceptedLabel {
|
|
60
|
+
let control_7: string;
|
|
63
61
|
export { control_7 as control };
|
|
64
62
|
let description_7: string;
|
|
65
63
|
export { description_7 as description };
|
|
66
64
|
}
|
|
67
|
-
namespace
|
|
65
|
+
namespace maxSizeLabel {
|
|
68
66
|
let control_8: string;
|
|
69
67
|
export { control_8 as control };
|
|
70
68
|
let description_8: string;
|
|
71
69
|
export { description_8 as description };
|
|
72
70
|
}
|
|
73
|
-
namespace
|
|
74
|
-
|
|
71
|
+
namespace uploadProgress {
|
|
72
|
+
export namespace control_9 {
|
|
73
|
+
let type_1: string;
|
|
74
|
+
export { type_1 as type };
|
|
75
|
+
export let min: number;
|
|
76
|
+
export let max: number;
|
|
77
|
+
export let step: number;
|
|
78
|
+
}
|
|
75
79
|
export { control_9 as control };
|
|
76
80
|
let description_9: string;
|
|
77
81
|
export { description_9 as description };
|
|
78
82
|
}
|
|
83
|
+
namespace isLoading {
|
|
84
|
+
let control_10: string;
|
|
85
|
+
export { control_10 as control };
|
|
86
|
+
let description_10: string;
|
|
87
|
+
export { description_10 as description };
|
|
88
|
+
}
|
|
89
|
+
namespace errorMessage {
|
|
90
|
+
let control_11: string;
|
|
91
|
+
export { control_11 as control };
|
|
92
|
+
let description_11: string;
|
|
93
|
+
export { description_11 as description };
|
|
94
|
+
}
|
|
79
95
|
}
|
|
80
96
|
}
|
|
81
97
|
export default _default;
|
|
@@ -85,4 +101,7 @@ export const WithoutDragAndDrop: any;
|
|
|
85
101
|
export const WithError: any;
|
|
86
102
|
export const WithProgress: any;
|
|
87
103
|
export const MultipleExtensions: any;
|
|
104
|
+
export const WithSquareCropping: any;
|
|
105
|
+
export const WithCircleCropping: any;
|
|
106
|
+
export const WithPreloadedImage: any;
|
|
88
107
|
import FileUploader from "./FileUploader";
|
|
@@ -77,6 +77,14 @@ export default {
|
|
|
77
77
|
control: "boolean",
|
|
78
78
|
description: "Active/désactive la zone de drag & drop",
|
|
79
79
|
},
|
|
80
|
+
enableCropping: {
|
|
81
|
+
control: "boolean",
|
|
82
|
+
description: "Active/désactive le cadrage pour les images",
|
|
83
|
+
},
|
|
84
|
+
cropShape: {
|
|
85
|
+
control: { type: "select", options: ["circle", "square"] },
|
|
86
|
+
description: "Forme du masque de cadrage (cercle ou carré)",
|
|
87
|
+
},
|
|
80
88
|
iconUrl: {
|
|
81
89
|
control: "text",
|
|
82
90
|
description: "URL de l'icône du type de fichier",
|
|
@@ -111,16 +119,21 @@ export default {
|
|
|
111
119
|
var Template = function (args) {
|
|
112
120
|
var _a = useState(null), file = _a[0], setFile = _a[1];
|
|
113
121
|
var _b = useState(null), fileContent = _b[0], setFileContent = _b[1];
|
|
114
|
-
var _c = useState(
|
|
115
|
-
var _d = useState(
|
|
116
|
-
var _e = useState(
|
|
117
|
-
var
|
|
122
|
+
var _c = useState(null), cropMetadata = _c[0], setCropMetadata = _c[1];
|
|
123
|
+
var _d = useState(0), progress = _d[0], setProgress = _d[1];
|
|
124
|
+
var _e = useState(false), loading = _e[0], setLoading = _e[1];
|
|
125
|
+
var _f = useState(null), error = _f[0], setError = _f[1];
|
|
126
|
+
var handleFileRead = function (file, content, cropMeta) { return __awaiter(void 0, void 0, void 0, function () {
|
|
118
127
|
return __generator(this, function (_a) {
|
|
119
128
|
switch (_a.label) {
|
|
120
129
|
case 0:
|
|
121
130
|
setLoading(true);
|
|
122
131
|
setFile(file);
|
|
123
132
|
setFileContent(content);
|
|
133
|
+
if (cropMeta) {
|
|
134
|
+
setCropMetadata(cropMeta);
|
|
135
|
+
console.log("Métadonnées de cadrage:", cropMeta);
|
|
136
|
+
}
|
|
124
137
|
// Simuler une progression
|
|
125
138
|
setProgress(2);
|
|
126
139
|
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 300); })];
|
|
@@ -144,21 +157,33 @@ var Template = function (args) {
|
|
|
144
157
|
var handleFileRemove = function () {
|
|
145
158
|
setFile(null);
|
|
146
159
|
setFileContent(null);
|
|
160
|
+
setCropMetadata(null);
|
|
147
161
|
setProgress(0);
|
|
148
162
|
setError(null);
|
|
149
163
|
console.log("Fichier supprimé");
|
|
150
164
|
};
|
|
151
|
-
return (React.createElement(
|
|
165
|
+
return (React.createElement("div", null,
|
|
166
|
+
React.createElement(FileUploader, __assign({}, args, { onFileRead: handleFileRead, onFileRemove: handleFileRemove, uploadProgress: progress, isLoading: loading, errorMessage: error })),
|
|
167
|
+
cropMetadata && (React.createElement("div", { style: {
|
|
168
|
+
marginTop: "20px",
|
|
169
|
+
padding: "10px",
|
|
170
|
+
backgroundColor: "#f5f5f5",
|
|
171
|
+
borderRadius: "8px",
|
|
172
|
+
} },
|
|
173
|
+
React.createElement("h4", { style: { margin: "0 0 10px 0", fontSize: "14px", color: "#456073" } }, "M\u00E9tadonn\u00E9es de cadrage:"),
|
|
174
|
+
React.createElement("code", { style: { display: "block", fontSize: "12px", color: "#333" } }, JSON.stringify(cropMetadata, null, 2))))));
|
|
152
175
|
};
|
|
153
176
|
export var Default = Template.bind({});
|
|
154
177
|
Default.args = {
|
|
155
|
-
acceptedExtensions: [".
|
|
178
|
+
acceptedExtensions: [".jpg", ".jpeg", ".png", ".webp"],
|
|
156
179
|
maxFileSizeMB: 5,
|
|
157
180
|
enableDragAndDrop: true,
|
|
158
|
-
|
|
159
|
-
|
|
181
|
+
enableCropping: true,
|
|
182
|
+
cropShape: "circle",
|
|
183
|
+
buttonLabel: "Choisir une image",
|
|
184
|
+
acceptedLabel: "Formats acceptés :",
|
|
160
185
|
maxSizeLabel: "Taille maximale :",
|
|
161
|
-
|
|
186
|
+
filePresentationLabel: "Voici votre logo.",
|
|
162
187
|
};
|
|
163
188
|
export var WithDescription = Template.bind({});
|
|
164
189
|
WithDescription.args = __assign(__assign({}, Default.args), { descriptionParts: {
|
|
@@ -176,3 +201,32 @@ WithProgress.args = __assign(__assign({}, Default.args), { uploadProgress: 6, is
|
|
|
176
201
|
// Exemple avec des extensions multiples
|
|
177
202
|
export var MultipleExtensions = Template.bind({});
|
|
178
203
|
MultipleExtensions.args = __assign(__assign({}, Default.args), { acceptedExtensions: [".pdf", ".doc", ".docx"] });
|
|
204
|
+
// Exemple avec le cadrage d'image en carré
|
|
205
|
+
export var WithSquareCropping = Template.bind({});
|
|
206
|
+
WithSquareCropping.args = {
|
|
207
|
+
acceptedExtensions: [".jpg", ".jpeg", ".png", ".webp"],
|
|
208
|
+
maxFileSizeMB: 5,
|
|
209
|
+
enableDragAndDrop: true,
|
|
210
|
+
enableCropping: true,
|
|
211
|
+
cropShape: "square",
|
|
212
|
+
buttonLabel: "Choisir une image",
|
|
213
|
+
acceptedLabel: "Formats acceptés :",
|
|
214
|
+
maxSizeLabel: "Taille maximale :",
|
|
215
|
+
};
|
|
216
|
+
// Exemple avec le cadrage d'image en cercle
|
|
217
|
+
export var WithCircleCropping = Template.bind({});
|
|
218
|
+
WithCircleCropping.args = {
|
|
219
|
+
acceptedExtensions: [".jpg", ".jpeg", ".png", ".webp"],
|
|
220
|
+
maxFileSizeMB: 5,
|
|
221
|
+
enableDragAndDrop: true,
|
|
222
|
+
enableCropping: true,
|
|
223
|
+
cropShape: "circle",
|
|
224
|
+
buttonLabel: "Choisir une image",
|
|
225
|
+
acceptedLabel: "Formats acceptés :",
|
|
226
|
+
maxSizeLabel: "Taille maximale :",
|
|
227
|
+
};
|
|
228
|
+
// Exemple avec l'affichage d'une image préuploadée
|
|
229
|
+
export var WithPreloadedImage = Template.bind({});
|
|
230
|
+
WithPreloadedImage.args = __assign(__assign({}, Default.args), {
|
|
231
|
+
// On simule un fichier déjà chargé pour la story
|
|
232
|
+
fileName: "logo-entreprise.png", fileSize: 1.2 * 1024 * 1024, filePresentationLabel: "Voici votre logo." });
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface CropMetadata {
|
|
3
|
+
zoom: number;
|
|
4
|
+
offsetX: number;
|
|
5
|
+
offsetY: number;
|
|
6
|
+
shape: "circle" | "square";
|
|
7
|
+
}
|
|
8
|
+
interface ImageCropperModalProps {
|
|
9
|
+
file: File;
|
|
10
|
+
shape: "circle" | "square";
|
|
11
|
+
onCancel: () => void;
|
|
12
|
+
onConfirm: (cropMetadata: CropMetadata) => void;
|
|
13
|
+
}
|
|
14
|
+
declare const ImageCropperModal: React.FC<ImageCropperModalProps>;
|
|
15
|
+
export default ImageCropperModal;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect } from "react";
|
|
2
|
+
import Image from "next/image";
|
|
3
|
+
import styles from "./ImageCropperModal.module.css";
|
|
4
|
+
var ImageCropperModal = function (_a) {
|
|
5
|
+
var file = _a.file, shape = _a.shape, onCancel = _a.onCancel, onConfirm = _a.onConfirm;
|
|
6
|
+
var _b = useState(1), zoom = _b[0], setZoom = _b[1]; // Facteur de zoom relatif (1-3)
|
|
7
|
+
var _c = useState(1), baseZoom = _c[0], setBaseZoom = _c[1]; // Zoom de base pour fit l'image
|
|
8
|
+
var _d = useState({
|
|
9
|
+
x: 0,
|
|
10
|
+
y: 0,
|
|
11
|
+
}), position = _d[0], setPosition = _d[1];
|
|
12
|
+
var _e = useState(false), isDragging = _e[0], setIsDragging = _e[1];
|
|
13
|
+
var _f = useState(null), imageSrc = _f[0], setImageSrc = _f[1];
|
|
14
|
+
var _g = useState({
|
|
15
|
+
x: 0,
|
|
16
|
+
y: 0,
|
|
17
|
+
}), dragStart = _g[0], setDragStart = _g[1];
|
|
18
|
+
var _h = useState(null), imageSize = _h[0], setImageSize = _h[1];
|
|
19
|
+
var imageRef = useRef(null);
|
|
20
|
+
var cropAreaRef = useRef(null);
|
|
21
|
+
var imageWrapperRef = useRef(null);
|
|
22
|
+
// Calcule le zoom de base pour que l'image "fit" parfaitement dans le cadre
|
|
23
|
+
var calculateFitZoom = function (imgWidth, imgHeight) {
|
|
24
|
+
if (!cropAreaRef.current)
|
|
25
|
+
return 1;
|
|
26
|
+
var cropRect = cropAreaRef.current.getBoundingClientRect();
|
|
27
|
+
var cropWidth = cropRect.width;
|
|
28
|
+
var cropHeight = cropRect.height;
|
|
29
|
+
// Calcul du zoom pour que l'image couvre complètement le cadre
|
|
30
|
+
// On utilise max pour assurer que l'image couvre tout le cadre, sans espace vide
|
|
31
|
+
var fitZoom = Math.max(cropWidth / imgWidth, cropHeight / imgHeight);
|
|
32
|
+
return fitZoom;
|
|
33
|
+
};
|
|
34
|
+
// Calcule les limites de déplacement pour éviter les zones vides
|
|
35
|
+
var calculateBoundaries = function () {
|
|
36
|
+
if (!cropAreaRef.current || !imageSize) {
|
|
37
|
+
return { minX: 0, maxX: 0, minY: 0, maxY: 0 };
|
|
38
|
+
}
|
|
39
|
+
var cropRect = cropAreaRef.current.getBoundingClientRect();
|
|
40
|
+
var cropWidth = cropRect.width;
|
|
41
|
+
var cropHeight = cropRect.height;
|
|
42
|
+
// Taille de l'image après application des deux zooms
|
|
43
|
+
var totalZoom = baseZoom * zoom;
|
|
44
|
+
var scaledWidth = imageSize.width * totalZoom;
|
|
45
|
+
var scaledHeight = imageSize.height * totalZoom;
|
|
46
|
+
// Si l'image est plus petite que le crop à cause du zoom
|
|
47
|
+
// ce qui ne devrait pas arriver avec notre logique, mais au cas où
|
|
48
|
+
if (scaledWidth <= cropWidth || scaledHeight <= cropHeight) {
|
|
49
|
+
return { minX: 0, maxX: 0, minY: 0, maxY: 0 };
|
|
50
|
+
}
|
|
51
|
+
// Calcule combien l'image peut être déplacée sans créer d'espace vide
|
|
52
|
+
var xLimit = Math.max(0, (scaledWidth - cropWidth) / 2);
|
|
53
|
+
var yLimit = Math.max(0, (scaledHeight - cropHeight) / 2);
|
|
54
|
+
return {
|
|
55
|
+
minX: -xLimit,
|
|
56
|
+
maxX: xLimit,
|
|
57
|
+
minY: -yLimit,
|
|
58
|
+
maxY: yLimit,
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
// Centre l'image dans la zone de crop
|
|
62
|
+
var centerImage = function () {
|
|
63
|
+
setPosition({ x: 0, y: 0 });
|
|
64
|
+
};
|
|
65
|
+
useEffect(function () {
|
|
66
|
+
var reader = new FileReader();
|
|
67
|
+
reader.onload = function (e) {
|
|
68
|
+
if (e.target && typeof e.target.result === "string") {
|
|
69
|
+
setImageSrc(e.target.result);
|
|
70
|
+
// Charge l'image pour obtenir ses dimensions
|
|
71
|
+
var img_1 = new window.Image();
|
|
72
|
+
img_1.onload = function () {
|
|
73
|
+
setImageSize({ width: img_1.width, height: img_1.height });
|
|
74
|
+
// Attendre que les refs soient disponibles
|
|
75
|
+
setTimeout(function () {
|
|
76
|
+
var fitZoom = calculateFitZoom(img_1.width, img_1.height);
|
|
77
|
+
setBaseZoom(fitZoom);
|
|
78
|
+
setZoom(1); // Zoom initial toujours à 1 (relatif)
|
|
79
|
+
centerImage();
|
|
80
|
+
}, 50);
|
|
81
|
+
};
|
|
82
|
+
img_1.src = e.target.result;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
reader.readAsDataURL(file);
|
|
86
|
+
return function () {
|
|
87
|
+
reader.abort();
|
|
88
|
+
};
|
|
89
|
+
}, [file]);
|
|
90
|
+
// Recalcule les limites quand le zoom change
|
|
91
|
+
useEffect(function () {
|
|
92
|
+
// Quand zoom change, recalculer les limites et ajuster la position si nécessaire
|
|
93
|
+
if (imageSize && cropAreaRef.current) {
|
|
94
|
+
var boundaries = calculateBoundaries();
|
|
95
|
+
// Ajuste la position pour rester dans les limites
|
|
96
|
+
setPosition({
|
|
97
|
+
x: Math.max(boundaries.minX, Math.min(boundaries.maxX, position.x)),
|
|
98
|
+
y: Math.max(boundaries.minY, Math.min(boundaries.maxY, position.y)),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}, [zoom, imageSize]);
|
|
102
|
+
var handleMouseDown = function (e) {
|
|
103
|
+
e.preventDefault();
|
|
104
|
+
setIsDragging(true);
|
|
105
|
+
setDragStart({
|
|
106
|
+
x: e.clientX - position.x,
|
|
107
|
+
y: e.clientY - position.y,
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
var handleTouchStart = function (e) {
|
|
111
|
+
if (e.touches.length !== 1)
|
|
112
|
+
return;
|
|
113
|
+
e.preventDefault();
|
|
114
|
+
setIsDragging(true);
|
|
115
|
+
setDragStart({
|
|
116
|
+
x: e.touches[0].clientX - position.x,
|
|
117
|
+
y: e.touches[0].clientY - position.y,
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
var updatePosition = function (clientX, clientY) {
|
|
121
|
+
if (!isDragging)
|
|
122
|
+
return;
|
|
123
|
+
var newX = clientX - dragStart.x;
|
|
124
|
+
var newY = clientY - dragStart.y;
|
|
125
|
+
var boundaries = calculateBoundaries();
|
|
126
|
+
setPosition({
|
|
127
|
+
x: Math.max(boundaries.minX, Math.min(boundaries.maxX, newX)),
|
|
128
|
+
y: Math.max(boundaries.minY, Math.min(boundaries.maxY, newY)),
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
var handleMouseMove = function (e) {
|
|
132
|
+
if (!isDragging)
|
|
133
|
+
return;
|
|
134
|
+
updatePosition(e.clientX, e.clientY);
|
|
135
|
+
};
|
|
136
|
+
var handleTouchMove = function (e) {
|
|
137
|
+
if (!isDragging || e.touches.length !== 1)
|
|
138
|
+
return;
|
|
139
|
+
e.preventDefault();
|
|
140
|
+
updatePosition(e.touches[0].clientX, e.touches[0].clientY);
|
|
141
|
+
};
|
|
142
|
+
var handleMouseUp = function () {
|
|
143
|
+
setIsDragging(false);
|
|
144
|
+
};
|
|
145
|
+
var handleTouchEnd = function () {
|
|
146
|
+
setIsDragging(false);
|
|
147
|
+
};
|
|
148
|
+
var handleZoomChange = function (e) {
|
|
149
|
+
var newZoom = parseFloat(e.target.value);
|
|
150
|
+
setZoom(Math.min(3, Math.max(1, newZoom)));
|
|
151
|
+
};
|
|
152
|
+
useEffect(function () {
|
|
153
|
+
if (isDragging) {
|
|
154
|
+
document.addEventListener("mousemove", handleDocumentMouseMove);
|
|
155
|
+
document.addEventListener("mouseup", handleDocumentMouseUp);
|
|
156
|
+
document.addEventListener("touchmove", handleDocumentTouchMove, {
|
|
157
|
+
passive: false,
|
|
158
|
+
});
|
|
159
|
+
document.addEventListener("touchend", handleDocumentTouchEnd);
|
|
160
|
+
}
|
|
161
|
+
return function () {
|
|
162
|
+
document.removeEventListener("mousemove", handleDocumentMouseMove);
|
|
163
|
+
document.removeEventListener("mouseup", handleDocumentMouseUp);
|
|
164
|
+
document.removeEventListener("touchmove", handleDocumentTouchMove);
|
|
165
|
+
document.removeEventListener("touchend", handleDocumentTouchEnd);
|
|
166
|
+
};
|
|
167
|
+
}, [isDragging, dragStart, zoom, imageSize]);
|
|
168
|
+
var handleDocumentMouseMove = function (e) {
|
|
169
|
+
updatePosition(e.clientX, e.clientY);
|
|
170
|
+
};
|
|
171
|
+
var handleDocumentTouchMove = function (e) {
|
|
172
|
+
if (e.touches.length !== 1)
|
|
173
|
+
return;
|
|
174
|
+
e.preventDefault();
|
|
175
|
+
updatePosition(e.touches[0].clientX, e.touches[0].clientY);
|
|
176
|
+
};
|
|
177
|
+
var handleDocumentMouseUp = function () {
|
|
178
|
+
setIsDragging(false);
|
|
179
|
+
};
|
|
180
|
+
var handleDocumentTouchEnd = function () {
|
|
181
|
+
setIsDragging(false);
|
|
182
|
+
};
|
|
183
|
+
return (React.createElement("div", { className: styles.modal_overlay },
|
|
184
|
+
React.createElement("div", { className: styles.modal_content },
|
|
185
|
+
React.createElement("div", { className: styles.modal_header },
|
|
186
|
+
React.createElement("h3", { className: styles.modal_title }, "Recadrer l'image")),
|
|
187
|
+
React.createElement("div", { className: styles.crop_container },
|
|
188
|
+
React.createElement("div", { className: styles.image_wrapper, ref: imageWrapperRef }, imageSrc && (React.createElement("div", { ref: imageRef, className: styles.image_container, style: {
|
|
189
|
+
transform: "translate(".concat(position.x, "px, ").concat(position.y, "px) scale(").concat(baseZoom * zoom, ")"),
|
|
190
|
+
cursor: isDragging ? "grabbing" : "grab",
|
|
191
|
+
} },
|
|
192
|
+
React.createElement(Image, { src: imageSrc, alt: "Aper\u00E7u", className: styles.image, draggable: false, width: 1000, height: 1000, priority: true, unoptimized: true })))),
|
|
193
|
+
React.createElement("div", { ref: cropAreaRef, className: "".concat(styles.crop_area, " ").concat(styles["crop_area_".concat(shape)]), onMouseDown: handleMouseDown, onMouseMove: handleMouseMove, onMouseUp: handleMouseUp, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd })),
|
|
194
|
+
React.createElement("div", { className: styles.controls },
|
|
195
|
+
React.createElement("div", { className: styles.zoom_control },
|
|
196
|
+
React.createElement("span", { className: styles.zoom_label }, "Zoom:"),
|
|
197
|
+
React.createElement("button", { className: styles.zoom_button, onClick: function () { return setZoom(Math.max(1, zoom - 0.1)); }, disabled: zoom <= 1 }, "\u2013"),
|
|
198
|
+
React.createElement("input", { type: "range", min: "1", max: "3", step: "0.1", value: zoom, onChange: handleZoomChange, className: styles.zoom_slider }),
|
|
199
|
+
React.createElement("button", { className: styles.zoom_button, onClick: function () { return setZoom(Math.min(3, zoom + 0.1)); }, disabled: zoom >= 3 }, "+")),
|
|
200
|
+
React.createElement("div", { className: styles.action_buttons },
|
|
201
|
+
React.createElement("button", { className: styles.cancel_button, onClick: onCancel }, "Annuler"),
|
|
202
|
+
React.createElement("button", { className: styles.confirm_button, onClick: function () {
|
|
203
|
+
// Retourner les métadonnées de cadrage avec le zoom total
|
|
204
|
+
onConfirm({
|
|
205
|
+
zoom: baseZoom * zoom,
|
|
206
|
+
offsetX: position.x,
|
|
207
|
+
offsetY: position.y,
|
|
208
|
+
shape: shape,
|
|
209
|
+
});
|
|
210
|
+
} }, "Valider"))))));
|
|
211
|
+
};
|
|
212
|
+
export default ImageCropperModal;
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
.modal_overlay {
|
|
2
|
+
position: fixed;
|
|
3
|
+
top: 0;
|
|
4
|
+
left: 0;
|
|
5
|
+
width: 100%;
|
|
6
|
+
height: 100%;
|
|
7
|
+
background-color: rgba(0, 0, 0, 0.7);
|
|
8
|
+
display: flex;
|
|
9
|
+
justify-content: center;
|
|
10
|
+
align-items: center;
|
|
11
|
+
z-index: 9999;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.modal_content {
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
background-color: transparent;
|
|
18
|
+
border-radius: 12px;
|
|
19
|
+
width: 90%;
|
|
20
|
+
max-width: 500px;
|
|
21
|
+
height: 80vh;
|
|
22
|
+
max-height: 600px;
|
|
23
|
+
overflow: hidden;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.modal_header {
|
|
27
|
+
padding: 1rem;
|
|
28
|
+
background-color: white;
|
|
29
|
+
border-bottom: 1px solid var(--grey-venom, #e6edf5);
|
|
30
|
+
border-radius: 12px 12px 0 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.modal_title {
|
|
34
|
+
font-family: var(--font-open-sans, sans-serif);
|
|
35
|
+
font-size: 18px;
|
|
36
|
+
font-weight: 600;
|
|
37
|
+
color: var(--noir, #171e25);
|
|
38
|
+
margin: 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.crop_container {
|
|
42
|
+
position: relative;
|
|
43
|
+
width: 100%;
|
|
44
|
+
flex: 1;
|
|
45
|
+
display: flex;
|
|
46
|
+
justify-content: center;
|
|
47
|
+
align-items: center;
|
|
48
|
+
padding: 1rem;
|
|
49
|
+
overflow: hidden;
|
|
50
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.crop_mask {
|
|
54
|
+
position: absolute;
|
|
55
|
+
top: 0;
|
|
56
|
+
left: 0;
|
|
57
|
+
right: 0;
|
|
58
|
+
bottom: 0;
|
|
59
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
60
|
+
pointer-events: none;
|
|
61
|
+
z-index: 10;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.crop_area {
|
|
65
|
+
position: relative;
|
|
66
|
+
width: 280px;
|
|
67
|
+
height: 280px;
|
|
68
|
+
background-color: transparent;
|
|
69
|
+
z-index: 20;
|
|
70
|
+
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.crop_area_square {
|
|
74
|
+
border-radius: 8px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.crop_area_circle {
|
|
78
|
+
border-radius: 50%;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.crop_area::before {
|
|
82
|
+
content: "";
|
|
83
|
+
position: absolute;
|
|
84
|
+
inset: 0;
|
|
85
|
+
border: 2px solid rgba(255, 255, 255, 0.8);
|
|
86
|
+
border-radius: inherit;
|
|
87
|
+
pointer-events: none;
|
|
88
|
+
z-index: 25;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.image_wrapper {
|
|
92
|
+
position: absolute;
|
|
93
|
+
top: 0;
|
|
94
|
+
left: 0;
|
|
95
|
+
width: 100%;
|
|
96
|
+
height: 100%;
|
|
97
|
+
display: flex;
|
|
98
|
+
justify-content: center;
|
|
99
|
+
align-items: center;
|
|
100
|
+
z-index: 5;
|
|
101
|
+
overflow: hidden;
|
|
102
|
+
pointer-events: none;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.image_container {
|
|
106
|
+
position: absolute;
|
|
107
|
+
transform-origin: center;
|
|
108
|
+
will-change: transform;
|
|
109
|
+
touch-action: none;
|
|
110
|
+
cursor: grab;
|
|
111
|
+
pointer-events: all;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.image {
|
|
115
|
+
width: auto !important;
|
|
116
|
+
height: auto !important;
|
|
117
|
+
max-width: none !important;
|
|
118
|
+
max-height: none !important;
|
|
119
|
+
user-select: none;
|
|
120
|
+
-webkit-user-drag: none;
|
|
121
|
+
display: block;
|
|
122
|
+
object-fit: cover !important;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.controls {
|
|
126
|
+
padding: 1rem;
|
|
127
|
+
background-color: white;
|
|
128
|
+
border-top: 1px solid var(--grey-venom, #e6edf5);
|
|
129
|
+
display: flex;
|
|
130
|
+
flex-direction: column;
|
|
131
|
+
gap: 1rem;
|
|
132
|
+
border-radius: 0 0 12px 12px;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.zoom_control {
|
|
136
|
+
display: flex;
|
|
137
|
+
align-items: center;
|
|
138
|
+
justify-content: space-between;
|
|
139
|
+
gap: 0.5rem;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.zoom_label {
|
|
143
|
+
font-family: var(--font-open-sans, sans-serif);
|
|
144
|
+
font-size: 14px;
|
|
145
|
+
color: var(--mid-grey, #728ea7);
|
|
146
|
+
white-space: nowrap;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.zoom_slider {
|
|
150
|
+
flex: 1;
|
|
151
|
+
-webkit-appearance: none;
|
|
152
|
+
appearance: none;
|
|
153
|
+
height: 4px;
|
|
154
|
+
background: var(--grey-venom, #e6edf5);
|
|
155
|
+
border-radius: 2px;
|
|
156
|
+
outline: none;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.zoom_slider::-webkit-slider-thumb {
|
|
160
|
+
-webkit-appearance: none;
|
|
161
|
+
appearance: none;
|
|
162
|
+
width: 18px;
|
|
163
|
+
height: 18px;
|
|
164
|
+
background: var(--bleu-allaw, #25beeb);
|
|
165
|
+
border-radius: 50%;
|
|
166
|
+
cursor: pointer;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.zoom_slider::-moz-range-thumb {
|
|
170
|
+
width: 18px;
|
|
171
|
+
height: 18px;
|
|
172
|
+
background: var(--bleu-allaw, #25beeb);
|
|
173
|
+
border-radius: 50%;
|
|
174
|
+
cursor: pointer;
|
|
175
|
+
border: none;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.zoom_button {
|
|
179
|
+
display: flex;
|
|
180
|
+
align-items: center;
|
|
181
|
+
justify-content: center;
|
|
182
|
+
width: 30px;
|
|
183
|
+
height: 30px;
|
|
184
|
+
border-radius: 50%;
|
|
185
|
+
border: 1px solid var(--grey-venom, #e6edf5);
|
|
186
|
+
background-color: white;
|
|
187
|
+
cursor: pointer;
|
|
188
|
+
font-size: 16px;
|
|
189
|
+
color: var(--noir, #171e25);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.zoom_button:disabled {
|
|
193
|
+
opacity: 0.5;
|
|
194
|
+
cursor: not-allowed;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.action_buttons {
|
|
198
|
+
display: flex;
|
|
199
|
+
justify-content: flex-end;
|
|
200
|
+
gap: 1rem;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.cancel_button,
|
|
204
|
+
.confirm_button {
|
|
205
|
+
padding: 0.5rem 1.5rem;
|
|
206
|
+
border-radius: 4px;
|
|
207
|
+
font-family: var(--font-open-sans, sans-serif);
|
|
208
|
+
font-size: 14px;
|
|
209
|
+
font-weight: 600;
|
|
210
|
+
cursor: pointer;
|
|
211
|
+
transition: all 0.2s ease;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.cancel_button {
|
|
215
|
+
background-color: transparent;
|
|
216
|
+
border: 1px solid var(--grey-venom, #e6edf5);
|
|
217
|
+
color: var(--mid-grey, #728ea7);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.confirm_button {
|
|
221
|
+
background-color: var(--bleu-allaw, #25beeb);
|
|
222
|
+
border: 1px solid var(--bleu-allaw, #25beeb);
|
|
223
|
+
color: white;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.cancel_button:hover {
|
|
227
|
+
background-color: #f8f9fb;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.confirm_button:hover {
|
|
231
|
+
background-color: #1da9d2;
|
|
232
|
+
}
|