@umituz/web-canvas 1.0.1 → 1.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.
@@ -81,6 +81,7 @@ var DEFAULT_CANVAS_CONFIG = {
81
81
  var CanvasRenderer = class {
82
82
  constructor(config = DEFAULT_CANVAS_CONFIG) {
83
83
  this.config = { ...DEFAULT_CANVAS_CONFIG, ...config };
84
+ this.imageCache = /* @__PURE__ */ new Map();
84
85
  }
85
86
  /**
86
87
  * Create a new canvas element
@@ -182,17 +183,37 @@ var CanvasRenderer = class {
182
183
  }
183
184
  }
184
185
  /**
185
- * Load image from URL
186
+ * Load image from URL with caching
186
187
  */
187
188
  loadImage(url) {
189
+ if (this.imageCache.has(url)) {
190
+ return Promise.resolve(this.imageCache.get(url));
191
+ }
188
192
  return new Promise((resolve, reject) => {
189
193
  const img = new Image();
190
- img.crossOrigin = this.config.useCORS ? "anonymous" : void 0;
191
- img.onload = () => resolve(img);
194
+ if (this.config.useCORS) {
195
+ img.crossOrigin = "anonymous";
196
+ }
197
+ img.onload = () => {
198
+ this.imageCache.set(url, img);
199
+ resolve(img);
200
+ };
192
201
  img.onerror = () => reject(new Error(`Failed to load image: ${url}`));
193
202
  img.src = url;
194
203
  });
195
204
  }
205
+ /**
206
+ * Clear image cache to free memory
207
+ */
208
+ clearImageCache() {
209
+ this.imageCache.clear();
210
+ }
211
+ /**
212
+ * Get cache size for monitoring
213
+ */
214
+ getCacheSize() {
215
+ return this.imageCache.size;
216
+ }
196
217
  /**
197
218
  * Create gradient
198
219
  */
@@ -274,10 +295,15 @@ var CanvasRenderer = class {
274
295
 
275
296
  // src/infrastructure/renderers/PatternRenderer.ts
276
297
  var PatternRenderer = class {
298
+ constructor() {
299
+ this.patternCanvasCache = /* @__PURE__ */ new Map();
300
+ this.MAX_CACHE_SIZE = 10;
301
+ }
277
302
  /**
278
303
  * Render pattern on canvas
279
304
  */
280
305
  renderPattern(ctx, pattern, dimensions, color = "#000000") {
306
+ if (pattern === "none") return;
281
307
  ctx.save();
282
308
  ctx.globalAlpha = 0.1;
283
309
  switch (pattern) {
@@ -296,62 +322,65 @@ var PatternRenderer = class {
296
322
  case "checkers":
297
323
  this.renderCheckerPattern(ctx, dimensions, color);
298
324
  break;
299
- case "none":
300
- default:
301
- break;
302
325
  }
303
326
  ctx.restore();
304
327
  }
305
328
  /**
306
- * Render dot pattern
329
+ * Clear pattern cache to free memory
330
+ */
331
+ clearCache() {
332
+ this.patternCanvasCache.clear();
333
+ }
334
+ /**
335
+ * Render dot pattern - optimized with batch rendering
307
336
  */
308
337
  renderDotPattern(ctx, dimensions, color) {
309
338
  ctx.fillStyle = color;
310
339
  const spacing = 30;
340
+ const radius = 2;
341
+ ctx.beginPath();
311
342
  for (let x = spacing; x < dimensions.width; x += spacing) {
312
343
  for (let y = spacing; y < dimensions.height; y += spacing) {
313
- ctx.beginPath();
314
- ctx.arc(x, y, 2, 0, Math.PI * 2);
315
- ctx.fill();
344
+ ctx.moveTo(x + radius, y);
345
+ ctx.arc(x, y, radius, 0, Math.PI * 2);
316
346
  }
317
347
  }
348
+ ctx.fill();
318
349
  }
319
350
  /**
320
- * Render grid pattern
351
+ * Render grid pattern - optimized with single path
321
352
  */
322
353
  renderGridPattern(ctx, dimensions, color) {
323
354
  ctx.strokeStyle = color;
324
355
  ctx.lineWidth = 1;
325
356
  const spacing = 30;
357
+ ctx.beginPath();
326
358
  for (let x = 0; x <= dimensions.width; x += spacing) {
327
- ctx.beginPath();
328
359
  ctx.moveTo(x, 0);
329
360
  ctx.lineTo(x, dimensions.height);
330
- ctx.stroke();
331
361
  }
332
362
  for (let y = 0; y <= dimensions.height; y += spacing) {
333
- ctx.beginPath();
334
363
  ctx.moveTo(0, y);
335
364
  ctx.lineTo(dimensions.width, y);
336
- ctx.stroke();
337
365
  }
366
+ ctx.stroke();
338
367
  }
339
368
  /**
340
- * Render line pattern
369
+ * Render line pattern - optimized with single path
341
370
  */
342
371
  renderLinePattern(ctx, dimensions, color) {
343
372
  ctx.strokeStyle = color;
344
373
  ctx.lineWidth = 2;
345
374
  const spacing = 20;
375
+ ctx.beginPath();
346
376
  for (let y = spacing; y < dimensions.height; y += spacing) {
347
- ctx.beginPath();
348
377
  ctx.moveTo(0, y);
349
378
  ctx.lineTo(dimensions.width, y);
350
- ctx.stroke();
351
379
  }
380
+ ctx.stroke();
352
381
  }
353
382
  /**
354
- * Render wave pattern
383
+ * Render wave pattern - optimized with pre-calculated values
355
384
  */
356
385
  renderWavePattern(ctx, dimensions, color) {
357
386
  ctx.strokeStyle = color;
@@ -359,15 +388,15 @@ var PatternRenderer = class {
359
388
  const amplitude = 10;
360
389
  const frequency = 0.1;
361
390
  const spacing = 40;
391
+ const step = 5;
362
392
  for (let y = spacing; y < dimensions.height; y += spacing) {
363
393
  ctx.beginPath();
364
- for (let x = 0; x <= dimensions.width; x += 5) {
394
+ const startX = 0;
395
+ const startY = y + Math.sin(startX * frequency) * amplitude;
396
+ ctx.moveTo(startX, startY);
397
+ for (let x = step; x <= dimensions.width; x += step) {
365
398
  const waveY = y + Math.sin(x * frequency) * amplitude;
366
- if (x === 0) {
367
- ctx.moveTo(x, waveY);
368
- } else {
369
- ctx.lineTo(x, waveY);
370
- }
399
+ ctx.lineTo(x, waveY);
371
400
  }
372
401
  ctx.stroke();
373
402
  }
@@ -409,6 +438,9 @@ var DEFAULT_DEVICE_CONFIG = {
409
438
  // src/infrastructure/services/DeviceConfigService.ts
410
439
  var DeviceConfigService = class _DeviceConfigService {
411
440
  constructor() {
441
+ this.phonesCache = null;
442
+ this.tabletsCache = null;
443
+ this.allDevicesCache = null;
412
444
  this.devices = /* @__PURE__ */ new Map();
413
445
  this.initializeDevices();
414
446
  }
@@ -505,13 +537,22 @@ var DeviceConfigService = class _DeviceConfigService {
505
537
  return this.devices.get(deviceType);
506
538
  }
507
539
  getAllDevices() {
508
- return Array.from(this.devices.values());
540
+ if (!this.allDevicesCache) {
541
+ this.allDevicesCache = Array.from(this.devices.values());
542
+ }
543
+ return this.allDevicesCache;
509
544
  }
510
545
  getPhones() {
511
- return this.getAllDevices().filter((d) => d.isPhone);
546
+ if (!this.phonesCache) {
547
+ this.phonesCache = this.getAllDevices().filter((d) => d.isPhone);
548
+ }
549
+ return this.phonesCache;
512
550
  }
513
551
  getTablets() {
514
- return this.getAllDevices().filter((d) => d.isTablet);
552
+ if (!this.tabletsCache) {
553
+ this.tabletsCache = this.getAllDevices().filter((d) => d.isTablet);
554
+ }
555
+ return this.tabletsCache;
515
556
  }
516
557
  isPhone(deviceType) {
517
558
  const config = this.getDeviceConfig(deviceType);
@@ -530,10 +571,20 @@ var DeviceConfigService = class _DeviceConfigService {
530
571
  // src/infrastructure/renderers/ScreenshotRenderer.ts
531
572
  var ScreenshotRenderer = class {
532
573
  constructor() {
574
+ this.renderCache = /* @__PURE__ */ new Map();
575
+ this.MAX_CACHE_SIZE = 5;
533
576
  this.canvasRenderer = new CanvasRenderer();
534
577
  this.patternRenderer = new PatternRenderer();
535
578
  this.deviceConfigService = DeviceConfigService.getInstance();
536
579
  }
580
+ /**
581
+ * Clear render cache to free memory
582
+ */
583
+ clearCache() {
584
+ this.renderCache.clear();
585
+ this.canvasRenderer.clearImageCache?.();
586
+ this.patternRenderer.clearCache?.();
587
+ }
537
588
  /**
538
589
  * Render device frame
539
590
  */
@@ -585,7 +636,7 @@ var ScreenshotRenderer = class {
585
636
  ctx.fill();
586
637
  }
587
638
  /**
588
- * Render background
639
+ * Render background - optimized
589
640
  */
590
641
  renderBackground(ctx, design, dimensions) {
591
642
  if (design.backgroundGradientEnd) {
@@ -795,10 +846,17 @@ var ScreenshotStudioService = class {
795
846
  * Generate batch screenshots
796
847
  */
797
848
  async generateBatchScreenshots(deviceTypes) {
798
- const promises = deviceTypes.map(
799
- (deviceType) => this.generateScreenshot({ deviceType })
800
- );
801
- return Promise.all(promises);
849
+ const BATCH_SIZE = 3;
850
+ const results = [];
851
+ for (let i = 0; i < deviceTypes.length; i += BATCH_SIZE) {
852
+ const batch = deviceTypes.slice(i, i + BATCH_SIZE);
853
+ const batchPromises = batch.map(
854
+ (deviceType) => this.generateScreenshot({ deviceType })
855
+ );
856
+ const batchResults = await Promise.all(batchPromises);
857
+ results.push(...batchResults);
858
+ }
859
+ return results;
802
860
  }
803
861
  /**
804
862
  * Generate designed screenshot
@@ -839,16 +897,18 @@ WHATS NEW:
839
897
  ${project.appMetadata.whatsNew}`;
840
898
  folder.file("aso_metadata.txt", metadataText);
841
899
  }
842
- for (let i = 0; i < project.screens.length; i++) {
843
- const screen = project.screens[i];
844
- const screenshot = await this.generateDesignedScreenshot(screen, project.design);
900
+ const screenshotPromises = project.screens.map(
901
+ (screen, index) => this.generateDesignedScreenshot(screen, project.design).then((screenshot) => ({ screenshot, index }))
902
+ );
903
+ const screenshots = await Promise.all(screenshotPromises);
904
+ screenshots.forEach(({ screenshot, index }) => {
845
905
  const base64Data = screenshot.url.split(",")[1];
846
906
  folder.file(
847
- `screen_${i + 1}_${project.design.deviceType}.png`,
907
+ `screen_${index + 1}_${project.design.deviceType}.png`,
848
908
  base64Data,
849
909
  { base64: true }
850
910
  );
851
- }
911
+ });
852
912
  return await zip.generateAsync({ type: "blob" });
853
913
  }
854
914
  /**
@@ -932,10 +992,13 @@ ${project.appMetadata.whatsNew}`;
932
992
  };
933
993
  }
934
994
  /**
935
- * Generate unique ID
995
+ * Generate unique ID - optimized version
936
996
  */
937
997
  generateId() {
938
- return Math.random().toString(36).substr(2, 9);
998
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
999
+ return crypto.randomUUID();
1000
+ }
1001
+ return `${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 9)}`;
939
1002
  }
940
1003
  };
941
1004
 
@@ -1033,9 +1096,16 @@ function useScreenshotStudio(options = {}) {
1033
1096
  ...DEFAULT_SCREENSHOT_STUDIO_CONFIG,
1034
1097
  ...config
1035
1098
  };
1036
- const serviceRef = (0, import_react.useRef)(
1037
- new ScreenshotStudioService(fullConfig)
1038
- );
1099
+ const serviceRef = (0, import_react.useRef)(null);
1100
+ if (!serviceRef.current) {
1101
+ serviceRef.current = new ScreenshotStudioService(fullConfig);
1102
+ }
1103
+ (0, import_react.useEffect)(() => {
1104
+ return () => {
1105
+ if (serviceRef.current) {
1106
+ }
1107
+ };
1108
+ }, []);
1039
1109
  const [screens, setScreens] = (0, import_react.useState)(DEFAULT_SCREENS);
1040
1110
  const [design, setDesign] = (0, import_react.useState)(DEFAULT_DESIGN);
1041
1111
  const [currentLanguage, setCurrentLanguage] = (0, import_react.useState)(
@@ -1156,6 +1226,7 @@ function useScreenshotStudio(options = {}) {
1156
1226
  );
1157
1227
  const generateDesignedScreenshot = (0, import_react.useCallback)(
1158
1228
  async (screenId) => {
1229
+ if (!serviceRef.current) return;
1159
1230
  const screen = screens.find((s) => s.id === screenId);
1160
1231
  if (!screen) return;
1161
1232
  setIsProcessing(true);
@@ -1176,7 +1247,9 @@ function useScreenshotStudio(options = {}) {
1176
1247
  );
1177
1248
  const exportAllScreensAsZip = (0, import_react.useCallback)(
1178
1249
  async (format = "png") => {
1250
+ if (!serviceRef.current) return;
1179
1251
  setIsProcessing(true);
1252
+ let url = null;
1180
1253
  try {
1181
1254
  const project = {
1182
1255
  id: projectId || "default-project",
@@ -1191,12 +1264,11 @@ function useScreenshotStudio(options = {}) {
1191
1264
  updatedAt: /* @__PURE__ */ new Date()
1192
1265
  };
1193
1266
  const blob = await serviceRef.current.exportProjectAsZip(project);
1194
- const url = URL.createObjectURL(blob);
1267
+ url = URL.createObjectURL(blob);
1195
1268
  const link = document.createElement("a");
1196
1269
  link.href = url;
1197
1270
  link.download = `Studio_${currentLanguage}_${Date.now()}.zip`;
1198
1271
  link.click();
1199
- URL.revokeObjectURL(url);
1200
1272
  onShowToast(
1201
1273
  "Success",
1202
1274
  `Exported ${screens.length} screens in ZIP archive.`
@@ -1205,6 +1277,14 @@ function useScreenshotStudio(options = {}) {
1205
1277
  onShowToast("Error", "ZIP export failed");
1206
1278
  } finally {
1207
1279
  setIsProcessing(false);
1280
+ if (url) {
1281
+ setTimeout(() => {
1282
+ try {
1283
+ URL.revokeObjectURL(url);
1284
+ } catch (e) {
1285
+ }
1286
+ }, 100);
1287
+ }
1208
1288
  }
1209
1289
  },
1210
1290
  [
@@ -1290,15 +1370,34 @@ function useCanvasExport(options = {}) {
1290
1370
  const [isExporting, setIsExporting] = (0, import_react2.useState)(false);
1291
1371
  const [progress, setProgress] = (0, import_react2.useState)(0);
1292
1372
  const [error, setError] = (0, import_react2.useState)(null);
1293
- const renderer = new CanvasRenderer(config);
1373
+ const rendererRef = (0, import_react2.useRef)(null);
1374
+ const urlCleanupRef = (0, import_react2.useRef)([]);
1375
+ if (!rendererRef.current) {
1376
+ rendererRef.current = new CanvasRenderer(config);
1377
+ }
1378
+ (0, import_react2.useEffect)(() => {
1379
+ return () => {
1380
+ urlCleanupRef.current.forEach((url) => {
1381
+ try {
1382
+ URL.revokeObjectURL(url);
1383
+ } catch (e) {
1384
+ }
1385
+ });
1386
+ urlCleanupRef.current = [];
1387
+ };
1388
+ }, []);
1294
1389
  const exportToBlob = (0, import_react2.useCallback)(
1295
1390
  async (canvas, options2) => {
1391
+ if (!rendererRef.current) return null;
1296
1392
  setIsExporting(true);
1297
1393
  setProgress(0);
1298
1394
  setError(null);
1299
1395
  try {
1300
- const result = await renderer.exportToBlob(canvas, options2);
1396
+ const result = await rendererRef.current.exportToBlob(canvas, options2);
1301
1397
  setProgress(100);
1398
+ if (result?.url) {
1399
+ urlCleanupRef.current.push(result.url);
1400
+ }
1302
1401
  return result;
1303
1402
  } catch (err) {
1304
1403
  const errorMessage = err instanceof Error ? err.message : "Export failed";
@@ -1308,19 +1407,20 @@ function useCanvasExport(options = {}) {
1308
1407
  setIsExporting(false);
1309
1408
  }
1310
1409
  },
1311
- [renderer]
1410
+ []
1312
1411
  );
1313
1412
  const exportToDataURL = (0, import_react2.useCallback)(
1314
1413
  (canvas, format, quality) => {
1414
+ if (!rendererRef.current) return null;
1315
1415
  try {
1316
- return renderer.exportToDataURL(canvas, format, quality);
1416
+ return rendererRef.current.exportToDataURL(canvas, format, quality);
1317
1417
  } catch (err) {
1318
1418
  const errorMessage = err instanceof Error ? err.message : "Export failed";
1319
1419
  setError(errorMessage);
1320
1420
  return null;
1321
1421
  }
1322
1422
  },
1323
- [renderer]
1423
+ []
1324
1424
  );
1325
1425
  const downloadCanvas = (0, import_react2.useCallback)(
1326
1426
  async (canvas, filename, options2) => {
@@ -1340,6 +1440,10 @@ function useCanvasExport(options = {}) {
1340
1440
  setProgress(100);
1341
1441
  setTimeout(() => {
1342
1442
  URL.revokeObjectURL(result.url);
1443
+ const index = urlCleanupRef.current.indexOf(result.url);
1444
+ if (index > -1) {
1445
+ urlCleanupRef.current.splice(index, 1);
1446
+ }
1343
1447
  }, 100);
1344
1448
  } catch (err) {
1345
1449
  const errorMessage = err instanceof Error ? err.message : "Download failed";
@@ -1352,15 +1456,16 @@ function useCanvasExport(options = {}) {
1352
1456
  );
1353
1457
  const cloneCanvas = (0, import_react2.useCallback)(
1354
1458
  (canvas) => {
1459
+ if (!rendererRef.current) return null;
1355
1460
  try {
1356
- return renderer.cloneCanvas(canvas);
1461
+ return rendererRef.current.cloneCanvas(canvas);
1357
1462
  } catch (err) {
1358
1463
  const errorMessage = err instanceof Error ? err.message : "Clone failed";
1359
1464
  setError(errorMessage);
1360
1465
  return null;
1361
1466
  }
1362
1467
  },
1363
- [renderer]
1468
+ []
1364
1469
  );
1365
1470
  return {
1366
1471
  isExporting,
@@ -1522,13 +1627,19 @@ var ReportService = class _ReportService {
1522
1627
  function useReportExport() {
1523
1628
  const [isExporting, setIsExporting] = (0, import_react3.useState)(false);
1524
1629
  const [error, setError] = (0, import_react3.useState)(null);
1525
- const reportService = ReportService.getInstance();
1630
+ const reportServiceRef = (0, import_react3.useRef)(null);
1631
+ if (!reportServiceRef.current) {
1632
+ reportServiceRef.current = ReportService.getInstance();
1633
+ }
1526
1634
  const exportToPDF = (0, import_react3.useCallback)(
1527
1635
  async (elementId, options) => {
1636
+ if (!reportServiceRef.current) {
1637
+ throw new Error("ReportService not initialized");
1638
+ }
1528
1639
  setIsExporting(true);
1529
1640
  setError(null);
1530
1641
  try {
1531
- await reportService.exportToPDF(elementId, options);
1642
+ await reportServiceRef.current.exportToPDF(elementId, options);
1532
1643
  } catch (err) {
1533
1644
  const errorMessage = err instanceof Error ? err.message : "PDF export failed";
1534
1645
  setError(errorMessage);
@@ -1537,14 +1648,17 @@ function useReportExport() {
1537
1648
  setIsExporting(false);
1538
1649
  }
1539
1650
  },
1540
- [reportService]
1651
+ []
1541
1652
  );
1542
1653
  const exportCanvasToPDF = (0, import_react3.useCallback)(
1543
1654
  async (canvas, options) => {
1655
+ if (!reportServiceRef.current) {
1656
+ throw new Error("ReportService not initialized");
1657
+ }
1544
1658
  setIsExporting(true);
1545
1659
  setError(null);
1546
1660
  try {
1547
- await reportService.exportCanvasToPDF(canvas, options);
1661
+ await reportServiceRef.current.exportCanvasToPDF(canvas, options);
1548
1662
  } catch (err) {
1549
1663
  const errorMessage = err instanceof Error ? err.message : "Canvas PDF export failed";
1550
1664
  setError(errorMessage);
@@ -1553,14 +1667,17 @@ function useReportExport() {
1553
1667
  setIsExporting(false);
1554
1668
  }
1555
1669
  },
1556
- [reportService]
1670
+ []
1557
1671
  );
1558
1672
  const exportMultipleToPDF = (0, import_react3.useCallback)(
1559
1673
  async (elementIds, options) => {
1674
+ if (!reportServiceRef.current) {
1675
+ throw new Error("ReportService not initialized");
1676
+ }
1560
1677
  setIsExporting(true);
1561
1678
  setError(null);
1562
1679
  try {
1563
- await reportService.exportMultipleToPDF(elementIds, options);
1680
+ await reportServiceRef.current.exportMultipleToPDF(elementIds, options);
1564
1681
  } catch (err) {
1565
1682
  const errorMessage = err instanceof Error ? err.message : "Multi-page PDF export failed";
1566
1683
  setError(errorMessage);
@@ -1569,14 +1686,17 @@ function useReportExport() {
1569
1686
  setIsExporting(false);
1570
1687
  }
1571
1688
  },
1572
- [reportService]
1689
+ []
1573
1690
  );
1574
1691
  const exportToExcel = (0, import_react3.useCallback)(
1575
1692
  async (data, options) => {
1693
+ if (!reportServiceRef.current) {
1694
+ throw new Error("ReportService not initialized");
1695
+ }
1576
1696
  setIsExporting(true);
1577
1697
  setError(null);
1578
1698
  try {
1579
- await reportService.exportToExcel(data, options);
1699
+ await reportServiceRef.current.exportToExcel(data, options);
1580
1700
  } catch (err) {
1581
1701
  const errorMessage = err instanceof Error ? err.message : "Excel export failed";
1582
1702
  setError(errorMessage);
@@ -1585,19 +1705,22 @@ function useReportExport() {
1585
1705
  setIsExporting(false);
1586
1706
  }
1587
1707
  },
1588
- [reportService]
1708
+ []
1589
1709
  );
1590
1710
  const exportToCSV = (0, import_react3.useCallback)(
1591
1711
  (data, filename, delimiter) => {
1712
+ if (!reportServiceRef.current) {
1713
+ throw new Error("ReportService not initialized");
1714
+ }
1592
1715
  try {
1593
- reportService.exportToCSV(data, filename, delimiter);
1716
+ reportServiceRef.current.exportToCSV(data, filename, delimiter);
1594
1717
  } catch (err) {
1595
1718
  const errorMessage = err instanceof Error ? err.message : "CSV export failed";
1596
1719
  setError(errorMessage);
1597
1720
  throw err;
1598
1721
  }
1599
1722
  },
1600
- [reportService]
1723
+ []
1601
1724
  );
1602
1725
  return {
1603
1726
  isExporting,
@@ -2,9 +2,9 @@ import {
2
2
  useCanvasExport,
3
3
  useReportExport,
4
4
  useScreenshotStudio
5
- } from "../chunk-SF6WND7M.mjs";
6
- import "../chunk-BN7JTXL7.mjs";
7
- import "../chunk-WMKPCJFI.mjs";
5
+ } from "../chunk-LOVI2LTO.mjs";
6
+ import "../chunk-DNKRSL6D.mjs";
7
+ import "../chunk-UOGTEMIJ.mjs";
8
8
  import "../chunk-Z6IB4BQV.mjs";
9
9
  import "../chunk-MC6PNV2F.mjs";
10
10
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/web-canvas",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Comprehensive canvas and screenshot generation library with device frames, patterns, and PDF export",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -48,7 +48,7 @@
48
48
  "scripts": {
49
49
  "build": "tsup src/index.ts src/domain/index.ts src/application/index.ts src/presentation/index.ts src/infrastructure/index.ts src/domain/config/index.ts --format cjs,esm --dts --clean --external react",
50
50
  "dev": "tsup src/index.ts src/domain/index.ts src/application/index.ts src/presentation/index.ts src/infrastructure/index.ts src/domain/config/index.ts --format cjs,esm --dts --watch --external react",
51
- "lint": "tsc --noEmit"
51
+ "lint": "eslint ."
52
52
  },
53
53
  "keywords": [
54
54
  "canvas",
@@ -69,13 +69,24 @@
69
69
  ],
70
70
  "author": "umituz",
71
71
  "license": "MIT",
72
+ "repository": {
73
+ "type": "git",
74
+ "url": "https://github.com/umituz/web-canvas"
75
+ },
76
+ "bugs": {
77
+ "url": "https://github.com/umituz/web-canvas/issues"
78
+ },
79
+ "homepage": "https://github.com/umituz/web-canvas#readme",
72
80
  "peerDependencies": {
73
81
  "react": ">=18"
74
82
  },
75
83
  "devDependencies": {
84
+ "@eslint/js": "^9.32.0",
76
85
  "@types/react": "^18.2.0",
86
+ "eslint": "^9.32.0",
77
87
  "tsup": "^8.0.0",
78
- "typescript": "^5.0.0"
88
+ "typescript": "^5.0.0",
89
+ "typescript-eslint": "^8.38.0"
79
90
  },
80
91
  "dependencies": {
81
92
  "html2canvas": "^1.4.1",
@@ -79,10 +79,21 @@ export class ScreenshotStudioService {
79
79
  public async generateBatchScreenshots(
80
80
  deviceTypes: DeviceType[]
81
81
  ): Promise<ScreenshotMetadata[]> {
82
- const promises = deviceTypes.map((deviceType) =>
83
- this.generateScreenshot({ deviceType })
84
- );
85
- return Promise.all(promises);
82
+ // Process in parallel with concurrency control for better performance
83
+ const BATCH_SIZE = 3; // Process 3 screenshots at a time to avoid memory issues
84
+
85
+ const results: ScreenshotMetadata[] = [];
86
+
87
+ for (let i = 0; i < deviceTypes.length; i += BATCH_SIZE) {
88
+ const batch = deviceTypes.slice(i, i + BATCH_SIZE);
89
+ const batchPromises = batch.map((deviceType) =>
90
+ this.generateScreenshot({ deviceType })
91
+ );
92
+ const batchResults = await Promise.all(batchPromises);
93
+ results.push(...batchResults);
94
+ }
95
+
96
+ return results;
86
97
  }
87
98
 
88
99
  /**
@@ -136,17 +147,23 @@ ${project.appMetadata.whatsNew}`;
136
147
  folder.file('aso_metadata.txt', metadataText);
137
148
  }
138
149
 
139
- // Generate screenshots
140
- for (let i = 0; i < project.screens.length; i++) {
141
- const screen = project.screens[i];
142
- const screenshot = await this.generateDesignedScreenshot(screen, project.design);
150
+ // Generate screenshots in parallel for better performance
151
+ const screenshotPromises = project.screens.map((screen, index) =>
152
+ this.generateDesignedScreenshot(screen, project.design)
153
+ .then(screenshot => ({ screenshot, index }))
154
+ );
155
+
156
+ const screenshots = await Promise.all(screenshotPromises);
157
+
158
+ // Add all screenshots to ZIP
159
+ screenshots.forEach(({ screenshot, index }) => {
143
160
  const base64Data = screenshot.url.split(',')[1];
144
161
  folder.file(
145
- `screen_${i + 1}_${project.design.deviceType}.png`,
162
+ `screen_${index + 1}_${project.design.deviceType}.png`,
146
163
  base64Data,
147
164
  { base64: true }
148
165
  );
149
- }
166
+ });
150
167
 
151
168
  return await zip.generateAsync({ type: 'blob' });
152
169
  }
@@ -235,9 +252,15 @@ ${project.appMetadata.whatsNew}`;
235
252
  }
236
253
 
237
254
  /**
238
- * Generate unique ID
255
+ * Generate unique ID - optimized version
239
256
  */
240
257
  private generateId(): string {
241
- return Math.random().toString(36).substr(2, 9);
258
+ // Use more efficient ID generation with crypto if available
259
+ if (typeof crypto !== 'undefined' && crypto.randomUUID) {
260
+ return crypto.randomUUID();
261
+ }
262
+
263
+ // Fallback to timestamp + random for better uniqueness
264
+ return `${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 9)}`;
242
265
  }
243
266
  }