@umituz/web-canvas 1.0.0

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 (55) hide show
  1. package/dist/CanvasConfig-pzoE0RHD.d.mts +310 -0
  2. package/dist/CanvasConfig-pzoE0RHD.d.ts +310 -0
  3. package/dist/application/index.d.mts +78 -0
  4. package/dist/application/index.d.ts +78 -0
  5. package/dist/application/index.js +1083 -0
  6. package/dist/application/index.mjs +11 -0
  7. package/dist/chunk-BN7JTXL7.mjs +346 -0
  8. package/dist/chunk-MC6PNV2F.mjs +34 -0
  9. package/dist/chunk-SF6WND7M.mjs +547 -0
  10. package/dist/chunk-TZGTTYTZ.mjs +0 -0
  11. package/dist/chunk-WMKPCJFI.mjs +661 -0
  12. package/dist/chunk-Z6IB4BQV.mjs +31 -0
  13. package/dist/domain/config/index.d.mts +87 -0
  14. package/dist/domain/config/index.d.ts +87 -0
  15. package/dist/domain/config/index.js +92 -0
  16. package/dist/domain/config/index.mjs +15 -0
  17. package/dist/domain/index.d.mts +79 -0
  18. package/dist/domain/index.d.ts +79 -0
  19. package/dist/domain/index.js +137 -0
  20. package/dist/domain/index.mjs +55 -0
  21. package/dist/index.d.mts +6 -0
  22. package/dist/index.d.ts +6 -0
  23. package/dist/index.js +1633 -0
  24. package/dist/index.mjs +38 -0
  25. package/dist/infrastructure/index.d.mts +174 -0
  26. package/dist/infrastructure/index.d.ts +174 -0
  27. package/dist/infrastructure/index.js +714 -0
  28. package/dist/infrastructure/index.mjs +13 -0
  29. package/dist/presentation/index.d.mts +108 -0
  30. package/dist/presentation/index.d.ts +108 -0
  31. package/dist/presentation/index.js +1617 -0
  32. package/dist/presentation/index.mjs +14 -0
  33. package/package.json +86 -0
  34. package/src/application/index.ts +7 -0
  35. package/src/application/services/ReportService.ts +200 -0
  36. package/src/application/services/ScreenshotStudioService.ts +243 -0
  37. package/src/domain/config/CanvasConfig.ts +36 -0
  38. package/src/domain/config/DeviceConfig.ts +47 -0
  39. package/src/domain/config/ScreenshotStudioConfig.ts +82 -0
  40. package/src/domain/config/index.ts +8 -0
  41. package/src/domain/index.ts +7 -0
  42. package/src/domain/types/canvas.types.ts +85 -0
  43. package/src/domain/types/index.ts +8 -0
  44. package/src/domain/types/report.types.ts +83 -0
  45. package/src/domain/types/screenshot.types.ts +238 -0
  46. package/src/index.ts +102 -0
  47. package/src/infrastructure/index.ts +9 -0
  48. package/src/infrastructure/renderers/CanvasRenderer.ts +287 -0
  49. package/src/infrastructure/renderers/PatternRenderer.ts +160 -0
  50. package/src/infrastructure/renderers/ScreenshotRenderer.ts +331 -0
  51. package/src/infrastructure/services/DeviceConfigService.ts +144 -0
  52. package/src/presentation/hooks/useCanvasExport.ts +162 -0
  53. package/src/presentation/hooks/useReportExport.ts +161 -0
  54. package/src/presentation/hooks/useScreenshotStudio.ts +480 -0
  55. package/src/presentation/index.ts +8 -0
