@webmcp-auto-ui/ui 2.5.19 → 2.5.20
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 +2 -1
- package/src/widgets/export-widget.ts +33 -70
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webmcp-auto-ui/ui",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.20",
|
|
4
4
|
"description": "Svelte 5 UI components — primitives, widgets, window manager",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"@types/d3": "^7.4.3",
|
|
51
51
|
"bits-ui": "^2.17.2",
|
|
52
52
|
"clsx": "^2.1.1",
|
|
53
|
+
"html-to-image": "^1.11.13",
|
|
53
54
|
"tailwind-merge": "^3.5.0",
|
|
54
55
|
"tailwind-variants": "^3.2.2"
|
|
55
56
|
}
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
* Determines the best export format based on widget type and triggers a file download.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { toPng } from 'html-to-image';
|
|
7
|
+
|
|
8
|
+
const TARGET_PNG_WIDTH = 2048;
|
|
9
|
+
|
|
6
10
|
// ── helpers ──────────────────────────────────────────────────────────────────
|
|
7
11
|
|
|
8
12
|
function timestamp(): string {
|
|
@@ -81,54 +85,27 @@ function exportCsv(type: string, data: Record<string, unknown>): void {
|
|
|
81
85
|
downloadFile(lines.join('\r\n'), filename(type, 'csv'), 'text/csv;charset=utf-8');
|
|
82
86
|
}
|
|
83
87
|
|
|
84
|
-
// ── PNG via
|
|
85
|
-
|
|
86
|
-
function svgToPng(svgEl: SVGElement, name: string): void {
|
|
87
|
-
// Inline computed styles on the SVG to improve fidelity when rendered off-DOM
|
|
88
|
-
const svgData = new XMLSerializer().serializeToString(svgEl);
|
|
89
|
-
const canvas = document.createElement('canvas');
|
|
90
|
-
const ctx = canvas.getContext('2d')!;
|
|
91
|
-
const img = new Image();
|
|
92
|
-
const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
|
93
|
-
const url = URL.createObjectURL(blob);
|
|
94
|
-
img.onload = () => {
|
|
95
|
-
canvas.width = img.naturalWidth || svgEl.clientWidth || 800;
|
|
96
|
-
canvas.height = img.naturalHeight || svgEl.clientHeight || 600;
|
|
97
|
-
ctx.drawImage(img, 0, 0);
|
|
98
|
-
const pngUrl = canvas.toDataURL('image/png');
|
|
99
|
-
downloadFile(pngUrl, name);
|
|
100
|
-
URL.revokeObjectURL(url);
|
|
101
|
-
};
|
|
102
|
-
img.onerror = () => {
|
|
103
|
-
// Fallback: try canvas.toDataURL directly if available
|
|
104
|
-
URL.revokeObjectURL(url);
|
|
105
|
-
};
|
|
106
|
-
img.src = url;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function canvasToPng(canvasEl: HTMLCanvasElement, name: string): void {
|
|
110
|
-
const url = canvasEl.toDataURL('image/png');
|
|
111
|
-
downloadFile(url, name);
|
|
112
|
-
}
|
|
88
|
+
// ── PNG via html-to-image ─────────────────────────────────────────────────────
|
|
113
89
|
|
|
114
|
-
function exportPng(type: string, containerEl: HTMLElement): void {
|
|
90
|
+
async function exportPng(type: string, containerEl: HTMLElement): Promise<void> {
|
|
115
91
|
const name = filename(type, 'png');
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
92
|
+
const clientW = Math.max(containerEl.clientWidth, 1);
|
|
93
|
+
const clientH = Math.max(containerEl.clientHeight, 1);
|
|
94
|
+
const pixelRatio = TARGET_PNG_WIDTH / clientW;
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const dataUrl = await toPng(containerEl, {
|
|
98
|
+
pixelRatio,
|
|
99
|
+
cacheBust: true,
|
|
100
|
+
// Let the DOM speak for its own styles — no forced background
|
|
101
|
+
// so dark/light themes both render correctly.
|
|
102
|
+
});
|
|
103
|
+
downloadFile(dataUrl, name);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
console.error('[exportWidget] PNG export failed for type', type, err);
|
|
106
|
+
alert(`Export PNG impossible : ${err instanceof Error ? err.message : String(err)}`);
|
|
122
107
|
}
|
|
123
|
-
|
|
124
|
-
// Fall back to SVG
|
|
125
|
-
const svgEl = containerEl.querySelector('svg');
|
|
126
|
-
if (svgEl) {
|
|
127
|
-
svgToPng(svgEl as SVGElement, name);
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
console.warn('[exportWidget] No <canvas> or <svg> found in container for type:', type);
|
|
108
|
+
// pixelRatio used as-is; final canvas size = clientW*pixelRatio × clientH*pixelRatio = 2048 × (2048 * clientH / clientW)
|
|
132
109
|
}
|
|
133
110
|
|
|
134
111
|
// ── Markdown ──────────────────────────────────────────────────────────────────
|
|
@@ -209,53 +186,39 @@ const HTML_FMT: ExportFormat = { id: 'html', label: 'HTML', icon: '🌐' };
|
|
|
209
186
|
* Return the list of export formats available for a given widget type.
|
|
210
187
|
*/
|
|
211
188
|
export function getExportFormats(type: string, containerEl?: HTMLElement): ExportFormat[] {
|
|
189
|
+
const base: ExportFormat[] = [PNG_FMT, JSON_FMT];
|
|
212
190
|
switch (type) {
|
|
213
191
|
case 'data-table':
|
|
214
192
|
case 'grid-data':
|
|
215
193
|
case 'kv':
|
|
216
194
|
case 'list':
|
|
217
|
-
return [CSV_FMT,
|
|
218
|
-
|
|
219
|
-
case 'chart':
|
|
220
|
-
case 'chart-rich':
|
|
221
|
-
case 'sankey':
|
|
222
|
-
case 'd3':
|
|
223
|
-
case 'hemicycle':
|
|
224
|
-
case 'map':
|
|
225
|
-
return [PNG_FMT, JSON_FMT];
|
|
226
|
-
|
|
195
|
+
return [CSV_FMT, ...base];
|
|
227
196
|
case 'text':
|
|
228
197
|
case 'code':
|
|
229
198
|
case 'log':
|
|
230
|
-
return [MD_FMT,
|
|
231
|
-
|
|
199
|
+
return [MD_FMT, ...base];
|
|
232
200
|
case 'js-sandbox':
|
|
233
|
-
return [HTML_FMT,
|
|
234
|
-
|
|
235
|
-
case 'gallery':
|
|
236
|
-
case 'carousel':
|
|
237
|
-
return [JSON_FMT];
|
|
238
|
-
|
|
201
|
+
return [HTML_FMT, ...base];
|
|
239
202
|
default:
|
|
240
|
-
return
|
|
203
|
+
return base;
|
|
241
204
|
}
|
|
242
205
|
}
|
|
243
206
|
|
|
244
207
|
/**
|
|
245
208
|
* Export a widget in a specific format chosen by the user.
|
|
246
209
|
*/
|
|
247
|
-
export function exportWidgetAs(
|
|
210
|
+
export async function exportWidgetAs(
|
|
248
211
|
format: string,
|
|
249
212
|
type: string,
|
|
250
213
|
data: Record<string, unknown>,
|
|
251
214
|
containerEl?: HTMLElement
|
|
252
|
-
): void {
|
|
215
|
+
): Promise<void> {
|
|
253
216
|
switch (format) {
|
|
254
217
|
case 'csv':
|
|
255
218
|
exportCsv(type, data);
|
|
256
219
|
break;
|
|
257
220
|
case 'png':
|
|
258
|
-
if (containerEl) exportPng(type, containerEl);
|
|
221
|
+
if (containerEl) await exportPng(type, containerEl);
|
|
259
222
|
else console.warn('[exportWidgetAs] containerEl required for PNG export');
|
|
260
223
|
break;
|
|
261
224
|
case 'md':
|
|
@@ -281,11 +244,11 @@ export function exportWidgetAs(
|
|
|
281
244
|
* @param data The widget's data object
|
|
282
245
|
* @param containerEl Optional DOM container — required for PNG exports
|
|
283
246
|
*/
|
|
284
|
-
export function exportWidget(
|
|
247
|
+
export async function exportWidget(
|
|
285
248
|
type: string,
|
|
286
249
|
data: Record<string, unknown>,
|
|
287
250
|
containerEl?: HTMLElement
|
|
288
|
-
): void {
|
|
251
|
+
): Promise<void> {
|
|
289
252
|
switch (type) {
|
|
290
253
|
// ── CSV ──
|
|
291
254
|
case 'data-table':
|
|
@@ -303,7 +266,7 @@ export function exportWidget(
|
|
|
303
266
|
case 'hemicycle':
|
|
304
267
|
case 'map':
|
|
305
268
|
if (containerEl) {
|
|
306
|
-
exportPng(type, containerEl);
|
|
269
|
+
await exportPng(type, containerEl);
|
|
307
270
|
} else {
|
|
308
271
|
console.warn('[exportWidget] containerEl required for PNG export of type:', type);
|
|
309
272
|
}
|