@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.
- package/dist/CanvasConfig-pzoE0RHD.d.mts +310 -0
- package/dist/CanvasConfig-pzoE0RHD.d.ts +310 -0
- package/dist/application/index.d.mts +78 -0
- package/dist/application/index.d.ts +78 -0
- package/dist/application/index.js +1083 -0
- package/dist/application/index.mjs +11 -0
- package/dist/chunk-BN7JTXL7.mjs +346 -0
- package/dist/chunk-MC6PNV2F.mjs +34 -0
- package/dist/chunk-SF6WND7M.mjs +547 -0
- package/dist/chunk-TZGTTYTZ.mjs +0 -0
- package/dist/chunk-WMKPCJFI.mjs +661 -0
- package/dist/chunk-Z6IB4BQV.mjs +31 -0
- package/dist/domain/config/index.d.mts +87 -0
- package/dist/domain/config/index.d.ts +87 -0
- package/dist/domain/config/index.js +92 -0
- package/dist/domain/config/index.mjs +15 -0
- package/dist/domain/index.d.mts +79 -0
- package/dist/domain/index.d.ts +79 -0
- package/dist/domain/index.js +137 -0
- package/dist/domain/index.mjs +55 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1633 -0
- package/dist/index.mjs +38 -0
- package/dist/infrastructure/index.d.mts +174 -0
- package/dist/infrastructure/index.d.ts +174 -0
- package/dist/infrastructure/index.js +714 -0
- package/dist/infrastructure/index.mjs +13 -0
- package/dist/presentation/index.d.mts +108 -0
- package/dist/presentation/index.d.ts +108 -0
- package/dist/presentation/index.js +1617 -0
- package/dist/presentation/index.mjs +14 -0
- package/package.json +86 -0
- package/src/application/index.ts +7 -0
- package/src/application/services/ReportService.ts +200 -0
- package/src/application/services/ScreenshotStudioService.ts +243 -0
- package/src/domain/config/CanvasConfig.ts +36 -0
- package/src/domain/config/DeviceConfig.ts +47 -0
- package/src/domain/config/ScreenshotStudioConfig.ts +82 -0
- package/src/domain/config/index.ts +8 -0
- package/src/domain/index.ts +7 -0
- package/src/domain/types/canvas.types.ts +85 -0
- package/src/domain/types/index.ts +8 -0
- package/src/domain/types/report.types.ts +83 -0
- package/src/domain/types/screenshot.types.ts +238 -0
- package/src/index.ts +102 -0
- package/src/infrastructure/index.ts +9 -0
- package/src/infrastructure/renderers/CanvasRenderer.ts +287 -0
- package/src/infrastructure/renderers/PatternRenderer.ts +160 -0
- package/src/infrastructure/renderers/ScreenshotRenderer.ts +331 -0
- package/src/infrastructure/services/DeviceConfigService.ts +144 -0
- package/src/presentation/hooks/useCanvasExport.ts +162 -0
- package/src/presentation/hooks/useReportExport.ts +161 -0
- package/src/presentation/hooks/useScreenshotStudio.ts +480 -0
- package/src/presentation/index.ts +8 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1633 @@
|
|
|
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/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
CanvasRenderer: () => CanvasRenderer,
|
|
34
|
+
DEFAULT_CANVAS_CONFIG: () => DEFAULT_CANVAS_CONFIG,
|
|
35
|
+
DEFAULT_DEVICE_CONFIG: () => DEFAULT_DEVICE_CONFIG,
|
|
36
|
+
DEFAULT_SCREENSHOT_STUDIO_CONFIG: () => DEFAULT_SCREENSHOT_STUDIO_CONFIG,
|
|
37
|
+
DEVICE_DIMENSIONS: () => DEVICE_DIMENSIONS,
|
|
38
|
+
DeviceConfigService: () => DeviceConfigService,
|
|
39
|
+
PatternRenderer: () => PatternRenderer,
|
|
40
|
+
ReportService: () => ReportService,
|
|
41
|
+
ScreenshotRenderer: () => ScreenshotRenderer,
|
|
42
|
+
ScreenshotStudioService: () => ScreenshotStudioService,
|
|
43
|
+
useCanvasExport: () => useCanvasExport,
|
|
44
|
+
useReportExport: () => useReportExport,
|
|
45
|
+
useScreenshotStudio: () => useScreenshotStudio
|
|
46
|
+
});
|
|
47
|
+
module.exports = __toCommonJS(index_exports);
|
|
48
|
+
|
|
49
|
+
// src/domain/config/CanvasConfig.ts
|
|
50
|
+
var DEFAULT_CANVAS_CONFIG = {
|
|
51
|
+
defaultFormat: "png",
|
|
52
|
+
defaultQuality: 0.9,
|
|
53
|
+
maxDimension: 4096,
|
|
54
|
+
scale: 2,
|
|
55
|
+
backgroundColor: "#ffffff",
|
|
56
|
+
useCORS: true
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// src/domain/config/DeviceConfig.ts
|
|
60
|
+
var DEVICE_DIMENSIONS = {
|
|
61
|
+
"iphone-15-pro": { width: 1290, height: 2796 },
|
|
62
|
+
"iphone-15": { width: 1179, height: 2556 },
|
|
63
|
+
"iphone-se": { width: 1080, height: 2340 },
|
|
64
|
+
"ipad-pro": { width: 2048, height: 2732 },
|
|
65
|
+
"ipad": { width: 1620, height: 2160 },
|
|
66
|
+
"android-phone": { width: 1440, height: 3120 },
|
|
67
|
+
"android-tablet": { width: 2e3, height: 2200 },
|
|
68
|
+
"desktop": { width: 1920, height: 1080 }
|
|
69
|
+
};
|
|
70
|
+
var DEFAULT_DEVICE_CONFIG = {
|
|
71
|
+
devices: Object.keys(DEVICE_DIMENSIONS),
|
|
72
|
+
defaultDevice: "iphone-15-pro",
|
|
73
|
+
showFrame: true,
|
|
74
|
+
frameColor: "#1f2937",
|
|
75
|
+
frameThickness: 8
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// src/domain/config/ScreenshotStudioConfig.ts
|
|
79
|
+
var DEFAULT_SCREENSHOT_STUDIO_CONFIG = {
|
|
80
|
+
canvas: {
|
|
81
|
+
defaultFormat: "png",
|
|
82
|
+
defaultQuality: 0.9,
|
|
83
|
+
scale: 2,
|
|
84
|
+
useCORS: true
|
|
85
|
+
},
|
|
86
|
+
device: {
|
|
87
|
+
devices: ["iphone-15-pro"],
|
|
88
|
+
defaultDevice: "iphone-15-pro",
|
|
89
|
+
showFrame: true,
|
|
90
|
+
frameColor: "#1f2937",
|
|
91
|
+
frameThickness: 8
|
|
92
|
+
},
|
|
93
|
+
export: {
|
|
94
|
+
includeMetadata: true,
|
|
95
|
+
folderStructure: "by-language",
|
|
96
|
+
defaultFormat: "png"
|
|
97
|
+
},
|
|
98
|
+
ai: {
|
|
99
|
+
enabled: true,
|
|
100
|
+
provider: "gemini"
|
|
101
|
+
},
|
|
102
|
+
languages: ["en-US", "tr-TR", "de-DE", "fr-FR", "es-ES", "ja-JP"],
|
|
103
|
+
defaultLanguage: "en-US"
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// src/infrastructure/renderers/CanvasRenderer.ts
|
|
107
|
+
var CanvasRenderer = class {
|
|
108
|
+
constructor(config = DEFAULT_CANVAS_CONFIG) {
|
|
109
|
+
this.config = { ...DEFAULT_CANVAS_CONFIG, ...config };
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Create a new canvas element
|
|
113
|
+
*/
|
|
114
|
+
createCanvas(dimensions) {
|
|
115
|
+
const canvas = document.createElement("canvas");
|
|
116
|
+
canvas.width = dimensions.width;
|
|
117
|
+
canvas.height = dimensions.height;
|
|
118
|
+
return canvas;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get canvas 2D context
|
|
122
|
+
*/
|
|
123
|
+
getContext(canvas) {
|
|
124
|
+
return canvas.getContext("2d");
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Apply context configuration
|
|
128
|
+
*/
|
|
129
|
+
configureContext(ctx, config) {
|
|
130
|
+
if (config.fillStyle !== void 0) {
|
|
131
|
+
ctx.fillStyle = config.fillStyle;
|
|
132
|
+
}
|
|
133
|
+
if (config.strokeStyle !== void 0) {
|
|
134
|
+
ctx.strokeStyle = config.strokeStyle;
|
|
135
|
+
}
|
|
136
|
+
if (config.lineWidth !== void 0) {
|
|
137
|
+
ctx.lineWidth = config.lineWidth;
|
|
138
|
+
}
|
|
139
|
+
if (config.lineCap !== void 0) {
|
|
140
|
+
ctx.lineCap = config.lineCap;
|
|
141
|
+
}
|
|
142
|
+
if (config.lineJoin !== void 0) {
|
|
143
|
+
ctx.lineJoin = config.lineJoin;
|
|
144
|
+
}
|
|
145
|
+
if (config.globalAlpha !== void 0) {
|
|
146
|
+
ctx.globalAlpha = config.globalAlpha;
|
|
147
|
+
}
|
|
148
|
+
if (config.font !== void 0) {
|
|
149
|
+
ctx.font = config.font;
|
|
150
|
+
}
|
|
151
|
+
if (config.textAlign !== void 0) {
|
|
152
|
+
ctx.textAlign = config.textAlign;
|
|
153
|
+
}
|
|
154
|
+
if (config.textBaseline !== void 0) {
|
|
155
|
+
ctx.textBaseline = config.textBaseline;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Clear canvas
|
|
160
|
+
*/
|
|
161
|
+
clearCanvas(ctx, dimensions) {
|
|
162
|
+
ctx.clearRect(0, 0, dimensions.width, dimensions.height);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Fill rectangle
|
|
166
|
+
*/
|
|
167
|
+
fillRect(ctx, x, y, width, height, color) {
|
|
168
|
+
if (color) {
|
|
169
|
+
ctx.fillStyle = color;
|
|
170
|
+
}
|
|
171
|
+
ctx.fillRect(x, y, width, height);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Stroke rectangle
|
|
175
|
+
*/
|
|
176
|
+
strokeRect(ctx, x, y, width, height, color, lineWidth) {
|
|
177
|
+
if (color) {
|
|
178
|
+
ctx.strokeStyle = color;
|
|
179
|
+
}
|
|
180
|
+
if (lineWidth !== void 0) {
|
|
181
|
+
ctx.lineWidth = lineWidth;
|
|
182
|
+
}
|
|
183
|
+
ctx.strokeRect(x, y, width, height);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Draw text
|
|
187
|
+
*/
|
|
188
|
+
drawText(ctx, text, x, y, config) {
|
|
189
|
+
if (config) {
|
|
190
|
+
this.configureContext(ctx, config);
|
|
191
|
+
}
|
|
192
|
+
ctx.fillText(text, x, y);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Draw image
|
|
196
|
+
*/
|
|
197
|
+
async drawImage(ctx, imageSource, x, y, width, height) {
|
|
198
|
+
let img;
|
|
199
|
+
if (typeof imageSource === "string") {
|
|
200
|
+
img = await this.loadImage(imageSource);
|
|
201
|
+
} else {
|
|
202
|
+
img = imageSource;
|
|
203
|
+
}
|
|
204
|
+
if (width && height) {
|
|
205
|
+
ctx.drawImage(img, x, y, width, height);
|
|
206
|
+
} else {
|
|
207
|
+
ctx.drawImage(img, x, y);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Load image from URL
|
|
212
|
+
*/
|
|
213
|
+
loadImage(url) {
|
|
214
|
+
return new Promise((resolve, reject) => {
|
|
215
|
+
const img = new Image();
|
|
216
|
+
img.crossOrigin = this.config.useCORS ? "anonymous" : void 0;
|
|
217
|
+
img.onload = () => resolve(img);
|
|
218
|
+
img.onerror = () => reject(new Error(`Failed to load image: ${url}`));
|
|
219
|
+
img.src = url;
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Create gradient
|
|
224
|
+
*/
|
|
225
|
+
createLinearGradient(ctx, x1, y1, x2, y2, colorStops) {
|
|
226
|
+
const gradient = ctx.createLinearGradient(x1, y1, x2, y2);
|
|
227
|
+
colorStops.forEach(({ offset, color }) => {
|
|
228
|
+
gradient.addColorStop(offset, color);
|
|
229
|
+
});
|
|
230
|
+
return gradient;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Export canvas to blob
|
|
234
|
+
*/
|
|
235
|
+
async exportToBlob(canvas, options = {}) {
|
|
236
|
+
const format = options.format || this.config.defaultFormat;
|
|
237
|
+
const quality = options.quality || this.config.defaultQuality;
|
|
238
|
+
const scale = options.scale || this.config.scale;
|
|
239
|
+
const scaledCanvas = (scale || 1) !== 1 ? this.scaleCanvas(canvas, scale || 1) : canvas;
|
|
240
|
+
const mimeType = `image/${format || "png"}`;
|
|
241
|
+
const blob = await new Promise((resolve) => {
|
|
242
|
+
scaledCanvas.toBlob(
|
|
243
|
+
(result) => resolve(result),
|
|
244
|
+
mimeType,
|
|
245
|
+
quality
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
if (!blob) {
|
|
249
|
+
throw new Error("Failed to export canvas to blob");
|
|
250
|
+
}
|
|
251
|
+
const url = URL.createObjectURL(blob);
|
|
252
|
+
return {
|
|
253
|
+
blob,
|
|
254
|
+
url,
|
|
255
|
+
dimensions: {
|
|
256
|
+
width: scaledCanvas.width,
|
|
257
|
+
height: scaledCanvas.height
|
|
258
|
+
},
|
|
259
|
+
format: format || "png",
|
|
260
|
+
size: blob.size
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Export canvas to data URL
|
|
265
|
+
*/
|
|
266
|
+
exportToDataURL(canvas, format, quality) {
|
|
267
|
+
const mimeType = format || `image/${this.config.defaultFormat}`;
|
|
268
|
+
const q = quality || this.config.defaultQuality;
|
|
269
|
+
return canvas.toDataURL(mimeType, q);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Scale canvas
|
|
273
|
+
*/
|
|
274
|
+
scaleCanvas(canvas, scale) {
|
|
275
|
+
const scaled = document.createElement("canvas");
|
|
276
|
+
scaled.width = canvas.width * scale;
|
|
277
|
+
scaled.height = canvas.height * scale;
|
|
278
|
+
const ctx = scaled.getContext("2d");
|
|
279
|
+
if (ctx) {
|
|
280
|
+
ctx.imageSmoothingEnabled = true;
|
|
281
|
+
ctx.imageSmoothingQuality = "high";
|
|
282
|
+
ctx.drawImage(canvas, 0, 0, scaled.width, scaled.height);
|
|
283
|
+
}
|
|
284
|
+
return scaled;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Clone canvas
|
|
288
|
+
*/
|
|
289
|
+
cloneCanvas(canvas) {
|
|
290
|
+
const clone = document.createElement("canvas");
|
|
291
|
+
clone.width = canvas.width;
|
|
292
|
+
clone.height = canvas.height;
|
|
293
|
+
const ctx = clone.getContext("2d");
|
|
294
|
+
if (ctx) {
|
|
295
|
+
ctx.drawImage(canvas, 0, 0);
|
|
296
|
+
}
|
|
297
|
+
return clone;
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
// src/infrastructure/renderers/PatternRenderer.ts
|
|
302
|
+
var PatternRenderer = class {
|
|
303
|
+
/**
|
|
304
|
+
* Render pattern on canvas
|
|
305
|
+
*/
|
|
306
|
+
renderPattern(ctx, pattern, dimensions, color = "#000000") {
|
|
307
|
+
ctx.save();
|
|
308
|
+
ctx.globalAlpha = 0.1;
|
|
309
|
+
switch (pattern) {
|
|
310
|
+
case "dots":
|
|
311
|
+
this.renderDotPattern(ctx, dimensions, color);
|
|
312
|
+
break;
|
|
313
|
+
case "grid":
|
|
314
|
+
this.renderGridPattern(ctx, dimensions, color);
|
|
315
|
+
break;
|
|
316
|
+
case "lines":
|
|
317
|
+
this.renderLinePattern(ctx, dimensions, color);
|
|
318
|
+
break;
|
|
319
|
+
case "waves":
|
|
320
|
+
this.renderWavePattern(ctx, dimensions, color);
|
|
321
|
+
break;
|
|
322
|
+
case "checkers":
|
|
323
|
+
this.renderCheckerPattern(ctx, dimensions, color);
|
|
324
|
+
break;
|
|
325
|
+
case "none":
|
|
326
|
+
default:
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
ctx.restore();
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Render dot pattern
|
|
333
|
+
*/
|
|
334
|
+
renderDotPattern(ctx, dimensions, color) {
|
|
335
|
+
ctx.fillStyle = color;
|
|
336
|
+
const spacing = 30;
|
|
337
|
+
for (let x = spacing; x < dimensions.width; x += spacing) {
|
|
338
|
+
for (let y = spacing; y < dimensions.height; y += spacing) {
|
|
339
|
+
ctx.beginPath();
|
|
340
|
+
ctx.arc(x, y, 2, 0, Math.PI * 2);
|
|
341
|
+
ctx.fill();
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Render grid pattern
|
|
347
|
+
*/
|
|
348
|
+
renderGridPattern(ctx, dimensions, color) {
|
|
349
|
+
ctx.strokeStyle = color;
|
|
350
|
+
ctx.lineWidth = 1;
|
|
351
|
+
const spacing = 30;
|
|
352
|
+
for (let x = 0; x <= dimensions.width; x += spacing) {
|
|
353
|
+
ctx.beginPath();
|
|
354
|
+
ctx.moveTo(x, 0);
|
|
355
|
+
ctx.lineTo(x, dimensions.height);
|
|
356
|
+
ctx.stroke();
|
|
357
|
+
}
|
|
358
|
+
for (let y = 0; y <= dimensions.height; y += spacing) {
|
|
359
|
+
ctx.beginPath();
|
|
360
|
+
ctx.moveTo(0, y);
|
|
361
|
+
ctx.lineTo(dimensions.width, y);
|
|
362
|
+
ctx.stroke();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Render line pattern
|
|
367
|
+
*/
|
|
368
|
+
renderLinePattern(ctx, dimensions, color) {
|
|
369
|
+
ctx.strokeStyle = color;
|
|
370
|
+
ctx.lineWidth = 2;
|
|
371
|
+
const spacing = 20;
|
|
372
|
+
for (let y = spacing; y < dimensions.height; y += spacing) {
|
|
373
|
+
ctx.beginPath();
|
|
374
|
+
ctx.moveTo(0, y);
|
|
375
|
+
ctx.lineTo(dimensions.width, y);
|
|
376
|
+
ctx.stroke();
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Render wave pattern
|
|
381
|
+
*/
|
|
382
|
+
renderWavePattern(ctx, dimensions, color) {
|
|
383
|
+
ctx.strokeStyle = color;
|
|
384
|
+
ctx.lineWidth = 2;
|
|
385
|
+
const amplitude = 10;
|
|
386
|
+
const frequency = 0.1;
|
|
387
|
+
const spacing = 40;
|
|
388
|
+
for (let y = spacing; y < dimensions.height; y += spacing) {
|
|
389
|
+
ctx.beginPath();
|
|
390
|
+
for (let x = 0; x <= dimensions.width; x += 5) {
|
|
391
|
+
const waveY = y + Math.sin(x * frequency) * amplitude;
|
|
392
|
+
if (x === 0) {
|
|
393
|
+
ctx.moveTo(x, waveY);
|
|
394
|
+
} else {
|
|
395
|
+
ctx.lineTo(x, waveY);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
ctx.stroke();
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Render checker pattern
|
|
403
|
+
*/
|
|
404
|
+
renderCheckerPattern(ctx, dimensions, color) {
|
|
405
|
+
ctx.fillStyle = color;
|
|
406
|
+
const size = 20;
|
|
407
|
+
for (let x = 0; x < dimensions.width; x += size * 2) {
|
|
408
|
+
for (let y = 0; y < dimensions.height; y += size * 2) {
|
|
409
|
+
ctx.fillRect(x, y, size, size);
|
|
410
|
+
ctx.fillRect(x + size, y + size, size, size);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
// src/infrastructure/services/DeviceConfigService.ts
|
|
417
|
+
var DeviceConfigService = class _DeviceConfigService {
|
|
418
|
+
constructor() {
|
|
419
|
+
this.devices = /* @__PURE__ */ new Map();
|
|
420
|
+
this.initializeDevices();
|
|
421
|
+
}
|
|
422
|
+
static getInstance() {
|
|
423
|
+
if (!_DeviceConfigService.instance) {
|
|
424
|
+
_DeviceConfigService.instance = new _DeviceConfigService();
|
|
425
|
+
}
|
|
426
|
+
return _DeviceConfigService.instance;
|
|
427
|
+
}
|
|
428
|
+
initializeDevices() {
|
|
429
|
+
const deviceConfigs = [
|
|
430
|
+
{
|
|
431
|
+
name: "iPhone 15 Pro",
|
|
432
|
+
dimensions: { width: 1290, height: 2796, scale: 3 },
|
|
433
|
+
isPhone: true,
|
|
434
|
+
hasNotch: true,
|
|
435
|
+
cornerRadius: 20,
|
|
436
|
+
frameThickness: 8
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
name: "iPhone 15",
|
|
440
|
+
dimensions: { width: 1179, height: 2556, scale: 3 },
|
|
441
|
+
isPhone: true,
|
|
442
|
+
hasNotch: true,
|
|
443
|
+
cornerRadius: 20,
|
|
444
|
+
frameThickness: 8
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
name: "iPhone SE",
|
|
448
|
+
dimensions: { width: 1080, height: 2340, scale: 3 },
|
|
449
|
+
isPhone: true,
|
|
450
|
+
hasNotch: false,
|
|
451
|
+
cornerRadius: 16,
|
|
452
|
+
frameThickness: 6
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
name: "iPad Pro",
|
|
456
|
+
dimensions: { width: 2048, height: 2732, scale: 2 },
|
|
457
|
+
isTablet: true,
|
|
458
|
+
hasNotch: false,
|
|
459
|
+
cornerRadius: 24,
|
|
460
|
+
frameThickness: 10
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
name: "iPad",
|
|
464
|
+
dimensions: { width: 1620, height: 2160, scale: 2 },
|
|
465
|
+
isTablet: true,
|
|
466
|
+
hasNotch: false,
|
|
467
|
+
cornerRadius: 20,
|
|
468
|
+
frameThickness: 10
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
name: "Android Phone",
|
|
472
|
+
dimensions: { width: 1440, height: 3120, scale: 3 },
|
|
473
|
+
isPhone: true,
|
|
474
|
+
hasNotch: false,
|
|
475
|
+
cornerRadius: 24,
|
|
476
|
+
frameThickness: 8
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
name: "Android Tablet",
|
|
480
|
+
dimensions: { width: 2e3, height: 2200, scale: 2 },
|
|
481
|
+
isTablet: true,
|
|
482
|
+
hasNotch: false,
|
|
483
|
+
cornerRadius: 24,
|
|
484
|
+
frameThickness: 10
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
name: "Desktop",
|
|
488
|
+
dimensions: { width: 1920, height: 1080, scale: 1 },
|
|
489
|
+
isPhone: false,
|
|
490
|
+
isTablet: false,
|
|
491
|
+
hasNotch: false,
|
|
492
|
+
cornerRadius: 0,
|
|
493
|
+
frameThickness: 0
|
|
494
|
+
}
|
|
495
|
+
];
|
|
496
|
+
Object.entries(DEVICE_DIMENSIONS).forEach(([type, dimensions]) => {
|
|
497
|
+
const deviceType = type;
|
|
498
|
+
const config = deviceConfigs.find((_, index) => {
|
|
499
|
+
const types = Object.keys(DEVICE_DIMENSIONS);
|
|
500
|
+
return types[index] === type;
|
|
501
|
+
});
|
|
502
|
+
if (config) {
|
|
503
|
+
this.devices.set(deviceType, {
|
|
504
|
+
type: deviceType,
|
|
505
|
+
...config,
|
|
506
|
+
dimensions
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
getDeviceConfig(deviceType) {
|
|
512
|
+
return this.devices.get(deviceType);
|
|
513
|
+
}
|
|
514
|
+
getAllDevices() {
|
|
515
|
+
return Array.from(this.devices.values());
|
|
516
|
+
}
|
|
517
|
+
getPhones() {
|
|
518
|
+
return this.getAllDevices().filter((d) => d.isPhone);
|
|
519
|
+
}
|
|
520
|
+
getTablets() {
|
|
521
|
+
return this.getAllDevices().filter((d) => d.isTablet);
|
|
522
|
+
}
|
|
523
|
+
isPhone(deviceType) {
|
|
524
|
+
const config = this.getDeviceConfig(deviceType);
|
|
525
|
+
return config?.isPhone || false;
|
|
526
|
+
}
|
|
527
|
+
isTablet(deviceType) {
|
|
528
|
+
const config = this.getDeviceConfig(deviceType);
|
|
529
|
+
return config?.isTablet || false;
|
|
530
|
+
}
|
|
531
|
+
hasNotch(deviceType) {
|
|
532
|
+
const config = this.getDeviceConfig(deviceType);
|
|
533
|
+
return config?.hasNotch || false;
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
// src/infrastructure/renderers/ScreenshotRenderer.ts
|
|
538
|
+
var ScreenshotRenderer = class {
|
|
539
|
+
constructor() {
|
|
540
|
+
this.canvasRenderer = new CanvasRenderer();
|
|
541
|
+
this.patternRenderer = new PatternRenderer();
|
|
542
|
+
this.deviceConfigService = DeviceConfigService.getInstance();
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Render device frame
|
|
546
|
+
*/
|
|
547
|
+
renderDeviceFrame(ctx, dimensions, deviceType, frameStyle, frameColor) {
|
|
548
|
+
const deviceConfig = this.deviceConfigService.getDeviceConfig(deviceType);
|
|
549
|
+
if (!deviceConfig) return;
|
|
550
|
+
if (frameStyle === "none") return;
|
|
551
|
+
const isPhone = this.deviceConfigService.isPhone(deviceType);
|
|
552
|
+
const isTablet = this.deviceConfigService.isTablet(deviceType);
|
|
553
|
+
if (!isPhone && !isTablet) return;
|
|
554
|
+
const frameThickness = deviceConfig.frameThickness || 8;
|
|
555
|
+
if (frameStyle === "realistic") {
|
|
556
|
+
this.renderRealisticFrame(ctx, dimensions, frameColor, frameThickness);
|
|
557
|
+
if (isPhone && this.deviceConfigService.hasNotch(deviceType)) {
|
|
558
|
+
this.renderNotch(ctx, dimensions);
|
|
559
|
+
}
|
|
560
|
+
} else if (frameStyle === "minimal") {
|
|
561
|
+
this.renderMinimalFrame(ctx, dimensions, frameColor, frameThickness);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Render realistic device frame
|
|
566
|
+
*/
|
|
567
|
+
renderRealisticFrame(ctx, dimensions, color, thickness) {
|
|
568
|
+
ctx.strokeStyle = color;
|
|
569
|
+
ctx.lineWidth = thickness;
|
|
570
|
+
ctx.strokeRect(
|
|
571
|
+
thickness / 2,
|
|
572
|
+
thickness / 2,
|
|
573
|
+
dimensions.width - thickness,
|
|
574
|
+
dimensions.height - thickness
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Render minimal device frame
|
|
579
|
+
*/
|
|
580
|
+
renderMinimalFrame(ctx, dimensions, color, thickness) {
|
|
581
|
+
ctx.strokeStyle = color;
|
|
582
|
+
ctx.lineWidth = thickness;
|
|
583
|
+
ctx.strokeRect(0, 0, dimensions.width, dimensions.height);
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Render device notch
|
|
587
|
+
*/
|
|
588
|
+
renderNotch(ctx, dimensions) {
|
|
589
|
+
ctx.fillStyle = "#1f2937";
|
|
590
|
+
ctx.beginPath();
|
|
591
|
+
ctx.arc(dimensions.width / 2, 20, 10, 0, Math.PI * 2);
|
|
592
|
+
ctx.fill();
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Render background
|
|
596
|
+
*/
|
|
597
|
+
renderBackground(ctx, design, dimensions) {
|
|
598
|
+
if (design.backgroundGradientEnd) {
|
|
599
|
+
const gradient = this.canvasRenderer.createLinearGradient(
|
|
600
|
+
ctx,
|
|
601
|
+
0,
|
|
602
|
+
0,
|
|
603
|
+
dimensions.width,
|
|
604
|
+
dimensions.height,
|
|
605
|
+
[
|
|
606
|
+
{ offset: 0, color: design.backgroundColor },
|
|
607
|
+
{ offset: 1, color: design.backgroundGradientEnd }
|
|
608
|
+
]
|
|
609
|
+
);
|
|
610
|
+
ctx.fillStyle = gradient;
|
|
611
|
+
} else {
|
|
612
|
+
ctx.fillStyle = design.backgroundColor;
|
|
613
|
+
}
|
|
614
|
+
ctx.fillRect(0, 0, dimensions.width, dimensions.height);
|
|
615
|
+
if (design.backgroundPattern && design.backgroundPattern !== "none") {
|
|
616
|
+
this.patternRenderer.renderPattern(
|
|
617
|
+
ctx,
|
|
618
|
+
design.backgroundPattern,
|
|
619
|
+
dimensions
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Render text content
|
|
625
|
+
*/
|
|
626
|
+
renderTextContent(ctx, content, design, dimensions) {
|
|
627
|
+
const titleConfig = {
|
|
628
|
+
fillStyle: design.textColor,
|
|
629
|
+
font: `bold ${design.titleFontSize}px ${design.fontFamily}`,
|
|
630
|
+
textAlign: design.textAlign
|
|
631
|
+
};
|
|
632
|
+
const subtitleConfig = {
|
|
633
|
+
fillStyle: design.textColor,
|
|
634
|
+
font: `${design.subtitleFontSize}px ${design.fontFamily}`,
|
|
635
|
+
textAlign: design.textAlign
|
|
636
|
+
};
|
|
637
|
+
const titleY = design.layout === "text-bottom" ? dimensions.height - 150 : 100 + (design.offsetY || 0);
|
|
638
|
+
this.canvasRenderer.drawText(
|
|
639
|
+
ctx,
|
|
640
|
+
this.formatText(content.title, design.textCase),
|
|
641
|
+
dimensions.width / 2,
|
|
642
|
+
titleY,
|
|
643
|
+
titleConfig
|
|
644
|
+
);
|
|
645
|
+
const subtitleY = titleY + design.titleFontSize + (design.gap || 20);
|
|
646
|
+
this.canvasRenderer.drawText(
|
|
647
|
+
ctx,
|
|
648
|
+
this.formatText(content.subtitle, design.textCase),
|
|
649
|
+
dimensions.width / 2,
|
|
650
|
+
subtitleY,
|
|
651
|
+
subtitleConfig
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Format text case
|
|
656
|
+
*/
|
|
657
|
+
formatText(text, textCase = "none") {
|
|
658
|
+
switch (textCase) {
|
|
659
|
+
case "uppercase":
|
|
660
|
+
return text.toUpperCase();
|
|
661
|
+
case "lowercase":
|
|
662
|
+
return text.toLowerCase();
|
|
663
|
+
default:
|
|
664
|
+
return text;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Render screenshot image
|
|
669
|
+
*/
|
|
670
|
+
async renderScreenshotImage(ctx, imageUrl, design, dimensions) {
|
|
671
|
+
if (!imageUrl) return;
|
|
672
|
+
const deviceConfig = this.deviceConfigService.getDeviceConfig(
|
|
673
|
+
design.deviceType
|
|
674
|
+
);
|
|
675
|
+
if (!deviceConfig) return;
|
|
676
|
+
const frameThickness = deviceConfig.frameThickness || 8;
|
|
677
|
+
const padding = 60 + frameThickness;
|
|
678
|
+
const screenshotWidth = dimensions.width - padding * 2;
|
|
679
|
+
const screenshotHeight = dimensions.height - padding * 2;
|
|
680
|
+
try {
|
|
681
|
+
await this.canvasRenderer.drawImage(
|
|
682
|
+
ctx,
|
|
683
|
+
imageUrl,
|
|
684
|
+
padding,
|
|
685
|
+
padding,
|
|
686
|
+
screenshotWidth,
|
|
687
|
+
screenshotHeight
|
|
688
|
+
);
|
|
689
|
+
} catch (error) {
|
|
690
|
+
console.error("Failed to render screenshot image:", error);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Render complete screenshot
|
|
695
|
+
*/
|
|
696
|
+
async renderDesignedScreenshot(content, design) {
|
|
697
|
+
const deviceConfig = this.deviceConfigService.getDeviceConfig(design.deviceType);
|
|
698
|
+
if (!deviceConfig) {
|
|
699
|
+
throw new Error(`Device config not found: ${design.deviceType}`);
|
|
700
|
+
}
|
|
701
|
+
const dimensions = deviceConfig.dimensions;
|
|
702
|
+
const canvas = this.canvasRenderer.createCanvas(dimensions);
|
|
703
|
+
const ctx = canvas.getContext("2d");
|
|
704
|
+
if (!ctx) {
|
|
705
|
+
throw new Error("Failed to get canvas context");
|
|
706
|
+
}
|
|
707
|
+
this.renderBackground(ctx, design, dimensions);
|
|
708
|
+
if (content.screenshot) {
|
|
709
|
+
await this.renderScreenshotImage(ctx, content.screenshot, design, dimensions);
|
|
710
|
+
}
|
|
711
|
+
this.renderTextContent(ctx, content, design, dimensions);
|
|
712
|
+
this.renderDeviceFrame(
|
|
713
|
+
ctx,
|
|
714
|
+
dimensions,
|
|
715
|
+
design.deviceType,
|
|
716
|
+
design.frameStyle,
|
|
717
|
+
design.deviceColor
|
|
718
|
+
);
|
|
719
|
+
return canvas;
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Render placeholder content
|
|
723
|
+
*/
|
|
724
|
+
renderPlaceholderContent(ctx, dimensions, deviceType) {
|
|
725
|
+
this.canvasRenderer.fillRect(ctx, 0, 0, dimensions.width, dimensions.height, "#ffffff");
|
|
726
|
+
this.canvasRenderer.fillRect(ctx, 0, 0, dimensions.width, 60, "#f3f4f6");
|
|
727
|
+
this.canvasRenderer.fillRect(
|
|
728
|
+
ctx,
|
|
729
|
+
0,
|
|
730
|
+
dimensions.height - 80,
|
|
731
|
+
dimensions.width,
|
|
732
|
+
80,
|
|
733
|
+
"#f3f4f6"
|
|
734
|
+
);
|
|
735
|
+
ctx.fillStyle = "#e5e7eb";
|
|
736
|
+
for (let i = 0; i < 5; i++) {
|
|
737
|
+
const y = 80 + i * 120;
|
|
738
|
+
const height = Math.min(100, dimensions.height - y - 100);
|
|
739
|
+
if (y < dimensions.height - 100) {
|
|
740
|
+
this.canvasRenderer.fillRect(ctx, 20, y, dimensions.width - 40, height);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
const deviceConfig = this.deviceConfigService.getDeviceConfig(deviceType);
|
|
744
|
+
if (deviceConfig) {
|
|
745
|
+
this.renderDeviceFrame(
|
|
746
|
+
ctx,
|
|
747
|
+
dimensions,
|
|
748
|
+
deviceType,
|
|
749
|
+
"realistic",
|
|
750
|
+
"#1f2937"
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
// src/application/services/ScreenshotStudioService.ts
|
|
757
|
+
var ScreenshotStudioService = class {
|
|
758
|
+
constructor(config) {
|
|
759
|
+
this.config = {
|
|
760
|
+
...DEFAULT_SCREENSHOT_STUDIO_CONFIG,
|
|
761
|
+
...config
|
|
762
|
+
};
|
|
763
|
+
this.renderer = new ScreenshotRenderer();
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Generate screenshot
|
|
767
|
+
*/
|
|
768
|
+
async generateScreenshot(options) {
|
|
769
|
+
const deviceConfig = this.config.device?.devices.find(
|
|
770
|
+
(d) => d === options.deviceType
|
|
771
|
+
);
|
|
772
|
+
if (!deviceConfig) {
|
|
773
|
+
throw new Error(`Device configuration not found for ${options.deviceType}`);
|
|
774
|
+
}
|
|
775
|
+
const canvas = this.renderer["canvasRenderer"].createCanvas(
|
|
776
|
+
options.customDimensions || { width: 1290, height: 2796 }
|
|
777
|
+
);
|
|
778
|
+
const ctx = canvas.getContext("2d");
|
|
779
|
+
if (!ctx) {
|
|
780
|
+
throw new Error("Failed to get canvas context");
|
|
781
|
+
}
|
|
782
|
+
this.renderer["renderPlaceholderContent"](
|
|
783
|
+
ctx,
|
|
784
|
+
{ width: canvas.width, height: canvas.height },
|
|
785
|
+
options.deviceType
|
|
786
|
+
);
|
|
787
|
+
const blob = await this.renderer["canvasRenderer"].exportToBlob(canvas, {
|
|
788
|
+
format: options.format || this.config.export?.defaultFormat || "png",
|
|
789
|
+
quality: options.quality || this.config.canvas?.defaultQuality || 0.9
|
|
790
|
+
});
|
|
791
|
+
return {
|
|
792
|
+
id: this.generateId(),
|
|
793
|
+
deviceType: options.deviceType,
|
|
794
|
+
dimensions: { width: canvas.width, height: canvas.height },
|
|
795
|
+
format: options.format || this.config.export?.defaultFormat || "png",
|
|
796
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
797
|
+
fileSize: blob.blob.size,
|
|
798
|
+
url: blob.url
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Generate batch screenshots
|
|
803
|
+
*/
|
|
804
|
+
async generateBatchScreenshots(deviceTypes) {
|
|
805
|
+
const promises = deviceTypes.map(
|
|
806
|
+
(deviceType) => this.generateScreenshot({ deviceType })
|
|
807
|
+
);
|
|
808
|
+
return Promise.all(promises);
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Generate designed screenshot
|
|
812
|
+
*/
|
|
813
|
+
async generateDesignedScreenshot(content, design) {
|
|
814
|
+
const canvas = await this.renderer.renderDesignedScreenshot(content, design);
|
|
815
|
+
const blob = await this.renderer["canvasRenderer"].exportToBlob(canvas, {
|
|
816
|
+
format: "png"
|
|
817
|
+
});
|
|
818
|
+
return {
|
|
819
|
+
id: this.generateId(),
|
|
820
|
+
deviceType: design.deviceType,
|
|
821
|
+
dimensions: { width: canvas.width, height: canvas.height },
|
|
822
|
+
format: "png",
|
|
823
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
824
|
+
fileSize: blob.blob.size,
|
|
825
|
+
url: blob.url
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Export project as ZIP
|
|
830
|
+
*/
|
|
831
|
+
async exportProjectAsZip(project) {
|
|
832
|
+
const JSZip = (await import("jszip")).default;
|
|
833
|
+
const zip = new JSZip();
|
|
834
|
+
const folder = zip.folder(project.currentLanguage);
|
|
835
|
+
if (!folder) {
|
|
836
|
+
throw new Error("Failed to create folder in ZIP");
|
|
837
|
+
}
|
|
838
|
+
if (this.config.export?.includeMetadata) {
|
|
839
|
+
const metadataText = `APP NAME: ${project.appMetadata.appName}
|
|
840
|
+
SUBTITLE: ${project.appMetadata.subtitle}
|
|
841
|
+
DESCRIPTION:
|
|
842
|
+
${project.appMetadata.description}
|
|
843
|
+
|
|
844
|
+
KEYWORDS: ${project.appMetadata.keywords}
|
|
845
|
+
WHATS NEW:
|
|
846
|
+
${project.appMetadata.whatsNew}`;
|
|
847
|
+
folder.file("aso_metadata.txt", metadataText);
|
|
848
|
+
}
|
|
849
|
+
for (let i = 0; i < project.screens.length; i++) {
|
|
850
|
+
const screen = project.screens[i];
|
|
851
|
+
const screenshot = await this.generateDesignedScreenshot(screen, project.design);
|
|
852
|
+
const base64Data = screenshot.url.split(",")[1];
|
|
853
|
+
folder.file(
|
|
854
|
+
`screen_${i + 1}_${project.design.deviceType}.png`,
|
|
855
|
+
base64Data,
|
|
856
|
+
{ base64: true }
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
return await zip.generateAsync({ type: "blob" });
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Create project
|
|
863
|
+
*/
|
|
864
|
+
createProject(name) {
|
|
865
|
+
return {
|
|
866
|
+
id: this.generateId(),
|
|
867
|
+
name,
|
|
868
|
+
design: this.getDefaultDesign(),
|
|
869
|
+
screens: [this.getDefaultScreen()],
|
|
870
|
+
currentLanguage: this.config.defaultLanguage || "en-US",
|
|
871
|
+
selectedScreenId: "1",
|
|
872
|
+
appMetadata: {
|
|
873
|
+
appName: "My App",
|
|
874
|
+
subtitle: "App Description",
|
|
875
|
+
promotionalText: "Promotional text",
|
|
876
|
+
keywords: "keyword1, keyword2",
|
|
877
|
+
description: "App description",
|
|
878
|
+
whatsNew: "What is new in this version"
|
|
879
|
+
},
|
|
880
|
+
appIcon: null,
|
|
881
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
882
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Get default design
|
|
887
|
+
*/
|
|
888
|
+
getDefaultDesign() {
|
|
889
|
+
return {
|
|
890
|
+
backgroundColor: "#6366f1",
|
|
891
|
+
backgroundGradientEnd: "#a855f7",
|
|
892
|
+
backgroundImage: null,
|
|
893
|
+
backgroundBlur: 0,
|
|
894
|
+
backgroundOverlayOpacity: 0,
|
|
895
|
+
backgroundPattern: "none",
|
|
896
|
+
textColor: "#ffffff",
|
|
897
|
+
highlightColor: "#fbbf24",
|
|
898
|
+
textAlign: "center",
|
|
899
|
+
deviceColor: "#1f2937",
|
|
900
|
+
deviceType: this.config.device?.defaultDevice || "iphone-15-pro",
|
|
901
|
+
frameStyle: "realistic",
|
|
902
|
+
layout: "text-top",
|
|
903
|
+
scale: 0.85,
|
|
904
|
+
fontFamily: "Inter",
|
|
905
|
+
titleFontSize: 60,
|
|
906
|
+
subtitleFontSize: 24,
|
|
907
|
+
titleFontWeight: "900",
|
|
908
|
+
subtitleFontWeight: "500",
|
|
909
|
+
textCase: "uppercase",
|
|
910
|
+
lineHeight: 0.9,
|
|
911
|
+
letterSpacing: -1,
|
|
912
|
+
rotation: 0,
|
|
913
|
+
rotationX: 0,
|
|
914
|
+
rotationY: 0,
|
|
915
|
+
rotationZ: 0,
|
|
916
|
+
offsetY: 0,
|
|
917
|
+
shadowIntensity: 0.5,
|
|
918
|
+
padding: 40,
|
|
919
|
+
gap: 20,
|
|
920
|
+
isPanoramic: false
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Get default screen
|
|
925
|
+
*/
|
|
926
|
+
getDefaultScreen() {
|
|
927
|
+
return {
|
|
928
|
+
id: "1",
|
|
929
|
+
title: "MY *APP*",
|
|
930
|
+
subtitle: "Your app description here",
|
|
931
|
+
screenshot: null,
|
|
932
|
+
translations: {
|
|
933
|
+
"en-US": { title: "MY *APP*", subtitle: "Your app description here" }
|
|
934
|
+
},
|
|
935
|
+
imageFit: "cover",
|
|
936
|
+
imageZoom: 1,
|
|
937
|
+
imageAnchorX: 50,
|
|
938
|
+
imageAnchorY: 50
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* Generate unique ID
|
|
943
|
+
*/
|
|
944
|
+
generateId() {
|
|
945
|
+
return Math.random().toString(36).substr(2, 9);
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
|
|
949
|
+
// src/application/services/ReportService.ts
|
|
950
|
+
var ReportService = class _ReportService {
|
|
951
|
+
static getInstance() {
|
|
952
|
+
if (!_ReportService.instance) {
|
|
953
|
+
_ReportService.instance = new _ReportService();
|
|
954
|
+
}
|
|
955
|
+
return _ReportService.instance;
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Export DOM element to PDF
|
|
959
|
+
*/
|
|
960
|
+
async exportToPDF(elementId, options = {}) {
|
|
961
|
+
const html2canvas = (await import("html2canvas")).default;
|
|
962
|
+
const jsPDF = (await import("jspdf")).default;
|
|
963
|
+
const element = document.getElementById(elementId);
|
|
964
|
+
if (!element) {
|
|
965
|
+
throw new Error("Report element not found");
|
|
966
|
+
}
|
|
967
|
+
const canvas = await html2canvas(element, {
|
|
968
|
+
scale: options.scale || 2,
|
|
969
|
+
useCORS: options.useCORS !== false,
|
|
970
|
+
backgroundColor: options.backgroundColor || "#ffffff"
|
|
971
|
+
});
|
|
972
|
+
const imgData = canvas.toDataURL("image/png");
|
|
973
|
+
const pdf = new jsPDF("p", "mm", "a4");
|
|
974
|
+
const imgWidth = 210;
|
|
975
|
+
const imgHeight = canvas.height * imgWidth / canvas.width;
|
|
976
|
+
pdf.addImage(imgData, "PNG", 0, 0, imgWidth, imgHeight);
|
|
977
|
+
pdf.save(options.filename || "report.pdf");
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Export canvas to PDF
|
|
981
|
+
*/
|
|
982
|
+
async exportCanvasToPDF(canvas, options = {}) {
|
|
983
|
+
const jsPDF = (await import("jspdf")).default;
|
|
984
|
+
const imgData = canvas.toDataURL("image/png");
|
|
985
|
+
const pdf = new jsPDF("p", "mm", "a4");
|
|
986
|
+
const imgWidth = 210;
|
|
987
|
+
const imgHeight = canvas.height * imgWidth / canvas.width;
|
|
988
|
+
pdf.addImage(imgData, "PNG", 0, 0, imgWidth, imgHeight);
|
|
989
|
+
pdf.save(options.filename || "canvas-export.pdf");
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* Export data to Excel
|
|
993
|
+
*/
|
|
994
|
+
async exportToExcel(data, options = {}) {
|
|
995
|
+
const XLSX = await import("xlsx");
|
|
996
|
+
const wb = XLSX.utils.book_new();
|
|
997
|
+
const apps = data.items.filter((i) => i.type === "app").map((i) => ({ Name: i.name, ...i.metrics, ...i.details }));
|
|
998
|
+
const campaigns = data.items.filter((i) => i.type === "campaign").map((i) => ({ Name: i.name, ...i.metrics, ...i.details }));
|
|
999
|
+
const posts = data.items.filter((i) => i.type === "post").map((i) => ({ Name: i.name, ...i.metrics, ...i.details }));
|
|
1000
|
+
if (apps.length > 0) {
|
|
1001
|
+
XLSX.utils.book_append_sheet(
|
|
1002
|
+
wb,
|
|
1003
|
+
XLSX.utils.json_to_sheet(apps),
|
|
1004
|
+
options.sheetName || "Apps"
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
1007
|
+
if (campaigns.length > 0) {
|
|
1008
|
+
XLSX.utils.book_append_sheet(
|
|
1009
|
+
wb,
|
|
1010
|
+
XLSX.utils.json_to_sheet(campaigns),
|
|
1011
|
+
"Campaigns"
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
if (posts.length > 0) {
|
|
1015
|
+
XLSX.utils.book_append_sheet(
|
|
1016
|
+
wb,
|
|
1017
|
+
XLSX.utils.json_to_sheet(posts),
|
|
1018
|
+
"Posts"
|
|
1019
|
+
);
|
|
1020
|
+
}
|
|
1021
|
+
if (options.includeSummary !== false) {
|
|
1022
|
+
const summary = [
|
|
1023
|
+
{ Metric: "Total Reach", Value: data.summary.totalReach },
|
|
1024
|
+
{
|
|
1025
|
+
Metric: "Total Engagement",
|
|
1026
|
+
Value: data.summary.totalEngagement
|
|
1027
|
+
},
|
|
1028
|
+
{
|
|
1029
|
+
Metric: "Average Score",
|
|
1030
|
+
Value: data.summary.averageScore
|
|
1031
|
+
},
|
|
1032
|
+
{
|
|
1033
|
+
Metric: "Export Date",
|
|
1034
|
+
Value: (/* @__PURE__ */ new Date()).toLocaleDateString()
|
|
1035
|
+
}
|
|
1036
|
+
];
|
|
1037
|
+
XLSX.utils.book_append_sheet(
|
|
1038
|
+
wb,
|
|
1039
|
+
XLSX.utils.json_to_sheet(summary),
|
|
1040
|
+
"Summary"
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
XLSX.writeFile(wb, options.filename || "report.xlsx");
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Export data to CSV
|
|
1047
|
+
*/
|
|
1048
|
+
exportToCSV(data, filename = "report.csv", delimiter = ",") {
|
|
1049
|
+
const headers = ["Type", "Name", "Metrics"];
|
|
1050
|
+
const rows = data.items.map((item) => [
|
|
1051
|
+
item.type,
|
|
1052
|
+
item.name,
|
|
1053
|
+
JSON.stringify(item.metrics)
|
|
1054
|
+
]);
|
|
1055
|
+
const csvContent = [headers, ...rows].map((row) => row.map((cell) => `"${cell}"`).join(delimiter)).join("\n");
|
|
1056
|
+
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
|
|
1057
|
+
const url = URL.createObjectURL(blob);
|
|
1058
|
+
const link = document.createElement("a");
|
|
1059
|
+
link.href = url;
|
|
1060
|
+
link.download = filename;
|
|
1061
|
+
link.click();
|
|
1062
|
+
URL.revokeObjectURL(url);
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Export multiple elements to PDF
|
|
1066
|
+
*/
|
|
1067
|
+
async exportMultipleToPDF(elementIds, options = {}) {
|
|
1068
|
+
const html2canvas = (await import("html2canvas")).default;
|
|
1069
|
+
const jsPDF = (await import("jspdf")).default;
|
|
1070
|
+
const pdf = new jsPDF("p", "mm", "a4");
|
|
1071
|
+
for (let i = 0; i < elementIds.length; i++) {
|
|
1072
|
+
const element = document.getElementById(elementIds[i]);
|
|
1073
|
+
if (!element) continue;
|
|
1074
|
+
const canvas = await html2canvas(element, {
|
|
1075
|
+
scale: options.scale || 2,
|
|
1076
|
+
useCORS: options.useCORS !== false,
|
|
1077
|
+
backgroundColor: options.backgroundColor || "#ffffff"
|
|
1078
|
+
});
|
|
1079
|
+
const imgData = canvas.toDataURL("image/png");
|
|
1080
|
+
const imgWidth = 210;
|
|
1081
|
+
const imgHeight = canvas.height * imgWidth / canvas.width;
|
|
1082
|
+
if (i > 0) {
|
|
1083
|
+
pdf.addPage();
|
|
1084
|
+
}
|
|
1085
|
+
pdf.addImage(imgData, "PNG", 0, 0, imgWidth, imgHeight);
|
|
1086
|
+
}
|
|
1087
|
+
pdf.save(options.filename || "multi-page-report.pdf");
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
|
|
1091
|
+
// src/presentation/hooks/useScreenshotStudio.ts
|
|
1092
|
+
var import_react = require("react");
|
|
1093
|
+
var DEFAULT_SCREENS = [
|
|
1094
|
+
{
|
|
1095
|
+
id: "1",
|
|
1096
|
+
title: "APPGROWTHFACTORY *MODERN*",
|
|
1097
|
+
subtitle: "Create content with AI power",
|
|
1098
|
+
screenshot: null,
|
|
1099
|
+
translations: {
|
|
1100
|
+
"en-US": {
|
|
1101
|
+
title: "APPGROWTHFACTORY *MODERN*",
|
|
1102
|
+
subtitle: "Create content with AI power"
|
|
1103
|
+
},
|
|
1104
|
+
"tr-TR": {
|
|
1105
|
+
title: "APPGROWTHFACTORY *MODERN*",
|
|
1106
|
+
subtitle: "Yapay zeka g\xFCc\xFCyle i\xE7erik \xFCret"
|
|
1107
|
+
}
|
|
1108
|
+
},
|
|
1109
|
+
imageFit: "cover",
|
|
1110
|
+
imageZoom: 1,
|
|
1111
|
+
imageAnchorX: 50,
|
|
1112
|
+
imageAnchorY: 50
|
|
1113
|
+
},
|
|
1114
|
+
{
|
|
1115
|
+
id: "2",
|
|
1116
|
+
title: "CONNECT *GLOBAL*",
|
|
1117
|
+
subtitle: "Post to all platforms instantly",
|
|
1118
|
+
screenshot: null,
|
|
1119
|
+
translations: {
|
|
1120
|
+
"en-US": {
|
|
1121
|
+
title: "CONNECT *GLOBAL*",
|
|
1122
|
+
subtitle: "Post to all platforms instantly"
|
|
1123
|
+
},
|
|
1124
|
+
"tr-TR": {
|
|
1125
|
+
title: "BA\u011ELAN *K\xDCRESEL*",
|
|
1126
|
+
subtitle: "T\xFCm platformlara an\u0131nda g\xF6nder"
|
|
1127
|
+
}
|
|
1128
|
+
},
|
|
1129
|
+
imageFit: "cover",
|
|
1130
|
+
imageZoom: 1,
|
|
1131
|
+
imageAnchorX: 50,
|
|
1132
|
+
imageAnchorY: 50
|
|
1133
|
+
}
|
|
1134
|
+
];
|
|
1135
|
+
var DEFAULT_DESIGN = {
|
|
1136
|
+
backgroundColor: "#6366f1",
|
|
1137
|
+
backgroundGradientEnd: "#a855f7",
|
|
1138
|
+
backgroundImage: null,
|
|
1139
|
+
backgroundBlur: 0,
|
|
1140
|
+
backgroundOverlayOpacity: 0,
|
|
1141
|
+
backgroundPattern: "none",
|
|
1142
|
+
textColor: "#ffffff",
|
|
1143
|
+
highlightColor: "#fbbf24",
|
|
1144
|
+
textAlign: "center",
|
|
1145
|
+
deviceColor: "#1f2937",
|
|
1146
|
+
deviceType: "iphone-15-pro",
|
|
1147
|
+
frameStyle: "realistic",
|
|
1148
|
+
layout: "text-top",
|
|
1149
|
+
scale: 0.85,
|
|
1150
|
+
fontFamily: "Inter",
|
|
1151
|
+
titleFontSize: 60,
|
|
1152
|
+
subtitleFontSize: 24,
|
|
1153
|
+
titleFontWeight: "900",
|
|
1154
|
+
subtitleFontWeight: "500",
|
|
1155
|
+
textCase: "uppercase",
|
|
1156
|
+
lineHeight: 0.9,
|
|
1157
|
+
letterSpacing: -1,
|
|
1158
|
+
rotation: 0,
|
|
1159
|
+
rotationX: 0,
|
|
1160
|
+
rotationY: 0,
|
|
1161
|
+
rotationZ: 0,
|
|
1162
|
+
offsetY: 0,
|
|
1163
|
+
shadowIntensity: 0.5,
|
|
1164
|
+
padding: 40,
|
|
1165
|
+
gap: 20,
|
|
1166
|
+
isPanoramic: false
|
|
1167
|
+
};
|
|
1168
|
+
var DEFAULT_APP_METADATA = {
|
|
1169
|
+
appName: "Growdo",
|
|
1170
|
+
subtitle: "AI Content Generator",
|
|
1171
|
+
promotionalText: "Unlock your creative potential",
|
|
1172
|
+
keywords: "ai, content, marketing, automation, generator",
|
|
1173
|
+
description: "The ultimate tool for AI-powered marketing content.",
|
|
1174
|
+
whatsNew: "New Design Studio integration!"
|
|
1175
|
+
};
|
|
1176
|
+
function useScreenshotStudio(options = {}) {
|
|
1177
|
+
const {
|
|
1178
|
+
config = {},
|
|
1179
|
+
onShowToast = () => {
|
|
1180
|
+
}
|
|
1181
|
+
} = options;
|
|
1182
|
+
const fullConfig = {
|
|
1183
|
+
...DEFAULT_SCREENSHOT_STUDIO_CONFIG,
|
|
1184
|
+
...config
|
|
1185
|
+
};
|
|
1186
|
+
const serviceRef = (0, import_react.useRef)(
|
|
1187
|
+
new ScreenshotStudioService(fullConfig)
|
|
1188
|
+
);
|
|
1189
|
+
const [screens, setScreens] = (0, import_react.useState)(DEFAULT_SCREENS);
|
|
1190
|
+
const [design, setDesign] = (0, import_react.useState)(DEFAULT_DESIGN);
|
|
1191
|
+
const [currentLanguage, setCurrentLanguage] = (0, import_react.useState)(
|
|
1192
|
+
fullConfig.defaultLanguage || "en-US"
|
|
1193
|
+
);
|
|
1194
|
+
const [selectedScreenId, setSelectedScreenId] = (0, import_react.useState)("1");
|
|
1195
|
+
const [isProcessing, setIsProcessing] = (0, import_react.useState)(false);
|
|
1196
|
+
const [isAnalyzing, setIsAnalyzing] = (0, import_react.useState)(false);
|
|
1197
|
+
const [appMetadata, setAppMetadata] = (0, import_react.useState)(
|
|
1198
|
+
DEFAULT_APP_METADATA
|
|
1199
|
+
);
|
|
1200
|
+
const [appIcon, setAppIcon] = (0, import_react.useState)(null);
|
|
1201
|
+
const [projectId, setProjectId] = (0, import_react.useState)(null);
|
|
1202
|
+
const activeScreen = screens.find((s) => s.id === selectedScreenId) || screens[0];
|
|
1203
|
+
const updateDesign = (0, import_react.useCallback)((updates) => {
|
|
1204
|
+
setDesign((prev) => ({ ...prev, ...updates }));
|
|
1205
|
+
}, []);
|
|
1206
|
+
const updateActiveScreen = (0, import_react.useCallback)(
|
|
1207
|
+
(updates) => {
|
|
1208
|
+
setScreens(
|
|
1209
|
+
(prev) => prev.map((screen) => {
|
|
1210
|
+
if (screen.id !== selectedScreenId) return screen;
|
|
1211
|
+
const updatedScreen = { ...screen, ...updates };
|
|
1212
|
+
if (updates.title || updates.subtitle) {
|
|
1213
|
+
updatedScreen.translations = {
|
|
1214
|
+
...updatedScreen.translations,
|
|
1215
|
+
[currentLanguage]: {
|
|
1216
|
+
title: updatedScreen.title,
|
|
1217
|
+
subtitle: updatedScreen.subtitle
|
|
1218
|
+
}
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
return updatedScreen;
|
|
1222
|
+
})
|
|
1223
|
+
);
|
|
1224
|
+
},
|
|
1225
|
+
[selectedScreenId, currentLanguage]
|
|
1226
|
+
);
|
|
1227
|
+
const changeLanguage = (0, import_react.useCallback)(
|
|
1228
|
+
(newLanguage) => {
|
|
1229
|
+
setCurrentLanguage(newLanguage);
|
|
1230
|
+
setScreens(
|
|
1231
|
+
(prev) => prev.map((screen) => {
|
|
1232
|
+
const translation = screen.translations[newLanguage] || screen.translations["en-US"] || { title: screen.title, subtitle: screen.subtitle };
|
|
1233
|
+
return {
|
|
1234
|
+
...screen,
|
|
1235
|
+
title: translation.title,
|
|
1236
|
+
subtitle: translation.subtitle
|
|
1237
|
+
};
|
|
1238
|
+
})
|
|
1239
|
+
);
|
|
1240
|
+
onShowToast("Language Changed", `Switched to ${newLanguage}`);
|
|
1241
|
+
},
|
|
1242
|
+
[onShowToast]
|
|
1243
|
+
);
|
|
1244
|
+
const addScreen = (0, import_react.useCallback)(() => {
|
|
1245
|
+
const newScreen = {
|
|
1246
|
+
id: Date.now().toString(),
|
|
1247
|
+
title: "NEW *POST*",
|
|
1248
|
+
subtitle: "Engage your audience",
|
|
1249
|
+
screenshot: null,
|
|
1250
|
+
translations: {
|
|
1251
|
+
[currentLanguage]: {
|
|
1252
|
+
title: "NEW *POST*",
|
|
1253
|
+
subtitle: "Engage your audience"
|
|
1254
|
+
}
|
|
1255
|
+
},
|
|
1256
|
+
imageFit: "cover",
|
|
1257
|
+
imageZoom: 1,
|
|
1258
|
+
imageAnchorX: 50,
|
|
1259
|
+
imageAnchorY: 50
|
|
1260
|
+
};
|
|
1261
|
+
setScreens((prev) => [...prev, newScreen]);
|
|
1262
|
+
setSelectedScreenId(newScreen.id);
|
|
1263
|
+
onShowToast("Success", "New screen added");
|
|
1264
|
+
}, [currentLanguage, onShowToast]);
|
|
1265
|
+
const removeScreen = (0, import_react.useCallback)(
|
|
1266
|
+
(screenId) => {
|
|
1267
|
+
if (screens.length <= 1) return;
|
|
1268
|
+
setScreens((prev) => {
|
|
1269
|
+
const newScreens = prev.filter((s) => s.id !== screenId);
|
|
1270
|
+
if (selectedScreenId === screenId) {
|
|
1271
|
+
setSelectedScreenId(newScreens[0].id);
|
|
1272
|
+
}
|
|
1273
|
+
return newScreens;
|
|
1274
|
+
});
|
|
1275
|
+
onShowToast("Info", "Screen removed");
|
|
1276
|
+
},
|
|
1277
|
+
[screens.length, selectedScreenId, onShowToast]
|
|
1278
|
+
);
|
|
1279
|
+
const duplicateScreen = (0, import_react.useCallback)(
|
|
1280
|
+
(screenId) => {
|
|
1281
|
+
const screenToDuplicate = screens.find((s) => s.id === screenId);
|
|
1282
|
+
if (!screenToDuplicate) return;
|
|
1283
|
+
const newScreen = {
|
|
1284
|
+
...screenToDuplicate,
|
|
1285
|
+
id: Date.now().toString(),
|
|
1286
|
+
title: `${screenToDuplicate.title} (Copy)`,
|
|
1287
|
+
translations: { ...screenToDuplicate.translations }
|
|
1288
|
+
};
|
|
1289
|
+
setScreens((prev) => {
|
|
1290
|
+
const index = prev.findIndex((s) => s.id === screenId);
|
|
1291
|
+
const newScreens = [...prev];
|
|
1292
|
+
newScreens.splice(index + 1, 0, newScreen);
|
|
1293
|
+
return newScreens;
|
|
1294
|
+
});
|
|
1295
|
+
setSelectedScreenId(newScreen.id);
|
|
1296
|
+
onShowToast("Success", "Screen duplicated");
|
|
1297
|
+
},
|
|
1298
|
+
[screens, onShowToast]
|
|
1299
|
+
);
|
|
1300
|
+
const applyTemplate = (0, import_react.useCallback)(
|
|
1301
|
+
(template) => {
|
|
1302
|
+
setDesign((prev) => ({ ...prev, ...template.design }));
|
|
1303
|
+
onShowToast("Template Applied", template.name);
|
|
1304
|
+
},
|
|
1305
|
+
[onShowToast]
|
|
1306
|
+
);
|
|
1307
|
+
const generateDesignedScreenshot = (0, import_react.useCallback)(
|
|
1308
|
+
async (screenId) => {
|
|
1309
|
+
const screen = screens.find((s) => s.id === screenId);
|
|
1310
|
+
if (!screen) return;
|
|
1311
|
+
setIsProcessing(true);
|
|
1312
|
+
try {
|
|
1313
|
+
const result = await serviceRef.current.generateDesignedScreenshot(
|
|
1314
|
+
screen,
|
|
1315
|
+
design
|
|
1316
|
+
);
|
|
1317
|
+
onShowToast("Success", "Screenshot generated");
|
|
1318
|
+
return { url: result.url, id: result.id };
|
|
1319
|
+
} catch {
|
|
1320
|
+
onShowToast("Error", "Screenshot generation failed");
|
|
1321
|
+
} finally {
|
|
1322
|
+
setIsProcessing(false);
|
|
1323
|
+
}
|
|
1324
|
+
},
|
|
1325
|
+
[screens, design, onShowToast]
|
|
1326
|
+
);
|
|
1327
|
+
const exportAllScreensAsZip = (0, import_react.useCallback)(
|
|
1328
|
+
async (format = "png") => {
|
|
1329
|
+
setIsProcessing(true);
|
|
1330
|
+
try {
|
|
1331
|
+
const project = {
|
|
1332
|
+
id: projectId || "default-project",
|
|
1333
|
+
name: "Export Project",
|
|
1334
|
+
design,
|
|
1335
|
+
screens,
|
|
1336
|
+
currentLanguage,
|
|
1337
|
+
selectedScreenId,
|
|
1338
|
+
appMetadata,
|
|
1339
|
+
appIcon,
|
|
1340
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1341
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1342
|
+
};
|
|
1343
|
+
const blob = await serviceRef.current.exportProjectAsZip(project);
|
|
1344
|
+
const url = URL.createObjectURL(blob);
|
|
1345
|
+
const link = document.createElement("a");
|
|
1346
|
+
link.href = url;
|
|
1347
|
+
link.download = `Studio_${currentLanguage}_${Date.now()}.zip`;
|
|
1348
|
+
link.click();
|
|
1349
|
+
URL.revokeObjectURL(url);
|
|
1350
|
+
onShowToast(
|
|
1351
|
+
"Success",
|
|
1352
|
+
`Exported ${screens.length} screens in ZIP archive.`
|
|
1353
|
+
);
|
|
1354
|
+
} catch {
|
|
1355
|
+
onShowToast("Error", "ZIP export failed");
|
|
1356
|
+
} finally {
|
|
1357
|
+
setIsProcessing(false);
|
|
1358
|
+
}
|
|
1359
|
+
},
|
|
1360
|
+
[
|
|
1361
|
+
screens,
|
|
1362
|
+
design,
|
|
1363
|
+
currentLanguage,
|
|
1364
|
+
appMetadata,
|
|
1365
|
+
appIcon,
|
|
1366
|
+
projectId,
|
|
1367
|
+
selectedScreenId,
|
|
1368
|
+
onShowToast
|
|
1369
|
+
]
|
|
1370
|
+
);
|
|
1371
|
+
const _getProjectState = (0, import_react.useCallback)(() => {
|
|
1372
|
+
return {
|
|
1373
|
+
id: projectId || "appgrowthfactory_main_project",
|
|
1374
|
+
name: "Default Project",
|
|
1375
|
+
design,
|
|
1376
|
+
screens,
|
|
1377
|
+
currentLanguage,
|
|
1378
|
+
selectedScreenId,
|
|
1379
|
+
appMetadata,
|
|
1380
|
+
appIcon,
|
|
1381
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1382
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1383
|
+
};
|
|
1384
|
+
}, [
|
|
1385
|
+
design,
|
|
1386
|
+
screens,
|
|
1387
|
+
currentLanguage,
|
|
1388
|
+
selectedScreenId,
|
|
1389
|
+
appMetadata,
|
|
1390
|
+
appIcon,
|
|
1391
|
+
projectId
|
|
1392
|
+
]);
|
|
1393
|
+
const _loadProjectState = (0, import_react.useCallback)(
|
|
1394
|
+
(state) => {
|
|
1395
|
+
if (!state) return;
|
|
1396
|
+
if (state.screens) setScreens(state.screens);
|
|
1397
|
+
if (state.design) setDesign(state.design);
|
|
1398
|
+
if (state.currentLanguage) setCurrentLanguage(state.currentLanguage);
|
|
1399
|
+
if (state.selectedScreenId && state.screens?.find((s) => s.id === state.selectedScreenId)) {
|
|
1400
|
+
setSelectedScreenId(state.selectedScreenId);
|
|
1401
|
+
}
|
|
1402
|
+
if (state.appMetadata) setAppMetadata(state.appMetadata);
|
|
1403
|
+
if (state.appIcon !== void 0) setAppIcon(state.appIcon);
|
|
1404
|
+
},
|
|
1405
|
+
[]
|
|
1406
|
+
);
|
|
1407
|
+
return {
|
|
1408
|
+
screens,
|
|
1409
|
+
design,
|
|
1410
|
+
currentLanguage,
|
|
1411
|
+
selectedScreenId,
|
|
1412
|
+
activeScreen,
|
|
1413
|
+
isProcessing,
|
|
1414
|
+
isAnalyzing,
|
|
1415
|
+
appMetadata,
|
|
1416
|
+
appIcon,
|
|
1417
|
+
projectId,
|
|
1418
|
+
updateDesign,
|
|
1419
|
+
updateActiveScreen,
|
|
1420
|
+
setSelectedScreenId,
|
|
1421
|
+
changeLanguage,
|
|
1422
|
+
applyTemplate,
|
|
1423
|
+
addScreen,
|
|
1424
|
+
removeScreen,
|
|
1425
|
+
duplicateScreen,
|
|
1426
|
+
generateDesignedScreenshot,
|
|
1427
|
+
exportAllScreensAsZip,
|
|
1428
|
+
setAppMetadata,
|
|
1429
|
+
setAppIcon,
|
|
1430
|
+
setProjectId,
|
|
1431
|
+
_getProjectState,
|
|
1432
|
+
_loadProjectState
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
// src/presentation/hooks/useCanvasExport.ts
|
|
1437
|
+
var import_react2 = require("react");
|
|
1438
|
+
function useCanvasExport(options = {}) {
|
|
1439
|
+
const { config = DEFAULT_CANVAS_CONFIG } = options;
|
|
1440
|
+
const [isExporting, setIsExporting] = (0, import_react2.useState)(false);
|
|
1441
|
+
const [progress, setProgress] = (0, import_react2.useState)(0);
|
|
1442
|
+
const [error, setError] = (0, import_react2.useState)(null);
|
|
1443
|
+
const renderer = new CanvasRenderer(config);
|
|
1444
|
+
const exportToBlob = (0, import_react2.useCallback)(
|
|
1445
|
+
async (canvas, options2) => {
|
|
1446
|
+
setIsExporting(true);
|
|
1447
|
+
setProgress(0);
|
|
1448
|
+
setError(null);
|
|
1449
|
+
try {
|
|
1450
|
+
const result = await renderer.exportToBlob(canvas, options2);
|
|
1451
|
+
setProgress(100);
|
|
1452
|
+
return result;
|
|
1453
|
+
} catch (err) {
|
|
1454
|
+
const errorMessage = err instanceof Error ? err.message : "Export failed";
|
|
1455
|
+
setError(errorMessage);
|
|
1456
|
+
return null;
|
|
1457
|
+
} finally {
|
|
1458
|
+
setIsExporting(false);
|
|
1459
|
+
}
|
|
1460
|
+
},
|
|
1461
|
+
[renderer]
|
|
1462
|
+
);
|
|
1463
|
+
const exportToDataURL = (0, import_react2.useCallback)(
|
|
1464
|
+
(canvas, format, quality) => {
|
|
1465
|
+
try {
|
|
1466
|
+
return renderer.exportToDataURL(canvas, format, quality);
|
|
1467
|
+
} catch (err) {
|
|
1468
|
+
const errorMessage = err instanceof Error ? err.message : "Export failed";
|
|
1469
|
+
setError(errorMessage);
|
|
1470
|
+
return null;
|
|
1471
|
+
}
|
|
1472
|
+
},
|
|
1473
|
+
[renderer]
|
|
1474
|
+
);
|
|
1475
|
+
const downloadCanvas = (0, import_react2.useCallback)(
|
|
1476
|
+
async (canvas, filename, options2) => {
|
|
1477
|
+
setIsExporting(true);
|
|
1478
|
+
setProgress(0);
|
|
1479
|
+
setError(null);
|
|
1480
|
+
try {
|
|
1481
|
+
const result = await exportToBlob(canvas, options2);
|
|
1482
|
+
if (!result) {
|
|
1483
|
+
throw new Error("Export failed");
|
|
1484
|
+
}
|
|
1485
|
+
setProgress(50);
|
|
1486
|
+
const link = document.createElement("a");
|
|
1487
|
+
link.href = result.url;
|
|
1488
|
+
link.download = filename;
|
|
1489
|
+
link.click();
|
|
1490
|
+
setProgress(100);
|
|
1491
|
+
setTimeout(() => {
|
|
1492
|
+
URL.revokeObjectURL(result.url);
|
|
1493
|
+
}, 100);
|
|
1494
|
+
} catch (err) {
|
|
1495
|
+
const errorMessage = err instanceof Error ? err.message : "Download failed";
|
|
1496
|
+
setError(errorMessage);
|
|
1497
|
+
} finally {
|
|
1498
|
+
setIsExporting(false);
|
|
1499
|
+
}
|
|
1500
|
+
},
|
|
1501
|
+
[exportToBlob]
|
|
1502
|
+
);
|
|
1503
|
+
const cloneCanvas = (0, import_react2.useCallback)(
|
|
1504
|
+
(canvas) => {
|
|
1505
|
+
try {
|
|
1506
|
+
return renderer.cloneCanvas(canvas);
|
|
1507
|
+
} catch (err) {
|
|
1508
|
+
const errorMessage = err instanceof Error ? err.message : "Clone failed";
|
|
1509
|
+
setError(errorMessage);
|
|
1510
|
+
return null;
|
|
1511
|
+
}
|
|
1512
|
+
},
|
|
1513
|
+
[renderer]
|
|
1514
|
+
);
|
|
1515
|
+
return {
|
|
1516
|
+
isExporting,
|
|
1517
|
+
progress,
|
|
1518
|
+
error,
|
|
1519
|
+
exportToBlob,
|
|
1520
|
+
exportToDataURL,
|
|
1521
|
+
downloadCanvas,
|
|
1522
|
+
cloneCanvas
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
// src/presentation/hooks/useReportExport.ts
|
|
1527
|
+
var import_react3 = require("react");
|
|
1528
|
+
function useReportExport() {
|
|
1529
|
+
const [isExporting, setIsExporting] = (0, import_react3.useState)(false);
|
|
1530
|
+
const [error, setError] = (0, import_react3.useState)(null);
|
|
1531
|
+
const reportService = ReportService.getInstance();
|
|
1532
|
+
const exportToPDF = (0, import_react3.useCallback)(
|
|
1533
|
+
async (elementId, options) => {
|
|
1534
|
+
setIsExporting(true);
|
|
1535
|
+
setError(null);
|
|
1536
|
+
try {
|
|
1537
|
+
await reportService.exportToPDF(elementId, options);
|
|
1538
|
+
} catch (err) {
|
|
1539
|
+
const errorMessage = err instanceof Error ? err.message : "PDF export failed";
|
|
1540
|
+
setError(errorMessage);
|
|
1541
|
+
throw err;
|
|
1542
|
+
} finally {
|
|
1543
|
+
setIsExporting(false);
|
|
1544
|
+
}
|
|
1545
|
+
},
|
|
1546
|
+
[reportService]
|
|
1547
|
+
);
|
|
1548
|
+
const exportCanvasToPDF = (0, import_react3.useCallback)(
|
|
1549
|
+
async (canvas, options) => {
|
|
1550
|
+
setIsExporting(true);
|
|
1551
|
+
setError(null);
|
|
1552
|
+
try {
|
|
1553
|
+
await reportService.exportCanvasToPDF(canvas, options);
|
|
1554
|
+
} catch (err) {
|
|
1555
|
+
const errorMessage = err instanceof Error ? err.message : "Canvas PDF export failed";
|
|
1556
|
+
setError(errorMessage);
|
|
1557
|
+
throw err;
|
|
1558
|
+
} finally {
|
|
1559
|
+
setIsExporting(false);
|
|
1560
|
+
}
|
|
1561
|
+
},
|
|
1562
|
+
[reportService]
|
|
1563
|
+
);
|
|
1564
|
+
const exportMultipleToPDF = (0, import_react3.useCallback)(
|
|
1565
|
+
async (elementIds, options) => {
|
|
1566
|
+
setIsExporting(true);
|
|
1567
|
+
setError(null);
|
|
1568
|
+
try {
|
|
1569
|
+
await reportService.exportMultipleToPDF(elementIds, options);
|
|
1570
|
+
} catch (err) {
|
|
1571
|
+
const errorMessage = err instanceof Error ? err.message : "Multi-page PDF export failed";
|
|
1572
|
+
setError(errorMessage);
|
|
1573
|
+
throw err;
|
|
1574
|
+
} finally {
|
|
1575
|
+
setIsExporting(false);
|
|
1576
|
+
}
|
|
1577
|
+
},
|
|
1578
|
+
[reportService]
|
|
1579
|
+
);
|
|
1580
|
+
const exportToExcel = (0, import_react3.useCallback)(
|
|
1581
|
+
async (data, options) => {
|
|
1582
|
+
setIsExporting(true);
|
|
1583
|
+
setError(null);
|
|
1584
|
+
try {
|
|
1585
|
+
await reportService.exportToExcel(data, options);
|
|
1586
|
+
} catch (err) {
|
|
1587
|
+
const errorMessage = err instanceof Error ? err.message : "Excel export failed";
|
|
1588
|
+
setError(errorMessage);
|
|
1589
|
+
throw err;
|
|
1590
|
+
} finally {
|
|
1591
|
+
setIsExporting(false);
|
|
1592
|
+
}
|
|
1593
|
+
},
|
|
1594
|
+
[reportService]
|
|
1595
|
+
);
|
|
1596
|
+
const exportToCSV = (0, import_react3.useCallback)(
|
|
1597
|
+
(data, filename, delimiter) => {
|
|
1598
|
+
try {
|
|
1599
|
+
reportService.exportToCSV(data, filename, delimiter);
|
|
1600
|
+
} catch (err) {
|
|
1601
|
+
const errorMessage = err instanceof Error ? err.message : "CSV export failed";
|
|
1602
|
+
setError(errorMessage);
|
|
1603
|
+
throw err;
|
|
1604
|
+
}
|
|
1605
|
+
},
|
|
1606
|
+
[reportService]
|
|
1607
|
+
);
|
|
1608
|
+
return {
|
|
1609
|
+
isExporting,
|
|
1610
|
+
error,
|
|
1611
|
+
exportToPDF,
|
|
1612
|
+
exportCanvasToPDF,
|
|
1613
|
+
exportMultipleToPDF,
|
|
1614
|
+
exportToExcel,
|
|
1615
|
+
exportToCSV
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1619
|
+
0 && (module.exports = {
|
|
1620
|
+
CanvasRenderer,
|
|
1621
|
+
DEFAULT_CANVAS_CONFIG,
|
|
1622
|
+
DEFAULT_DEVICE_CONFIG,
|
|
1623
|
+
DEFAULT_SCREENSHOT_STUDIO_CONFIG,
|
|
1624
|
+
DEVICE_DIMENSIONS,
|
|
1625
|
+
DeviceConfigService,
|
|
1626
|
+
PatternRenderer,
|
|
1627
|
+
ReportService,
|
|
1628
|
+
ScreenshotRenderer,
|
|
1629
|
+
ScreenshotStudioService,
|
|
1630
|
+
useCanvasExport,
|
|
1631
|
+
useReportExport,
|
|
1632
|
+
useScreenshotStudio
|
|
1633
|
+
});
|