sa2kit 1.4.2 → 1.5.1

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.
Files changed (63) hide show
  1. package/dist/ConfigService-7MEZXKJ5.js +21 -0
  2. package/dist/ConfigService-7MEZXKJ5.js.map +1 -0
  3. package/dist/ConfigService-BV57YYFW.mjs +4 -0
  4. package/dist/ConfigService-BV57YYFW.mjs.map +1 -0
  5. package/dist/ConfigService-BxK06xP6.d.mts +262 -0
  6. package/dist/ConfigService-BxK06xP6.d.ts +262 -0
  7. package/dist/audioDetection/index.d.mts +449 -0
  8. package/dist/audioDetection/index.d.ts +449 -0
  9. package/dist/audioDetection/index.js +1244 -0
  10. package/dist/audioDetection/index.js.map +1 -0
  11. package/dist/audioDetection/index.mjs +1227 -0
  12. package/dist/audioDetection/index.mjs.map +1 -0
  13. package/dist/chunk-5XUE72Y3.mjs +1001 -0
  14. package/dist/chunk-5XUE72Y3.mjs.map +1 -0
  15. package/dist/chunk-DQVPZTVC.js +1009 -0
  16. package/dist/chunk-DQVPZTVC.js.map +1 -0
  17. package/dist/chunk-NEPD75MX.mjs +467 -0
  18. package/dist/chunk-NEPD75MX.mjs.map +1 -0
  19. package/dist/chunk-OEDY7GI4.js +473 -0
  20. package/dist/chunk-OEDY7GI4.js.map +1 -0
  21. package/dist/chunk-TFQF2HDO.mjs +354 -0
  22. package/dist/chunk-TFQF2HDO.mjs.map +1 -0
  23. package/dist/chunk-TOC5FSHP.js +358 -0
  24. package/dist/chunk-TOC5FSHP.js.map +1 -0
  25. package/dist/imageCrop/index.d.mts +165 -0
  26. package/dist/imageCrop/index.d.ts +165 -0
  27. package/dist/imageCrop/index.js +559 -0
  28. package/dist/imageCrop/index.js.map +1 -0
  29. package/dist/imageCrop/index.mjs +540 -0
  30. package/dist/imageCrop/index.mjs.map +1 -0
  31. package/dist/index.d.mts +139 -0
  32. package/dist/index.d.ts +139 -0
  33. package/dist/index.js +670 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/index.mjs +662 -0
  36. package/dist/index.mjs.map +1 -1
  37. package/dist/mmd/index.d.mts +113 -2
  38. package/dist/mmd/index.d.ts +113 -2
  39. package/dist/mmd/index.js +484 -2
  40. package/dist/mmd/index.js.map +1 -1
  41. package/dist/mmd/index.mjs +482 -4
  42. package/dist/mmd/index.mjs.map +1 -1
  43. package/dist/testYourself/admin/index.d.mts +58 -0
  44. package/dist/testYourself/admin/index.d.ts +58 -0
  45. package/dist/testYourself/admin/index.js +17 -0
  46. package/dist/testYourself/admin/index.js.map +1 -0
  47. package/dist/testYourself/admin/index.mjs +4 -0
  48. package/dist/testYourself/admin/index.mjs.map +1 -0
  49. package/dist/testYourself/index.d.mts +6 -98
  50. package/dist/testYourself/index.d.ts +6 -98
  51. package/dist/testYourself/index.js +90 -334
  52. package/dist/testYourself/index.js.map +1 -1
  53. package/dist/testYourself/index.mjs +47 -333
  54. package/dist/testYourself/index.mjs.map +1 -1
  55. package/dist/testYourself/server/index.d.mts +1029 -0
  56. package/dist/testYourself/server/index.d.ts +1029 -0
  57. package/dist/testYourself/server/index.js +42 -0
  58. package/dist/testYourself/server/index.js.map +1 -0
  59. package/dist/testYourself/server/index.mjs +5 -0
  60. package/dist/testYourself/server/index.mjs.map +1 -0
  61. package/dist/universalFile/server/index.js +5 -5
  62. package/dist/universalFile/server/index.mjs +1 -1
  63. package/package.json +62 -20
