@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 umituz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# @umituz/web-canvas
|
|
2
|
+
|
|
3
|
+
Comprehensive canvas and screenshot generation library with device frames, patterns, and PDF export.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Canvas-based screenshot generation
|
|
8
|
+
- Device frame templates (iOS, Android)
|
|
9
|
+
- PDF export functionality
|
|
10
|
+
- Pattern generation
|
|
11
|
+
- HTML2Canvas integration
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @umituz/web-canvas
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { generateScreenshot } from '@umituz/web-canvas';
|
|
23
|
+
|
|
24
|
+
const screenshot = await generateScreenshot({
|
|
25
|
+
url: 'https://example.com',
|
|
26
|
+
device: 'iphone-14-pro',
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## License
|
|
31
|
+
|
|
32
|
+
MIT
|
|
@@ -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
|
-
|
|
187
|
-
|
|
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
|
-
*
|
|
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.
|
|
310
|
-
ctx.arc(x, y,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
536
|
+
if (!this.allDevicesCache) {
|
|
537
|
+
this.allDevicesCache = Array.from(this.devices.values());
|
|
538
|
+
}
|
|
539
|
+
return this.allDevicesCache;
|
|
505
540
|
}
|
|
506
541
|
getPhones() {
|
|
507
|
-
|
|
542
|
+
if (!this.phonesCache) {
|
|
543
|
+
this.phonesCache = this.getAllDevices().filter((d) => d.isPhone);
|
|
544
|
+
}
|
|
545
|
+
return this.phonesCache;
|
|
508
546
|
}
|
|
509
547
|
getTablets() {
|
|
510
|
-
|
|
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
|
|
795
|
-
|
|
796
|
-
)
|
|
797
|
-
|
|
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
|
-
|
|
839
|
-
|
|
840
|
-
|
|
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_${
|
|
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
|
-
|
|
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-
|
|
5
|
-
import "../chunk-
|
|
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-
|
|
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
|
|
58
|
-
|
|
59
|
-
)
|
|
60
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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_${
|
|
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
|
-
|
|
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
|
|