@@ -0,0 +1,1083 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/application/index.ts
31
+ var application_exports = {};
32
+ __export(application_exports, {
33
+ ReportService: () => ReportService,
34
+ ScreenshotStudioService: () => ScreenshotStudioService
35
+ });
36
+ module.exports = __toCommonJS(application_exports);
37
+
38
+ // src/domain/config/ScreenshotStudioConfig.ts
39
+ var DEFAULT_SCREENSHOT_STUDIO_CONFIG = {
40
+ canvas: {
41
+ defaultFormat: "png",
42
+ defaultQuality: 0.9,
43
+ scale: 2,
44
+ useCORS: true
45
+ },
46
+ device: {
47
+ devices: ["iphone-15-pro"],
48
+ defaultDevice: "iphone-15-pro",
49
+ showFrame: true,
50
+ frameColor: "#1f2937",
51
+ frameThickness: 8
52
+ },
53
+ export: {
54
+ includeMetadata: true,
55
+ folderStructure: "by-language",
56
+ defaultFormat: "png"
57
+ },
58
+ ai: {
59
+ enabled: true,
60
+ provider: "gemini"
61
+ },
62
+ languages: ["en-US", "tr-TR", "de-DE", "fr-FR", "es-ES", "ja-JP"],
63
+ defaultLanguage: "en-US"
64
+ };
65
+
66
+ // src/domain/config/CanvasConfig.ts
67
+ var DEFAULT_CANVAS_CONFIG = {
68
+ defaultFormat: "png",
69
+ defaultQuality: 0.9,
70
+ maxDimension: 4096,
71
+ scale: 2,
72
+ backgroundColor: "#ffffff",
73
+ useCORS: true
74
+ };
75
+
76
+ // src/infrastructure/renderers/CanvasRenderer.ts
77
+ var CanvasRenderer = class {
78
+ constructor(config = DEFAULT_CANVAS_CONFIG) {
79
+ this.config = { ...DEFAULT_CANVAS_CONFIG, ...config };
80
+ }
81
+ /**
82
+ * Create a new canvas element
83
+ */
84
+ createCanvas(dimensions) {
85
+ const canvas = document.createElement("canvas");
86
+ canvas.width = dimensions.width;
87
+ canvas.height = dimensions.height;
88
+ return canvas;
89
+ }
90
+ /**
91
+ * Get canvas 2D context
92
+ */
93
+ getContext(canvas) {
94
+ return canvas.getContext("2d");
95
+ }
96
+ /**
97
+ * Apply context configuration
98
+ */
99
+ configureContext(ctx, config) {
100
+ if (config.fillStyle !== void 0) {
101
+ ctx.fillStyle = config.fillStyle;
102
+ }
103
+ if (config.strokeStyle !== void 0) {
104
+ ctx.strokeStyle = config.strokeStyle;
105
+ }
106
+ if (config.lineWidth !== void 0) {
107
+ ctx.lineWidth = config.lineWidth;
108
+ }
109
+ if (config.lineCap !== void 0) {
110
+ ctx.lineCap = config.lineCap;
111
+ }
112
+ if (config.lineJoin !== void 0) {
113
+ ctx.lineJoin = config.lineJoin;
114
+ }
115
+ if (config.globalAlpha !== void 0) {
116
+ ctx.globalAlpha = config.globalAlpha;
117
+ }
118
+ if (config.font !== void 0) {
119
+ ctx.font = config.font;
120
+ }
121
+ if (config.textAlign !== void 0) {
122
+ ctx.textAlign = config.textAlign;
123
+ }
124
+ if (config.textBaseline !== void 0) {
125
+ ctx.textBaseline = config.textBaseline;
126
+ }
127
+ }
128
+ /**
129
+ * Clear canvas
130
+ */
131
+ clearCanvas(ctx, dimensions) {
132
+ ctx.clearRect(0, 0, dimensions.width, dimensions.height);
133
+ }
134
+ /**
135
+ * Fill rectangle
136
+ */
137
+ fillRect(ctx, x, y, width, height, color) {
138
+ if (color) {
139
+ ctx.fillStyle = color;
140
+ }
141
+ ctx.fillRect(x, y, width, height);
142
+ }
143
+ /**
144
+ * Stroke rectangle
145
+ */
146
+ strokeRect(ctx, x, y, width, height, color, lineWidth) {
147
+ if (color) {
148
+ ctx.strokeStyle = color;
149
+ }
150
+ if (lineWidth !== void 0) {
151
+ ctx.lineWidth = lineWidth;
152
+ }
153
+ ctx.strokeRect(x, y, width, height);
154
+ }
155
+ /**
156
+ * Draw text
157
+ */
158
+ drawText(ctx, text, x, y, config) {
159
+ if (config) {
160
+ this.configureContext(ctx, config);
161
+ }
162
+ ctx.fillText(text, x, y);
163
+ }
164
+ /**
165
+ * Draw image
166
+ */
167
+ async drawImage(ctx, imageSource, x, y, width, height) {
168
+ let img;
169
+ if (typeof imageSource === "string") {
170
+ img = await this.loadImage(imageSource);
171
+ } else {
172
+ img = imageSource;
173
+ }
174
+ if (width && height) {
175
+ ctx.drawImage(img, x, y, width, height);
176
+ } else {
177
+ ctx.drawImage(img, x, y);
178
+ }
179
+ }
180
+ /**
181
+ * Load image from URL
182
+ */
183
+ loadImage(url) {
184
+ return new Promise((resolve, reject) => {
185
+ const img = new Image();
186
+ img.crossOrigin = this.config.useCORS ? "anonymous" : void 0;
187
+ img.onload = () => resolve(img);
188
+ img.onerror = () => reject(new Error(`Failed to load image: ${url}`));
189
+ img.src = url;
190
+ });
191
+ }
192
+ /**
193
+ * Create gradient
194
+ */
195
+ createLinearGradient(ctx, x1, y1, x2, y2, colorStops) {
196
+ const gradient = ctx.createLinearGradient(x1, y1, x2, y2);
197
+ colorStops.forEach(({ offset, color }) => {
198
+ gradient.addColorStop(offset, color);
199
+ });
200
+ return gradient;
201
+ }
202
+ /**
203
+ * Export canvas to blob
204
+ */
205
+ async exportToBlob(canvas, options = {}) {
206
+ const format = options.format || this.config.defaultFormat;
207
+ const quality = options.quality || this.config.defaultQuality;
208
+ const scale = options.scale || this.config.scale;
209
+ const scaledCanvas = (scale || 1) !== 1 ? this.scaleCanvas(canvas, scale || 1) : canvas;
210
+ const mimeType = `image/${format || "png"}`;
211
+ const blob = await new Promise((resolve) => {
212
+ scaledCanvas.toBlob(
213
+ (result) => resolve(result),
214
+ mimeType,
215
+ quality
216
+ );
217
+ });
218
+ if (!blob) {
219
+ throw new Error("Failed to export canvas to blob");
220
+ }
221
+ const url = URL.createObjectURL(blob);
222
+ return {
223
+ blob,
224
+ url,
225
+ dimensions: {
226
+ width: scaledCanvas.width,
227
+ height: scaledCanvas.height
228
+ },
229
+ format: format || "png",
230
+ size: blob.size
231
+ };
232
+ }
233
+ /**
234
+ * Export canvas to data URL
235
+ */
236
+ exportToDataURL(canvas, format, quality) {
237
+ const mimeType = format || `image/${this.config.defaultFormat}`;
238
+ const q = quality || this.config.defaultQuality;
239
+ return canvas.toDataURL(mimeType, q);
240
+ }
241
+ /**
242
+ * Scale canvas
243
+ */
244
+ scaleCanvas(canvas, scale) {
245
+ const scaled = document.createElement("canvas");
246
+ scaled.width = canvas.width * scale;
247
+ scaled.height = canvas.height * scale;
248
+ const ctx = scaled.getContext("2d");
249
+ if (ctx) {
250
+ ctx.imageSmoothingEnabled = true;
251
+ ctx.imageSmoothingQuality = "high";
252
+ ctx.drawImage(canvas, 0, 0, scaled.width, scaled.height);
253
+ }
254
+ return scaled;
255
+ }
256
+ /**
257
+ * Clone canvas
258
+ */
259
+ cloneCanvas(canvas) {
260
+ const clone = document.createElement("canvas");
261
+ clone.width = canvas.width;
262
+ clone.height = canvas.height;
263
+ const ctx = clone.getContext("2d");
264
+ if (ctx) {
265
+ ctx.drawImage(canvas, 0, 0);
266
+ }
267
+ return clone;
268
+ }
269
+ };
270
+
271
+ // src/infrastructure/renderers/PatternRenderer.ts
272
+ var PatternRenderer = class {
273
+ /**
274
+ * Render pattern on canvas
275
+ */
276
+ renderPattern(ctx, pattern, dimensions, color = "#000000") {
277
+ ctx.save();
278
+ ctx.globalAlpha = 0.1;
279
+ switch (pattern) {
280
+ case "dots":
281
+ this.renderDotPattern(ctx, dimensions, color);
282
+ break;
283
+ case "grid":
284
+ this.renderGridPattern(ctx, dimensions, color);
285
+ break;
286
+ case "lines":
287
+ this.renderLinePattern(ctx, dimensions, color);
288
+ break;
289
+ case "waves":
290
+ this.renderWavePattern(ctx, dimensions, color);
291
+ break;
292
+ case "checkers":
293
+ this.renderCheckerPattern(ctx, dimensions, color);
294
+ break;
295
+ case "none":
296
+ default:
297
+ break;
298
+ }
299
+ ctx.restore();
300
+ }
301
+ /**
302
+ * Render dot pattern
303
+ */
304
+ renderDotPattern(ctx, dimensions, color) {
305
+ ctx.fillStyle = color;
306
+ const spacing = 30;
307
+ for (let x = spacing; x < dimensions.width; x += spacing) {
308
+ 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();
312
+ }
313
+ }
314
+ }
315
+ /**
316
+ * Render grid pattern
317
+ */
318
+ renderGridPattern(ctx, dimensions, color) {
319
+ ctx.strokeStyle = color;
320
+ ctx.lineWidth = 1;
321
+ const spacing = 30;
322
+ for (let x = 0; x <= dimensions.width; x += spacing) {
323
+ ctx.beginPath();
324
+ ctx.moveTo(x, 0);
325
+ ctx.lineTo(x, dimensions.height);
326
+ ctx.stroke();
327
+ }
328
+ for (let y = 0; y <= dimensions.height; y += spacing) {
329
+ ctx.beginPath();
330
+ ctx.moveTo(0, y);
331
+ ctx.lineTo(dimensions.width, y);
332
+ ctx.stroke();
333
+ }
334
+ }
335
+ /**
336
+ * Render line pattern
337
+ */
338
+ renderLinePattern(ctx, dimensions, color) {
339
+ ctx.strokeStyle = color;
340
+ ctx.lineWidth = 2;
341
+ const spacing = 20;
342
+ for (let y = spacing; y < dimensions.height; y += spacing) {
343
+ ctx.beginPath();
344
+ ctx.moveTo(0, y);
345
+ ctx.lineTo(dimensions.width, y);
346
+ ctx.stroke();
347
+ }
348
+ }
349
+ /**
350
+ * Render wave pattern
351
+ */
352
+ renderWavePattern(ctx, dimensions, color) {
353
+ ctx.strokeStyle = color;
354
+ ctx.lineWidth = 2;
355
+ const amplitude = 10;
356
+ const frequency = 0.1;
357
+ const spacing = 40;
358
+ for (let y = spacing; y < dimensions.height; y += spacing) {
359
+ ctx.beginPath();
360
+ for (let x = 0; x <= dimensions.width; x += 5) {
361
+ 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
+ }
367
+ }
368
+ ctx.stroke();
369
+ }
370
+ }
371
+ /**
372
+ * Render checker pattern
373
+ */
374
+ renderCheckerPattern(ctx, dimensions, color) {
375
+ ctx.fillStyle = color;
376
+ const size = 20;
377
+ for (let x = 0; x < dimensions.width; x += size * 2) {
378
+ for (let y = 0; y < dimensions.height; y += size * 2) {
379
+ ctx.fillRect(x, y, size, size);
380
+ ctx.fillRect(x + size, y + size, size, size);
381
+ }
382
+ }
383
+ }
384
+ };
385
+
386
+ // src/domain/config/DeviceConfig.ts
387
+ var DEVICE_DIMENSIONS = {
388
+ "iphone-15-pro": { width: 1290, height: 2796 },
389
+ "iphone-15": { width: 1179, height: 2556 },
390
+ "iphone-se": { width: 1080, height: 2340 },
391
+ "ipad-pro": { width: 2048, height: 2732 },
392
+ "ipad": { width: 1620, height: 2160 },
393
+ "android-phone": { width: 1440, height: 3120 },
394
+ "android-tablet": { width: 2e3, height: 2200 },
395
+ "desktop": { width: 1920, height: 1080 }
396
+ };
397
+ var DEFAULT_DEVICE_CONFIG = {
398
+ devices: Object.keys(DEVICE_DIMENSIONS),
399
+ defaultDevice: "iphone-15-pro",
400
+ showFrame: true,
401
+ frameColor: "#1f2937",
402
+ frameThickness: 8
403
+ };
404
+
405
+ // src/infrastructure/services/DeviceConfigService.ts
406
+ var DeviceConfigService = class _DeviceConfigService {
407
+ constructor() {
408
+ this.devices = /* @__PURE__ */ new Map();
409
+ this.initializeDevices();
410
+ }
411
+ static getInstance() {
412
+ if (!_DeviceConfigService.instance) {
413
+ _DeviceConfigService.instance = new _DeviceConfigService();
414
+ }
415
+ return _DeviceConfigService.instance;
416
+ }
417
+ initializeDevices() {
418
+ const deviceConfigs = [
419
+ {
420
+ name: "iPhone 15 Pro",
421
+ dimensions: { width: 1290, height: 2796, scale: 3 },
422
+ isPhone: true,
423
+ hasNotch: true,
424
+ cornerRadius: 20,
425
+ frameThickness: 8
426
+ },
427
+ {
428
+ name: "iPhone 15",
429
+ dimensions: { width: 1179, height: 2556, scale: 3 },
430
+ isPhone: true,
431
+ hasNotch: true,
432
+ cornerRadius: 20,
433
+ frameThickness: 8
434
+ },
435
+ {
436
+ name: "iPhone SE",
437
+ dimensions: { width: 1080, height: 2340, scale: 3 },
438
+ isPhone: true,
439
+ hasNotch: false,
440
+ cornerRadius: 16,
441
+ frameThickness: 6
442
+ },
443
+ {
444
+ name: "iPad Pro",
445
+ dimensions: { width: 2048, height: 2732, scale: 2 },
446
+ isTablet: true,
447
+ hasNotch: false,
448
+ cornerRadius: 24,
449
+ frameThickness: 10
450
+ },
451
+ {
452
+ name: "iPad",
453
+ dimensions: { width: 1620, height: 2160, scale: 2 },
454
+ isTablet: true,
455
+ hasNotch: false,
456
+ cornerRadius: 20,
457
+ frameThickness: 10
458
+ },
459
+ {
460
+ name: "Android Phone",
461
+ dimensions: { width: 1440, height: 3120, scale: 3 },
462
+ isPhone: true,
463
+ hasNotch: false,
464
+ cornerRadius: 24,
465
+ frameThickness: 8
466
+ },
467
+ {
468
+ name: "Android Tablet",
469
+ dimensions: { width: 2e3, height: 2200, scale: 2 },
470
+ isTablet: true,
471
+ hasNotch: false,
472
+ cornerRadius: 24,
473
+ frameThickness: 10
474
+ },
475
+ {
476
+ name: "Desktop",
477
+ dimensions: { width: 1920, height: 1080, scale: 1 },
478
+ isPhone: false,
479
+ isTablet: false,
480
+ hasNotch: false,
481
+ cornerRadius: 0,
482
+ frameThickness: 0
483
+ }
484
+ ];
485
+ Object.entries(DEVICE_DIMENSIONS).forEach(([type, dimensions]) => {
486
+ const deviceType = type;
487
+ const config = deviceConfigs.find((_, index) => {
488
+ const types = Object.keys(DEVICE_DIMENSIONS);
489
+ return types[index] === type;
490
+ });
491
+ if (config) {
492
+ this.devices.set(deviceType, {
493
+ type: deviceType,
494
+ ...config,
495
+ dimensions
496
+ });
497
+ }
498
+ });
499
+ }
500
+ getDeviceConfig(deviceType) {
501
+ return this.devices.get(deviceType);
502
+ }
503
+ getAllDevices() {
504
+ return Array.from(this.devices.values());
505
+ }
506
+ getPhones() {
507
+ return this.getAllDevices().filter((d) => d.isPhone);
508
+ }
509
+ getTablets() {
510
+ return this.getAllDevices().filter((d) => d.isTablet);
511
+ }
512
+ isPhone(deviceType) {
513
+ const config = this.getDeviceConfig(deviceType);
514
+ return config?.isPhone || false;
515
+ }
516
+ isTablet(deviceType) {
517
+ const config = this.getDeviceConfig(deviceType);
518
+ return config?.isTablet || false;
519
+ }
520
+ hasNotch(deviceType) {
521
+ const config = this.getDeviceConfig(deviceType);
522
+ return config?.hasNotch || false;
523
+ }
524
+ };
525
+
526
+ // src/infrastructure/renderers/ScreenshotRenderer.ts
527
+ var ScreenshotRenderer = class {
528
+ constructor() {
529
+ this.canvasRenderer = new CanvasRenderer();
530
+ this.patternRenderer = new PatternRenderer();
531
+ this.deviceConfigService = DeviceConfigService.getInstance();
532
+ }
533
+ /**
534
+ * Render device frame
535
+ */
536
+ renderDeviceFrame(ctx, dimensions, deviceType, frameStyle, frameColor) {
537
+ const deviceConfig = this.deviceConfigService.getDeviceConfig(deviceType);
538
+ if (!deviceConfig) return;
539
+ if (frameStyle === "none") return;
540
+ const isPhone = this.deviceConfigService.isPhone(deviceType);
541
+ const isTablet = this.deviceConfigService.isTablet(deviceType);
542
+ if (!isPhone && !isTablet) return;
543
+ const frameThickness = deviceConfig.frameThickness || 8;
544
+ if (frameStyle === "realistic") {
545
+ this.renderRealisticFrame(ctx, dimensions, frameColor, frameThickness);
546
+ if (isPhone && this.deviceConfigService.hasNotch(deviceType)) {
547
+ this.renderNotch(ctx, dimensions);
548
+ }
549
+ } else if (frameStyle === "minimal") {
550
+ this.renderMinimalFrame(ctx, dimensions, frameColor, frameThickness);
551
+ }
552
+ }
553
+ /**
554
+ * Render realistic device frame
555
+ */
556
+ renderRealisticFrame(ctx, dimensions, color, thickness) {
557
+ ctx.strokeStyle = color;
558
+ ctx.lineWidth = thickness;
559
+ ctx.strokeRect(
560
+ thickness / 2,
561
+ thickness / 2,
562
+ dimensions.width - thickness,
563
+ dimensions.height - thickness
564
+ );
565
+ }
566
+ /**
567
+ * Render minimal device frame
568
+ */
569
+ renderMinimalFrame(ctx, dimensions, color, thickness) {
570
+ ctx.strokeStyle = color;
571
+ ctx.lineWidth = thickness;
572
+ ctx.strokeRect(0, 0, dimensions.width, dimensions.height);
573
+ }
574
+ /**
575
+ * Render device notch
576
+ */
577
+ renderNotch(ctx, dimensions) {
578
+ ctx.fillStyle = "#1f2937";
579
+ ctx.beginPath();
580
+ ctx.arc(dimensions.width / 2, 20, 10, 0, Math.PI * 2);
581
+ ctx.fill();
582
+ }
583
+ /**
584
+ * Render background
585
+ */
586
+ renderBackground(ctx, design, dimensions) {
587
+ if (design.backgroundGradientEnd) {
588
+ const gradient = this.canvasRenderer.createLinearGradient(
589
+ ctx,
590
+ 0,
591
+ 0,
592
+ dimensions.width,
593
+ dimensions.height,
594
+ [
595
+ { offset: 0, color: design.backgroundColor },
596
+ { offset: 1, color: design.backgroundGradientEnd }
597
+ ]
598
+ );
599
+ ctx.fillStyle = gradient;
600
+ } else {
601
+ ctx.fillStyle = design.backgroundColor;
602
+ }
603
+ ctx.fillRect(0, 0, dimensions.width, dimensions.height);
604
+ if (design.backgroundPattern && design.backgroundPattern !== "none") {
605
+ this.patternRenderer.renderPattern(
606
+ ctx,
607
+ design.backgroundPattern,
608
+ dimensions
609
+ );
610
+ }
611
+ }
612
+ /**
613
+ * Render text content
614
+ */
615
+ renderTextContent(ctx, content, design, dimensions) {
616
+ const titleConfig = {
617
+ fillStyle: design.textColor,
618
+ font: `bold ${design.titleFontSize}px ${design.fontFamily}`,
619
+ textAlign: design.textAlign
620
+ };
621
+ const subtitleConfig = {
622
+ fillStyle: design.textColor,
623
+ font: `${design.subtitleFontSize}px ${design.fontFamily}`,
624
+ textAlign: design.textAlign
625
+ };
626
+ const titleY = design.layout === "text-bottom" ? dimensions.height - 150 : 100 + (design.offsetY || 0);
627
+ this.canvasRenderer.drawText(
628
+ ctx,
629
+ this.formatText(content.title, design.textCase),
630
+ dimensions.width / 2,
631
+ titleY,
632
+ titleConfig
633
+ );
634
+ const subtitleY = titleY + design.titleFontSize + (design.gap || 20);
635
+ this.canvasRenderer.drawText(
636
+ ctx,
637
+ this.formatText(content.subtitle, design.textCase),
638
+ dimensions.width / 2,
639
+ subtitleY,
640
+ subtitleConfig
641
+ );
642
+ }
643
+ /**
644
+ * Format text case
645
+ */
646
+ formatText(text, textCase = "none") {
647
+ switch (textCase) {
648
+ case "uppercase":
649
+ return text.toUpperCase();
650
+ case "lowercase":
651
+ return text.toLowerCase();
652
+ default:
653
+ return text;
654
+ }
655
+ }
656
+ /**
657
+ * Render screenshot image
658
+ */
659
+ async renderScreenshotImage(ctx, imageUrl, design, dimensions) {
660
+ if (!imageUrl) return;
661
+ const deviceConfig = this.deviceConfigService.getDeviceConfig(
662
+ design.deviceType
663
+ );
664
+ if (!deviceConfig) return;
665
+ const frameThickness = deviceConfig.frameThickness || 8;
666
+ const padding = 60 + frameThickness;
667
+ const screenshotWidth = dimensions.width - padding * 2;
668
+ const screenshotHeight = dimensions.height - padding * 2;
669
+ try {
670
+ await this.canvasRenderer.drawImage(
671
+ ctx,
672
+ imageUrl,
673
+ padding,
674
+ padding,
675
+ screenshotWidth,
676
+ screenshotHeight
677
+ );
678
+ } catch (error) {
679
+ console.error("Failed to render screenshot image:", error);
680
+ }
681
+ }
682
+ /**
683
+ * Render complete screenshot
684
+ */
685
+ async renderDesignedScreenshot(content, design) {
686
+ const deviceConfig = this.deviceConfigService.getDeviceConfig(design.deviceType);
687
+ if (!deviceConfig) {
688
+ throw new Error(`Device config not found: ${design.deviceType}`);
689
+ }
690
+ const dimensions = deviceConfig.dimensions;
691
+ const canvas = this.canvasRenderer.createCanvas(dimensions);
692
+ const ctx = canvas.getContext("2d");
693
+ if (!ctx) {
694
+ throw new Error("Failed to get canvas context");
695
+ }
696
+ this.renderBackground(ctx, design, dimensions);
697
+ if (content.screenshot) {
698
+ await this.renderScreenshotImage(ctx, content.screenshot, design, dimensions);
699
+ }
700
+ this.renderTextContent(ctx, content, design, dimensions);
701
+ this.renderDeviceFrame(
702
+ ctx,
703
+ dimensions,
704
+ design.deviceType,
705
+ design.frameStyle,
706
+ design.deviceColor
707
+ );
708
+ return canvas;
709
+ }
710
+ /**
711
+ * Render placeholder content
712
+ */
713
+ renderPlaceholderContent(ctx, dimensions, deviceType) {
714
+ this.canvasRenderer.fillRect(ctx, 0, 0, dimensions.width, dimensions.height, "#ffffff");
715
+ this.canvasRenderer.fillRect(ctx, 0, 0, dimensions.width, 60, "#f3f4f6");
716
+ this.canvasRenderer.fillRect(
717
+ ctx,
718
+ 0,
719
+ dimensions.height - 80,
720
+ dimensions.width,
721
+ 80,
722
+ "#f3f4f6"
723
+ );
724
+ ctx.fillStyle = "#e5e7eb";
725
+ for (let i = 0; i < 5; i++) {
726
+ const y = 80 + i * 120;
727
+ const height = Math.min(100, dimensions.height - y - 100);
728
+ if (y < dimensions.height - 100) {
729
+ this.canvasRenderer.fillRect(ctx, 20, y, dimensions.width - 40, height);
730
+ }
731
+ }
732
+ const deviceConfig = this.deviceConfigService.getDeviceConfig(deviceType);
733
+ if (deviceConfig) {
734
+ this.renderDeviceFrame(
735
+ ctx,
736
+ dimensions,
737
+ deviceType,
738
+ "realistic",
739
+ "#1f2937"
740
+ );
741
+ }
742
+ }
743
+ };
744
+
745
+ // src/application/services/ScreenshotStudioService.ts
746
+ var ScreenshotStudioService = class {
747
+ constructor(config) {
748
+ this.config = {
749
+ ...DEFAULT_SCREENSHOT_STUDIO_CONFIG,
750
+ ...config
751
+ };
752
+ this.renderer = new ScreenshotRenderer();
753
+ }
754
+ /**
755
+ * Generate screenshot
756
+ */
757
+ async generateScreenshot(options) {
758
+ const deviceConfig = this.config.device?.devices.find(
759
+ (d) => d === options.deviceType
760
+ );
761
+ if (!deviceConfig) {
762
+ throw new Error(`Device configuration not found for ${options.deviceType}`);
763
+ }
764
+ const canvas = this.renderer["canvasRenderer"].createCanvas(
765
+ options.customDimensions || { width: 1290, height: 2796 }
766
+ );
767
+ const ctx = canvas.getContext("2d");
768
+ if (!ctx) {
769
+ throw new Error("Failed to get canvas context");
770
+ }
771
+ this.renderer["renderPlaceholderContent"](
772
+ ctx,
773
+ { width: canvas.width, height: canvas.height },
774
+ options.deviceType
775
+ );
776
+ const blob = await this.renderer["canvasRenderer"].exportToBlob(canvas, {
777
+ format: options.format || this.config.export?.defaultFormat || "png",
778
+ quality: options.quality || this.config.canvas?.defaultQuality || 0.9
779
+ });
780
+ return {
781
+ id: this.generateId(),
782
+ deviceType: options.deviceType,
783
+ dimensions: { width: canvas.width, height: canvas.height },
784
+ format: options.format || this.config.export?.defaultFormat || "png",
785
+ timestamp: /* @__PURE__ */ new Date(),
786
+ fileSize: blob.blob.size,
787
+ url: blob.url
788
+ };
789
+ }
790
+ /**
791
+ * Generate batch screenshots
792
+ */
793
+ async generateBatchScreenshots(deviceTypes) {
794
+ const promises = deviceTypes.map(
795
+ (deviceType) => this.generateScreenshot({ deviceType })
796
+ );
797
+ return Promise.all(promises);
798
+ }
799
+ /**
800
+ * Generate designed screenshot
801
+ */
802
+ async generateDesignedScreenshot(content, design) {
803
+ const canvas = await this.renderer.renderDesignedScreenshot(content, design);
804
+ const blob = await this.renderer["canvasRenderer"].exportToBlob(canvas, {
805
+ format: "png"
806
+ });
807
+ return {
808
+ id: this.generateId(),
809
+ deviceType: design.deviceType,
810
+ dimensions: { width: canvas.width, height: canvas.height },
811
+ format: "png",
812
+ timestamp: /* @__PURE__ */ new Date(),
813
+ fileSize: blob.blob.size,
814
+ url: blob.url
815
+ };
816
+ }
817
+ /**
818
+ * Export project as ZIP
819
+ */
820
+ async exportProjectAsZip(project) {
821
+ const JSZip = (await import("jszip")).default;
822
+ const zip = new JSZip();
823
+ const folder = zip.folder(project.currentLanguage);
824
+ if (!folder) {
825
+ throw new Error("Failed to create folder in ZIP");
826
+ }
827
+ if (this.config.export?.includeMetadata) {
828
+ const metadataText = `APP NAME: ${project.appMetadata.appName}
829
+ SUBTITLE: ${project.appMetadata.subtitle}
830
+ DESCRIPTION:
831
+ ${project.appMetadata.description}
832
+
833
+ KEYWORDS: ${project.appMetadata.keywords}
834
+ WHATS NEW:
835
+ ${project.appMetadata.whatsNew}`;
836
+ folder.file("aso_metadata.txt", metadataText);
837
+ }
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);
841
+ const base64Data = screenshot.url.split(",")[1];
842
+ folder.file(
843
+ `screen_${i + 1}_${project.design.deviceType}.png`,
844
+ base64Data,
845
+ { base64: true }
846
+ );
847
+ }
848
+ return await zip.generateAsync({ type: "blob" });
849
+ }
850
+ /**
851
+ * Create project
852
+ */
853
+ createProject(name) {
854
+ return {
855
+ id: this.generateId(),
856
+ name,
857
+ design: this.getDefaultDesign(),
858
+ screens: [this.getDefaultScreen()],
859
+ currentLanguage: this.config.defaultLanguage || "en-US",
860
+ selectedScreenId: "1",
861
+ appMetadata: {
862
+ appName: "My App",
863
+ subtitle: "App Description",
864
+ promotionalText: "Promotional text",
865
+ keywords: "keyword1, keyword2",
866
+ description: "App description",
867
+ whatsNew: "What is new in this version"
868
+ },
869
+ appIcon: null,
870
+ createdAt: /* @__PURE__ */ new Date(),
871
+ updatedAt: /* @__PURE__ */ new Date()
872
+ };
873
+ }
874
+ /**
875
+ * Get default design
876
+ */
877
+ getDefaultDesign() {
878
+ return {
879
+ backgroundColor: "#6366f1",
880
+ backgroundGradientEnd: "#a855f7",
881
+ backgroundImage: null,
882
+ backgroundBlur: 0,
883
+ backgroundOverlayOpacity: 0,
884
+ backgroundPattern: "none",
885
+ textColor: "#ffffff",
886
+ highlightColor: "#fbbf24",
887
+ textAlign: "center",
888
+ deviceColor: "#1f2937",
889
+ deviceType: this.config.device?.defaultDevice || "iphone-15-pro",
890
+ frameStyle: "realistic",
891
+ layout: "text-top",
892
+ scale: 0.85,
893
+ fontFamily: "Inter",
894
+ titleFontSize: 60,
895
+ subtitleFontSize: 24,
896
+ titleFontWeight: "900",
897
+ subtitleFontWeight: "500",
898
+ textCase: "uppercase",
899
+ lineHeight: 0.9,
900
+ letterSpacing: -1,
901
+ rotation: 0,
902
+ rotationX: 0,
903
+ rotationY: 0,
904
+ rotationZ: 0,
905
+ offsetY: 0,
906
+ shadowIntensity: 0.5,
907
+ padding: 40,
908
+ gap: 20,
909
+ isPanoramic: false
910
+ };
911
+ }
912
+ /**
913
+ * Get default screen
914
+ */
915
+ getDefaultScreen() {
916
+ return {
917
+ id: "1",
918
+ title: "MY *APP*",
919
+ subtitle: "Your app description here",
920
+ screenshot: null,
921
+ translations: {
922
+ "en-US": { title: "MY *APP*", subtitle: "Your app description here" }
923
+ },
924
+ imageFit: "cover",
925
+ imageZoom: 1,
926
+ imageAnchorX: 50,
927
+ imageAnchorY: 50
928
+ };
929
+ }
930
+ /**
931
+ * Generate unique ID
932
+ */
933
+ generateId() {
934
+ return Math.random().toString(36).substr(2, 9);
935
+ }
936
+ };
937
+
938
+ // src/application/services/ReportService.ts
939
+ var ReportService = class _ReportService {
940
+ static getInstance() {
941
+ if (!_ReportService.instance) {
942
+ _ReportService.instance = new _ReportService();
943
+ }
944
+ return _ReportService.instance;
945
+ }
946
+ /**
947
+ * Export DOM element to PDF
948
+ */
949
+ async exportToPDF(elementId, options = {}) {
950
+ const html2canvas = (await import("html2canvas")).default;
951
+ const jsPDF = (await import("jspdf")).default;
952
+ const element = document.getElementById(elementId);
953
+ if (!element) {
954
+ throw new Error("Report element not found");
955
+ }
956
+ const canvas = await html2canvas(element, {
957
+ scale: options.scale || 2,
958
+ useCORS: options.useCORS !== false,
959
+ backgroundColor: options.backgroundColor || "#ffffff"
960
+ });
961
+ const imgData = canvas.toDataURL("image/png");
962
+ const pdf = new jsPDF("p", "mm", "a4");
963
+ const imgWidth = 210;
964
+ const imgHeight = canvas.height * imgWidth / canvas.width;
965
+ pdf.addImage(imgData, "PNG", 0, 0, imgWidth, imgHeight);
966
+ pdf.save(options.filename || "report.pdf");
967
+ }
968
+ /**
969
+ * Export canvas to PDF
970
+ */
971
+ async exportCanvasToPDF(canvas, options = {}) {
972
+ const jsPDF = (await import("jspdf")).default;
973
+ const imgData = canvas.toDataURL("image/png");
974
+ const pdf = new jsPDF("p", "mm", "a4");
975
+ const imgWidth = 210;
976
+ const imgHeight = canvas.height * imgWidth / canvas.width;
977
+ pdf.addImage(imgData, "PNG", 0, 0, imgWidth, imgHeight);
978
+ pdf.save(options.filename || "canvas-export.pdf");
979
+ }
980
+ /**
981
+ * Export data to Excel
982
+ */
983
+ async exportToExcel(data, options = {}) {
984
+ const XLSX = await import("xlsx");
985
+ const wb = XLSX.utils.book_new();
986
+ const apps = data.items.filter((i) => i.type === "app").map((i) => ({ Name: i.name, ...i.metrics, ...i.details }));
987
+ const campaigns = data.items.filter((i) => i.type === "campaign").map((i) => ({ Name: i.name, ...i.metrics, ...i.details }));
988
+ const posts = data.items.filter((i) => i.type === "post").map((i) => ({ Name: i.name, ...i.metrics, ...i.details }));
989
+ if (apps.length > 0) {
990
+ XLSX.utils.book_append_sheet(
991
+ wb,
992
+ XLSX.utils.json_to_sheet(apps),
993
+ options.sheetName || "Apps"
994
+ );
995
+ }
996
+ if (campaigns.length > 0) {
997
+ XLSX.utils.book_append_sheet(
998
+ wb,
999
+ XLSX.utils.json_to_sheet(campaigns),
1000
+ "Campaigns"
1001
+ );
1002
+ }
1003
+ if (posts.length > 0) {
1004
+ XLSX.utils.book_append_sheet(
1005
+ wb,
1006
+ XLSX.utils.json_to_sheet(posts),
1007
+ "Posts"
1008
+ );
1009
+ }
1010
+ if (options.includeSummary !== false) {
1011
+ const summary = [
1012
+ { Metric: "Total Reach", Value: data.summary.totalReach },
1013
+ {
1014
+ Metric: "Total Engagement",
1015
+ Value: data.summary.totalEngagement
1016
+ },
1017
+ {
1018
+ Metric: "Average Score",
1019
+ Value: data.summary.averageScore
1020
+ },
1021
+ {
1022
+ Metric: "Export Date",
1023
+ Value: (/* @__PURE__ */ new Date()).toLocaleDateString()
1024
+ }
1025
+ ];
1026
+ XLSX.utils.book_append_sheet(
1027
+ wb,
1028
+ XLSX.utils.json_to_sheet(summary),
1029
+ "Summary"
1030
+ );
1031
+ }
1032
+ XLSX.writeFile(wb, options.filename || "report.xlsx");
1033
+ }
1034
+ /**
1035
+ * Export data to CSV
1036
+ */
1037
+ exportToCSV(data, filename = "report.csv", delimiter = ",") {
1038
+ const headers = ["Type", "Name", "Metrics"];
1039
+ const rows = data.items.map((item) => [
1040
+ item.type,
1041
+ item.name,
1042
+ JSON.stringify(item.metrics)
1043
+ ]);
1044
+ const csvContent = [headers, ...rows].map((row) => row.map((cell) => `"${cell}"`).join(delimiter)).join("\n");
1045
+ const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
1046
+ const url = URL.createObjectURL(blob);
1047
+ const link = document.createElement("a");
1048
+ link.href = url;
1049
+ link.download = filename;
1050
+ link.click();
1051
+ URL.revokeObjectURL(url);
1052
+ }
1053
+ /**
1054
+ * Export multiple elements to PDF
1055
+ */
1056
+ async exportMultipleToPDF(elementIds, options = {}) {
1057
+ const html2canvas = (await import("html2canvas")).default;
1058
+ const jsPDF = (await import("jspdf")).default;
1059
+ const pdf = new jsPDF("p", "mm", "a4");
1060
+ for (let i = 0; i < elementIds.length; i++) {
1061
+ const element = document.getElementById(elementIds[i]);
1062
+ if (!element) continue;
1063
+ const canvas = await html2canvas(element, {
1064
+ scale: options.scale || 2,
1065
+ useCORS: options.useCORS !== false,
1066
+ backgroundColor: options.backgroundColor || "#ffffff"
1067
+ });
1068
+ const imgData = canvas.toDataURL("image/png");
1069
+ const imgWidth = 210;
1070
+ const imgHeight = canvas.height * imgWidth / canvas.width;
1071
+ if (i > 0) {
1072
+ pdf.addPage();
1073
+ }
1074
+ pdf.addImage(imgData, "PNG", 0, 0, imgWidth, imgHeight);
1075
+ }
1076
+ pdf.save(options.filename || "multi-page-report.pdf");
1077
+ }
1078
+ };
1079
+ // Annotate the CommonJS export names for ESM import in node:
1080
+ 0 && (module.exports = {
1081
+ ReportService,
1082
+ ScreenshotStudioService
1083
+ });