@umituz/web-canvas 1.0.1 → 1.0.2

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.
@@ -40,7 +40,7 @@ declare class ScreenshotStudioService {
40
40
  */
41
41
  private getDefaultScreen;
42
42
  /**
43
- * Generate unique ID
43
+ * Generate unique ID - optimized version
44
44
  */
45
45
  private generateId;
46
46
  }
@@ -40,7 +40,7 @@ declare class ScreenshotStudioService {
40
40
  */
41
41
  private getDefaultScreen;
42
42
  /**
43
- * Generate unique ID
43
+ * Generate unique ID - optimized version
44
44
  */
45
45
  private generateId;
46
46
  }
@@ -77,6 +77,7 @@ var DEFAULT_CANVAS_CONFIG = {
77
77
  var CanvasRenderer = class {
78
78
  constructor(config = DEFAULT_CANVAS_CONFIG) {
79
79
  this.config = { ...DEFAULT_CANVAS_CONFIG, ...config };
80
+ this.imageCache = /* @__PURE__ */ new Map();
80
81
  }
81
82
  /**
82
83
  * Create a new canvas element
@@ -178,17 +179,37 @@ var CanvasRenderer = class {
178
179
  }
179
180
  }
180
181
  /**
181
- * Load image from URL
182
+ * Load image from URL with caching
182
183
  */
183
184
  loadImage(url) {
185
+ if (this.imageCache.has(url)) {
186
+ return Promise.resolve(this.imageCache.get(url));
187
+ }
184
188
  return new Promise((resolve, reject) => {
185
189
  const img = new Image();
186
- img.crossOrigin = this.config.useCORS ? "anonymous" : void 0;
187
- img.onload = () => resolve(img);
190
+ if (this.config.useCORS) {
191
+ img.crossOrigin = "anonymous";
192
+ }
193
+ img.onload = () => {
194
+ this.imageCache.set(url, img);
195
+ resolve(img);
196
+ };
188
197
  img.onerror = () => reject(new Error(`Failed to load image: ${url}`));
189
198
  img.src = url;
190
199
  });
191
200
  }
201
+ /**
202
+ * Clear image cache to free memory
203
+ */
204
+ clearImageCache() {
205
+ this.imageCache.clear();
206
+ }
207
+ /**
208
+ * Get cache size for monitoring
209
+ */
210
+ getCacheSize() {
211
+ return this.imageCache.size;
212
+ }
192
213
  /**
193
214
  * Create gradient
194
215
  */
@@ -270,10 +291,15 @@ var CanvasRenderer = class {
270
291
 
271
292
  // src/infrastructure/renderers/PatternRenderer.ts
272
293
  var PatternRenderer = class {
294
+ constructor() {
295
+ this.patternCanvasCache = /* @__PURE__ */ new Map();
296
+ this.MAX_CACHE_SIZE = 10;
297
+ }
273
298
  /**
274
299
  * Render pattern on canvas
275
300
  */
276
301
  renderPattern(ctx, pattern, dimensions, color = "#000000") {
302
+ if (pattern === "none") return;
277
303
  ctx.save();
278
304
  ctx.globalAlpha = 0.1;
279
305
  switch (pattern) {
@@ -292,62 +318,65 @@ var PatternRenderer = class {
292
318
  case "checkers":
293
319
  this.renderCheckerPattern(ctx, dimensions, color);
294
320
  break;
295
- case "none":
296
- default:
297
- break;
298
321
  }
299
322
  ctx.restore();
300
323
  }
301
324
  /**
302
- * Render dot pattern
325
+ * Clear pattern cache to free memory
326
+ */
327
+ clearCache() {
328
+ this.patternCanvasCache.clear();
329
+ }
330
+ /**
331
+ * Render dot pattern - optimized with batch rendering
303
332
  */
304
333
  renderDotPattern(ctx, dimensions, color) {
305
334
  ctx.fillStyle = color;
306
335
  const spacing = 30;
336
+ const radius = 2;
337
+ ctx.beginPath();
307
338
  for (let x = spacing; x < dimensions.width; x += spacing) {
308
339
  for (let y = spacing; y < dimensions.height; y += spacing) {
309
- ctx.beginPath();
310
- ctx.arc(x, y, 2, 0, Math.PI * 2);
311
- ctx.fill();
340
+ ctx.moveTo(x + radius, y);
341
+ ctx.arc(x, y, radius, 0, Math.PI * 2);
312
342
  }
313
343
  }
344
+ ctx.fill();
314
345
  }
315
346
  /**
316
- * Render grid pattern
347
+ * Render grid pattern - optimized with single path
317
348
  */
318
349
  renderGridPattern(ctx, dimensions, color) {
319
350
  ctx.strokeStyle = color;
320
351
  ctx.lineWidth = 1;
321
352
  const spacing = 30;
353
+ ctx.beginPath();
322
354
  for (let x = 0; x <= dimensions.width; x += spacing) {
323
- ctx.beginPath();
324
355
  ctx.moveTo(x, 0);
325
356
  ctx.lineTo(x, dimensions.height);
326
- ctx.stroke();
327
357
  }
328
358
  for (let y = 0; y <= dimensions.height; y += spacing) {
329
- ctx.beginPath();
330
359
  ctx.moveTo(0, y);
331
360
  ctx.lineTo(dimensions.width, y);
332
- ctx.stroke();
333
361
  }
362
+ ctx.stroke();
334
363
  }
335
364
  /**
336
- * Render line pattern
365
+ * Render line pattern - optimized with single path
337
366
  */
338
367
  renderLinePattern(ctx, dimensions, color) {
339
368
  ctx.strokeStyle = color;
340
369
  ctx.lineWidth = 2;
341
370
  const spacing = 20;
371
+ ctx.beginPath();
342
372
  for (let y = spacing; y < dimensions.height; y += spacing) {
343
- ctx.beginPath();
344
373
  ctx.moveTo(0, y);
345
374
  ctx.lineTo(dimensions.width, y);
346
- ctx.stroke();
347
375
  }
376
+ ctx.stroke();
348
377
  }
349
378
  /**
350
- * Render wave pattern
379
+ * Render wave pattern - optimized with pre-calculated values
351
380
  */
352
381
  renderWavePattern(ctx, dimensions, color) {
353
382
  ctx.strokeStyle = color;
@@ -355,15 +384,15 @@ var PatternRenderer = class {
355
384
  const amplitude = 10;
356
385
  const frequency = 0.1;
357
386
  const spacing = 40;
387
+ const step = 5;
358
388
  for (let y = spacing; y < dimensions.height; y += spacing) {
359
389
  ctx.beginPath();
360
- for (let x = 0; x <= dimensions.width; x += 5) {
390
+ const startX = 0;
391
+ const startY = y + Math.sin(startX * frequency) * amplitude;
392
+ ctx.moveTo(startX, startY);
393
+ for (let x = step; x <= dimensions.width; x += step) {
361
394
  const waveY = y + Math.sin(x * frequency) * amplitude;
362
- if (x === 0) {
363
- ctx.moveTo(x, waveY);
364
- } else {
365
- ctx.lineTo(x, waveY);
366
- }
395
+ ctx.lineTo(x, waveY);
367
396
  }
368
397
  ctx.stroke();
369
398
  }
@@ -405,6 +434,9 @@ var DEFAULT_DEVICE_CONFIG = {
405
434
  // src/infrastructure/services/DeviceConfigService.ts
406
435
  var DeviceConfigService = class _DeviceConfigService {
407
436
  constructor() {
437
+ this.phonesCache = null;
438
+ this.tabletsCache = null;
439
+ this.allDevicesCache = null;
408
440
  this.devices = /* @__PURE__ */ new Map();
409
441
  this.initializeDevices();
410
442
  }
@@ -501,13 +533,22 @@ var DeviceConfigService = class _DeviceConfigService {
501
533
  return this.devices.get(deviceType);
502
534
  }
503
535
  getAllDevices() {
504
- return Array.from(this.devices.values());
536
+ if (!this.allDevicesCache) {
537
+ this.allDevicesCache = Array.from(this.devices.values());
538
+ }
539
+ return this.allDevicesCache;
505
540
  }
506
541
  getPhones() {
507
- return this.getAllDevices().filter((d) => d.isPhone);
542
+ if (!this.phonesCache) {
543
+ this.phonesCache = this.getAllDevices().filter((d) => d.isPhone);
544
+ }
545
+ return this.phonesCache;
508
546
  }
509
547
  getTablets() {
510
- return this.getAllDevices().filter((d) => d.isTablet);
548
+ if (!this.tabletsCache) {
549
+ this.tabletsCache = this.getAllDevices().filter((d) => d.isTablet);
550
+ }
551
+ return this.tabletsCache;
511
552
  }
512
553
  isPhone(deviceType) {
513
554
  const config = this.getDeviceConfig(deviceType);
@@ -526,10 +567,20 @@ var DeviceConfigService = class _DeviceConfigService {
526
567
  // src/infrastructure/renderers/ScreenshotRenderer.ts
527
568
  var ScreenshotRenderer = class {
528
569
  constructor() {
570
+ this.renderCache = /* @__PURE__ */ new Map();
571
+ this.MAX_CACHE_SIZE = 5;
529
572
  this.canvasRenderer = new CanvasRenderer();
530
573
  this.patternRenderer = new PatternRenderer();
531
574
  this.deviceConfigService = DeviceConfigService.getInstance();
532
575
  }
576
+ /**
577
+ * Clear render cache to free memory
578
+ */
579
+ clearCache() {
580
+ this.renderCache.clear();
581
+ this.canvasRenderer.clearImageCache?.();
582
+ this.patternRenderer.clearCache?.();
583
+ }
533
584
  /**
534
585
  * Render device frame
535
586
  */
@@ -581,7 +632,7 @@ var ScreenshotRenderer = class {
581
632
  ctx.fill();
582
633
  }
583
634
  /**
584
- * Render background
635
+ * Render background - optimized
585
636
  */
586
637
  renderBackground(ctx, design, dimensions) {
587
638
  if (design.backgroundGradientEnd) {
@@ -791,10 +842,17 @@ var ScreenshotStudioService = class {
791
842
  * Generate batch screenshots
792
843
  */
793
844
  async generateBatchScreenshots(deviceTypes) {
794
- const promises = deviceTypes.map(
795
- (deviceType) => this.generateScreenshot({ deviceType })
796
- );
797
- return Promise.all(promises);
845
+ const BATCH_SIZE = 3;
846
+ const results = [];
847
+ for (let i = 0; i < deviceTypes.length; i += BATCH_SIZE) {
848
+ const batch = deviceTypes.slice(i, i + BATCH_SIZE);
849
+ const batchPromises = batch.map(
850
+ (deviceType) => this.generateScreenshot({ deviceType })
851
+ );
852
+ const batchResults = await Promise.all(batchPromises);
853
+ results.push(...batchResults);
854
+ }
855
+ return results;
798
856
  }
799
857
  /**
800
858
  * Generate designed screenshot
@@ -835,16 +893,18 @@ WHATS NEW:
835
893
  ${project.appMetadata.whatsNew}`;
836
894
  folder.file("aso_metadata.txt", metadataText);
837
895
  }
838
- for (let i = 0; i < project.screens.length; i++) {
839
- const screen = project.screens[i];
840
- const screenshot = await this.generateDesignedScreenshot(screen, project.design);
896
+ const screenshotPromises = project.screens.map(
897
+ (screen, index) => this.generateDesignedScreenshot(screen, project.design).then((screenshot) => ({ screenshot, index }))
898
+ );
899
+ const screenshots = await Promise.all(screenshotPromises);
900
+ screenshots.forEach(({ screenshot, index }) => {
841
901
  const base64Data = screenshot.url.split(",")[1];
842
902
  folder.file(
843
- `screen_${i + 1}_${project.design.deviceType}.png`,
903
+ `screen_${index + 1}_${project.design.deviceType}.png`,
844
904
  base64Data,
845
905
  { base64: true }
846
906
  );
847
- }
907
+ });
848
908
  return await zip.generateAsync({ type: "blob" });
849
909
  }
850
910
  /**
@@ -928,10 +988,13 @@ ${project.appMetadata.whatsNew}`;
928
988
  };
929
989
  }
930
990
  /**
931
- * Generate unique ID
991
+ * Generate unique ID - optimized version
932
992
  */
933
993
  generateId() {
934
- return Math.random().toString(36).substr(2, 9);
994
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
995
+ return crypto.randomUUID();
996
+ }
997
+ return `${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 9)}`;
935
998
  }
936
999
  };
937
1000
 
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  ReportService,
3
3
  ScreenshotStudioService
4
- } from "../chunk-BN7JTXL7.mjs";
5
- import "../chunk-WMKPCJFI.mjs";
4
+ } from "../chunk-DNKRSL6D.mjs";
5
+ import "../chunk-UOGTEMIJ.mjs";
6
6
  import "../chunk-Z6IB4BQV.mjs";
7
7
  import "../chunk-MC6PNV2F.mjs";
8
8
  export {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ScreenshotRenderer
3
- } from "./chunk-WMKPCJFI.mjs";
3
+ } from "./chunk-UOGTEMIJ.mjs";
4
4
  import {
5
5
  DEFAULT_SCREENSHOT_STUDIO_CONFIG
6
6
  } from "./chunk-Z6IB4BQV.mjs";
@@ -54,10 +54,17 @@ var ScreenshotStudioService = class {
54
54
  * Generate batch screenshots
55
55
  */
56
56
  async generateBatchScreenshots(deviceTypes) {
57
- const promises = deviceTypes.map(
58
- (deviceType) => this.generateScreenshot({ deviceType })
59
- );
60
- return Promise.all(promises);
57
+ const BATCH_SIZE = 3;
58
+ const results = [];
59
+ for (let i = 0; i < deviceTypes.length; i += BATCH_SIZE) {
60
+ const batch = deviceTypes.slice(i, i + BATCH_SIZE);
61
+ const batchPromises = batch.map(
62
+ (deviceType) => this.generateScreenshot({ deviceType })
63
+ );
64
+ const batchResults = await Promise.all(batchPromises);
65
+ results.push(...batchResults);
66
+ }
67
+ return results;
61
68
  }
62
69
  /**
63
70
  * Generate designed screenshot
@@ -98,16 +105,18 @@ WHATS NEW:
98
105
  ${project.appMetadata.whatsNew}`;
99
106
  folder.file("aso_metadata.txt", metadataText);
100
107
  }
101
- for (let i = 0; i < project.screens.length; i++) {
102
- const screen = project.screens[i];
103
- const screenshot = await this.generateDesignedScreenshot(screen, project.design);
108
+ const screenshotPromises = project.screens.map(
109
+ (screen, index) => this.generateDesignedScreenshot(screen, project.design).then((screenshot) => ({ screenshot, index }))
110
+ );
111
+ const screenshots = await Promise.all(screenshotPromises);
112
+ screenshots.forEach(({ screenshot, index }) => {
104
113
  const base64Data = screenshot.url.split(",")[1];
105
114
  folder.file(
106
- `screen_${i + 1}_${project.design.deviceType}.png`,
115
+ `screen_${index + 1}_${project.design.deviceType}.png`,
107
116
  base64Data,
108
117
  { base64: true }
109
118
  );
110
- }
119
+ });
111
120
  return await zip.generateAsync({ type: "blob" });
112
121
  }
113
122
  /**
@@ -191,10 +200,13 @@ ${project.appMetadata.whatsNew}`;
191
200
  };
192
201
  }
193
202
  /**
194
- * Generate unique ID
203
+ * Generate unique ID - optimized version
195
204
  */
196
205
  generateId() {
197
- return Math.random().toString(36).substr(2, 9);
206
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
207
+ return crypto.randomUUID();
208
+ }
209
+ return `${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 9)}`;
198
210
  }
199
211
  };
200
212
 
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  ReportService,
3
3
  ScreenshotStudioService
4
- } from "./chunk-BN7JTXL7.mjs";
4
+ } from "./chunk-DNKRSL6D.mjs";
5
5
  import {
6
6
  CanvasRenderer
7
- } from "./chunk-WMKPCJFI.mjs";
7
+ } from "./chunk-UOGTEMIJ.mjs";
8
8
  import {
9
9
  DEFAULT_SCREENSHOT_STUDIO_CONFIG
10
10
  } from "./chunk-Z6IB4BQV.mjs";
@@ -13,7 +13,7 @@ import {
13
13
  } from "./chunk-MC6PNV2F.mjs";
14
14
 
15
15
  // src/presentation/hooks/useScreenshotStudio.ts
16
- import { useState, useCallback, useRef } from "react";
16
+ import { useState, useCallback, useRef, useEffect } from "react";
17
17
  var DEFAULT_SCREENS = [
18
18
  {
19
19
  id: "1",
@@ -107,9 +107,16 @@ function useScreenshotStudio(options = {}) {
107
107
  ...DEFAULT_SCREENSHOT_STUDIO_CONFIG,
108
108
  ...config
109
109
  };
110
- const serviceRef = useRef(
111
- new ScreenshotStudioService(fullConfig)
112
- );
110
+ const serviceRef = useRef(null);
111
+ if (!serviceRef.current) {
112
+ serviceRef.current = new ScreenshotStudioService(fullConfig);
113
+ }
114
+ useEffect(() => {
115
+ return () => {
116
+ if (serviceRef.current) {
117
+ }
118
+ };
119
+ }, []);
113
120
  const [screens, setScreens] = useState(DEFAULT_SCREENS);
114
121
  const [design, setDesign] = useState(DEFAULT_DESIGN);
115
122
  const [currentLanguage, setCurrentLanguage] = useState(
@@ -230,6 +237,7 @@ function useScreenshotStudio(options = {}) {
230
237
  );
231
238
  const generateDesignedScreenshot = useCallback(
232
239
  async (screenId) => {
240
+ if (!serviceRef.current) return;
233
241
  const screen = screens.find((s) => s.id === screenId);
234
242
  if (!screen) return;
235
243
  setIsProcessing(true);
@@ -250,7 +258,9 @@ function useScreenshotStudio(options = {}) {
250
258
  );
251
259
  const exportAllScreensAsZip = useCallback(
252
260
  async (format = "png") => {
261
+ if (!serviceRef.current) return;
253
262
  setIsProcessing(true);
263
+ let url = null;
254
264
  try {
255
265
  const project = {
256
266
  id: projectId || "default-project",
@@ -265,12 +275,11 @@ function useScreenshotStudio(options = {}) {
265
275
  updatedAt: /* @__PURE__ */ new Date()
266
276
  };
267
277
  const blob = await serviceRef.current.exportProjectAsZip(project);
268
- const url = URL.createObjectURL(blob);
278
+ url = URL.createObjectURL(blob);
269
279
  const link = document.createElement("a");
270
280
  link.href = url;
271
281
  link.download = `Studio_${currentLanguage}_${Date.now()}.zip`;
272
282
  link.click();
273
- URL.revokeObjectURL(url);
274
283
  onShowToast(
275
284
  "Success",
276
285
  `Exported ${screens.length} screens in ZIP archive.`
@@ -279,6 +288,14 @@ function useScreenshotStudio(options = {}) {
279
288
  onShowToast("Error", "ZIP export failed");
280
289
  } finally {
281
290
  setIsProcessing(false);
291
+ if (url) {
292
+ setTimeout(() => {
293
+ try {
294
+ URL.revokeObjectURL(url);
295
+ } catch (e) {
296
+ }
297
+ }, 100);
298
+ }
282
299
  }
283
300
  },
284
301
  [
@@ -358,21 +375,40 @@ function useScreenshotStudio(options = {}) {
358
375
  }
359
376
 
360
377
  // src/presentation/hooks/useCanvasExport.ts
361
- import { useState as useState2, useCallback as useCallback2 } from "react";
378
+ import { useState as useState2, useCallback as useCallback2, useRef as useRef2, useEffect as useEffect2 } from "react";
362
379
  function useCanvasExport(options = {}) {
363
380
  const { config = DEFAULT_CANVAS_CONFIG } = options;
364
381
  const [isExporting, setIsExporting] = useState2(false);
365
382
  const [progress, setProgress] = useState2(0);
366
383
  const [error, setError] = useState2(null);
367
- const renderer = new CanvasRenderer(config);
384
+ const rendererRef = useRef2(null);
385
+ const urlCleanupRef = useRef2([]);
386
+ if (!rendererRef.current) {
387
+ rendererRef.current = new CanvasRenderer(config);
388
+ }
389
+ useEffect2(() => {
390
+ return () => {
391
+ urlCleanupRef.current.forEach((url) => {
392
+ try {
393
+ URL.revokeObjectURL(url);
394
+ } catch (e) {
395
+ }
396
+ });
397
+ urlCleanupRef.current = [];
398
+ };
399
+ }, []);
368
400
  const exportToBlob = useCallback2(
369
401
  async (canvas, options2) => {
402
+ if (!rendererRef.current) return null;
370
403
  setIsExporting(true);
371
404
  setProgress(0);
372
405
  setError(null);
373
406
  try {
374
- const result = await renderer.exportToBlob(canvas, options2);
407
+ const result = await rendererRef.current.exportToBlob(canvas, options2);
375
408
  setProgress(100);
409
+ if (result?.url) {
410
+ urlCleanupRef.current.push(result.url);
411
+ }
376
412
  return result;
377
413
  } catch (err) {
378
414
  const errorMessage = err instanceof Error ? err.message : "Export failed";
@@ -382,19 +418,20 @@ function useCanvasExport(options = {}) {
382
418
  setIsExporting(false);
383
419
  }
384
420
  },
385
- [renderer]
421
+ []
386
422
  );
387
423
  const exportToDataURL = useCallback2(
388
424
  (canvas, format, quality) => {
425
+ if (!rendererRef.current) return null;
389
426
  try {
390
- return renderer.exportToDataURL(canvas, format, quality);
427
+ return rendererRef.current.exportToDataURL(canvas, format, quality);
391
428
  } catch (err) {
392
429
  const errorMessage = err instanceof Error ? err.message : "Export failed";
393
430
  setError(errorMessage);
394
431
  return null;
395
432
  }
396
433
  },
397
- [renderer]
434
+ []
398
435
  );
399
436
  const downloadCanvas = useCallback2(
400
437
  async (canvas, filename, options2) => {
@@ -414,6 +451,10 @@ function useCanvasExport(options = {}) {
414
451
  setProgress(100);
415
452
  setTimeout(() => {
416
453
  URL.revokeObjectURL(result.url);
454
+ const index = urlCleanupRef.current.indexOf(result.url);
455
+ if (index > -1) {
456
+ urlCleanupRef.current.splice(index, 1);
457
+ }
417
458
  }, 100);
418
459
  } catch (err) {
419
460
  const errorMessage = err instanceof Error ? err.message : "Download failed";
@@ -426,15 +467,16 @@ function useCanvasExport(options = {}) {
426
467
  );
427
468
  const cloneCanvas = useCallback2(
428
469
  (canvas) => {
470
+ if (!rendererRef.current) return null;
429
471
  try {
430
- return renderer.cloneCanvas(canvas);
472
+ return rendererRef.current.cloneCanvas(canvas);
431
473
  } catch (err) {
432
474
  const errorMessage = err instanceof Error ? err.message : "Clone failed";
433
475
  setError(errorMessage);
434
476
  return null;
435
477
  }
436
478
  },
437
- [renderer]
479
+ []
438
480
  );
439
481
  return {
440
482
  isExporting,
@@ -448,17 +490,23 @@ function useCanvasExport(options = {}) {
448
490
  }
449
491
 
450
492
  // src/presentation/hooks/useReportExport.ts
451
- import { useState as useState3, useCallback as useCallback3 } from "react";
493
+ import { useState as useState3, useCallback as useCallback3, useRef as useRef3 } from "react";
452
494
  function useReportExport() {
453
495
  const [isExporting, setIsExporting] = useState3(false);
454
496
  const [error, setError] = useState3(null);
455
- const reportService = ReportService.getInstance();
497
+ const reportServiceRef = useRef3(null);
498
+ if (!reportServiceRef.current) {
499
+ reportServiceRef.current = ReportService.getInstance();
500
+ }
456
501
  const exportToPDF = useCallback3(
457
502
  async (elementId, options) => {
503
+ if (!reportServiceRef.current) {
504
+ throw new Error("ReportService not initialized");
505
+ }
458
506
  setIsExporting(true);
459
507
  setError(null);
460
508
  try {
461
- await reportService.exportToPDF(elementId, options);
509
+ await reportServiceRef.current.exportToPDF(elementId, options);
462
510
  } catch (err) {
463
511
  const errorMessage = err instanceof Error ? err.message : "PDF export failed";
464
512
  setError(errorMessage);
@@ -467,14 +515,17 @@ function useReportExport() {
467
515
  setIsExporting(false);
468
516
  }
469
517
  },
470
- [reportService]
518
+ []
471
519
  );
472
520
  const exportCanvasToPDF = useCallback3(
473
521
  async (canvas, options) => {
522
+ if (!reportServiceRef.current) {
523
+ throw new Error("ReportService not initialized");
524
+ }
474
525
  setIsExporting(true);
475
526
  setError(null);
476
527
  try {
477
- await reportService.exportCanvasToPDF(canvas, options);
528
+ await reportServiceRef.current.exportCanvasToPDF(canvas, options);
478
529
  } catch (err) {
479
530
  const errorMessage = err instanceof Error ? err.message : "Canvas PDF export failed";
480
531
  setError(errorMessage);
@@ -483,14 +534,17 @@ function useReportExport() {
483
534
  setIsExporting(false);
484
535
  }
485
536
  },
486
- [reportService]
537
+ []
487
538
  );
488
539
  const exportMultipleToPDF = useCallback3(
489
540
  async (elementIds, options) => {
541
+ if (!reportServiceRef.current) {
542
+ throw new Error("ReportService not initialized");
543
+ }
490
544
  setIsExporting(true);
491
545
  setError(null);
492
546
  try {
493
- await reportService.exportMultipleToPDF(elementIds, options);
547
+ await reportServiceRef.current.exportMultipleToPDF(elementIds, options);
494
548
  } catch (err) {
495
549
  const errorMessage = err instanceof Error ? err.message : "Multi-page PDF export failed";
496
550
  setError(errorMessage);
@@ -499,14 +553,17 @@ function useReportExport() {
499
553
  setIsExporting(false);
500
554
  }
501
555
  },
502
- [reportService]
556
+ []
503
557
  );
504
558
  const exportToExcel = useCallback3(
505
559
  async (data, options) => {
560
+ if (!reportServiceRef.current) {
561
+ throw new Error("ReportService not initialized");
562
+ }
506
563
  setIsExporting(true);
507
564
  setError(null);
508
565
  try {
509
- await reportService.exportToExcel(data, options);
566
+ await reportServiceRef.current.exportToExcel(data, options);
510
567
  } catch (err) {
511
568
  const errorMessage = err instanceof Error ? err.message : "Excel export failed";
512
569
  setError(errorMessage);
@@ -515,19 +572,22 @@ function useReportExport() {
515
572
  setIsExporting(false);
516
573
  }
517
574
  },
518
- [reportService]
575
+ []
519
576
  );
520
577
  const exportToCSV = useCallback3(
521
578
  (data, filename, delimiter) => {
579
+ if (!reportServiceRef.current) {
580
+ throw new Error("ReportService not initialized");
581
+ }
522
582
  try {
523
- reportService.exportToCSV(data, filename, delimiter);
583
+ reportServiceRef.current.exportToCSV(data, filename, delimiter);
524
584
  } catch (err) {
525
585
  const errorMessage = err instanceof Error ? err.message : "CSV export failed";
526
586
  setError(errorMessage);
527
587
  throw err;
528
588
  }
529
589
  },
530
- [reportService]
590
+ []
531
591
  );
532
592
  return {
533
593
  isExporting,