@@ -0,0 +1,559 @@
1
+ 'use strict';
2
+
3
+ require('../chunk-DGUM43GV.js');
4
+ var React2 = require('react');
5
+ var lucideReact = require('lucide-react');
6
+ var JSZip = require('jszip');
7
+
8
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
+
10
+ var React2__default = /*#__PURE__*/_interopDefault(React2);
11
+ var JSZip__default = /*#__PURE__*/_interopDefault(JSZip);
12
+
13
+ // src/imageCrop/utils/cropUtils.ts
14
+ async function loadImageFromFile(file) {
15
+ return new Promise((resolve, reject) => {
16
+ const reader = new FileReader();
17
+ reader.onload = (e) => {
18
+ const dataUrl = e.target?.result;
19
+ const img = new Image();
20
+ img.onload = () => {
21
+ resolve({
22
+ width: img.naturalWidth,
23
+ height: img.naturalHeight,
24
+ naturalWidth: img.naturalWidth,
25
+ naturalHeight: img.naturalHeight,
26
+ image: img,
27
+ file
28
+ });
29
+ };
30
+ img.onerror = () => {
31
+ reject(new Error("\u56FE\u7247\u52A0\u8F7D\u5931\u8D25"));
32
+ };
33
+ img.src = dataUrl;
34
+ };
35
+ reader.onerror = () => {
36
+ reject(new Error("\u6587\u4EF6\u8BFB\u53D6\u5931\u8D25"));
37
+ };
38
+ reader.readAsDataURL(file);
39
+ });
40
+ }
41
+ async function cropGridCell(imageInfo, cell, cellWidth, cellHeight, options = {}) {
42
+ const {
43
+ format = "image/png",
44
+ quality = 0.92,
45
+ filenamePrefix = "crop"
46
+ } = options;
47
+ if (!imageInfo.image) {
48
+ throw new Error("\u56FE\u7247\u8D44\u6E90\u672A\u52A0\u8F7D");
49
+ }
50
+ const canvas = document.createElement("canvas");
51
+ canvas.width = cellWidth;
52
+ canvas.height = cellHeight;
53
+ const ctx = canvas.getContext("2d");
54
+ if (!ctx) {
55
+ throw new Error("Canvas context \u521B\u5EFA\u5931\u8D25");
56
+ }
57
+ const sourceX = cell.offsetX;
58
+ const sourceY = cell.offsetY;
59
+ const sourceWidth = cellWidth;
60
+ const sourceHeight = cellHeight;
61
+ ctx.drawImage(
62
+ imageInfo.image,
63
+ sourceX,
64
+ sourceY,
65
+ sourceWidth,
66
+ sourceHeight,
67
+ 0,
68
+ 0,
69
+ cellWidth,
70
+ cellHeight
71
+ );
72
+ const blob = await new Promise((resolve, reject) => {
73
+ canvas.toBlob(
74
+ (blob2) => {
75
+ if (blob2) {
76
+ resolve(blob2);
77
+ } else {
78
+ reject(new Error("\u56FE\u7247\u8F6C\u6362\u5931\u8D25"));
79
+ }
80
+ },
81
+ format,
82
+ quality
83
+ );
84
+ });
85
+ const extension = format.split("/")[1] || "png";
86
+ const filename = `${filenamePrefix}_r${cell.row}_c${cell.column}.${extension}`;
87
+ return {
88
+ blob,
89
+ dataUrl: canvas.toDataURL(format, quality),
90
+ index: cell.row * 1e3 + cell.column,
91
+ // 简单的索引生成
92
+ row: cell.row,
93
+ column: cell.column,
94
+ cell,
95
+ filename
96
+ };
97
+ }
98
+ async function cropMultipleCells(imageInfo, cells, cellWidth, cellHeight, options = {}, onProgress) {
99
+ const results = [];
100
+ for (let i = 0; i < cells.length; i++) {
101
+ const cell = cells[i];
102
+ if (!cell || !cell.selected) continue;
103
+ try {
104
+ const result = await cropGridCell(imageInfo, cell, cellWidth, cellHeight, options);
105
+ results.push(result);
106
+ if (onProgress) {
107
+ onProgress(i + 1, cells.length);
108
+ }
109
+ } catch (error) {
110
+ console.error(`\u88C1\u526A\u5355\u5143\u683C (${cell.row}, ${cell.column}) \u5931\u8D25:`, error);
111
+ }
112
+ }
113
+ return results;
114
+ }
115
+ async function generateCellPreview(imageInfo, cell, cellWidth, cellHeight, previewSize = 100) {
116
+ const canvas = document.createElement("canvas");
117
+ const scale = Math.min(previewSize / cellWidth, previewSize / cellHeight);
118
+ canvas.width = cellWidth * scale;
119
+ canvas.height = cellHeight * scale;
120
+ const ctx = canvas.getContext("2d");
121
+ if (!ctx || !imageInfo.image) {
122
+ throw new Error("Canvas context \u521B\u5EFA\u5931\u8D25\u6216\u56FE\u7247\u672A\u52A0\u8F7D");
123
+ }
124
+ ctx.drawImage(
125
+ imageInfo.image,
126
+ cell.offsetX,
127
+ cell.offsetY,
128
+ cellWidth,
129
+ cellHeight,
130
+ 0,
131
+ 0,
132
+ canvas.width,
133
+ canvas.height
134
+ );
135
+ return canvas.toDataURL("image/png");
136
+ }
137
+ function validateCropArea(imageWidth, imageHeight, offsetX, offsetY, cropWidth, cropHeight) {
138
+ return offsetX >= 0 && offsetY >= 0 && offsetX + cropWidth <= imageWidth && offsetY + cropHeight <= imageHeight;
139
+ }
140
+ function constrainOffset(imageWidth, imageHeight, offsetX, offsetY, cropWidth, cropHeight) {
141
+ const constrainedX = Math.max(0, Math.min(offsetX, imageWidth - cropWidth));
142
+ const constrainedY = Math.max(0, Math.min(offsetY, imageHeight - cropHeight));
143
+ return {
144
+ offsetX: constrainedX,
145
+ offsetY: constrainedY
146
+ };
147
+ }
148
+ async function downloadAsZip(results, zipFilename = "cropped_images.zip") {
149
+ if (results.length === 0) {
150
+ throw new Error("\u6CA1\u6709\u53EF\u4E0B\u8F7D\u7684\u56FE\u7247");
151
+ }
152
+ const zip = new JSZip__default.default();
153
+ results.forEach((result, idx) => {
154
+ const filename = result.filename || `crop_${idx}.png`;
155
+ zip.file(filename, result.blob);
156
+ });
157
+ const zipBlob = await zip.generateAsync({
158
+ type: "blob",
159
+ compression: "DEFLATE",
160
+ compressionOptions: {
161
+ level: 6
162
+ }
163
+ });
164
+ downloadBlob(zipBlob, zipFilename);
165
+ }
166
+ function downloadBlob(blob, filename) {
167
+ const url = URL.createObjectURL(blob);
168
+ const link = document.createElement("a");
169
+ link.href = url;
170
+ link.download = filename;
171
+ document.body.appendChild(link);
172
+ link.click();
173
+ document.body.removeChild(link);
174
+ setTimeout(() => {
175
+ URL.revokeObjectURL(url);
176
+ }, 100);
177
+ }
178
+ async function downloadMultipleFiles(results, delay = 100) {
179
+ for (let i = 0; i < results.length; i++) {
180
+ const result = results[i];
181
+ if (!result) continue;
182
+ const filename = result.filename || `crop_${i}.png`;
183
+ downloadBlob(result.blob, filename);
184
+ if (i < results.length - 1) {
185
+ await new Promise((resolve) => setTimeout(resolve, delay));
186
+ }
187
+ }
188
+ }
189
+ function calculateTotalSize(results) {
190
+ return results.reduce((total, result) => total + result.blob.size, 0);
191
+ }
192
+ function formatFileSize(bytes) {
193
+ if (bytes === 0) return "0 B";
194
+ const k = 1024;
195
+ const sizes = ["B", "KB", "MB", "GB"];
196
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
197
+ return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
198
+ }
199
+ var GridControls = ({
200
+ config,
201
+ onChange,
202
+ disabled = false,
203
+ maxRows = 20,
204
+ maxColumns = 20,
205
+ maxCellSize = 2e3,
206
+ minCellSize = 10,
207
+ showReset = true,
208
+ onReset
209
+ }) => {
210
+ const handleChange = (field, value) => {
211
+ let constrainedValue = value;
212
+ if (field === "rows") {
213
+ constrainedValue = Math.max(1, Math.min(maxRows, value));
214
+ } else if (field === "columns") {
215
+ constrainedValue = Math.max(1, Math.min(maxColumns, value));
216
+ } else if (field === "cellWidth" || field === "cellHeight") {
217
+ constrainedValue = Math.max(minCellSize, Math.min(maxCellSize, value));
218
+ }
219
+ onChange({
220
+ ...config,
221
+ [field]: constrainedValue
222
+ });
223
+ };
224
+ return /* @__PURE__ */ React2__default.default.createElement("div", { className: "space-y-4 p-4 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700" }, /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React2__default.default.createElement(lucideReact.Grid, { className: "w-5 h-5 text-blue-500" }), /* @__PURE__ */ React2__default.default.createElement("h3", { className: "text-lg font-semibold text-gray-800 dark:text-gray-200" }, "\u7F51\u683C\u8BBE\u7F6E")), showReset && /* @__PURE__ */ React2__default.default.createElement(
225
+ "button",
226
+ {
227
+ onClick: onReset,
228
+ disabled,
229
+ className: "flex items-center gap-1 px-3 py-1.5 text-sm text-gray-600 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-400 transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
230
+ title: "\u91CD\u7F6E\u4E3A\u9ED8\u8BA4\u503C"
231
+ },
232
+ /* @__PURE__ */ React2__default.default.createElement(lucideReact.RefreshCw, { className: "w-4 h-4" }),
233
+ "\u91CD\u7F6E"
234
+ )), /* @__PURE__ */ React2__default.default.createElement("div", { className: "grid grid-cols-2 gap-4" }, /* @__PURE__ */ React2__default.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React2__default.default.createElement("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300" }, "\u884C\u6570"), /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React2__default.default.createElement(
235
+ "input",
236
+ {
237
+ type: "number",
238
+ min: 1,
239
+ max: maxRows,
240
+ value: config.rows,
241
+ onChange: (e) => handleChange("rows", parseInt(e.target.value) || 1),
242
+ disabled,
243
+ className: "flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed"
244
+ }
245
+ ), /* @__PURE__ */ React2__default.default.createElement(
246
+ "input",
247
+ {
248
+ type: "range",
249
+ min: 1,
250
+ max: maxRows,
251
+ value: config.rows,
252
+ onChange: (e) => handleChange("rows", parseInt(e.target.value)),
253
+ disabled,
254
+ className: "flex-1 h-2 bg-gray-200 dark:bg-gray-700 rounded-lg appearance-none cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
255
+ }
256
+ ))), /* @__PURE__ */ React2__default.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React2__default.default.createElement("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300" }, "\u5217\u6570"), /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React2__default.default.createElement(
257
+ "input",
258
+ {
259
+ type: "number",
260
+ min: 1,
261
+ max: maxColumns,
262
+ value: config.columns,
263
+ onChange: (e) => handleChange("columns", parseInt(e.target.value) || 1),
264
+ disabled,
265
+ className: "flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed"
266
+ }
267
+ ), /* @__PURE__ */ React2__default.default.createElement(
268
+ "input",
269
+ {
270
+ type: "range",
271
+ min: 1,
272
+ max: maxColumns,
273
+ value: config.columns,
274
+ onChange: (e) => handleChange("columns", parseInt(e.target.value)),
275
+ disabled,
276
+ className: "flex-1 h-2 bg-gray-200 dark:bg-gray-700 rounded-lg appearance-none cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
277
+ }
278
+ )))), /* @__PURE__ */ React2__default.default.createElement("div", { className: "grid grid-cols-2 gap-4" }, /* @__PURE__ */ React2__default.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React2__default.default.createElement("label", { className: "flex items-center gap-2 text-sm font-medium text-gray-700 dark:text-gray-300" }, /* @__PURE__ */ React2__default.default.createElement(lucideReact.Maximize2, { className: "w-4 h-4" }), "\u5355\u5143\u683C\u5BBD\u5EA6 (px)"), /* @__PURE__ */ React2__default.default.createElement(
279
+ "input",
280
+ {
281
+ type: "number",
282
+ min: minCellSize,
283
+ max: maxCellSize,
284
+ value: config.cellWidth,
285
+ onChange: (e) => handleChange("cellWidth", parseInt(e.target.value) || minCellSize),
286
+ disabled,
287
+ className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed"
288
+ }
289
+ )), /* @__PURE__ */ React2__default.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React2__default.default.createElement("label", { className: "flex items-center gap-2 text-sm font-medium text-gray-700 dark:text-gray-300" }, /* @__PURE__ */ React2__default.default.createElement(lucideReact.Maximize2, { className: "w-4 h-4" }), "\u5355\u5143\u683C\u9AD8\u5EA6 (px)"), /* @__PURE__ */ React2__default.default.createElement(
290
+ "input",
291
+ {
292
+ type: "number",
293
+ min: minCellSize,
294
+ max: maxCellSize,
295
+ value: config.cellHeight,
296
+ onChange: (e) => handleChange("cellHeight", parseInt(e.target.value) || minCellSize),
297
+ disabled,
298
+ className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed"
299
+ }
300
+ ))), /* @__PURE__ */ React2__default.default.createElement("div", { className: "pt-3 border-t border-gray-200 dark:border-gray-700" }, /* @__PURE__ */ React2__default.default.createElement("div", { className: "grid grid-cols-2 gap-4 text-sm text-gray-600 dark:text-gray-400" }, /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex justify-between" }, /* @__PURE__ */ React2__default.default.createElement("span", null, "\u603B\u5355\u5143\u683C\u6570:"), /* @__PURE__ */ React2__default.default.createElement("span", { className: "font-medium text-gray-900 dark:text-gray-100" }, config.rows * config.columns)), /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex justify-between" }, /* @__PURE__ */ React2__default.default.createElement("span", null, "\u603B\u5C3A\u5BF8:"), /* @__PURE__ */ React2__default.default.createElement("span", { className: "font-medium text-gray-900 dark:text-gray-100" }, config.cellWidth * config.columns, " \xD7 ", config.cellHeight * config.rows)))));
301
+ };
302
+
303
+ // src/imageCrop/components/ImageGridCropper.tsx
304
+ var ImageGridCropper = ({
305
+ config = {},
306
+ onExportSuccess,
307
+ onExportError,
308
+ className = ""
309
+ }) => {
310
+ const {
311
+ defaultRows = 3,
312
+ defaultColumns = 3,
313
+ defaultCellWidth = 256,
314
+ defaultCellHeight = 256,
315
+ maxRows = 20,
316
+ maxColumns = 20,
317
+ maxCellSize = 2e3,
318
+ minCellSize = 10
319
+ } = config;
320
+ const [imageInfo, setImageInfo] = React2.useState(null);
321
+ const [gridConfig, setGridConfig] = React2.useState({
322
+ rows: defaultRows,
323
+ columns: defaultColumns,
324
+ cellWidth: defaultCellWidth,
325
+ cellHeight: defaultCellHeight
326
+ });
327
+ const [gridCells, setGridCells] = React2.useState([]);
328
+ const [isProcessing, setIsProcessing] = React2.useState(false);
329
+ const [progress, setProgress] = React2.useState({ current: 0, total: 0 });
330
+ const [error, setError] = React2.useState(null);
331
+ const canvasRef = React2.useRef(null);
332
+ const fileInputRef = React2.useRef(null);
333
+ const generateGridCells = React2.useCallback(() => {
334
+ const cells = [];
335
+ const { rows, columns, cellWidth, cellHeight } = gridConfig;
336
+ for (let row = 0; row < rows; row++) {
337
+ for (let col = 0; col < columns; col++) {
338
+ const offsetX = col * cellWidth;
339
+ const offsetY = row * cellHeight;
340
+ cells.push({
341
+ id: `cell_${row}_${col}`,
342
+ row,
343
+ column: col,
344
+ x: offsetX,
345
+ y: offsetY,
346
+ offsetX,
347
+ offsetY,
348
+ width: cellWidth,
349
+ height: cellHeight,
350
+ selected: true
351
+ // 默认全选
352
+ });
353
+ }
354
+ }
355
+ setGridCells(cells);
356
+ }, [gridConfig]);
357
+ const handleFileSelect = async (event) => {
358
+ const file = event.target.files?.[0];
359
+ if (!file) return;
360
+ if (!file.type.startsWith("image/")) {
361
+ setError("\u8BF7\u9009\u62E9\u56FE\u7247\u6587\u4EF6");
362
+ return;
363
+ }
364
+ try {
365
+ setError(null);
366
+ setIsProcessing(true);
367
+ const info = await loadImageFromFile(file);
368
+ setImageInfo(info);
369
+ generateGridCells();
370
+ } catch (err) {
371
+ const errorMsg = err instanceof Error ? err.message : "\u56FE\u7247\u52A0\u8F7D\u5931\u8D25";
372
+ setError(errorMsg);
373
+ onExportError?.(errorMsg);
374
+ } finally {
375
+ setIsProcessing(false);
376
+ }
377
+ event.target.value = "";
378
+ };
379
+ const drawPreview = React2.useCallback(() => {
380
+ if (!imageInfo || !canvasRef.current) return;
381
+ const canvas = canvasRef.current;
382
+ const ctx = canvas.getContext("2d");
383
+ if (!ctx) return;
384
+ const maxWidth = 800;
385
+ const maxHeight = 600;
386
+ const scale = Math.min(
387
+ maxWidth / imageInfo.width,
388
+ maxHeight / imageInfo.height,
389
+ 1
390
+ );
391
+ canvas.width = imageInfo.width * scale;
392
+ canvas.height = imageInfo.height * scale;
393
+ if (!imageInfo.image) return;
394
+ ctx.drawImage(imageInfo.image, 0, 0, canvas.width, canvas.height);
395
+ ctx.strokeStyle = "rgba(59, 130, 246, 0.6)";
396
+ ctx.lineWidth = 2;
397
+ const { cellWidth, cellHeight } = gridConfig;
398
+ gridCells.forEach((cell) => {
399
+ const x = cell.offsetX * scale;
400
+ const y = cell.offsetY * scale;
401
+ const w = cellWidth * scale;
402
+ const h = cellHeight * scale;
403
+ if (cell.selected) {
404
+ ctx.strokeStyle = "rgba(34, 197, 94, 0.8)";
405
+ } else {
406
+ ctx.strokeStyle = "rgba(156, 163, 175, 0.5)";
407
+ }
408
+ ctx.strokeRect(x, y, w, h);
409
+ ctx.fillStyle = cell.selected ? "rgba(34, 197, 94, 0.9)" : "rgba(156, 163, 175, 0.7)";
410
+ ctx.font = "12px sans-serif";
411
+ ctx.fillText(`R${cell.row}C${cell.column}`, x + 5, y + 15);
412
+ });
413
+ }, [imageInfo, gridConfig, gridCells]);
414
+ const toggleCellSelection = (cellId) => {
415
+ setGridCells(
416
+ (prev) => prev.map(
417
+ (cell) => cell.id === cellId ? { ...cell, selected: !cell.selected } : cell
418
+ )
419
+ );
420
+ };
421
+ const toggleSelectAll = () => {
422
+ const allSelected = gridCells.every((cell) => cell.selected);
423
+ setGridCells(
424
+ (prev) => prev.map((cell) => ({ ...cell, selected: !allSelected }))
425
+ );
426
+ };
427
+ const handleExport = async (options = {}) => {
428
+ if (!imageInfo) {
429
+ setError("\u8BF7\u5148\u4E0A\u4F20\u56FE\u7247");
430
+ return;
431
+ }
432
+ const selectedCells = gridCells.filter((cell) => cell.selected);
433
+ if (selectedCells.length === 0) {
434
+ setError("\u8BF7\u81F3\u5C11\u9009\u62E9\u4E00\u4E2A\u5355\u5143\u683C");
435
+ return;
436
+ }
437
+ try {
438
+ setError(null);
439
+ setIsProcessing(true);
440
+ setProgress({ current: 0, total: selectedCells.length });
441
+ const results = await cropMultipleCells(
442
+ imageInfo,
443
+ selectedCells,
444
+ gridConfig.cellWidth,
445
+ gridConfig.cellHeight,
446
+ options.cropOptions,
447
+ (current, total) => setProgress({ current, total })
448
+ );
449
+ if (results.length === 0) {
450
+ throw new Error("\u6CA1\u6709\u6210\u529F\u88C1\u526A\u7684\u56FE\u7247");
451
+ }
452
+ const zipFilename = options.zipFilename || `cropped_${Date.now()}.zip`;
453
+ await downloadAsZip(results, zipFilename);
454
+ onExportSuccess?.(results);
455
+ } catch (err) {
456
+ const errorMsg = err instanceof Error ? err.message : "\u5BFC\u51FA\u5931\u8D25";
457
+ setError(errorMsg);
458
+ onExportError?.(errorMsg);
459
+ } finally {
460
+ setIsProcessing(false);
461
+ setProgress({ current: 0, total: 0 });
462
+ }
463
+ };
464
+ const handleReset = () => {
465
+ setGridConfig({
466
+ rows: defaultRows,
467
+ columns: defaultColumns,
468
+ cellWidth: defaultCellWidth,
469
+ cellHeight: defaultCellHeight
470
+ });
471
+ };
472
+ React2.useEffect(() => {
473
+ if (imageInfo) {
474
+ generateGridCells();
475
+ }
476
+ }, [imageInfo, generateGridCells]);
477
+ React2.useEffect(() => {
478
+ drawPreview();
479
+ }, [drawPreview]);
480
+ const selectedCount = gridCells.filter((cell) => cell.selected).length;
481
+ return /* @__PURE__ */ React2__default.default.createElement("div", { className: `w-full space-y-6 ${className}` }, /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ React2__default.default.createElement(lucideReact.Scissors, { className: "w-7 h-7 text-blue-500" }), /* @__PURE__ */ React2__default.default.createElement("div", null, /* @__PURE__ */ React2__default.default.createElement("h2", { className: "text-2xl font-bold text-gray-800 dark:text-gray-200" }, "\u7F51\u683C\u5F0F\u56FE\u7247\u88C1\u526A\u5DE5\u5177"), /* @__PURE__ */ React2__default.default.createElement("p", { className: "text-sm text-gray-600 dark:text-gray-400" }, "\u4E0A\u4F20\u56FE\u7247\uFF0C\u8BBE\u7F6E\u7F51\u683C\uFF0C\u88C1\u526A\u5E76\u5BFC\u51FA")))), error && /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex items-center gap-2 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg" }, /* @__PURE__ */ React2__default.default.createElement(lucideReact.AlertCircle, { className: "w-5 h-5 text-red-500" }), /* @__PURE__ */ React2__default.default.createElement("p", { className: "text-sm text-red-700 dark:text-red-400" }, error)), /* @__PURE__ */ React2__default.default.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React2__default.default.createElement("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300" }, "\u4E0A\u4F20\u56FE\u7247"), /* @__PURE__ */ React2__default.default.createElement(
482
+ "div",
483
+ {
484
+ className: "border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg p-8 text-center cursor-pointer hover:border-blue-400 dark:hover:border-blue-500 transition-colors",
485
+ onClick: () => fileInputRef.current?.click()
486
+ },
487
+ /* @__PURE__ */ React2__default.default.createElement(lucideReact.Image, { className: "w-12 h-12 mx-auto mb-4 text-gray-400" }),
488
+ /* @__PURE__ */ React2__default.default.createElement("p", { className: "text-gray-600 dark:text-gray-400 mb-2" }, "\u70B9\u51FB\u9009\u62E9\u56FE\u7247\u6216\u62D6\u62FD\u6587\u4EF6\u5230\u8FD9\u91CC"),
489
+ /* @__PURE__ */ React2__default.default.createElement("p", { className: "text-sm text-gray-500" }, "\u652F\u6301 PNG, JPG, WEBP \u7B49\u683C\u5F0F"),
490
+ imageInfo && imageInfo.file && /* @__PURE__ */ React2__default.default.createElement("p", { className: "mt-4 text-sm text-green-600 dark:text-green-400" }, "\u2713 \u5DF2\u4E0A\u4F20: ", imageInfo.file.name, " (", imageInfo.width, " \xD7 ", imageInfo.height, ")")
491
+ ), /* @__PURE__ */ React2__default.default.createElement(
492
+ "input",
493
+ {
494
+ ref: fileInputRef,
495
+ type: "file",
496
+ accept: "image/*",
497
+ onChange: handleFileSelect,
498
+ className: "hidden"
499
+ }
500
+ )), imageInfo && /* @__PURE__ */ React2__default.default.createElement(
501
+ GridControls,
502
+ {
503
+ config: gridConfig,
504
+ onChange: setGridConfig,
505
+ disabled: isProcessing,
506
+ maxRows,
507
+ maxColumns,
508
+ maxCellSize,
509
+ minCellSize,
510
+ onReset: handleReset
511
+ }
512
+ ), imageInfo && /* @__PURE__ */ React2__default.default.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React2__default.default.createElement("h3", { className: "text-lg font-semibold text-gray-800 dark:text-gray-200" }, "\u9884\u89C8\u4E0E\u8C03\u6574"), /* @__PURE__ */ React2__default.default.createElement(
513
+ "button",
514
+ {
515
+ onClick: toggleSelectAll,
516
+ className: "px-4 py-2 text-sm text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-md transition-colors"
517
+ },
518
+ gridCells.every((cell) => cell.selected) ? "\u53D6\u6D88\u5168\u9009" : "\u5168\u9009"
519
+ )), /* @__PURE__ */ React2__default.default.createElement("div", { className: "border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden bg-gray-50 dark:bg-gray-900 p-4" }, /* @__PURE__ */ React2__default.default.createElement(
520
+ "canvas",
521
+ {
522
+ ref: canvasRef,
523
+ className: "max-w-full mx-auto border border-gray-300 dark:border-gray-600"
524
+ }
525
+ )), /* @__PURE__ */ React2__default.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React2__default.default.createElement("h4", { className: "text-sm font-medium text-gray-700 dark:text-gray-300" }, "\u5355\u5143\u683C\u5217\u8868 (", selectedCount, "/", gridCells.length, " \u5DF2\u9009\u4E2D)"), /* @__PURE__ */ React2__default.default.createElement("div", { className: "grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2 max-h-64 overflow-y-auto" }, gridCells.map((cell) => /* @__PURE__ */ React2__default.default.createElement(
526
+ "div",
527
+ {
528
+ key: cell.id,
529
+ className: `p-3 rounded border ${cell.selected ? "border-green-500 bg-green-50 dark:bg-green-900/20" : "border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800"} cursor-pointer transition-colors`,
530
+ onClick: () => toggleCellSelection(cell.id)
531
+ },
532
+ /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex items-center justify-between mb-2" }, /* @__PURE__ */ React2__default.default.createElement("span", { className: "text-sm font-medium text-gray-700 dark:text-gray-300" }, "R", cell.row, " C", cell.column), cell.selected ? /* @__PURE__ */ React2__default.default.createElement(lucideReact.CheckCircle2, { className: "w-4 h-4 text-green-500" }) : /* @__PURE__ */ React2__default.default.createElement(lucideReact.XCircle, { className: "w-4 h-4 text-gray-400" })),
533
+ /* @__PURE__ */ React2__default.default.createElement("div", { className: "text-xs text-gray-500 space-y-1" }, /* @__PURE__ */ React2__default.default.createElement("p", null, "X: ", cell.offsetX, "px"), /* @__PURE__ */ React2__default.default.createElement("p", null, "Y: ", cell.offsetY, "px"))
534
+ ))))), imageInfo && /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex items-center justify-between p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800" }, /* @__PURE__ */ React2__default.default.createElement("div", { className: "text-sm text-gray-700 dark:text-gray-300" }, /* @__PURE__ */ React2__default.default.createElement("p", { className: "font-medium" }, "\u51C6\u5907\u5BFC\u51FA ", selectedCount, " \u4E2A\u88C1\u526A\u56FE\u7247"), isProcessing && /* @__PURE__ */ React2__default.default.createElement("p", { className: "text-xs text-gray-600 dark:text-gray-400 mt-1" }, "\u8FDB\u5EA6: ", progress.current, "/", progress.total)), /* @__PURE__ */ React2__default.default.createElement(
535
+ "button",
536
+ {
537
+ onClick: () => handleExport(),
538
+ disabled: isProcessing || selectedCount === 0,
539
+ className: "flex items-center gap-2 px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
540
+ },
541
+ isProcessing ? /* @__PURE__ */ React2__default.default.createElement(React2__default.default.Fragment, null, /* @__PURE__ */ React2__default.default.createElement(lucideReact.Loader2, { className: "w-5 h-5 animate-spin" }), "\u5904\u7406\u4E2D...") : /* @__PURE__ */ React2__default.default.createElement(React2__default.default.Fragment, null, /* @__PURE__ */ React2__default.default.createElement(lucideReact.Download, { className: "w-5 h-5" }), "\u5BFC\u51FA\u4E3A ZIP")
542
+ )));
543
+ };
544
+
545
+ exports.GridControls = GridControls;
546
+ exports.ImageGridCropper = ImageGridCropper;
547
+ exports.calculateTotalSize = calculateTotalSize;
548
+ exports.constrainOffset = constrainOffset;
549
+ exports.cropGridCell = cropGridCell;
550
+ exports.cropMultipleCells = cropMultipleCells;
551
+ exports.downloadAsZip = downloadAsZip;
552
+ exports.downloadBlob = downloadBlob;
553
+ exports.downloadMultipleFiles = downloadMultipleFiles;
554
+ exports.formatFileSize = formatFileSize;
555
+ exports.generateCellPreview = generateCellPreview;
556
+ exports.loadImageFromFile = loadImageFromFile;
557
+ exports.validateCropArea = validateCropArea;
558
+ //# sourceMappingURL=index.js.map
559
+ //# sourceMappingURL=index.js.map