@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.
- package/LICENSE +21 -0
- package/README.md +32 -0
- package/dist/application/index.d.mts +1 -1
- package/dist/application/index.d.ts +1 -1
- package/dist/application/index.js +103 -40
- package/dist/application/index.mjs +2 -2
- package/dist/{chunk-BN7JTXL7.mjs → chunk-DNKRSL6D.mjs} +24 -12
- package/dist/{chunk-SF6WND7M.mjs → chunk-LOVI2LTO.mjs} +88 -28
- package/dist/{chunk-WMKPCJFI.mjs → chunk-UOGTEMIJ.mjs} +80 -29
- package/dist/index.js +186 -63
- package/dist/index.mjs +3 -3
- package/dist/infrastructure/index.d.mts +30 -6
- package/dist/infrastructure/index.d.ts +30 -6
- package/dist/infrastructure/index.js +80 -29
- package/dist/infrastructure/index.mjs +1 -1
- package/dist/presentation/index.js +186 -63
- package/dist/presentation/index.mjs +3 -3
- package/package.json +14 -3
- package/src/application/services/ScreenshotStudioService.ts +35 -12
- package/src/infrastructure/renderers/CanvasRenderer.ts +30 -3
- package/src/infrastructure/renderers/PatternRenderer.ts +43 -23
- package/src/infrastructure/renderers/ScreenshotRenderer.ts +14 -1
- package/src/infrastructure/services/DeviceConfigService.ts +18 -3
- package/src/presentation/hooks/useCanvasExport.ts +48 -8
- package/src/presentation/hooks/useReportExport.ts +38 -12
- package/src/presentation/hooks/useScreenshotStudio.ts +35 -6
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ReportService,
|
|
3
3
|
ScreenshotStudioService
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-DNKRSL6D.mjs";
|
|
5
5
|
import {
|
|
6
6
|
CanvasRenderer
|
|
7
|
-
} from "./chunk-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
[
|
|
421
|
+
[]
|
|
386
422
|
);
|
|
387
423
|
const exportToDataURL = useCallback2(
|
|
388
424
|
(canvas, format, quality) => {
|
|
425
|
+
if (!rendererRef.current) return null;
|
|
389
426
|
try {
|
|
390
|
-
return
|
|
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
|
-
[
|
|
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
|
|
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
|
-
[
|
|
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
|
|
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
|
|
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
|
-
[
|
|
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
|
|
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
|
-
[
|
|
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
|
|
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
|
-
[
|
|
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
|
|
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
|
-
[
|
|
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
|
-
|
|
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
|
-
[
|
|
590
|
+
[]
|
|
531
591
|
);
|
|
532
592
|
return {
|
|
533
593
|
isExporting,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
var CanvasRenderer = class {
|
|
8
8
|
constructor(config = DEFAULT_CANVAS_CONFIG) {
|
|
9
9
|
this.config = { ...DEFAULT_CANVAS_CONFIG, ...config };
|
|
10
|
+
this.imageCache = /* @__PURE__ */ new Map();
|
|
10
11
|
}
|
|
11
12
|
/**
|
|
12
13
|
* Create a new canvas element
|
|
@@ -108,17 +109,37 @@ var CanvasRenderer = class {
|
|
|
108
109
|
}
|
|
109
110
|
}
|
|
110
111
|
/**
|
|
111
|
-
* Load image from URL
|
|
112
|
+
* Load image from URL with caching
|
|
112
113
|
*/
|
|
113
114
|
loadImage(url) {
|
|
115
|
+
if (this.imageCache.has(url)) {
|
|
116
|
+
return Promise.resolve(this.imageCache.get(url));
|
|
117
|
+
}
|
|
114
118
|
return new Promise((resolve, reject) => {
|
|
115
119
|
const img = new Image();
|
|
116
|
-
|
|
117
|
-
|
|
120
|
+
if (this.config.useCORS) {
|
|
121
|
+
img.crossOrigin = "anonymous";
|
|
122
|
+
}
|
|
123
|
+
img.onload = () => {
|
|
124
|
+
this.imageCache.set(url, img);
|
|
125
|
+
resolve(img);
|
|
126
|
+
};
|
|
118
127
|
img.onerror = () => reject(new Error(`Failed to load image: ${url}`));
|
|
119
128
|
img.src = url;
|
|
120
129
|
});
|
|
121
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Clear image cache to free memory
|
|
133
|
+
*/
|
|
134
|
+
clearImageCache() {
|
|
135
|
+
this.imageCache.clear();
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Get cache size for monitoring
|
|
139
|
+
*/
|
|
140
|
+
getCacheSize() {
|
|
141
|
+
return this.imageCache.size;
|
|
142
|
+
}
|
|
122
143
|
/**
|
|
123
144
|
* Create gradient
|
|
124
145
|
*/
|
|
@@ -200,10 +221,15 @@ var CanvasRenderer = class {
|
|
|
200
221
|
|
|
201
222
|
// src/infrastructure/renderers/PatternRenderer.ts
|
|
202
223
|
var PatternRenderer = class {
|
|
224
|
+
constructor() {
|
|
225
|
+
this.patternCanvasCache = /* @__PURE__ */ new Map();
|
|
226
|
+
this.MAX_CACHE_SIZE = 10;
|
|
227
|
+
}
|
|
203
228
|
/**
|
|
204
229
|
* Render pattern on canvas
|
|
205
230
|
*/
|
|
206
231
|
renderPattern(ctx, pattern, dimensions, color = "#000000") {
|
|
232
|
+
if (pattern === "none") return;
|
|
207
233
|
ctx.save();
|
|
208
234
|
ctx.globalAlpha = 0.1;
|
|
209
235
|
switch (pattern) {
|
|
@@ -222,62 +248,65 @@ var PatternRenderer = class {
|
|
|
222
248
|
case "checkers":
|
|
223
249
|
this.renderCheckerPattern(ctx, dimensions, color);
|
|
224
250
|
break;
|
|
225
|
-
case "none":
|
|
226
|
-
default:
|
|
227
|
-
break;
|
|
228
251
|
}
|
|
229
252
|
ctx.restore();
|
|
230
253
|
}
|
|
231
254
|
/**
|
|
232
|
-
*
|
|
255
|
+
* Clear pattern cache to free memory
|
|
256
|
+
*/
|
|
257
|
+
clearCache() {
|
|
258
|
+
this.patternCanvasCache.clear();
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Render dot pattern - optimized with batch rendering
|
|
233
262
|
*/
|
|
234
263
|
renderDotPattern(ctx, dimensions, color) {
|
|
235
264
|
ctx.fillStyle = color;
|
|
236
265
|
const spacing = 30;
|
|
266
|
+
const radius = 2;
|
|
267
|
+
ctx.beginPath();
|
|
237
268
|
for (let x = spacing; x < dimensions.width; x += spacing) {
|
|
238
269
|
for (let y = spacing; y < dimensions.height; y += spacing) {
|
|
239
|
-
ctx.
|
|
240
|
-
ctx.arc(x, y,
|
|
241
|
-
ctx.fill();
|
|
270
|
+
ctx.moveTo(x + radius, y);
|
|
271
|
+
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
|
242
272
|
}
|
|
243
273
|
}
|
|
274
|
+
ctx.fill();
|
|
244
275
|
}
|
|
245
276
|
/**
|
|
246
|
-
* Render grid pattern
|
|
277
|
+
* Render grid pattern - optimized with single path
|
|
247
278
|
*/
|
|
248
279
|
renderGridPattern(ctx, dimensions, color) {
|
|
249
280
|
ctx.strokeStyle = color;
|
|
250
281
|
ctx.lineWidth = 1;
|
|
251
282
|
const spacing = 30;
|
|
283
|
+
ctx.beginPath();
|
|
252
284
|
for (let x = 0; x <= dimensions.width; x += spacing) {
|
|
253
|
-
ctx.beginPath();
|
|
254
285
|
ctx.moveTo(x, 0);
|
|
255
286
|
ctx.lineTo(x, dimensions.height);
|
|
256
|
-
ctx.stroke();
|
|
257
287
|
}
|
|
258
288
|
for (let y = 0; y <= dimensions.height; y += spacing) {
|
|
259
|
-
ctx.beginPath();
|
|
260
289
|
ctx.moveTo(0, y);
|
|
261
290
|
ctx.lineTo(dimensions.width, y);
|
|
262
|
-
ctx.stroke();
|
|
263
291
|
}
|
|
292
|
+
ctx.stroke();
|
|
264
293
|
}
|
|
265
294
|
/**
|
|
266
|
-
* Render line pattern
|
|
295
|
+
* Render line pattern - optimized with single path
|
|
267
296
|
*/
|
|
268
297
|
renderLinePattern(ctx, dimensions, color) {
|
|
269
298
|
ctx.strokeStyle = color;
|
|
270
299
|
ctx.lineWidth = 2;
|
|
271
300
|
const spacing = 20;
|
|
301
|
+
ctx.beginPath();
|
|
272
302
|
for (let y = spacing; y < dimensions.height; y += spacing) {
|
|
273
|
-
ctx.beginPath();
|
|
274
303
|
ctx.moveTo(0, y);
|
|
275
304
|
ctx.lineTo(dimensions.width, y);
|
|
276
|
-
ctx.stroke();
|
|
277
305
|
}
|
|
306
|
+
ctx.stroke();
|
|
278
307
|
}
|
|
279
308
|
/**
|
|
280
|
-
* Render wave pattern
|
|
309
|
+
* Render wave pattern - optimized with pre-calculated values
|
|
281
310
|
*/
|
|
282
311
|
renderWavePattern(ctx, dimensions, color) {
|
|
283
312
|
ctx.strokeStyle = color;
|
|
@@ -285,15 +314,15 @@ var PatternRenderer = class {
|
|
|
285
314
|
const amplitude = 10;
|
|
286
315
|
const frequency = 0.1;
|
|
287
316
|
const spacing = 40;
|
|
317
|
+
const step = 5;
|
|
288
318
|
for (let y = spacing; y < dimensions.height; y += spacing) {
|
|
289
319
|
ctx.beginPath();
|
|
290
|
-
|
|
320
|
+
const startX = 0;
|
|
321
|
+
const startY = y + Math.sin(startX * frequency) * amplitude;
|
|
322
|
+
ctx.moveTo(startX, startY);
|
|
323
|
+
for (let x = step; x <= dimensions.width; x += step) {
|
|
291
324
|
const waveY = y + Math.sin(x * frequency) * amplitude;
|
|
292
|
-
|
|
293
|
-
ctx.moveTo(x, waveY);
|
|
294
|
-
} else {
|
|
295
|
-
ctx.lineTo(x, waveY);
|
|
296
|
-
}
|
|
325
|
+
ctx.lineTo(x, waveY);
|
|
297
326
|
}
|
|
298
327
|
ctx.stroke();
|
|
299
328
|
}
|
|
@@ -316,6 +345,9 @@ var PatternRenderer = class {
|
|
|
316
345
|
// src/infrastructure/services/DeviceConfigService.ts
|
|
317
346
|
var DeviceConfigService = class _DeviceConfigService {
|
|
318
347
|
constructor() {
|
|
348
|
+
this.phonesCache = null;
|
|
349
|
+
this.tabletsCache = null;
|
|
350
|
+
this.allDevicesCache = null;
|
|
319
351
|
this.devices = /* @__PURE__ */ new Map();
|
|
320
352
|
this.initializeDevices();
|
|
321
353
|
}
|
|
@@ -412,13 +444,22 @@ var DeviceConfigService = class _DeviceConfigService {
|
|
|
412
444
|
return this.devices.get(deviceType);
|
|
413
445
|
}
|
|
414
446
|
getAllDevices() {
|
|
415
|
-
|
|
447
|
+
if (!this.allDevicesCache) {
|
|
448
|
+
this.allDevicesCache = Array.from(this.devices.values());
|
|
449
|
+
}
|
|
450
|
+
return this.allDevicesCache;
|
|
416
451
|
}
|
|
417
452
|
getPhones() {
|
|
418
|
-
|
|
453
|
+
if (!this.phonesCache) {
|
|
454
|
+
this.phonesCache = this.getAllDevices().filter((d) => d.isPhone);
|
|
455
|
+
}
|
|
456
|
+
return this.phonesCache;
|
|
419
457
|
}
|
|
420
458
|
getTablets() {
|
|
421
|
-
|
|
459
|
+
if (!this.tabletsCache) {
|
|
460
|
+
this.tabletsCache = this.getAllDevices().filter((d) => d.isTablet);
|
|
461
|
+
}
|
|
462
|
+
return this.tabletsCache;
|
|
422
463
|
}
|
|
423
464
|
isPhone(deviceType) {
|
|
424
465
|
const config = this.getDeviceConfig(deviceType);
|
|
@@ -437,10 +478,20 @@ var DeviceConfigService = class _DeviceConfigService {
|
|
|
437
478
|
// src/infrastructure/renderers/ScreenshotRenderer.ts
|
|
438
479
|
var ScreenshotRenderer = class {
|
|
439
480
|
constructor() {
|
|
481
|
+
this.renderCache = /* @__PURE__ */ new Map();
|
|
482
|
+
this.MAX_CACHE_SIZE = 5;
|
|
440
483
|
this.canvasRenderer = new CanvasRenderer();
|
|
441
484
|
this.patternRenderer = new PatternRenderer();
|
|
442
485
|
this.deviceConfigService = DeviceConfigService.getInstance();
|
|
443
486
|
}
|
|
487
|
+
/**
|
|
488
|
+
* Clear render cache to free memory
|
|
489
|
+
*/
|
|
490
|
+
clearCache() {
|
|
491
|
+
this.renderCache.clear();
|
|
492
|
+
this.canvasRenderer.clearImageCache?.();
|
|
493
|
+
this.patternRenderer.clearCache?.();
|
|
494
|
+
}
|
|
444
495
|
/**
|
|
445
496
|
* Render device frame
|
|
446
497
|
*/
|
|
@@ -492,7 +543,7 @@ var ScreenshotRenderer = class {
|
|
|
492
543
|
ctx.fill();
|
|
493
544
|
}
|
|
494
545
|
/**
|
|
495
|
-
* Render background
|
|
546
|
+
* Render background - optimized
|
|
496
547
|
*/
|
|
497
548
|
renderBackground(ctx, design, dimensions) {
|
|
498
549
|
if (design.backgroundGradientEnd) {
|