@umituz/web-canvas 1.2.1 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/application/index.ts +0 -4
- package/src/application/services/ReportService.ts +0 -22
- package/src/application/services/ScreenshotStudioService.ts +1 -35
- package/src/domain/config/CanvasConfig.ts +0 -16
- package/src/domain/config/DeviceConfig.ts +0 -18
- package/src/domain/config/ScreenshotStudioConfig.ts +0 -29
- package/src/domain/config/index.ts +0 -4
- package/src/domain/index.ts +0 -4
- package/src/domain/types/canvas.types.ts +1 -35
- package/src/domain/types/index.ts +0 -4
- package/src/domain/types/report.types.ts +0 -31
- package/src/domain/types/screenshot.types.ts +1 -65
- package/src/index.ts +0 -12
- package/src/infrastructure/index.ts +0 -4
- package/src/infrastructure/renderers/CanvasRenderer.ts +0 -55
- package/src/infrastructure/renderers/PatternRenderer.ts +0 -30
- package/src/infrastructure/renderers/ScreenshotRenderer.ts +0 -48
- package/src/infrastructure/services/DeviceConfigService.ts +0 -10
- package/src/infrastructure/services/minimalXlsx.ts +0 -11
- package/src/presentation/hooks/useCanvasExport.ts +2 -22
- package/src/presentation/hooks/useReportExport.ts +0 -12
- package/src/presentation/hooks/useScreenshotStudio.ts +1 -45
- package/src/presentation/index.ts +0 -4
package/src/index.ts
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @umituz/web-canvas
|
|
3
|
-
* Comprehensive canvas and screenshot generation library
|
|
4
|
-
*/
|
|
5
1
|
|
|
6
|
-
// Domain exports
|
|
7
2
|
export type {
|
|
8
|
-
// Canvas types
|
|
9
3
|
CanvasFormat,
|
|
10
4
|
CanvasQuality,
|
|
11
5
|
CanvasDimensions,
|
|
@@ -17,7 +11,6 @@ export type {
|
|
|
17
11
|
CanvasResult,
|
|
18
12
|
CanvasOperation,
|
|
19
13
|
|
|
20
|
-
// Screenshot types
|
|
21
14
|
DeviceType,
|
|
22
15
|
FrameStyle,
|
|
23
16
|
LayoutStyle,
|
|
@@ -34,7 +27,6 @@ export type {
|
|
|
34
27
|
AppStoreMetadata,
|
|
35
28
|
ScreenshotProject,
|
|
36
29
|
|
|
37
|
-
// Report types
|
|
38
30
|
ReportItemType,
|
|
39
31
|
ReportItem,
|
|
40
32
|
ReportSummary,
|
|
@@ -45,7 +37,6 @@ export type {
|
|
|
45
37
|
CSVExportOptions,
|
|
46
38
|
ReportExportResult,
|
|
47
39
|
|
|
48
|
-
// Freehand types
|
|
49
40
|
FreehandPoint,
|
|
50
41
|
FreehandStroke,
|
|
51
42
|
UseFreehandCanvasOptions,
|
|
@@ -72,7 +63,6 @@ export {
|
|
|
72
63
|
DEFAULT_SCREENSHOT_STUDIO_CONFIG,
|
|
73
64
|
} from './domain/config/ScreenshotStudioConfig';
|
|
74
65
|
|
|
75
|
-
// Infrastructure exports
|
|
76
66
|
export { CanvasRenderer } from './infrastructure/renderers/CanvasRenderer';
|
|
77
67
|
export { PatternRenderer } from './infrastructure/renderers/PatternRenderer';
|
|
78
68
|
export {
|
|
@@ -82,13 +72,11 @@ export {
|
|
|
82
72
|
DeviceConfigService,
|
|
83
73
|
} from './infrastructure/services/DeviceConfigService';
|
|
84
74
|
|
|
85
|
-
// Application exports
|
|
86
75
|
export {
|
|
87
76
|
ScreenshotStudioService,
|
|
88
77
|
} from './application/services/ScreenshotStudioService';
|
|
89
78
|
export { ReportService } from './application/services/ReportService';
|
|
90
79
|
|
|
91
|
-
// Presentation exports
|
|
92
80
|
export {
|
|
93
81
|
useScreenshotStudio,
|
|
94
82
|
type UseScreenshotStudioOptions,
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Canvas Renderer
|
|
3
|
-
* Core canvas rendering operations
|
|
4
|
-
*/
|
|
5
1
|
|
|
6
2
|
import type {
|
|
7
3
|
CanvasDimensions,
|
|
@@ -20,9 +16,6 @@ export class CanvasRenderer {
|
|
|
20
16
|
this.imageCache = new Map();
|
|
21
17
|
}
|
|
22
18
|
|
|
23
|
-
/**
|
|
24
|
-
* Create a new canvas element
|
|
25
|
-
*/
|
|
26
19
|
public createCanvas(dimensions: CanvasDimensions): HTMLCanvasElement {
|
|
27
20
|
const canvas = document.createElement('canvas');
|
|
28
21
|
canvas.width = dimensions.width;
|
|
@@ -30,18 +23,12 @@ export class CanvasRenderer {
|
|
|
30
23
|
return canvas;
|
|
31
24
|
}
|
|
32
25
|
|
|
33
|
-
/**
|
|
34
|
-
* Get canvas 2D context
|
|
35
|
-
*/
|
|
36
26
|
public getContext(
|
|
37
27
|
canvas: HTMLCanvasElement
|
|
38
28
|
): CanvasRenderingContext2D | null {
|
|
39
29
|
return canvas.getContext('2d');
|
|
40
30
|
}
|
|
41
31
|
|
|
42
|
-
/**
|
|
43
|
-
* Apply context configuration
|
|
44
|
-
*/
|
|
45
32
|
public configureContext(
|
|
46
33
|
ctx: CanvasRenderingContext2D,
|
|
47
34
|
config: CanvasContextConfig
|
|
@@ -75,9 +62,6 @@ export class CanvasRenderer {
|
|
|
75
62
|
}
|
|
76
63
|
}
|
|
77
64
|
|
|
78
|
-
/**
|
|
79
|
-
* Clear canvas
|
|
80
|
-
*/
|
|
81
65
|
public clearCanvas(
|
|
82
66
|
ctx: CanvasRenderingContext2D,
|
|
83
67
|
dimensions: CanvasDimensions
|
|
@@ -85,9 +69,6 @@ export class CanvasRenderer {
|
|
|
85
69
|
ctx.clearRect(0, 0, dimensions.width, dimensions.height);
|
|
86
70
|
}
|
|
87
71
|
|
|
88
|
-
/**
|
|
89
|
-
* Fill rectangle
|
|
90
|
-
*/
|
|
91
72
|
public fillRect(
|
|
92
73
|
ctx: CanvasRenderingContext2D,
|
|
93
74
|
x: number,
|
|
@@ -102,9 +83,6 @@ export class CanvasRenderer {
|
|
|
102
83
|
ctx.fillRect(x, y, width, height);
|
|
103
84
|
}
|
|
104
85
|
|
|
105
|
-
/**
|
|
106
|
-
* Stroke rectangle
|
|
107
|
-
*/
|
|
108
86
|
public strokeRect(
|
|
109
87
|
ctx: CanvasRenderingContext2D,
|
|
110
88
|
x: number,
|
|
@@ -123,9 +101,6 @@ export class CanvasRenderer {
|
|
|
123
101
|
ctx.strokeRect(x, y, width, height);
|
|
124
102
|
}
|
|
125
103
|
|
|
126
|
-
/**
|
|
127
|
-
* Draw text
|
|
128
|
-
*/
|
|
129
104
|
public drawText(
|
|
130
105
|
ctx: CanvasRenderingContext2D,
|
|
131
106
|
text: string,
|
|
@@ -139,9 +114,6 @@ export class CanvasRenderer {
|
|
|
139
114
|
ctx.fillText(text, x, y);
|
|
140
115
|
}
|
|
141
116
|
|
|
142
|
-
/**
|
|
143
|
-
* Draw image
|
|
144
|
-
*/
|
|
145
117
|
public async drawImage(
|
|
146
118
|
ctx: CanvasRenderingContext2D,
|
|
147
119
|
imageSource: string | HTMLImageElement,
|
|
@@ -165,11 +137,7 @@ export class CanvasRenderer {
|
|
|
165
137
|
}
|
|
166
138
|
}
|
|
167
139
|
|
|
168
|
-
/**
|
|
169
|
-
* Load image from URL with caching
|
|
170
|
-
*/
|
|
171
140
|
private loadImage(url: string): Promise<HTMLImageElement> {
|
|
172
|
-
// Check cache first
|
|
173
141
|
if (this.imageCache.has(url)) {
|
|
174
142
|
return Promise.resolve(this.imageCache.get(url)!);
|
|
175
143
|
}
|
|
@@ -180,7 +148,6 @@ export class CanvasRenderer {
|
|
|
180
148
|
img.crossOrigin = 'anonymous';
|
|
181
149
|
}
|
|
182
150
|
img.onload = () => {
|
|
183
|
-
// Cache the loaded image
|
|
184
151
|
this.imageCache.set(url, img);
|
|
185
152
|
resolve(img);
|
|
186
153
|
};
|
|
@@ -189,23 +156,14 @@ export class CanvasRenderer {
|
|
|
189
156
|
});
|
|
190
157
|
}
|
|
191
158
|
|
|
192
|
-
/**
|
|
193
|
-
* Clear image cache to free memory
|
|
194
|
-
*/
|
|
195
159
|
public clearImageCache(): void {
|
|
196
160
|
this.imageCache.clear();
|
|
197
161
|
}
|
|
198
162
|
|
|
199
|
-
/**
|
|
200
|
-
* Get cache size for monitoring
|
|
201
|
-
*/
|
|
202
163
|
public getCacheSize(): number {
|
|
203
164
|
return this.imageCache.size;
|
|
204
165
|
}
|
|
205
166
|
|
|
206
|
-
/**
|
|
207
|
-
* Create gradient
|
|
208
|
-
*/
|
|
209
167
|
public createLinearGradient(
|
|
210
168
|
ctx: CanvasRenderingContext2D,
|
|
211
169
|
x1: number,
|
|
@@ -221,9 +179,6 @@ export class CanvasRenderer {
|
|
|
221
179
|
return gradient;
|
|
222
180
|
}
|
|
223
181
|
|
|
224
|
-
/**
|
|
225
|
-
* Export canvas to blob
|
|
226
|
-
*/
|
|
227
182
|
public async exportToBlob(
|
|
228
183
|
canvas: HTMLCanvasElement,
|
|
229
184
|
options: CanvasExportOptions = {}
|
|
@@ -232,7 +187,6 @@ export class CanvasRenderer {
|
|
|
232
187
|
const quality = options.quality || this.config.defaultQuality;
|
|
233
188
|
const scale = options.scale || this.config.scale;
|
|
234
189
|
|
|
235
|
-
// Scale canvas if needed
|
|
236
190
|
const scaledCanvas = (scale || 1) !== 1 ? this.scaleCanvas(canvas, scale || 1) : canvas;
|
|
237
191
|
|
|
238
192
|
const mimeType = `image/${format || 'png'}`;
|
|
@@ -262,9 +216,6 @@ export class CanvasRenderer {
|
|
|
262
216
|
};
|
|
263
217
|
}
|
|
264
218
|
|
|
265
|
-
/**
|
|
266
|
-
* Export canvas to data URL
|
|
267
|
-
*/
|
|
268
219
|
public exportToDataURL(
|
|
269
220
|
canvas: HTMLCanvasElement,
|
|
270
221
|
format?: string,
|
|
@@ -275,9 +226,6 @@ export class CanvasRenderer {
|
|
|
275
226
|
return canvas.toDataURL(mimeType, q);
|
|
276
227
|
}
|
|
277
228
|
|
|
278
|
-
/**
|
|
279
|
-
* Scale canvas
|
|
280
|
-
*/
|
|
281
229
|
private scaleCanvas(
|
|
282
230
|
canvas: HTMLCanvasElement,
|
|
283
231
|
scale: number
|
|
@@ -296,9 +244,6 @@ export class CanvasRenderer {
|
|
|
296
244
|
return scaled;
|
|
297
245
|
}
|
|
298
246
|
|
|
299
|
-
/**
|
|
300
|
-
* Clone canvas
|
|
301
|
-
*/
|
|
302
247
|
public cloneCanvas(canvas: HTMLCanvasElement): HTMLCanvasElement {
|
|
303
248
|
const clone = document.createElement('canvas');
|
|
304
249
|
clone.width = canvas.width;
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pattern Renderer
|
|
3
|
-
* Background pattern rendering operations
|
|
4
|
-
*/
|
|
5
1
|
|
|
6
2
|
import type { CanvasDimensions } from '../../domain/types/canvas.types';
|
|
7
3
|
import type { BackgroundPattern } from '../../domain/types/screenshot.types';
|
|
@@ -10,9 +6,6 @@ export class PatternRenderer {
|
|
|
10
6
|
private patternCanvasCache: Map<string, HTMLCanvasElement> = new Map();
|
|
11
7
|
private readonly MAX_CACHE_SIZE = 10;
|
|
12
8
|
|
|
13
|
-
/**
|
|
14
|
-
* Render pattern on canvas
|
|
15
|
-
*/
|
|
16
9
|
public renderPattern(
|
|
17
10
|
ctx: CanvasRenderingContext2D,
|
|
18
11
|
pattern: BackgroundPattern,
|
|
@@ -45,16 +38,10 @@ export class PatternRenderer {
|
|
|
45
38
|
ctx.restore();
|
|
46
39
|
}
|
|
47
40
|
|
|
48
|
-
/**
|
|
49
|
-
* Clear pattern cache to free memory
|
|
50
|
-
*/
|
|
51
41
|
public clearCache(): void {
|
|
52
42
|
this.patternCanvasCache.clear();
|
|
53
43
|
}
|
|
54
44
|
|
|
55
|
-
/**
|
|
56
|
-
* Render dot pattern - optimized with batch rendering
|
|
57
|
-
*/
|
|
58
45
|
private renderDotPattern(
|
|
59
46
|
ctx: CanvasRenderingContext2D,
|
|
60
47
|
dimensions: CanvasDimensions,
|
|
@@ -64,7 +51,6 @@ export class PatternRenderer {
|
|
|
64
51
|
const spacing = 30;
|
|
65
52
|
const radius = 2;
|
|
66
53
|
|
|
67
|
-
// Batch rendering for better performance
|
|
68
54
|
ctx.beginPath();
|
|
69
55
|
for (let x = spacing; x < dimensions.width; x += spacing) {
|
|
70
56
|
for (let y = spacing; y < dimensions.height; y += spacing) {
|
|
@@ -75,9 +61,6 @@ export class PatternRenderer {
|
|
|
75
61
|
ctx.fill();
|
|
76
62
|
}
|
|
77
63
|
|
|
78
|
-
/**
|
|
79
|
-
* Render grid pattern - optimized with single path
|
|
80
|
-
*/
|
|
81
64
|
private renderGridPattern(
|
|
82
65
|
ctx: CanvasRenderingContext2D,
|
|
83
66
|
dimensions: CanvasDimensions,
|
|
@@ -89,13 +72,11 @@ export class PatternRenderer {
|
|
|
89
72
|
|
|
90
73
|
ctx.beginPath();
|
|
91
74
|
|
|
92
|
-
// Vertical lines
|
|
93
75
|
for (let x = 0; x <= dimensions.width; x += spacing) {
|
|
94
76
|
ctx.moveTo(x, 0);
|
|
95
77
|
ctx.lineTo(x, dimensions.height);
|
|
96
78
|
}
|
|
97
79
|
|
|
98
|
-
// Horizontal lines
|
|
99
80
|
for (let y = 0; y <= dimensions.height; y += spacing) {
|
|
100
81
|
ctx.moveTo(0, y);
|
|
101
82
|
ctx.lineTo(dimensions.width, y);
|
|
@@ -104,9 +85,6 @@ export class PatternRenderer {
|
|
|
104
85
|
ctx.stroke();
|
|
105
86
|
}
|
|
106
87
|
|
|
107
|
-
/**
|
|
108
|
-
* Render line pattern - optimized with single path
|
|
109
|
-
*/
|
|
110
88
|
private renderLinePattern(
|
|
111
89
|
ctx: CanvasRenderingContext2D,
|
|
112
90
|
dimensions: CanvasDimensions,
|
|
@@ -118,7 +96,6 @@ export class PatternRenderer {
|
|
|
118
96
|
|
|
119
97
|
ctx.beginPath();
|
|
120
98
|
|
|
121
|
-
// Draw all lines in a single path
|
|
122
99
|
for (let y = spacing; y < dimensions.height; y += spacing) {
|
|
123
100
|
ctx.moveTo(0, y);
|
|
124
101
|
ctx.lineTo(dimensions.width, y);
|
|
@@ -127,9 +104,6 @@ export class PatternRenderer {
|
|
|
127
104
|
ctx.stroke();
|
|
128
105
|
}
|
|
129
106
|
|
|
130
|
-
/**
|
|
131
|
-
* Render wave pattern - optimized with pre-calculated values
|
|
132
|
-
*/
|
|
133
107
|
private renderWavePattern(
|
|
134
108
|
ctx: CanvasRenderingContext2D,
|
|
135
109
|
dimensions: CanvasDimensions,
|
|
@@ -145,7 +119,6 @@ export class PatternRenderer {
|
|
|
145
119
|
for (let y = spacing; y < dimensions.height; y += spacing) {
|
|
146
120
|
ctx.beginPath();
|
|
147
121
|
|
|
148
|
-
// Pre-calculate wave points and use more efficient path drawing
|
|
149
122
|
const startX = 0;
|
|
150
123
|
const startY = y + Math.sin(startX * frequency) * amplitude;
|
|
151
124
|
ctx.moveTo(startX, startY);
|
|
@@ -159,9 +132,6 @@ export class PatternRenderer {
|
|
|
159
132
|
}
|
|
160
133
|
}
|
|
161
134
|
|
|
162
|
-
/**
|
|
163
|
-
* Render checker pattern
|
|
164
|
-
*/
|
|
165
135
|
private renderCheckerPattern(
|
|
166
136
|
ctx: CanvasRenderingContext2D,
|
|
167
137
|
dimensions: CanvasDimensions,
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Screenshot Renderer
|
|
3
|
-
* Device frame and screenshot rendering operations
|
|
4
|
-
*/
|
|
5
1
|
|
|
6
2
|
import type {
|
|
7
3
|
CanvasDimensions,
|
|
@@ -31,18 +27,12 @@ export class ScreenshotRenderer {
|
|
|
31
27
|
this.deviceConfigService = DeviceConfigService.getInstance();
|
|
32
28
|
}
|
|
33
29
|
|
|
34
|
-
/**
|
|
35
|
-
* Clear render cache to free memory
|
|
36
|
-
*/
|
|
37
30
|
public clearCache(): void {
|
|
38
31
|
this.renderCache.clear();
|
|
39
32
|
this.canvasRenderer.clearImageCache?.();
|
|
40
33
|
this.patternRenderer.clearCache?.();
|
|
41
34
|
}
|
|
42
35
|
|
|
43
|
-
/**
|
|
44
|
-
* Render device frame
|
|
45
|
-
*/
|
|
46
36
|
public renderDeviceFrame(
|
|
47
37
|
ctx: CanvasRenderingContext2D,
|
|
48
38
|
dimensions: CanvasDimensions,
|
|
@@ -72,9 +62,6 @@ export class ScreenshotRenderer {
|
|
|
72
62
|
}
|
|
73
63
|
}
|
|
74
64
|
|
|
75
|
-
/**
|
|
76
|
-
* Render realistic device frame
|
|
77
|
-
*/
|
|
78
65
|
private renderRealisticFrame(
|
|
79
66
|
ctx: CanvasRenderingContext2D,
|
|
80
67
|
dimensions: CanvasDimensions,
|
|
@@ -91,9 +78,6 @@ export class ScreenshotRenderer {
|
|
|
91
78
|
);
|
|
92
79
|
}
|
|
93
80
|
|
|
94
|
-
/**
|
|
95
|
-
* Render minimal device frame
|
|
96
|
-
*/
|
|
97
81
|
private renderMinimalFrame(
|
|
98
82
|
ctx: CanvasRenderingContext2D,
|
|
99
83
|
dimensions: CanvasDimensions,
|
|
@@ -105,9 +89,6 @@ export class ScreenshotRenderer {
|
|
|
105
89
|
ctx.strokeRect(0, 0, dimensions.width, dimensions.height);
|
|
106
90
|
}
|
|
107
91
|
|
|
108
|
-
/**
|
|
109
|
-
* Render device notch
|
|
110
|
-
*/
|
|
111
92
|
private renderNotch(
|
|
112
93
|
ctx: CanvasRenderingContext2D,
|
|
113
94
|
dimensions: CanvasDimensions
|
|
@@ -118,15 +99,11 @@ export class ScreenshotRenderer {
|
|
|
118
99
|
ctx.fill();
|
|
119
100
|
}
|
|
120
101
|
|
|
121
|
-
/**
|
|
122
|
-
* Render background - optimized
|
|
123
|
-
*/
|
|
124
102
|
public renderBackground(
|
|
125
103
|
ctx: CanvasRenderingContext2D,
|
|
126
104
|
design: ScreenshotDesign,
|
|
127
105
|
dimensions: CanvasDimensions
|
|
128
106
|
): void {
|
|
129
|
-
// Fill background
|
|
130
107
|
if (design.backgroundGradientEnd) {
|
|
131
108
|
const gradient = this.canvasRenderer.createLinearGradient(
|
|
132
109
|
ctx,
|
|
@@ -146,7 +123,6 @@ export class ScreenshotRenderer {
|
|
|
146
123
|
|
|
147
124
|
ctx.fillRect(0, 0, dimensions.width, dimensions.height);
|
|
148
125
|
|
|
149
|
-
// Render pattern if specified (and not none)
|
|
150
126
|
if (design.backgroundPattern && design.backgroundPattern !== 'none') {
|
|
151
127
|
this.patternRenderer.renderPattern(
|
|
152
128
|
ctx,
|
|
@@ -156,9 +132,6 @@ export class ScreenshotRenderer {
|
|
|
156
132
|
}
|
|
157
133
|
}
|
|
158
134
|
|
|
159
|
-
/**
|
|
160
|
-
* Render text content
|
|
161
|
-
*/
|
|
162
135
|
public renderTextContent(
|
|
163
136
|
ctx: CanvasRenderingContext2D,
|
|
164
137
|
content: ScreenshotContent,
|
|
@@ -200,9 +173,6 @@ export class ScreenshotRenderer {
|
|
|
200
173
|
);
|
|
201
174
|
}
|
|
202
175
|
|
|
203
|
-
/**
|
|
204
|
-
* Format text case
|
|
205
|
-
*/
|
|
206
176
|
private formatText(
|
|
207
177
|
text: string,
|
|
208
178
|
textCase: 'uppercase' | 'lowercase' | 'none' = 'none'
|
|
@@ -217,9 +187,6 @@ export class ScreenshotRenderer {
|
|
|
217
187
|
}
|
|
218
188
|
}
|
|
219
189
|
|
|
220
|
-
/**
|
|
221
|
-
* Render screenshot image
|
|
222
|
-
*/
|
|
223
190
|
public async renderScreenshotImage(
|
|
224
191
|
ctx: CanvasRenderingContext2D,
|
|
225
192
|
imageUrl: string,
|
|
@@ -233,7 +200,6 @@ export class ScreenshotRenderer {
|
|
|
233
200
|
);
|
|
234
201
|
if (!deviceConfig) return;
|
|
235
202
|
|
|
236
|
-
// Calculate screenshot area (inside device frame)
|
|
237
203
|
const frameThickness = deviceConfig.frameThickness || 8;
|
|
238
204
|
const padding = 60 + frameThickness;
|
|
239
205
|
const screenshotWidth = dimensions.width - padding * 2;
|
|
@@ -253,9 +219,6 @@ export class ScreenshotRenderer {
|
|
|
253
219
|
}
|
|
254
220
|
}
|
|
255
221
|
|
|
256
|
-
/**
|
|
257
|
-
* Render complete screenshot
|
|
258
|
-
*/
|
|
259
222
|
public async renderDesignedScreenshot(
|
|
260
223
|
content: ScreenshotContent,
|
|
261
224
|
design: ScreenshotDesign
|
|
@@ -274,18 +237,14 @@ export class ScreenshotRenderer {
|
|
|
274
237
|
throw new Error('Failed to get canvas context');
|
|
275
238
|
}
|
|
276
239
|
|
|
277
|
-
// Render background
|
|
278
240
|
this.renderBackground(ctx, design, dimensions);
|
|
279
241
|
|
|
280
|
-
// Render screenshot image if available
|
|
281
242
|
if (content.screenshot) {
|
|
282
243
|
await this.renderScreenshotImage(ctx, content.screenshot, design, dimensions);
|
|
283
244
|
}
|
|
284
245
|
|
|
285
|
-
// Render text content
|
|
286
246
|
this.renderTextContent(ctx, content, design, dimensions);
|
|
287
247
|
|
|
288
|
-
// Render device frame
|
|
289
248
|
this.renderDeviceFrame(
|
|
290
249
|
ctx,
|
|
291
250
|
dimensions,
|
|
@@ -297,18 +256,13 @@ export class ScreenshotRenderer {
|
|
|
297
256
|
return canvas;
|
|
298
257
|
}
|
|
299
258
|
|
|
300
|
-
/**
|
|
301
|
-
* Render placeholder content
|
|
302
|
-
*/
|
|
303
259
|
public renderPlaceholderContent(
|
|
304
260
|
ctx: CanvasRenderingContext2D,
|
|
305
261
|
dimensions: CanvasDimensions,
|
|
306
262
|
deviceType: DeviceType
|
|
307
263
|
): void {
|
|
308
|
-
// Background
|
|
309
264
|
this.canvasRenderer.fillRect(ctx, 0, 0, dimensions.width, dimensions.height, '#ffffff');
|
|
310
265
|
|
|
311
|
-
// Header area
|
|
312
266
|
this.canvasRenderer.fillRect(ctx, 0, 0, dimensions.width, 60, '#f3f4f6');
|
|
313
267
|
this.canvasRenderer.fillRect(
|
|
314
268
|
ctx,
|
|
@@ -319,7 +273,6 @@ export class ScreenshotRenderer {
|
|
|
319
273
|
'#f3f4f6'
|
|
320
274
|
);
|
|
321
275
|
|
|
322
|
-
// Placeholder content blocks
|
|
323
276
|
ctx.fillStyle = '#e5e7eb';
|
|
324
277
|
for (let i = 0; i < 5; i++) {
|
|
325
278
|
const y = 80 + i * 120;
|
|
@@ -329,7 +282,6 @@ export class ScreenshotRenderer {
|
|
|
329
282
|
}
|
|
330
283
|
}
|
|
331
284
|
|
|
332
|
-
// Device frame
|
|
333
285
|
const deviceConfig = this.deviceConfigService.getDeviceConfig(deviceType);
|
|
334
286
|
if (deviceConfig) {
|
|
335
287
|
this.renderDeviceFrame(
|
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Device Configuration Service
|
|
3
|
-
* Manages device configurations and dimensions
|
|
4
|
-
*/
|
|
5
1
|
|
|
6
2
|
import { DeviceType, DeviceConfig } from '../../domain/types/screenshot.types';
|
|
7
3
|
import { DEVICE_DIMENSIONS } from '../../domain/config/DeviceConfig';
|
|
8
4
|
|
|
9
|
-
/**
|
|
10
|
-
* Device configuration service
|
|
11
|
-
*/
|
|
12
5
|
export class DeviceConfigService {
|
|
13
6
|
private static instance: DeviceConfigService;
|
|
14
7
|
private devices: Map<DeviceType, DeviceConfig>;
|
|
@@ -119,7 +112,6 @@ export class DeviceConfigService {
|
|
|
119
112
|
}
|
|
120
113
|
|
|
121
114
|
public getAllDevices(): DeviceConfig[] {
|
|
122
|
-
// Use cached value if available
|
|
123
115
|
if (!this.allDevicesCache) {
|
|
124
116
|
this.allDevicesCache = Array.from(this.devices.values());
|
|
125
117
|
}
|
|
@@ -127,7 +119,6 @@ export class DeviceConfigService {
|
|
|
127
119
|
}
|
|
128
120
|
|
|
129
121
|
public getPhones(): DeviceConfig[] {
|
|
130
|
-
// Use cached value if available
|
|
131
122
|
if (!this.phonesCache) {
|
|
132
123
|
this.phonesCache = this.getAllDevices().filter((d) => d.isPhone);
|
|
133
124
|
}
|
|
@@ -135,7 +126,6 @@ export class DeviceConfigService {
|
|
|
135
126
|
}
|
|
136
127
|
|
|
137
128
|
public getTablets(): DeviceConfig[] {
|
|
138
|
-
// Use cached value if available
|
|
139
129
|
if (!this.tabletsCache) {
|
|
140
130
|
this.tabletsCache = this.getAllDevices().filter((d) => d.isTablet);
|
|
141
131
|
}
|
|
@@ -1,15 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal XLSX writer — replaces the unmaintained npm `xlsx` (SheetJS)
|
|
3
|
-
* dependency, whose registry build carries open prototype-pollution and
|
|
4
|
-
* ReDoS advisories with no registry fix. The report export only ever
|
|
5
|
-
* writes flat tables (header row + primitive cells), so a hand-rolled
|
|
6
|
-
* SpreadsheetML package on top of jszip covers the need: inline-string
|
|
7
|
-
* and numeric cells, multiple sheets, Excel/LibreOffice/Sheets-compatible.
|
|
8
|
-
*/
|
|
9
1
|
|
|
10
2
|
export interface XlsxSheet {
|
|
11
3
|
name: string;
|
|
12
|
-
/** Rows of primitive cells; the first row is the header. */
|
|
13
4
|
rows: (string | number | boolean | null | undefined)[][];
|
|
14
5
|
}
|
|
15
6
|
|
|
@@ -24,7 +15,6 @@ function escapeXml(value: string): string {
|
|
|
24
15
|
.replace(/'/g, ''');
|
|
25
16
|
}
|
|
26
17
|
|
|
27
|
-
/** Excel sheet names: max 31 chars, no : \ / ? * [ ]. */
|
|
28
18
|
function safeSheetName(name: string, index: number): string {
|
|
29
19
|
const cleaned = name.replace(/[:\\/?*[\]]/g, ' ').slice(0, 31).trim();
|
|
30
20
|
return cleaned.length > 0 ? cleaned : `Sheet${index + 1}`;
|
|
@@ -66,7 +56,6 @@ function sheetXml(rows: XlsxSheet['rows']): string {
|
|
|
66
56
|
return `${XML_HEADER}<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>${body}</sheetData></worksheet>`;
|
|
67
57
|
}
|
|
68
58
|
|
|
69
|
-
/** Assembles the sheets into a .xlsx zip blob using jszip. */
|
|
70
59
|
export async function writeXlsxBlob(sheets: XlsxSheet[], JSZip: typeof import('jszip')): Promise<Blob> {
|
|
71
60
|
const zip = new JSZip();
|
|
72
61
|
const safeNames = sheets.map((sheet, index) => safeSheetName(sheet.name, index));
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useCanvasExport Hook
|
|
3
|
-
* React hook for canvas export operations
|
|
4
|
-
*/
|
|
5
1
|
|
|
6
2
|
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
7
3
|
import type { CanvasExportOptions, CanvasResult } from '../../domain/types/canvas.types';
|
|
@@ -9,16 +5,10 @@ import type { CanvasConfig } from '../../domain/config/CanvasConfig';
|
|
|
9
5
|
import { DEFAULT_CANVAS_CONFIG } from '../../domain/config/CanvasConfig';
|
|
10
6
|
import { CanvasRenderer } from '../../infrastructure/renderers/CanvasRenderer';
|
|
11
7
|
|
|
12
|
-
/**
|
|
13
|
-
* useCanvasExport hook options
|
|
14
|
-
*/
|
|
15
8
|
export interface UseCanvasExportOptions {
|
|
16
9
|
config?: CanvasConfig;
|
|
17
10
|
}
|
|
18
11
|
|
|
19
|
-
/**
|
|
20
|
-
* useCanvasExport hook return
|
|
21
|
-
*/
|
|
22
12
|
export interface UseCanvasExportReturn {
|
|
23
13
|
isExporting: boolean;
|
|
24
14
|
progress: number;
|
|
@@ -40,9 +30,6 @@ export interface UseCanvasExportReturn {
|
|
|
40
30
|
cloneCanvas: (canvas: HTMLCanvasElement) => HTMLCanvasElement | null;
|
|
41
31
|
}
|
|
42
32
|
|
|
43
|
-
/**
|
|
44
|
-
* useCanvasExport hook
|
|
45
|
-
*/
|
|
46
33
|
export function useCanvasExport(
|
|
47
34
|
options: UseCanvasExportOptions = {}
|
|
48
35
|
): UseCanvasExportReturn {
|
|
@@ -52,26 +39,22 @@ export function useCanvasExport(
|
|
|
52
39
|
const [progress, setProgress] = useState(0);
|
|
53
40
|
const [error, setError] = useState<string | null>(null);
|
|
54
41
|
|
|
55
|
-
// Use ref to prevent recreating renderer on every render
|
|
56
42
|
const rendererRef = useRef<CanvasRenderer | null>(null);
|
|
57
43
|
const urlCleanupRef = useRef<string[]>([]);
|
|
58
44
|
|
|
59
|
-
// Initialize renderer once
|
|
60
45
|
if (!rendererRef.current) {
|
|
61
46
|
rendererRef.current = new CanvasRenderer(config);
|
|
62
47
|
}
|
|
63
48
|
|
|
64
|
-
// Cleanup URLs on unmount
|
|
65
49
|
useEffect(() => {
|
|
66
50
|
return () => {
|
|
67
|
-
// Clean up any remaining URLs
|
|
68
51
|
urlCleanupRef.current.forEach(url => {
|
|
69
52
|
try {
|
|
70
53
|
URL.revokeObjectURL(url);
|
|
71
|
-
} catch
|
|
72
|
-
// Ignore errors during cleanup
|
|
54
|
+
} catch {
|
|
73
55
|
}
|
|
74
56
|
});
|
|
57
|
+
|
|
75
58
|
urlCleanupRef.current = [];
|
|
76
59
|
};
|
|
77
60
|
}, []);
|
|
@@ -91,7 +74,6 @@ export function useCanvasExport(
|
|
|
91
74
|
const result = await rendererRef.current.exportToBlob(canvas, options);
|
|
92
75
|
setProgress(100);
|
|
93
76
|
|
|
94
|
-
// Track URL for cleanup
|
|
95
77
|
if (result?.url) {
|
|
96
78
|
urlCleanupRef.current.push(result.url);
|
|
97
79
|
}
|
|
@@ -154,10 +136,8 @@ export function useCanvasExport(
|
|
|
154
136
|
|
|
155
137
|
setProgress(100);
|
|
156
138
|
|
|
157
|
-
// Schedule URL cleanup after a delay
|
|
158
139
|
setTimeout(() => {
|
|
159
140
|
URL.revokeObjectURL(result.url);
|
|
160
|
-
// Remove from cleanup tracking
|
|
161
141
|
const index = urlCleanupRef.current.indexOf(result.url);
|
|
162
142
|
if (index > -1) {
|
|
163
143
|
urlCleanupRef.current.splice(index, 1);
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useReportExport Hook
|
|
3
|
-
* React hook for report export operations
|
|
4
|
-
*/
|
|
5
1
|
|
|
6
2
|
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
7
3
|
import type { ReportData } from '../../domain/types/report.types';
|
|
@@ -11,9 +7,6 @@ import type {
|
|
|
11
7
|
} from '../../domain/types/report.types';
|
|
12
8
|
import { ReportService } from '../../application/services/ReportService';
|
|
13
9
|
|
|
14
|
-
/**
|
|
15
|
-
* useReportExport hook return
|
|
16
|
-
*/
|
|
17
10
|
export interface UseReportExportReturn {
|
|
18
11
|
isExporting: boolean;
|
|
19
12
|
error: string | null;
|
|
@@ -40,17 +33,12 @@ export interface UseReportExportReturn {
|
|
|
40
33
|
) => void;
|
|
41
34
|
}
|
|
42
35
|
|
|
43
|
-
/**
|
|
44
|
-
* useReportExport hook
|
|
45
|
-
*/
|
|
46
36
|
export function useReportExport(): UseReportExportReturn {
|
|
47
37
|
const [isExporting, setIsExporting] = useState(false);
|
|
48
38
|
const [error, setError] = useState<string | null>(null);
|
|
49
39
|
|
|
50
|
-
// Use ref to prevent recreating service instance on every render
|
|
51
40
|
const reportServiceRef = useRef<ReportService | null>(null);
|
|
52
41
|
|
|
53
|
-
// Initialize service once
|
|
54
42
|
if (!reportServiceRef.current) {
|
|
55
43
|
reportServiceRef.current = ReportService.getInstance();
|
|
56
44
|
}
|